Skip to content

Commit eb833e6

Browse files
committed
perf(linter): support getting as_member_expression_kind() variants
1 parent 6bb9902 commit eb833e6

File tree

5 files changed

+134
-32
lines changed

5 files changed

+134
-32
lines changed

crates/oxc_linter/src/generated/rule_runner_impls.rs

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,11 @@ impl RuleRunner for crate::rules::eslint::no_irregular_whitespace::NoIrregularWh
446446
}
447447

448448
impl RuleRunner for crate::rules::eslint::no_iterator::NoIterator {
449-
const NODE_TYPES: Option<&AstTypesBitset> = None;
449+
const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[
450+
AstType::ComputedMemberExpression,
451+
AstType::PrivateFieldExpression,
452+
AstType::StaticMemberExpression,
453+
]));
450454
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
451455
}
452456

@@ -572,7 +576,11 @@ impl RuleRunner for crate::rules::eslint::no_plusplus::NoPlusplus {
572576
}
573577

574578
impl RuleRunner for crate::rules::eslint::no_proto::NoProto {
575-
const NODE_TYPES: Option<&AstTypesBitset> = None;
579+
const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[
580+
AstType::ComputedMemberExpression,
581+
AstType::PrivateFieldExpression,
582+
AstType::StaticMemberExpression,
583+
]));
576584
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
577585
}
578586

@@ -1201,7 +1209,11 @@ impl RuleRunner for crate::rules::jest::no_confusing_set_timeout::NoConfusingSet
12011209
}
12021210

12031211
impl RuleRunner for crate::rules::jest::no_deprecated_functions::NoDeprecatedFunctions {
1204-
const NODE_TYPES: Option<&AstTypesBitset> = None;
1212+
const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[
1213+
AstType::ComputedMemberExpression,
1214+
AstType::PrivateFieldExpression,
1215+
AstType::StaticMemberExpression,
1216+
]));
12051217
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
12061218
}
12071219

@@ -2066,7 +2078,11 @@ impl RuleRunner for crate::rules::promise::prefer_catch::PreferCatch {
20662078
}
20672079

20682080
impl RuleRunner for crate::rules::promise::spec_only::SpecOnly {
2069-
const NODE_TYPES: Option<&AstTypesBitset> = None;
2081+
const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[
2082+
AstType::ComputedMemberExpression,
2083+
AstType::PrivateFieldExpression,
2084+
AstType::StaticMemberExpression,
2085+
]));
20702086
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
20712087
}
20722088

@@ -3011,7 +3027,11 @@ impl RuleRunner for crate::rules::unicorn::no_array_sort::NoArraySort {
30113027
}
30123028

30133029
impl RuleRunner for crate::rules::unicorn::no_await_expression_member::NoAwaitExpressionMember {
3014-
const NODE_TYPES: Option<&AstTypesBitset> = None;
3030+
const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[
3031+
AstType::ComputedMemberExpression,
3032+
AstType::PrivateFieldExpression,
3033+
AstType::StaticMemberExpression,
3034+
]));
30153035
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
30163036
}
30173037

tasks/linter_codegen/src/let_else_detector.rs

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
use syn::{Expr, Pat, Stmt};
22

33
use crate::{
4-
CollectionResult,
4+
CollectionResult, RuleRunnerData,
55
node_type_set::NodeTypeSet,
66
utils::{astkind_variant_from_path, is_node_kind_call},
77
};
88

99
/// Detects top-level `let AstKind::... = node.kind() else { return; }` patterns in the `run` method.
10-
pub struct LetElseDetector {
10+
pub struct LetElseDetector<'a> {
1111
node_types: NodeTypeSet,
12+
rule_runner_data: &'a RuleRunnerData,
1213
}
1314

