Skip to content

Commit 12a9934

Browse files
committed
perf(linter): detect diverging match blocks for node type skipping (#14631)
Adds a new kind of code analysis to the linter codegen. This new detector looks for diverging statements in lint rules, like: ```rust let something = match node.kind() { AstKind::CallExpression(_) => { ... } _ => return, } ``` The important part is that the expression _must_ diverge (i.e., it must have something like a `return` in its wildcard block). That way, we know only the matched node types in the match arms are used in the rule.
1 parent 82196e7 commit 12a9934

File tree

3 files changed

+140
-19
lines changed

3 files changed

+140
-19
lines changed

crates/oxc_linter/src/generated/rule_runner_impls.rs

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ use oxc_semantic::AstTypesBitset;
99
use crate::rule::{RuleRunFunctionsImplemented, RuleRunner};
1010

1111
impl RuleRunner for crate::rules::eslint::array_callback_return::ArrayCallbackReturn {
12-
const NODE_TYPES: Option<&AstTypesBitset> = None;
12+
const NODE_TYPES: Option<&AstTypesBitset> =
13+
Some(&AstTypesBitset::from_types(&[AstType::ArrowFunctionExpression, AstType::Function]));
1314
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
1415
}
1516

@@ -164,7 +165,8 @@ impl RuleRunner for crate::rules::eslint::no_alert::NoAlert {
164165
}
165166

166167
impl RuleRunner for crate::rules::eslint::no_array_constructor::NoArrayConstructor {
167-
const NODE_TYPES: Option<&AstTypesBitset> = None;
168+
const NODE_TYPES: Option<&AstTypesBitset> =
169+
Some(&AstTypesBitset::from_types(&[AstType::CallExpression, AstType::NewExpression]));
168170
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
169171
}
170172

@@ -175,7 +177,8 @@ impl RuleRunner for crate::rules::eslint::no_async_promise_executor::NoAsyncProm
175177
}
176178

177179
impl RuleRunner for crate::rules::eslint::no_await_in_loop::NoAwaitInLoop {
178-
const NODE_TYPES: Option<&AstTypesBitset> = None;
180+
const NODE_TYPES: Option<&AstTypesBitset> =
181+
Some(&AstTypesBitset::from_types(&[AstType::AwaitExpression, AstType::ForOfStatement]));
179182
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
180183
}
181184

@@ -225,7 +228,10 @@ impl RuleRunner for crate::rules::eslint::no_cond_assign::NoCondAssign {
225228
}
226229

227230
impl RuleRunner for crate::rules::eslint::no_console::NoConsole {
228-
const NODE_TYPES: Option<&AstTypesBitset> = None;
231+
const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[
232+
AstType::ComputedMemberExpression,
233+
AstType::StaticMemberExpression,
234+
]));
229235
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
230236
}
231237

@@ -429,7 +435,8 @@ impl RuleRunner for crate::rules::eslint::no_inner_declarations::NoInnerDeclarat
429435
}
430436

431437
impl RuleRunner for crate::rules::eslint::no_invalid_regexp::NoInvalidRegexp {
432-
const NODE_TYPES: Option<&AstTypesBitset> = None;
438+
const NODE_TYPES: Option<&AstTypesBitset> =
439+
Some(&AstTypesBitset::from_types(&[AstType::CallExpression, AstType::NewExpression]));
433440
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
434441
}
435442

@@ -547,7 +554,8 @@ impl RuleRunner for crate::rules::eslint::no_obj_calls::NoObjCalls {
547554
}
548555

549556
impl RuleRunner for crate::rules::eslint::no_object_constructor::NoObjectConstructor {
550-
const NODE_TYPES: Option<&AstTypesBitset> = None;
557+
const NODE_TYPES: Option<&AstTypesBitset> =
558+
Some(&AstTypesBitset::from_types(&[AstType::CallExpression, AstType::NewExpression]));
551559
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
552560
}
553561

@@ -964,7 +972,8 @@ impl RuleRunner for crate::rules::eslint::use_isnan::UseIsnan {
964972
}
965973

966974
impl RuleRunner for crate::rules::eslint::valid_typeof::ValidTypeof {
967-
const NODE_TYPES: Option<&AstTypesBitset> = None;
975+
const NODE_TYPES: Option<&AstTypesBitset> =
976+
Some(&AstTypesBitset::from_types(&[AstType::UnaryExpression]));
968977
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
969978
}
970979

@@ -1434,12 +1443,14 @@ impl RuleRunner for crate::rules::jsdoc::no_defaults::NoDefaults {
14341443
}
14351444

14361445
impl RuleRunner for crate::rules::jsdoc::require_param::RequireParam {
1437-
const NODE_TYPES: Option<&AstTypesBitset> = None;
1446+
const NODE_TYPES: Option<&AstTypesBitset> =
1447+
Some(&AstTypesBitset::from_types(&[AstType::ArrowFunctionExpression, AstType::Function]));
14381448
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
14391449
}
14401450

