From 2de4ec24ab074cd44b27134684c1c484cbea3bdb Mon Sep 17 00:00:00 2001 From: Boshen Date: Sun, 9 Aug 2026 14:20:32 +0000 Subject: [PATCH] perf(linter): reduce visitor code size (#25441) ## Summary - use `VisitJs` for linter visitors that only inspect runtime JavaScript nodes - retain full traversal where TypeScript type-space references affect correctness - add regression coverage for type-only references ## Impact - reduces the shipped oxlint binary from 13,979,872 to 13,930,208 bytes - saves 49,664 bytes (48.5 KiB, 0.355%) and reduces `__text` by 52,532 bytes - adds no allocations or dynamic dispatch AI assistance was used for analysis and implementation. The changes were reviewed and validated by the contributor. --- .../src/rules/eslint/class_methods_use_this.rs | 1 + .../oxc_linter/src/rules/eslint/no_extra_bind.rs | 4 ++-- .../oxc_linter/src/rules/eslint/no_loop_func.rs | 16 ++++++++++------ .../rules/eslint/no_unmodified_loop_condition.rs | 9 ++++++--- .../src/rules/eslint/object_shorthand.rs | 4 ++++ .../src/rules/eslint/prefer_arrow_callback.rs | 1 + crates/oxc_linter/src/rules/oxc/no_map_spread.rs | 6 +++--- .../jest_vitest/prefer_mock_return_shorthand.rs | 4 ++-- .../jest_vitest/valid_expect_in_promise.rs | 4 ++-- 9 files changed, 31 insertions(+), 18 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/class_methods_use_this.rs b/crates/oxc_linter/src/rules/eslint/class_methods_use_this.rs index d345cc6a77..e1174604f2 100644 --- a/crates/oxc_linter/src/rules/eslint/class_methods_use_this.rs +++ b/crates/oxc_linter/src/rules/eslint/class_methods_use_this.rs @@ -355,6 +355,7 @@ fn test() { ("class A { static foo = function() {} }", None), ("class A { static foo = () => {} }", None), ("class A { foo() { return class { [this.foo] = 1 }; } }", None), + ("class A { foo() { let x: typeof this.bar; } }", None), ("class A { static {} }", None), ("class A { accessor foo = function() {this} }", None), ("class A { accessor foo = () => {this} }", None), diff --git a/crates/oxc_linter/src/rules/eslint/no_extra_bind.rs b/crates/oxc_linter/src/rules/eslint/no_extra_bind.rs index 93829121e0..66d24b15b4 100644 --- a/crates/oxc_linter/src/rules/eslint/no_extra_bind.rs +++ b/crates/oxc_linter/src/rules/eslint/no_extra_bind.rs @@ -2,7 +2,7 @@ use oxc_ast::{ AstKind, ast::{Argument, Expression, Function, ThisExpression}, }; -use oxc_ast_visit::Visit; +use oxc_ast_visit::VisitJs; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_semantic::ScopeFlags; @@ -109,7 +109,7 @@ struct ThisFinder { found: bool, } -impl<'a> Visit<'a> for ThisFinder { +impl<'a> VisitJs<'a> for ThisFinder { fn visit_this_expression(&mut self, _expr: &ThisExpression) { self.found = true; } diff --git a/crates/oxc_linter/src/rules/eslint/no_loop_func.rs b/crates/oxc_linter/src/rules/eslint/no_loop_func.rs index 9e6aa898ef..51dd27cd5b 100644 --- a/crates/oxc_linter/src/rules/eslint/no_loop_func.rs +++ b/crates/oxc_linter/src/rules/eslint/no_loop_func.rs @@ -7,7 +7,7 @@ use oxc_ast::{ Function, IdentifierReference, Statement, WhileStatement, }, }; -use oxc_ast_visit::{Visit, VisitJs, walk}; +use oxc_ast_visit::{VisitJs, walk_js}; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_semantic::{AstNode, NodeId, ScopeId, SymbolId}; @@ -192,7 +192,7 @@ impl NoLoopFunc { finder.found } - fn visit_function_node<'a, V: Visit<'a>>(func_node: &AstNode<'a>, visitor: &mut V) { + fn visit_function_node<'a, V: VisitJs<'a>>(func_node: &AstNode<'a>, visitor: &mut V) { match func_node.kind() { AstKind::Function(function) => visitor.visit_function(function, ScopeFlags::Function), AstKind::ArrowFunctionExpression(arrow) => { @@ -599,16 +599,16 @@ impl<'a, 'ctx> NestedFunctionFinder<'a, 'ctx> { } } -impl<'a> Visit<'a> for NestedFunctionFinder<'a, '_> { +impl<'a> VisitJs<'a> for NestedFunctionFinder<'a, '_> { fn visit_function(&mut self, function: &Function<'a>, flags: ScopeFlags) { if self.should_walk_function(function.node_id(), function.r#async || function.generator) { - walk::walk_function(self, function, flags); + walk_js::walk_function(self, function, flags); } } fn visit_arrow_function_expression(&mut self, arrow: &ArrowFunctionExpression<'a>) { if self.should_walk_function(arrow.node_id(), arrow.r#async) { - walk::walk_arrow_function_expression(self, arrow); + walk_js::walk_arrow_function_expression(self, arrow); } } } @@ -629,7 +629,7 @@ impl<'a, 'ctx> UnsafeReferenceFinder<'a, 'ctx> { } } -impl<'a> Visit<'a> for UnsafeReferenceFinder<'a, '_> { +impl<'a> VisitJs<'a> for UnsafeReferenceFinder<'a, '_> { fn visit_identifier_reference(&mut self, identifier: &IdentifierReference<'a>) { if self.found { return; @@ -821,6 +821,10 @@ fn test() { }; } ", + // A value binding referenced only from type-space is not captured at runtime. + "for (var i = 0; i < 10; i++) { + const callback = function (): typeof i { return 0; }; + }", // Function in the for-update slot is not in the loop body. "for (var i = 0; i < l; i++, (function () { i; })()) { }", r" diff --git a/crates/oxc_linter/src/rules/eslint/no_unmodified_loop_condition.rs b/crates/oxc_linter/src/rules/eslint/no_unmodified_loop_condition.rs index 7492cafc52..d2c92cf14e 100644 --- a/crates/oxc_linter/src/rules/eslint/no_unmodified_loop_condition.rs +++ b/crates/oxc_linter/src/rules/eslint/no_unmodified_loop_condition.rs @@ -5,7 +5,7 @@ use oxc_ast::{ AstKind, ast::{Expression, IdentifierReference}, }; -use oxc_ast_visit::{Visit, walk}; +use oxc_ast_visit::{VisitJs, walk_js}; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_semantic::{NodeId, Reference, SymbolId}; @@ -315,7 +315,7 @@ impl<'a, 'ctx> ConditionSymbolsCollector<'a, 'ctx> { } } -impl<'a> Visit<'a> for ConditionSymbolsCollector<'a, '_> { +impl<'a> VisitJs<'a> for ConditionSymbolsCollector<'a, '_> { fn visit_expression(&mut self, expression: &Expression<'a>) { let is_group_expression = matches!( expression, @@ -356,7 +356,7 @@ impl<'a> Visit<'a> for ConditionSymbolsCollector<'a, '_> { return; } - walk::walk_expression(self, expression); + walk_js::walk_expression(self, expression); if is_group_expression { self.group_stack.pop(); } @@ -411,6 +411,9 @@ fn test() { "for (var foo = 0; foo; ++foo) { }", "for (var foo = 0; foo;) { ++foo }", "var foo = 0, bar = 0; for (bar; foo;) { ++foo }", + // `type_source` appears only in the type assertion and is not part of the condition. + "let keep_going = true; let type_source = 0; + while (keep_going as typeof type_source) { keep_going = false; }", "var foo; if (foo) { }", "var a = [1, 2, 3]; var len = a.length; for (var i = 0; i < len - 1; i++) {}", ]; diff --git a/crates/oxc_linter/src/rules/eslint/object_shorthand.rs b/crates/oxc_linter/src/rules/eslint/object_shorthand.rs index 1461a33990..a5a5369ef6 100644 --- a/crates/oxc_linter/src/rules/eslint/object_shorthand.rs +++ b/crates/oxc_linter/src/rules/eslint/object_shorthand.rs @@ -835,6 +835,10 @@ fn test() { "({ x: () => { this; } })", Some(serde_json::json!(["always", { "avoidExplicitReturnArrows": true }])), ), + ( + "({ x: (): typeof this.foo => { return 1; } })", + Some(serde_json::json!(["always", { "avoidExplicitReturnArrows": true }])), + ), ( "function foo() { ({ x: () => { arguments; } }) }", Some(serde_json::json!(["always", { "avoidExplicitReturnArrows": true }])), diff --git a/crates/oxc_linter/src/rules/eslint/prefer_arrow_callback.rs b/crates/oxc_linter/src/rules/eslint/prefer_arrow_callback.rs index e3956f6820..66bef5dcd0 100644 --- a/crates/oxc_linter/src/rules/eslint/prefer_arrow_callback.rs +++ b/crates/oxc_linter/src/rules/eslint/prefer_arrow_callback.rs @@ -400,6 +400,7 @@ fn test() { ("foo(a => a);", None), ("foo(function*() {});", None), ("foo(function() { this; });", None), + ("foo(function() { let x: typeof this.foo; });", None), ("foo(function bar() {});", Some(serde_json::json!([{ "allowNamedFunctions": true }]))), ("foo(function() { (() => this); });", None), ("foo(function() { this; }.bind(obj));", None), diff --git a/crates/oxc_linter/src/rules/oxc/no_map_spread.rs b/crates/oxc_linter/src/rules/oxc/no_map_spread.rs index cb792b2f01..9aabab678a 100644 --- a/crates/oxc_linter/src/rules/oxc/no_map_spread.rs +++ b/crates/oxc_linter/src/rules/oxc/no_map_spread.rs @@ -11,7 +11,7 @@ use oxc_ast::{ ObjectPropertyKind, ReturnStatement, }, }; -use oxc_ast_visit::{Visit, walk}; +use oxc_ast_visit::{VisitJs, walk_js}; use oxc_diagnostics::{LabeledSpan, OxcDiagnostic}; use oxc_macros::declare_oxc_lint; use oxc_semantic::{ReferenceId, ScopeId, SymbolId}; @@ -632,7 +632,7 @@ where } } -impl<'a, F> Visit<'a> for SpreadInReturnVisitor<'a, '_, F> +impl<'a, F> VisitJs<'a> for SpreadInReturnVisitor<'a, '_, F> where F: FnMut(Spread<'a, '_>), { @@ -640,7 +640,7 @@ where self.is_in_return = true; self.return_span = stmt.argument.as_ref().map(GetSpan::span); - walk::walk_return_statement(self, stmt); + walk_js::walk_return_statement(self, stmt); self.is_in_return = false; // NOTE: do not clear `return_span` here. We want to keep the last diff --git a/crates/oxc_linter/src/rules/shared/jest_vitest/prefer_mock_return_shorthand.rs b/crates/oxc_linter/src/rules/shared/jest_vitest/prefer_mock_return_shorthand.rs index 07445ba21c..509045504e 100644 --- a/crates/oxc_linter/src/rules/shared/jest_vitest/prefer_mock_return_shorthand.rs +++ b/crates/oxc_linter/src/rules/shared/jest_vitest/prefer_mock_return_shorthand.rs @@ -5,7 +5,7 @@ use oxc_ast::{ Statement, TaggedTemplateExpression, VariableDeclarationKind, }, }; -use oxc_ast_visit::{Visit, VisitJs}; +use oxc_ast_visit::VisitJs; use oxc_diagnostics::OxcDiagnostic; use oxc_semantic::{ReferenceId, SymbolId}; use oxc_span::{GetSpan, Span}; @@ -257,7 +257,7 @@ impl IdentifierCollectorVisitor { } } -impl<'a> Visit<'a> for IdentifierCollectorVisitor { +impl<'a> VisitJs<'a> for IdentifierCollectorVisitor { fn visit_identifier_reference(&mut self, ident: &IdentifierReference<'a>) { self.references.insert(ident.reference_id()); } diff --git a/crates/oxc_linter/src/rules/shared/jest_vitest/valid_expect_in_promise.rs b/crates/oxc_linter/src/rules/shared/jest_vitest/valid_expect_in_promise.rs index 8188aee9f5..d63b8e3aee 100644 --- a/crates/oxc_linter/src/rules/shared/jest_vitest/valid_expect_in_promise.rs +++ b/crates/oxc_linter/src/rules/shared/jest_vitest/valid_expect_in_promise.rs @@ -6,7 +6,7 @@ use oxc_ast::{ SimpleAssignmentTarget, Statement, }, }; -use oxc_ast_visit::{Visit, VisitJs, walk_js}; +use oxc_ast_visit::{VisitJs, walk_js}; use oxc_diagnostics::OxcDiagnostic; use oxc_span::{GetSpan, Span}; use oxc_str::CompactStr; @@ -259,7 +259,7 @@ struct IdentifierFinder<'b> { found: bool, } -impl<'a> Visit<'a> for IdentifierFinder<'_> { +impl<'a> VisitJs<'a> for IdentifierFinder<'_> { fn visit_identifier_reference(&mut self, ident: &oxc_ast::ast::IdentifierReference<'a>) { if ident.name == self.name { self.found = true;