14-
impl LetElseDetector {
15-
pub fn from_run_func(run_func: &syn::ImplItemFn) -> Option<NodeTypeSet> {
15+
impl<'a> LetElseDetector<'a> {
16+
pub fn from_run_func(
17+
run_func: &syn::ImplItemFn,
18+
rule_runner_data: &'a RuleRunnerData,
19+
) -> Option<NodeTypeSet> {
1620
// Only consider when the body's first statement is `let AstKind::... = node.kind() else { ... }`,
1721
// and the body of the `else` is just `return`.
1822
let block = &run_func.block;
@@ -37,14 +41,14 @@ impl LetElseDetector {
3741
return None;
3842
}
3943

40-
let mut detector = Self { node_types: NodeTypeSet::new() };
44+
let mut detector = Self { node_types: NodeTypeSet::new(), rule_runner_data };
4145

4246
if is_node_kind_as_call(&init.expr) {
4347
// If the initializer is `node.kind().as_<variant>()`, extract that variant.
4448
if let Expr::MethodCall(mc) = &*init.expr
45-
&& let Some(variant) = extract_variant_from_as_call(mc)
49+
&& let Some(variants) = detector.extract_variants_from_as_call(mc)
4650
{
47-
detector.node_types.insert(variant);
51+
detector.node_types.extend(variants);
4852
}
4953
} else {
5054
// Otherwise, the initializer is `node.kind()`, so extract from the pattern.
@@ -74,6 +78,25 @@ impl LetElseDetector {
7478
_ => CollectionResult::Incomplete,
7579
}
7680
}
81+
82+
fn extract_variants_from_as_call(&self, mc: &syn::ExprMethodCall) -> Option<NodeTypeSet> {
83+
// Looking for `node.kind().as_<snake_case_variant>()`
84+
let method_ident = mc.method.to_string();
85+
if !method_ident.starts_with("as_") || !mc.args.is_empty() {
86+
return None;
87+
}
88+
// Receiver must be `node.kind()`
89+
if !is_node_kind_call(&mc.receiver) {
90+
return None;
91+
}
92+
let snake_variant = &method_ident[3..]; // strip `as_`
93+
if snake_variant == "member_expression_kind" {
94+
return Some(self.rule_runner_data.member_expression_kinds.clone());
95+
}
96+
let mut node_type_set = NodeTypeSet::new();
97+
node_type_set.insert(snake_to_pascal_case(snake_variant));
98+
Some(node_type_set)
99+
}
77100
}
78101

79102
/// Checks if is `node.kind().as_some_ast_kind()`
@@ -88,23 +111,6 @@ pub fn is_node_kind_as_call(expr: &Expr) -> bool {
88111
false
89112
}
90113

91-
fn extract_variant_from_as_call(mc: &syn::ExprMethodCall) -> Option<String> {
92-
// Looking for `node.kind().as_<snake_case_variant>()`
93-
let method_ident = mc.method.to_string();
94-
if !method_ident.starts_with("as_") || !mc.args.is_empty() {
95-
return None;
96-
}
97-
// Receiver must be `node.kind()`
98-
if !is_node_kind_call(&mc.receiver) {
99-
return None;
100-
}
101-
let snake_variant = &method_ident[3..]; // strip `as_`
102-
if snake_variant == "member_expression_kind" {
103-
return None;
104-
}
105-
Some(snake_to_pascal_case(snake_variant))
106-
}
107-
108114
fn snake_to_pascal_case(s: &str) -> String {
109115
s.split('_')
110116
.filter(|seg| !seg.is_empty())

tasks/linter_codegen/src/main.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::{
55
if_else_detector::IfElseKindDetector,
66
let_else_detector::LetElseDetector,
77
match_detector::MatchDetector,
8+
member_expression_kinds::get_member_expression_kinds,
89
node_type_set::NodeTypeSet,
910
rules::{RuleEntry, find_rule_source_file, get_all_rules},
1011
utils::{find_impl_function, find_rule_impl_block},
@@ -22,6 +23,7 @@ mod early_diverge_detector;
2223
mod if_else_detector;
2324
mod let_else_detector;
2425
mod match_detector;
26+
mod member_expression_kinds;
2527
mod node_type_set;
2628
mod rules;
2729
mod utils;
@@ -32,13 +34,19 @@ fn main() -> io::Result<()> {
3234

3335
/// # Errors
3436
/// Returns `io::Error` if file operations fail.
37+
/// # Panics
38+
/// - Panics if member expression kinds could not be read from source
3539
pub fn generate_rule_runner_impls() -> io::Result<()> {
3640
let root = project_root::get_project_root()
3741
.map_err(|e| std::io::Error::other(format!("could not find project root: {e}")))?;
3842

3943
let rules_file_contents = fs::read_to_string(root.join("crates/oxc_linter/src/rules.rs"))?;
4044
let rule_entries = get_all_rules(&rules_file_contents)?;
4145

46+
let member_expression_kinds =
47+
get_member_expression_kinds().expect("Failed to get member expression kinds");
48+
let rule_runner_data = RuleRunnerData { member_expression_kinds };
49+
4250
let mut out = String::new();
4351
out.push_str("// Auto-generated code, DO NOT EDIT DIRECTLY!\n");
4452
out.push_str("// To regenerate: `cargo run -p oxc_linter_codegen`\n\n");
@@ -56,7 +64,7 @@ pub fn generate_rule_runner_impls() -> io::Result<()> {
5664
&& let Ok(src_contents) = fs::read_to_string(&src_path)
5765
&& let Ok(file) = syn::parse_file(&src_contents)
5866
{
59-
if let Some(node_types) = detect_top_level_node_types(&file, rule) {
67+
if let Some(node_types) = detect_top_level_node_types(&file, rule, &rule_runner_data) {
6068
detected_types.extend(node_types);
6169
}
6270

@@ -108,11 +116,15 @@ impl RuleRunner for crate::rules::{plugin_module}::{rule_module}::{rule_struct}
108116

109117
/// Detect the top-level node types used in a lint rule file by analyzing the Rust AST with `syn`.
110118
/// Returns `Some(bitset)` if at least one node type can be determined, otherwise `None`.
111-
fn detect_top_level_node_types(file: &File, rule: &RuleEntry) -> Option<NodeTypeSet> {
119+
fn detect_top_level_node_types(
120+
file: &File,
121+
rule: &RuleEntry,
122+
rule_runner_data: &RuleRunnerData,
123+
) -> Option<NodeTypeSet> {
112124
let rule_impl = find_rule_impl_block(file, &rule.rule_struct_name())?;
113125
let run_func = find_impl_function(rule_impl, "run")?;
114126

115-
let node_types = LetElseDetector::from_run_func(run_func);
127+
let node_types = LetElseDetector::from_run_func(run_func, rule_runner_data);
116128
if let Some(node_types) = node_types
117129
&& !node_types.is_empty()
118130
{
@@ -184,6 +196,11 @@ enum CollectionResult {
184196
Incomplete,
185197
}
186198

199+
/// Additional data collected for rule runner impl generation
200+
struct RuleRunnerData {
201+
member_expression_kinds: NodeTypeSet,
202+
}
203+
187204
/// Format Rust code with `rustfmt`.
188205
///
189206
/// Does not format on disk - interfaces with `rustfmt` via stdin/stdout.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use syn::{Expr, Pat, Stmt};
2+
3+
use crate::{node_type_set::NodeTypeSet, utils::find_impl_function};
4+
5+
/// Fetches the current list of variants that can be returned by `AstKind::as_member_expression_kind()`.
6+
/// We read the source file to avoid hardcoding the list here and ensure this will stay updated.
7+
pub fn get_member_expression_kinds() -> Option<NodeTypeSet> {
8+
// Read crates/oxc_ast/src/ast_kind_impl.rs and extract all variants in `as_member_expression_kind` function
9+
let ast_kind_impl_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
10+
.parent()?
11+
.parent()?
12+
.join("crates")
13+
.join("oxc_ast")
14+
.join("src")
15+
.join("ast_kind_impl.rs");
16+
let content = std::fs::read_to_string(ast_kind_impl_path).ok()?;
17+
let syntax = syn::parse_file(&content).ok()?;
18+
let mut node_type_set = NodeTypeSet::new();
19+
for item in syntax.items {
20+
if let syn::Item::Impl(impl_block) = item
21+
&& let syn::Type::Path(type_path) = impl_block.self_ty.as_ref()
22+
&& type_path.path.segments.last()?.ident == "AstKind"
23+
{
24+
let impl_fn = find_impl_function(&impl_block, "as_member_expression_kind")
25+
.expect("as_member_expression_kind function not found");
26+
27+
// Look for `match self { ... }` inside the function body
28+
if impl_fn.block.stmts.len() != 1 {
29+
return None;
30+
}
31+
let stmt = &impl_fn.block.stmts[0];
32+
if let Stmt::Expr(Expr::Match(match_expr), _) = stmt {
33+
for arm in &match_expr.arms {
34+
if let Pat::TupleStruct(ts) = &arm.pat
35+
&& let Some(variant) = self_astkind_variant_from_path(&ts.path)
36+
{
37+
node_type_set.insert(variant);
38+
}
39+
}
40+
if !node_type_set.is_empty() {
41+
return Some(node_type_set);
42+
}
43+
}
44+
}
45+
}
46+
None
47+
}
48+
49+
fn self_astkind_variant_from_path(path: &syn::Path) -> Option<String> {
50+
// Expect `Self::Variant`
51+
if path.segments.len() != 2 {
52+
return None;
53+
}
54+
if path.segments[0].ident != "Self" {
55+
return None;
56+
}
57+
Some(path.segments[1].ident.to_string())
58+
}

tasks/linter_codegen/src/node_type_set.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use rustc_hash::FxHashSet;
22

33
/// A set of AstKind variants, used for storing the unique node types detected in a rule,
44
/// or a portion of the rule file.
5+
#[derive(Clone)]
56
pub struct NodeTypeSet {
67
node_types: FxHashSet<String>,
78
}

0 commit comments

Comments
 (0)