14411451
impl RuleRunner for crate::rules::jsdoc::require_param_description::RequireParamDescription {
1442-
const NODE_TYPES: Option<&AstTypesBitset> = None;
1452+
const NODE_TYPES: Option<&AstTypesBitset> =
1453+
Some(&AstTypesBitset::from_types(&[AstType::ArrowFunctionExpression, AstType::Function]));
14431454
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
14441455
}
14451456

@@ -1449,7 +1460,8 @@ impl RuleRunner for crate::rules::jsdoc::require_param_name::RequireParamName {
14491460
}
14501461

14511462
impl RuleRunner for crate::rules::jsdoc::require_param_type::RequireParamType {
1452-
const NODE_TYPES: Option<&AstTypesBitset> = None;
1463+
const NODE_TYPES: Option<&AstTypesBitset> =
1464+
Some(&AstTypesBitset::from_types(&[AstType::ArrowFunctionExpression, AstType::Function]));
14531465
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
14541466
}
14551467

@@ -1953,7 +1965,8 @@ impl RuleRunner for crate::rules::oxc::number_arg_out_of_range::NumberArgOutOfRa
19531965
}
19541966

19551967
impl RuleRunner for crate::rules::oxc::only_used_in_recursion::OnlyUsedInRecursion {
1956-
const NODE_TYPES: Option<&AstTypesBitset> = None;
1968+
const NODE_TYPES: Option<&AstTypesBitset> =
1969+
Some(&AstTypesBitset::from_types(&[AstType::ArrowFunctionExpression, AstType::Function]));
19571970
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
19581971
}
19591972

@@ -2269,7 +2282,8 @@ impl RuleRunner for crate::rules::react::prefer_es6_class::PreferEs6Class {
22692282
}
22702283

22712284
impl RuleRunner for crate::rules::react::react_in_jsx_scope::ReactInJsxScope {
2272-
const NODE_TYPES: Option<&AstTypesBitset> = None;
2285+
const NODE_TYPES: Option<&AstTypesBitset> =
2286+
Some(&AstTypesBitset::from_types(&[AstType::JSXFragment, AstType::JSXOpeningElement]));
22732287
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
22742288
}
22752289

@@ -2707,7 +2721,11 @@ impl RuleRunner for crate::rules::typescript::no_var_requires::NoVarRequires {
27072721
}
27082722

27092723
impl RuleRunner for crate::rules::typescript::no_wrapper_object_types::NoWrapperObjectTypes {
2710-
const NODE_TYPES: Option<&AstTypesBitset> = None;
2724+
const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[
2725+
AstType::TSClassImplements,
2726+
AstType::TSInterfaceHeritage,
2727+
AstType::TSTypeReference,
2728+
]));
27112729
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
27122730
}
27132731

@@ -2885,12 +2903,19 @@ impl RuleRunner
28852903
}
28862904

28872905
impl RuleRunner for crate::rules::unicorn::consistent_function_scoping::ConsistentFunctionScoping {
2888-
const NODE_TYPES: Option<&AstTypesBitset> = None;
2906+
const NODE_TYPES: Option<&AstTypesBitset> =
2907+
Some(&AstTypesBitset::from_types(&[AstType::ArrowFunctionExpression, AstType::Function]));
28892908
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
28902909
}
28912910

28922911
impl RuleRunner for crate::rules::unicorn::empty_brace_spaces::EmptyBraceSpaces {
2893-
const NODE_TYPES: Option<&AstTypesBitset> = None;
2912+
const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[
2913+
AstType::BlockStatement,
2914+
AstType::Class,
2915+
AstType::FunctionBody,
2916+
AstType::ObjectExpression,
2917+
AstType::StaticBlock,
2918+
]));
28942919
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
28952920
}
28962921

@@ -2937,7 +2962,10 @@ impl RuleRunner for crate::rules::unicorn::no_accessor_recursion::NoAccessorRecu
29372962
}
29382963

29392964
impl RuleRunner for crate::rules::unicorn::no_anonymous_default_export::NoAnonymousDefaultExport {
2940-
const NODE_TYPES: Option<&AstTypesBitset> = None;
2965+
const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[
2966+
AstType::AssignmentExpression,
2967+
AstType::ExportDefaultDeclaration,
2968+
]));
29412969
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
29422970
}
29432971

@@ -3240,7 +3268,8 @@ impl RuleRunner for crate::rules::unicorn::no_zero_fractions::NoZeroFractions {
32403268
}
32413269

32423270
impl RuleRunner for crate::rules::unicorn::number_literal_case::NumberLiteralCase {
3243-
const NODE_TYPES: Option<&AstTypesBitset> = None;
3271+
const NODE_TYPES: Option<&AstTypesBitset> =
3272+
Some(&AstTypesBitset::from_types(&[AstType::BigIntLiteral, AstType::NumericLiteral]));
32443273
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
32453274
}
32463275

@@ -3384,7 +3413,11 @@ impl RuleRunner for crate::rules::unicorn::prefer_math_min_max::PreferMathMinMax
33843413
}
33853414

