Skip to content
This repository has been archived by the owner on Aug 31, 2023. It is now read-only.

Commit

Permalink
refactor: use custom visitor
Browse files Browse the repository at this point in the history
  • Loading branch information
kaioduarte committed Jan 4, 2023
1 parent 7bc2fe1 commit 1ada43c
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 41 deletions.
124 changes: 85 additions & 39 deletions crates/rome_js_analyze/src/analyzers/nursery/use_yield.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use rome_analyze::context::RuleContext;
use rome_analyze::{declare_rule, Ast, Rule, RuleDiagnostic};
use rome_analyze::{
declare_rule, AddVisitor, Phases, QueryMatch, Queryable, Rule, RuleDiagnostic, ServiceBag,
Visitor, VisitorContext,
};
use rome_console::markup;
use rome_js_syntax::{
AnyJsClass, AnyJsFunction, JsLanguage, JsMethodClassMember, JsMethodObjectMember,
JsStatementList, JsSyntaxKind, WalkEvent,
AnyJsFunction, JsLanguage, JsMethodClassMember, JsMethodObjectMember, JsStatementList,
JsYieldExpression, TextRange, WalkEvent,
};
use rome_rowan::{declare_node_union, AstNode, AstNodeList, SyntaxNode};
use rome_rowan::{declare_node_union, AstNode, AstNodeList, Language, SyntaxNode};

