diff --git a/crates/oxc_linter/src/generated/rule_runner_impls.rs b/crates/oxc_linter/src/generated/rule_runner_impls.rs index e0347ea2a8d1e..748cab5029fc5 100644 --- a/crates/oxc_linter/src/generated/rule_runner_impls.rs +++ b/crates/oxc_linter/src/generated/rule_runner_impls.rs @@ -9,7 +9,8 @@ use oxc_semantic::AstTypesBitset; use crate::rule::{RuleRunFunctionsImplemented, RuleRunner}; impl RuleRunner for crate::rules::eslint::array_callback_return::ArrayCallbackReturn { - const NODE_TYPES: Option<&AstTypesBitset> = None; + const NODE_TYPES: Option<&AstTypesBitset> = + Some(&AstTypesBitset::from_types(&[AstType::ArrowFunctionExpression, AstType::Function])); const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } @@ -164,7 +165,8 @@ impl RuleRunner for crate::rules::eslint::no_alert::NoAlert { } impl RuleRunner for crate::rules::eslint::no_array_constructor::NoArrayConstructor { - const NODE_TYPES: Option<&AstTypesBitset> = None; + const NODE_TYPES: Option<&AstTypesBitset> = + Some(&AstTypesBitset::from_types(&[AstType::CallExpression, AstType::NewExpression])); const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } @@ -175,7 +177,8 @@ impl RuleRunner for crate::rules::eslint::no_async_promise_executor::NoAsyncProm } impl RuleRunner for crate::rules::eslint::no_await_in_loop::NoAwaitInLoop { - const NODE_TYPES: Option<&AstTypesBitset> = None; + const NODE_TYPES: Option<&AstTypesBitset> = + Some(&AstTypesBitset::from_types(&[AstType::AwaitExpression, AstType::ForOfStatement])); const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } @@ -225,7 +228,10 @@ impl RuleRunner for crate::rules::eslint::no_cond_assign::NoCondAssign { } impl RuleRunner for crate::rules::eslint::no_console::NoConsole { - const NODE_TYPES: Option<&AstTypesBitset> = None; + const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[ + AstType::ComputedMemberExpression, + AstType::StaticMemberExpression, + ])); const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } @@ -429,7 +435,8 @@ impl RuleRunner for crate::rules::eslint::no_inner_declarations::NoInnerDeclarat } impl RuleRunner for crate::rules::eslint::no_invalid_regexp::NoInvalidRegexp { - const NODE_TYPES: Option<&AstTypesBitset> = None; + const NODE_TYPES: Option<&AstTypesBitset> = + Some(&AstTypesBitset::from_types(&[AstType::CallExpression, AstType::NewExpression])); const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } @@ -547,7 +554,8 @@ impl RuleRunner for crate::rules::eslint::no_obj_calls::NoObjCalls { } impl RuleRunner for crate::rules::eslint::no_object_constructor::NoObjectConstructor { - const NODE_TYPES: Option<&AstTypesBitset> = None; + const NODE_TYPES: Option<&AstTypesBitset> = + Some(&AstTypesBitset::from_types(&[AstType::CallExpression, AstType::NewExpression])); const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } @@ -964,7 +972,8 @@ impl RuleRunner for crate::rules::eslint::use_isnan::UseIsnan { } impl RuleRunner for crate::rules::eslint::valid_typeof::ValidTypeof { - const NODE_TYPES: Option<&AstTypesBitset> = None; + const NODE_TYPES: Option<&AstTypesBitset> = + Some(&AstTypesBitset::from_types(&[AstType::UnaryExpression])); const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } @@ -1434,12 +1443,14 @@ impl RuleRunner for crate::rules::jsdoc::no_defaults::NoDefaults { } impl RuleRunner for crate::rules::jsdoc::require_param::RequireParam { - const NODE_TYPES: Option<&AstTypesBitset> = None; + const NODE_TYPES: Option<&AstTypesBitset> = + Some(&AstTypesBitset::from_types(&[AstType::ArrowFunctionExpression, AstType::Function])); const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } impl RuleRunner for crate::rules::jsdoc::require_param_description::RequireParamDescription { - const NODE_TYPES: Option<&AstTypesBitset> = None; + const NODE_TYPES: Option<&AstTypesBitset> = + Some(&AstTypesBitset::from_types(&[AstType::ArrowFunctionExpression, AstType::Function])); const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } @@ -1449,7 +1460,8 @@ impl RuleRunner for crate::rules::jsdoc::require_param_name::RequireParamName { } impl RuleRunner for crate::rules::jsdoc::require_param_type::RequireParamType { - const NODE_TYPES: Option<&AstTypesBitset> = None; + const NODE_TYPES: Option<&AstTypesBitset> = + Some(&AstTypesBitset::from_types(&[AstType::ArrowFunctionExpression, AstType::Function])); const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } @@ -1953,7 +1965,8 @@ impl RuleRunner for crate::rules::oxc::number_arg_out_of_range::NumberArgOutOfRa } impl RuleRunner for crate::rules::oxc::only_used_in_recursion::OnlyUsedInRecursion { - const NODE_TYPES: Option<&AstTypesBitset> = None; + const NODE_TYPES: Option<&AstTypesBitset> = + Some(&AstTypesBitset::from_types(&[AstType::ArrowFunctionExpression, AstType::Function])); const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } @@ -2269,7 +2282,8 @@ impl RuleRunner for crate::rules::react::prefer_es6_class::PreferEs6Class { } impl RuleRunner for crate::rules::react::react_in_jsx_scope::ReactInJsxScope { - const NODE_TYPES: Option<&AstTypesBitset> = None; + const NODE_TYPES: Option<&AstTypesBitset> = + Some(&AstTypesBitset::from_types(&[AstType::JSXFragment, AstType::JSXOpeningElement])); const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } @@ -2707,7 +2721,11 @@ impl RuleRunner for crate::rules::typescript::no_var_requires::NoVarRequires { } impl RuleRunner for crate::rules::typescript::no_wrapper_object_types::NoWrapperObjectTypes { - const NODE_TYPES: Option<&AstTypesBitset> = None; + const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[ + AstType::TSClassImplements, + AstType::TSInterfaceHeritage, + AstType::TSTypeReference, + ])); const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } @@ -2885,12 +2903,19 @@ impl RuleRunner } impl RuleRunner for crate::rules::unicorn::consistent_function_scoping::ConsistentFunctionScoping { - const NODE_TYPES: Option<&AstTypesBitset> = None; + const NODE_TYPES: Option<&AstTypesBitset> = + Some(&AstTypesBitset::from_types(&[AstType::ArrowFunctionExpression, AstType::Function])); const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } impl RuleRunner for crate::rules::unicorn::empty_brace_spaces::EmptyBraceSpaces { - const NODE_TYPES: Option<&AstTypesBitset> = None; + const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[ + AstType::BlockStatement, + AstType::Class, + AstType::FunctionBody, + AstType::ObjectExpression, + AstType::StaticBlock, + ])); const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } @@ -2937,7 +2962,10 @@ impl RuleRunner for crate::rules::unicorn::no_accessor_recursion::NoAccessorRecu } impl RuleRunner for crate::rules::unicorn::no_anonymous_default_export::NoAnonymousDefaultExport { - const NODE_TYPES: Option<&AstTypesBitset> = None; + const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[ + AstType::AssignmentExpression, + AstType::ExportDefaultDeclaration, + ])); const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } @@ -3240,7 +3268,8 @@ impl RuleRunner for crate::rules::unicorn::no_zero_fractions::NoZeroFractions { } impl RuleRunner for crate::rules::unicorn::number_literal_case::NumberLiteralCase { - const NODE_TYPES: Option<&AstTypesBitset> = None; + const NODE_TYPES: Option<&AstTypesBitset> = + Some(&AstTypesBitset::from_types(&[AstType::BigIntLiteral, AstType::NumericLiteral])); const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } @@ -3384,7 +3413,11 @@ impl RuleRunner for crate::rules::unicorn::prefer_math_min_max::PreferMathMinMax } impl RuleRunner for crate::rules::unicorn::prefer_math_trunc::PreferMathTrunc { - const NODE_TYPES: Option<&AstTypesBitset> = None; + const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[ + AstType::AssignmentExpression, + AstType::BinaryExpression, + AstType::UnaryExpression, + ])); const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } @@ -3566,7 +3599,8 @@ impl RuleRunner for crate::rules::unicorn::switch_case_braces::SwitchCaseBraces impl RuleRunner for crate::rules::unicorn::text_encoding_identifier_case::TextEncodingIdentifierCase { - const NODE_TYPES: Option<&AstTypesBitset> = None; + const NODE_TYPES: Option<&AstTypesBitset> = + Some(&AstTypesBitset::from_types(&[AstType::JSXText, AstType::StringLiteral])); const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } diff --git a/tasks/linter_codegen/src/early_diverge_detector.rs b/tasks/linter_codegen/src/early_diverge_detector.rs new file mode 100644 index 0000000000000..1f01e4990d56c --- /dev/null +++ b/tasks/linter_codegen/src/early_diverge_detector.rs @@ -0,0 +1,78 @@ +use syn::{Arm, Expr, Pat, Stmt}; + +use crate::{ + CollectionResult, NodeTypeSet, + utils::{astkind_variant_from_path, is_node_kind_call}, +}; + +/// Detects various kinds of diverging statements that narrow by more than one AST node type. +pub struct EarlyDivergeDetector { + node_types: NodeTypeSet, +} + +impl EarlyDivergeDetector { + pub fn from_run_func(run_func: &syn::ImplItemFn) -> Option { + // Only look at cases where the body has more than one top-level statement. + let block = &run_func.block; + if block.stmts.len() <= 1 { + return None; + } + + // Look at the first statement in the function body. + let stmt = block.stmts.first()?; + + // Check if it's `let something = match node.kind() { ... }` that diverges + if let Stmt::Local(local) = stmt + && let Some(init) = &local.init + && let Expr::Match(match_expr) = &*init.expr + && is_node_kind_call(&match_expr.expr) + { + let mut detector = Self { node_types: NodeTypeSet::new() }; + let result = detector.extract_variants_from_diverging_match_expr(match_expr); + if result == CollectionResult::Incomplete || detector.node_types.is_empty() { + return None; + } + return Some(detector.node_types); + } + + None + } + + fn extract_variants_from_diverging_match_expr( + &mut self, + match_expr: &syn::ExprMatch, + ) -> CollectionResult { + let mut overall_result = CollectionResult::Complete; + for arm in &match_expr.arms { + let result = self.extract_variants_from_diverging_match_arm(arm); + if result == CollectionResult::Incomplete { + overall_result = CollectionResult::Incomplete; + } + } + overall_result + } + + fn extract_variants_from_diverging_match_arm(&mut self, arm: &Arm) -> CollectionResult { + let pat = &arm.pat; + match pat { + Pat::TupleStruct(ts) => { + if let Some(variant) = astkind_variant_from_path(&ts.path) { + // NOTE: If there is a guard, we assume that it may or may not be taken and collect all AST kinds + // regardless of the guard condition. + self.node_types.insert(variant); + CollectionResult::Complete + } else { + CollectionResult::Incomplete + } + } + Pat::Wild(_) => { + // Body must be diverging (i.e., returning from function) + if let Expr::Return(_) = *arm.body { + return CollectionResult::Complete; + } + CollectionResult::Incomplete + } + _ => CollectionResult::Incomplete, + } + } +} diff --git a/tasks/linter_codegen/src/main.rs b/tasks/linter_codegen/src/main.rs index bec83592dee5d..48b7168f0734d 100644 --- a/tasks/linter_codegen/src/main.rs +++ b/tasks/linter_codegen/src/main.rs @@ -1,6 +1,7 @@ #![allow(clippy::print_stdout)] use crate::{ + early_diverge_detector::EarlyDivergeDetector, if_else_detector::IfElseKindDetector, let_else_detector::LetElseDetector, match_detector::MatchDetector, @@ -17,6 +18,7 @@ use std::{ }; use syn::File; +mod early_diverge_detector; mod if_else_detector; mod let_else_detector; mod match_detector; @@ -131,6 +133,13 @@ fn detect_top_level_node_types(file: &File, rule: &RuleEntry) -> Option