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.
This commit is contained in:
Boshen committed 2026-08-09 14:20:32 +00:00
1 parent fd02a8956e
commit 2de4ec24ab
9 files changed
+31 -18

No files matched your search

@@ -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),
@@ -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;
}
@@ -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"
@@ -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++) {}",
];
@@ -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 }])),
@@ -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),
@@ -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
@@ -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());
}
@@ -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;