Skip to content

Commit 422f54e

Browse files
camchenrycamc314
authored andcommitted
perf(linter): only run rule run functions if implemented (#14454)
Optimizes the native linter runtime by skipping rule run functions which are known to be no-ops. If we don't know anything about the rule functions implemented, we run all of them by default.
1 parent 9e9c5ba commit 422f54e

File tree

2 files changed

+61
-18
lines changed

2 files changed

+61
-18
lines changed

crates/oxc_linter/src/lib.rs

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ impl Linter {
184184
let should_run_on_jest_node =
185185
ctx_host.plugins().has_test() && ctx_host.frameworks().is_test();
186186

187-
let execute_rules = |with_ast_kind_filtering: bool| {
187+
let execute_rules = |with_runtime_optimization: bool| {
188188
// IMPORTANT: We have two branches here for performance reasons:
189189
//
190190
// 1) Branch where we iterate over each node, then each rule
@@ -217,22 +217,32 @@ impl Linter {
217217

218218
for (rule, ctx) in &rules {
219219
let rule = *rule;
220+
let run_info = rule.run_info();
220221
// Collect node type information for rules. In large files, benchmarking showed it was worth
221222
// collecting rules into buckets by AST node type to avoid iterating over all rules for each node.
222-
if with_ast_kind_filtering && let Some(ast_types) = rule.types_info() {
223+
if with_runtime_optimization
224+
&& let Some(ast_types) = rule.types_info()
225+
&& run_info.is_run_implemented()
226+
{
223227
for ty in ast_types {
224228
rules_by_ast_type[ty as usize].push((rule, ctx));
225229
}
226230
} else {
227231
rules_any_ast_type.push((rule, ctx));
228232
}
229233

230-
rule.run_once(ctx);
234+
if !with_runtime_optimization || run_info.is_run_once_implemented() {
235+
rule.run_once(ctx);
236+
}
231237
}
232238

233239
for symbol in semantic.scoping().symbol_ids() {
234240
for (rule, ctx) in &rules {
235-
rule.run_on_symbol(symbol, ctx);
241+
if !with_runtime_optimization
242+
|| rule.run_info().is_run_on_symbol_implemented()
243+
{
244+
rule.run_on_symbol(symbol, ctx);
245+
}
236246
}
237247
}
238248

@@ -249,33 +259,48 @@ impl Linter {
249259
if should_run_on_jest_node {
250260
for jest_node in iter_possible_jest_call_node(semantic) {
251261
for (rule, ctx) in &rules {
252-
rule.run_on_jest_node(&jest_node, ctx);
262+
if !with_runtime_optimization
263+
|| rule.run_info().is_run_on_jest_node_implemented()
264+
{
265+
rule.run_on_jest_node(&jest_node, ctx);
266+
}
253267
}
254268
}
255269
}
256270
} else {
257271
for (rule, ctx) in &rules {
258-
rule.run_once(ctx);
272+
let run_info = rule.run_info();
273+
if !with_runtime_optimization || run_info.is_run_once_implemented() {
274+
rule.run_once(ctx);
275+
}
259276

260-
for symbol in semantic.scoping().symbol_ids() {
261-
rule.run_on_symbol(symbol, ctx);
277+
if !with_runtime_optimization || run_info.is_run_on_symbol_implemented() {
278+
for symbol in semantic.scoping().symbol_ids() {
279+
rule.run_on_symbol(symbol, ctx);
280+
}
262281
}
263282

264-
// For smaller files, benchmarking showed it was faster to iterate over all rules and just check the
265-
// node types as we go, rather than pre-bucketing rules by AST node type and doing extra allocations.
266-
if with_ast_kind_filtering && let Some(ast_types) = rule.types_info() {
267-
for node in semantic.nodes() {
268-
if ast_types.has(node.kind().ty()) {
283+
if !with_runtime_optimization || run_info.is_run_implemented() {
284+
// For smaller files, benchmarking showed it was faster to iterate over all rules and just check the
285+
// node types as we go, rather than pre-bucketing rules by AST node type and doing extra allocations.
286+
if with_runtime_optimization && let Some(ast_types) = rule.types_info()
287+
{
288+
for node in semantic.nodes() {
289+
if ast_types.has(node.kind().ty()) {
290+
rule.run(node, ctx);
291+
}
292+
}
293+
} else {
294+
for node in semantic.nodes() {
269295
rule.run(node, ctx);
270296
}
271297
}
272-
} else {
273-
for node in semantic.nodes() {
274-
rule.run(node, ctx);
275-
}
276298
}
277299

278-
if should_run_on_jest_node {
300+
if should_run_on_jest_node
301+
&& (!with_runtime_optimization
302+
|| run_info.is_run_on_jest_node_implemented())
303+
{
279304
for jest_node in iter_possible_jest_call_node(semantic) {
280305
rule.run_on_jest_node(&jest_node, ctx);
281306
}

crates/oxc_linter/src/rule.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,24 @@ pub enum RuleRunFunctionsImplemented {
100100
RunOnJestNode,
101101
}
102102

103+
impl RuleRunFunctionsImplemented {
104+
pub fn is_run_implemented(self) -> bool {
105+
matches!(self, Self::Run | Self::Unknown)
106+
}
107+
108+
pub fn is_run_once_implemented(self) -> bool {
109+
matches!(self, Self::RunOnce | Self::Unknown)
110+
}
111+
112+
pub fn is_run_on_symbol_implemented(self) -> bool {
113+
matches!(self, Self::RunOnSymbol | Self::Unknown)
114+
}
115+
116+
pub fn is_run_on_jest_node_implemented(self) -> bool {
117+
matches!(self, Self::RunOnJestNode | Self::Unknown)
118+
}
119+
}
120+
103121
pub trait RuleMeta {
104122
const NAME: &'static str;
105123

0 commit comments

Comments
 (0)