33863415
impl RuleRunner for crate::rules::unicorn::prefer_math_trunc::PreferMathTrunc {
3387-
const NODE_TYPES: Option<&AstTypesBitset> = None;
3416+
const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[
3417+
AstType::AssignmentExpression,
3418+
AstType::BinaryExpression,
3419+
AstType::UnaryExpression,
3420+
]));
33883421
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
33893422
}
33903423

@@ -3566,7 +3599,8 @@ impl RuleRunner for crate::rules::unicorn::switch_case_braces::SwitchCaseBraces
35663599
impl RuleRunner
35673600
for crate::rules::unicorn::text_encoding_identifier_case::TextEncodingIdentifierCase
35683601
{
3569-
const NODE_TYPES: Option<&AstTypesBitset> = None;
3602+
const NODE_TYPES: Option<&AstTypesBitset> =
3603+
Some(&AstTypesBitset::from_types(&[AstType::JSXText, AstType::StringLiteral]));
35703604
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
35713605
}
35723606

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
use syn::{Arm, Expr, Pat, Stmt};
2+
3+
use crate::{
4+
CollectionResult, NodeTypeSet,
5+
utils::{astkind_variant_from_path, is_node_kind_call},
6+
};
7+
8+
/// Detects various kinds of diverging statements that narrow by more than one AST node type.
9+
pub struct EarlyDivergeDetector {
10+
node_types: NodeTypeSet,
11+
}
12+
13+
impl EarlyDivergeDetector {
14+
pub fn from_run_func(run_func: &syn::ImplItemFn) -> Option<NodeTypeSet> {
15+
// Only look at cases where the body has more than one top-level statement.
16+
let block = &run_func.block;
17+
if block.stmts.len() <= 1 {
18+
return None;
19+
}
20+
21+
// Look at the first statement in the function body.
22+
let stmt = block.stmts.first()?;
23+
24+
// Check if it's `let something = match node.kind() { ... }` that diverges
25+
if let Stmt::Local(local) = stmt
26+
&& let Some(init) = &local.init
27+
&& let Expr::Match(match_expr) = &*init.expr
28+
&& is_node_kind_call(&match_expr.expr)
29+
{
30+
let mut detector = Self { node_types: NodeTypeSet::new() };
31+
let result = detector.extract_variants_from_diverging_match_expr(match_expr);
32+
if result == CollectionResult::Incomplete || detector.node_types.is_empty() {
33+
return None;
34+
}
35+
return Some(detector.node_types);
36+
}
37+
38+
None
39+
}
40+
41+
fn extract_variants_from_diverging_match_expr(
42+
&mut self,
43+
match_expr: &syn::ExprMatch,
44+
) -> CollectionResult {
45+
let mut overall_result = CollectionResult::Complete;
46+
for arm in &match_expr.arms {
47+
let result = self.extract_variants_from_diverging_match_arm(arm);
48+
if result == CollectionResult::Incomplete {
49+
overall_result = CollectionResult::Incomplete;
50+
}
51+
}
52+
overall_result
53+
}
54+
55+
fn extract_variants_from_diverging_match_arm(&mut self, arm: &Arm) -> CollectionResult {
56+
let pat = &arm.pat;
57+
match pat {
58+
Pat::TupleStruct(ts) => {
59+
if let Some(variant) = astkind_variant_from_path(&ts.path) {
60+
// NOTE: If there is a guard, we assume that it may or may not be taken and collect all AST kinds
61+
// regardless of the guard condition.
62+
self.node_types.insert(variant);
63+
CollectionResult::Complete
64+
} else {
65+
CollectionResult::Incomplete
66+
}
67+
}
68+
Pat::Wild(_) => {
69+
// Body must be diverging (i.e., returning from function)
70+
if let Expr::Return(_) = *arm.body {
71+
return CollectionResult::Complete;
72+
}
73+
CollectionResult::Incomplete
74+
}
75+
_ => CollectionResult::Incomplete,
76+
}
77+
}
78+
}

tasks/linter_codegen/src/main.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#![allow(clippy::print_stdout)]
22

33
use crate::{
4+
early_diverge_detector::EarlyDivergeDetector,
45
if_else_detector::IfElseKindDetector,
56
let_else_detector::LetElseDetector,
67
match_detector::MatchDetector,
@@ -17,6 +18,7 @@ use std::{
1718
};
1819
use syn::File;
1920

21+
mod early_diverge_detector;
2022
mod if_else_detector;
2123
mod let_else_detector;
2224
mod match_detector;
@@ -131,6 +133,13 @@ fn detect_top_level_node_types(file: &File, rule: &RuleEntry) -> Option<NodeType
131133
return Some(node_types);
132134
}
133135

136+
let node_types = EarlyDivergeDetector::from_run_func(run_func);
137+
if let Some(node_types) = node_types
138+
&& !node_types.is_empty()
139+
{
140+
return Some(node_types);
141+
}
142+
134143
None
135144
}
136145

0 commit comments

Comments
 (0)