Skip to content

Commit b27c5b9

Browse files
committed
perf(linter): add codegen support for regex nodes (#14874)
Adds linter codegen support for recognizing `run` functions which have only a call to `run_regex_node` in them. In that case, we return the `AstKind`s that we parsed out from the `run_regex_node` file and return that as the node types to check for.
1 parent 1611b4f commit b27c5b9

File tree

3 files changed

+72
-4
lines changed

3 files changed

+72
-4
lines changed

crates/oxc_linter/src/generated/rule_runner_impls.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,11 @@ impl RuleRunner for crate::rules::eslint::no_continue::NoContinue {
285285
}
286286

287287
impl RuleRunner for crate::rules::eslint::no_control_regex::NoControlRegex {
288-
const NODE_TYPES: Option<&AstTypesBitset> = None;
288+
const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[
289+
AstType::CallExpression,
290+
AstType::NewExpression,
291+
AstType::RegExpLiteral,
292+
]));
289293
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
290294
}
291295

@@ -506,7 +510,11 @@ impl RuleRunner for crate::rules::eslint::no_magic_numbers::NoMagicNumbers {
506510
impl RuleRunner
507511
for crate::rules::eslint::no_misleading_character_class::NoMisleadingCharacterClass
508512
{
509-
const NODE_TYPES: Option<&AstTypesBitset> = None;
513+
const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[
514+
AstType::CallExpression,
515+
AstType::NewExpression,
516+
AstType::RegExpLiteral,
517+
]));
510518
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
511519
}
512520

@@ -804,7 +812,11 @@ impl RuleRunner for crate::rules::eslint::no_unused_vars::NoUnusedVars {
804812
}
805813

806814
impl RuleRunner for crate::rules::eslint::no_useless_backreference::NoUselessBackreference {
807-
const NODE_TYPES: Option<&AstTypesBitset> = None;
815+
const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[
816+
AstType::CallExpression,
817+
AstType::NewExpression,
818+
AstType::RegExpLiteral,
819+
]));
808820
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run;
809821
}
810822

tasks/linter_codegen/src/main.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ mod let_else_detector;
2525
mod match_detector;
2626
mod member_expression_kinds;
2727
mod node_type_set;
28+
mod regex_node_kinds;
2829
mod rules;
2930
mod utils;
3031

@@ -45,7 +46,9 @@ pub fn generate_rule_runner_impls() -> io::Result<()> {
4546

4647
let member_expression_kinds =
4748
get_member_expression_kinds().expect("Failed to get member expression kinds");
48-
let rule_runner_data = RuleRunnerData { member_expression_kinds };
49+
let regex_node_kinds =
50+
regex_node_kinds::get_regex_node_kinds().expect("Failed to get regex node kinds");
51+
let rule_runner_data = RuleRunnerData { member_expression_kinds, regex_node_kinds };
4952

5053
let mut out = String::new();
5154
out.push_str("// Auto-generated code, DO NOT EDIT DIRECTLY!\n");
@@ -152,6 +155,16 @@ fn detect_top_level_node_types(
152155
return Some(node_types);
153156
}
154157

158+
// Detect if entire body is call to `run_on_regex_node` and return those node types
159+
if run_func.block.stmts.len() == 1
160+
&& let syn::Stmt::Expr(syn::Expr::Call(call_expr), _) = &run_func.block.stmts[0]
161+
&& call_expr.args.len() == 3
162+
&& let syn::Expr::Path(path_expr) = &*call_expr.func
163+
&& path_expr.path.is_ident("run_on_regex_node")
164+
{
165+
return Some(rule_runner_data.regex_node_kinds.clone());
166+
}
167+
155168
None
156169
}
157170

@@ -199,6 +212,7 @@ enum CollectionResult {
199212
/// Additional data collected for rule runner impl generation
200213
struct RuleRunnerData {
201214
member_expression_kinds: NodeTypeSet,
215+
regex_node_kinds: NodeTypeSet,
202216
}
203217

204218
/// Format Rust code with `rustfmt`.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
use syn::{Expr, Pat, Stmt};
2+
3+
use crate::{node_type_set::NodeTypeSet, utils::astkind_variant_from_path};
4+
5+
/// Fetches the current list of variants that are handled by `run_on_regex_node`.
6+
/// We read the source file to avoid hardcoding the list here and ensure this will stay updated.
7+
pub fn get_regex_node_kinds() -> Option<NodeTypeSet> {
8+
// Read crates/oxc_linter/src/utils/regex.rs and extract all variants in `run_on_regex_node` function
9+
let regex_utils_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
10+
.parent()?
11+
.parent()?
12+
.join("crates")
13+
.join("oxc_linter")
14+
.join("src")
15+
.join("utils")
16+
.join("regex.rs");
17+
let content = std::fs::read_to_string(regex_utils_path).ok()?;
18+
let syntax = syn::parse_file(&content).ok()?;
19+
let mut node_type_set = NodeTypeSet::new();
20+
for item in syntax.items {
21+
if let syn::Item::Fn(func) = item
22+
&& func.sig.ident == "run_on_regex_node"
23+
{
24+
// Look for `match node.kind() { ... }` inside the function body
25+
for stmt in &func.block.stmts {
26+
if let Stmt::Expr(Expr::Match(match_expr), _) = stmt {
27+
for arm in &match_expr.arms {
28+
if let Pat::TupleStruct(ts) = &arm.pat
29+
&& let Some(variant) = astkind_variant_from_path(&ts.path)
30+
{
31+
node_type_set.insert(variant);
32+
}
33+
}
34+
if !node_type_set.is_empty() {
35+
return Some(node_type_set);
36+
}
37+
}
38+
}
39+
}
40+
}
41+
None
42+
}

0 commit comments

Comments
 (0)