declare_rule! {
/// Require generator functions to contain `yield`.
Expand Down Expand Up @@ -39,7 +42,7 @@ declare_rule! {
/// function* foo() { }
/// ```
pub(crate) UseYield {
version: "12.0.0",
version: "next",
name: "useYield",
recommended: true,
}
Expand Down Expand Up @@ -79,24 +82,91 @@ impl AnyFunctionLike {
}
}

#[derive(Default)]
struct MissingYieldVisitor {
stack: Vec<(AnyFunctionLike, bool)>,
}

impl Visitor for MissingYieldVisitor {
type Language = JsLanguage;

fn visit(
&mut self,
event: &WalkEvent<SyntaxNode<Self::Language>>,
mut ctx: VisitorContext<Self::Language>,
) {
match event {
WalkEvent::Enter(node) => {
// When the visitor enters a function node, push a new entry on the stack
if let Some(node) = AnyFunctionLike::cast_ref(node) {
self.stack.push((node, false));
}

if let Some((_, has_yield)) = self.stack.last_mut() {
// When the visitor enters a `yield` expression, set the
// `has_yield` flag for the top entry on the stack to `true`
if JsYieldExpression::can_cast(node.kind()) {
*has_yield = true;
}
}
}
WalkEvent::Leave(node) => {
// When the visitor exits a function, if it matches the node of the top-most
// entry of the stack and the `has_yield` flag is `false`, emit a query match
if let Some(exit_node) = AnyFunctionLike::cast_ref(node) {
if let Some((enter_node, has_yield)) = self.stack.pop() {
assert_eq!(enter_node, exit_node);
if !has_yield {
ctx.match_query(MissingYield(enter_node));
}
}
}
}
}
}
}

pub(crate) struct MissingYield(AnyFunctionLike);

impl QueryMatch for MissingYield {
fn text_range(&self) -> TextRange {
self.0.range()
}
}

impl Queryable for MissingYield {
type Input = Self;
type Language = JsLanguage;
type Output = AnyFunctionLike;
type Services = ();

fn build_visitor(
analyzer: &mut impl AddVisitor<Self::Language>,
_: &<Self::Language as Language>::Root,
) {
analyzer.add_visitor(Phases::Syntax, MissingYieldVisitor::default);
}

fn unwrap_match(_: &ServiceBag, query: &Self::Input) -> Self::Output {
query.0.clone()
}
}

impl Rule for UseYield {
type Query = Ast<AnyFunctionLike>;
type Query = MissingYield;
type State = ();
type Signals = Option<Self::State>;
type Options = ();

fn run(ctx: &RuleContext<Self>) -> Self::Signals {
let node = ctx.query();
let function_body_statements = node.statements()?;

if node.is_generator()
&& !function_body_statements.is_empty()
&& !has_yield_expression(function_body_statements.syntax())
{
return Some(());
let query = ctx.query();

// Don't emit diagnostic for non-generators or generators with an empty body
if !query.is_generator() || query.statements()?.is_empty() {
return None;
}

None
Some(())
}

fn diagnostic(ctx: &RuleContext<Self>, _: &Self::State) -> Option<RuleDiagnostic> {
Expand All @@ -107,27 +177,3 @@ impl Rule for UseYield {
))
}
}

/// Traverses the syntax tree and verifies the presence of a yield expression.
fn has_yield_expression(node: &SyntaxNode<JsLanguage>) -> bool {
let mut iter = node.preorder();

while let Some(event) = iter.next() {
match event {
WalkEvent::Enter(enter) => {
let kind = enter.kind();

if kind == JsSyntaxKind::JS_YIELD_EXPRESSION {
return true;
}

if AnyJsClass::can_cast(kind) || AnyFunctionLike::can_cast(kind) {
iter.skip_subtree();
}
}
WalkEvent::Leave(_) => {}
};
}

false
}
98 changes: 97 additions & 1 deletion website/src/pages/lint/rules/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ Enforce that anchor elements have content and that the content is accessible to
Enforces the usage of the attribute <code>type</code> for the element <code>button</code>
</section>
<section class="rule">
<h3 data-toc-exclude id="useHtmlLang">
<a href="/lint/rules/useHtmlLang">useHtmlLang</a>
<span class="recommended">recommended</span>
</h3>
Enforce that <code>html</code> element has <code>lang</code> attribute.
This allows users to choose a language other than the default.
</section>
<section class="rule">
<h3 data-toc-exclude id="useKeyWithClickEvents">
<a href="/lint/rules/useKeyWithClickEvents">useKeyWithClickEvents</a>
<span class="recommended">recommended</span>
Expand Down Expand Up @@ -406,7 +414,7 @@ Disallow duplicate function arguments name.
<a href="/lint/rules/noExplicitAny">noExplicitAny</a>
<span class="recommended">recommended</span>
</h3>
Disallow the <code>any</code> type usage
Disallow the <code>any</code> type usage.
</section>
<section class="rule">
<h3 data-toc-exclude id="noFunctionAssign">
Expand Down Expand Up @@ -492,6 +500,18 @@ Disallow assignments in expressions.
Disallow certain types.
</section>
<section class="rule">
<h3 data-toc-exclude id="noClassAssign">
<a href="/lint/rules/noClassAssign">noClassAssign</a>
</h3>
Disallow reassigning class members.
</section>
<section class="rule">
<h3 data-toc-exclude id="noCommaOperator">
<a href="/lint/rules/noCommaOperator">noCommaOperator</a>
</h3>
Disallow comma operator.
</section>
<section class="rule">
<h3 data-toc-exclude id="noConstEnum">
<a href="/lint/rules/noConstEnum">noConstEnum</a>
</h3>
Expand All @@ -510,6 +530,19 @@ Disallow returning a value from a <code>constructor</code>.
Enforces that no distracting elements are used.
</section>
<section class="rule">
<h3 data-toc-exclude id="noDuplicateCase">
<a href="/lint/rules/noDuplicateCase">noDuplicateCase</a>
</h3>
Disallow duplicate case labels.
If a switch statement has duplicate test expressions in case clauses, it is likely that a programmer copied a case clause but forgot to change the test expression.
</section>
<section class="rule">
<h3 data-toc-exclude id="noDuplicateJsxProps">
<a href="/lint/rules/noDuplicateJsxProps">noDuplicateJsxProps</a>
</h3>
Prevents JSX properties to be assigned multiple times.
</section>
<section class="rule">
<h3 data-toc-exclude id="noDuplicateObjectKeys">
<a href="/lint/rules/noDuplicateObjectKeys">noDuplicateObjectKeys</a>
</h3>
Expand All @@ -529,12 +562,25 @@ Disallow the declaration of empty interfaces.
Prevents the wrong usage of the non-null assertion operator (<code>!</code>) in TypeScript files.
</section>
<section class="rule">
<h3 data-toc-exclude id="noExtraSemicolons">
<a href="/lint/rules/noExtraSemicolons">noExtraSemicolons</a>
</h3>
Typing mistakes and misunderstandings about where semicolons are required can lead to semicolons that are unnecessary.
While not technically an error, extra semicolons can cause confusion when reading code.
</section>
<section class="rule">
<h3 data-toc-exclude id="noHeaderScope">
<a href="/lint/rules/noHeaderScope">noHeaderScope</a>
</h3>
Check that the scope attribute is only used on <code>th</code> elements.
</section>
<section class="rule">
<h3 data-toc-exclude id="noInnerDeclarations">
<a href="/lint/rules/noInnerDeclarations">noInnerDeclarations</a>
</h3>
Disallow <code>function</code> and <code>var</code> declarations in nested blocks.
</section>
<section class="rule">
<h3 data-toc-exclude id="noInvalidConstructorSuper">
<a href="/lint/rules/noInvalidConstructorSuper">noInvalidConstructorSuper</a>
</h3>
Expand All @@ -548,6 +594,12 @@ It also checks whether a call <code>super()</code> is missing from classes that
Disallow non-null assertions using the <code>!</code> postfix operator.
</section>
<section class="rule">
<h3 data-toc-exclude id="noNoninteractiveElementToInteractiveRole">
<a href="/lint/rules/noNoninteractiveElementToInteractiveRole">noNoninteractiveElementToInteractiveRole</a>
</h3>
Enforce that interactive ARIA roles are not assigned to non-interactive HTML elements.
</section>
<section class="rule">
<h3 data-toc-exclude id="noPrecisionLoss">
<a href="/lint/rules/noPrecisionLoss">noPrecisionLoss</a>
</h3>
Expand All @@ -572,6 +624,12 @@ Prevents from having redundant <code>&quot;use strict&quot;</code>.
This rule allows you to specify global variable names that you don’t want to use in your application.
</section>
<section class="rule">
<h3 data-toc-exclude id="noSelfCompare">
<a href="/lint/rules/noSelfCompare">noSelfCompare</a>
</h3>
Disallow comparisons where both sides are exactly the same.
</section>
<section class="rule">
<h3 data-toc-exclude id="noSetterReturn">
<a href="/lint/rules/noSetterReturn">noSetterReturn</a>
</h3>
Expand All @@ -584,6 +642,14 @@ Disallow returning a value from a setter
Disallow comparison of expressions modifying the string case with non-compliant value.
</section>
<section class="rule">
<h3 data-toc-exclude id="noUnreachableSuper">
<a href="/lint/rules/noUnreachableSuper">noUnreachableSuper</a>
</h3>
Ensures the <code>super()</code> constructor is called exactly once on every code
path in a class constructor before <code>this</code> is accessed if the class has
a superclass
</section>
<section class="rule">
<h3 data-toc-exclude id="noUnsafeFinally">
<a href="/lint/rules/noUnsafeFinally">noUnsafeFinally</a>
</h3>
Expand Down Expand Up @@ -675,12 +741,42 @@ Enforce that all React hooks are being called from the Top Level
component functions.
</section>
<section class="rule">
<h3 data-toc-exclude id="useIframeTitle">
<a href="/lint/rules/useIframeTitle">useIframeTitle</a>
</h3>
Enforces the usage of the attribute <code>title</code> for the element <code>iframe</code>
</section>
<section class="rule">
<h3 data-toc-exclude id="useIsNan">
<a href="/lint/rules/useIsNan">useIsNan</a>
</h3>
Require calls to <code>isNaN()</code> when checking for <code>NaN</code>.
</section>
<section class="rule">
<h3 data-toc-exclude id="useMediaCaption">
<a href="/lint/rules/useMediaCaption">useMediaCaption</a>
</h3>
Enforces that <code>audio</code> and <code>video</code> elements must have a <code>track</code> for captions.
</section>
<section class="rule">
<h3 data-toc-exclude id="useNumericLiterals">
<a href="/lint/rules/useNumericLiterals">useNumericLiterals</a>
</h3>
Disallow <code>parseInt()</code> and <code>Number.parseInt()</code> in favor of binary, octal, and hexadecimal literals
</section>
<section class="rule">
<h3 data-toc-exclude id="useValidAriaProps">
<a href="/lint/rules/useValidAriaProps">useValidAriaProps</a>
</h3>
Ensures that ARIA properties <code>aria-*</code> are all valid.
</section>
<section class="rule">
<h3 data-toc-exclude id="useValidLang">
<a href="/lint/rules/useValidLang">useValidLang</a>
</h3>
Ensure that the attribute passed to the <code>lang</code> attribute is a correct ISO language and/or country.
</section>
<section class="rule">
<h3 data-toc-exclude id="useYield">
<a href="/lint/rules/useYield">useYield</a>
</h3>
Expand Down
6 changes: 5 additions & 1 deletion website/src/pages/lint/rules/useYield.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: Lint Rule useYield
parent: lint/rules/index
---

# useYield (since v12.0.0)
# useYield (since vnext)

Require generator functions to contain `yield`.

Expand Down Expand Up @@ -50,3 +50,7 @@ function foo() {
function* foo() { }
```

## Related links

- [Disable a rule](/linter/#disable-a-lint-rule)
- [Rule options](/linter/#rule-options)

0 comments on commit 1ada43c

Please sign in to comment.