diff --git a/crates/rome_js_analyze/src/analyzers/nursery/use_yield.rs b/crates/rome_js_analyze/src/analyzers/nursery/use_yield.rs index e53d680c26eb..c064d0565184 100644 --- a/crates/rome_js_analyze/src/analyzers/nursery/use_yield.rs +++ b/crates/rome_js_analyze/src/analyzers/nursery/use_yield.rs @@ -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`. @@ -39,7 +42,7 @@ declare_rule! { /// function* foo() { } /// ``` pub(crate) UseYield { - version: "12.0.0", + version: "next", name: "useYield", recommended: true, } @@ -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>, + mut ctx: VisitorContext, + ) { + 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, + _: &::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; + type Query = MissingYield; type State = (); type Signals = Option; type Options = (); fn run(ctx: &RuleContext) -> 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::State) -> Option { @@ -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) -> 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 -} diff --git a/website/src/pages/lint/rules/index.mdx b/website/src/pages/lint/rules/index.mdx index 37ad245f0986..84c6e3b5908c 100644 --- a/website/src/pages/lint/rules/index.mdx +++ b/website/src/pages/lint/rules/index.mdx @@ -57,6 +57,14 @@ Enforce that anchor elements have content and that the content is accessible to Enforces the usage of the attribute type for the element button
+

+ useHtmlLang + recommended +

+Enforce that html element has lang attribute. +This allows users to choose a language other than the default. +
+

useKeyWithClickEvents recommended @@ -406,7 +414,7 @@ Disallow duplicate function arguments name. noExplicitAny recommended

-Disallow the any type usage +Disallow the any type usage.

@@ -492,6 +500,18 @@ Disallow assignments in expressions. Disallow certain types.

+

+ noClassAssign +

+Disallow reassigning class members. +
+
+

+ noCommaOperator +

+Disallow comma operator. +
+

noConstEnum

@@ -510,6 +530,19 @@ Disallow returning a value from a constructor. Enforces that no distracting elements are used.
+

+ noDuplicateCase +

+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. +
+
+

+ noDuplicateJsxProps +

+Prevents JSX properties to be assigned multiple times. +
+

noDuplicateObjectKeys

@@ -529,12 +562,25 @@ Disallow the declaration of empty interfaces. Prevents the wrong usage of the non-null assertion operator (!) in TypeScript files.
+

+ noExtraSemicolons +

+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. +
+

noHeaderScope

Check that the scope attribute is only used on th elements.
+

+ noInnerDeclarations +

+Disallow function and var declarations in nested blocks. +
+

noInvalidConstructorSuper

@@ -548,6 +594,12 @@ It also checks whether a call super() is missing from classes that Disallow non-null assertions using the ! postfix operator.
+

+ noNoninteractiveElementToInteractiveRole +

+Enforce that interactive ARIA roles are not assigned to non-interactive HTML elements. +
+

noPrecisionLoss

@@ -572,6 +624,12 @@ Prevents from having redundant "use strict". This rule allows you to specify global variable names that you don’t want to use in your application.
+

+ noSelfCompare +

+Disallow comparisons where both sides are exactly the same. +
+

noSetterReturn

@@ -584,6 +642,14 @@ Disallow returning a value from a setter Disallow comparison of expressions modifying the string case with non-compliant value.
+

+ noUnreachableSuper +

+Ensures the super() constructor is called exactly once on every code +path in a class constructor before this is accessed if the class has +a superclass +
+

noUnsafeFinally

@@ -675,12 +741,42 @@ Enforce that all React hooks are being called from the Top Level component functions.
+

+ useIframeTitle +

+Enforces the usage of the attribute title for the element iframe +
+
+

+ useIsNan +

+Require calls to isNaN() when checking for NaN. +
+
+

+ useMediaCaption +

+Enforces that audio and video elements must have a track for captions. +
+

useNumericLiterals

Disallow parseInt() and Number.parseInt() in favor of binary, octal, and hexadecimal literals
+

+ useValidAriaProps +

+Ensures that ARIA properties aria-* are all valid. +
+
+

+ useValidLang +

+Ensure that the attribute passed to the lang attribute is a correct ISO language and/or country. +
+

useYield

diff --git a/website/src/pages/lint/rules/useYield.md b/website/src/pages/lint/rules/useYield.md index f37ee8e9b389..3d842cc90c76 100644 --- a/website/src/pages/lint/rules/useYield.md +++ b/website/src/pages/lint/rules/useYield.md @@ -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`. @@ -50,3 +50,7 @@ function foo() { function* foo() { } ``` +## Related links + +- [Disable a rule](/linter/#disable-a-lint-rule) +- [Rule options](/linter/#rule-options)