mirror of
https://github.com/oxc-project/oxc.git
synced 2026-09-14 19:36:11 +08:00
161d2f1b16
Migrate 33 JS-only `Visit` implementors in `oxc_linter` (plus the 3 in `tasks/rulegen`) to `VisitJs` (#24499). Part of #24317; transformer/minifier half in #24532. Behavior is unchanged — all 1169 rule tests pass with identical snapshots. Together with #24532: `oxlint` −129.4 KiB (−0.94%) stripped, `oxc_transform` napi −16.2 KiB (−0.46%). ## Why each visitor can switch A visitor is safe on `VisitJs` when its hooks cannot observe anything inside the pruned type grammar. The three ways that holds here: the hooked node kinds are value-space-only (`return`, `throw`, `await`, calls, real functions — none exist inside `TSType`), the visitor already skipped type space via hand-written `visit_ts_*` stubs, or its type inspection is by direct field access rather than walking. | Visitor | File (`crates/oxc_linter/src/`) | Why equivalent | |---|---|---| | `ReturnStatementFinder` | `rules/eslint/array_callback_return/return_checker.rs` | Hooks only `visit_return_statement`; `return` cannot occur in type grammar. | | `ComplexityVisitor` | `rules/eslint/complexity.rs` | All scoring hooks are value-space; the one explicit walk into property type annotations is deleted — valid type grammar contains no complexity-scoring constructs (no `?:`/`&&`/`?.`/default params). | | `StatementCounter` | `rules/eslint/max_statements.rs` | Counts statements via function/class/static-block body hooks; type declarations contain no statements. | | `YieldBeforeLoopExitFinder` | `rules/eslint/no_constant_condition.rs` | Hooks only `visit_yield_expression`; `yield` cannot occur in type grammar. | | `LoopFunctionCollector` | `rules/eslint/no_loop_func.rs` | Hooks `visit_function`/`visit_arrow_function_expression`; those nodes never appear in type grammar (`TSFunctionType` is a distinct node). | | `ReturnStatementFinder` | `rules/eslint/no_promise_executor_return.rs` | `return`-statement hook only. | | `ThrowFinder` | `rules/eslint/preserve_caught_error.rs` | Hooks only `visit_throw_statement`; `throw` cannot occur in type grammar. | | `AwaitFinder` | `rules/eslint/require_await.rs` | Hooks `visit_await_expression`/`visit_for_of_statement`; `await`/`for await` are runtime-only. | | `RequireCallScanner` | `rules/import/newline_after_import.rs` | Matches `require(...)` calls; calls cannot occur in valid type grammar. | | `ResolveFinder` | `rules/promise/no_multiple_resolved.rs` | `leave_node` records only value-space kinds (new/import/yield/member); its `visit_call_expression` doesn't descend. | | `ReturnWrapFinder` | `rules/promise/no_return_wrap.rs` | `return`-statement hook only. | | `AssertionVisitor` | `rules/shared/jest_vitest/expect_expect.rs` | Call/if/block hooks hunting assertion calls; none can fire in type space. | | `HookScanner`, `BodyScanner` | `rules/shared/jest_vitest/prefer_expect_assertions.rs` | Call + loop/function-body hooks; `expect.hasAssertions()` calls and loops cannot occur in types. | | `CallLikeExpressionVisitor` | `rules/shared/jest_vitest/prefer_mock_return_shorthand.rs` | call/new/tagged-template/import hooks — value-space only. (Sibling `IdentifierCollectorVisitor` stays on `Visit`: it collects identifiers, which do leak through type space.) | | `PromiseExpectScanner` | `rules/shared/jest_vitest/valid_expect_in_promise.rs` | call/await hooks. (Sibling `IdentifierFinder` stays on `Visit` for the same reason as above.) | | `AwaitDetector`, `ExposeAfterAwaitVisitor` | `rules/vue/no_expose_after_await.rs` | await/call hooks. | | `LifecycleAfterAwaitVisitor` | `rules/vue/no_lifecycle_after_await.rs` | await/call hooks. | | `WatchAfterAwaitVisitor` | `rules/vue/no_watch_after_await.rs` | await/expression-statement hooks. | | `ReturnFinder` | `rules/vue/require_direct_export.rs` | `return`-statement hook only. | | `ReturnVisitor` | `rules/vue/return_in_emits_validator.rs` | function/arrow depth tracking + return hook; none fire for `TSFunctionType`. | | `DefineOptionsChecker` | `rules/vue/valid_define_options.rs` | Matches `defineOptions()` calls; reads `type_arguments` by field access, not by walking. | | `LocalReferenceChecker` | `rules/vue/valid_define_options.rs` | Already skipped type space via a hand-written `visit_ts_type` stub ("`as X`/`typeof str` are statically resolvable") — `VisitJs` subsumes it; stub deleted. | | `ExhaustiveDepsVisitor` | `rules/react/exhaustive_deps.rs` | Already skipped type space via 4 noop `visit_ts_*` stubs (deleted). The `visit_ts_type_query` stub shows `typeof x` deps were deliberately not collected — `VisitJs` preserves exactly that. | | `ComponentFinder` | `rules/react/no_multi_comp.rs` | Component-defining hooks (class/function/call/object-property/assignment) are all value-space kinds. | | `MayThrowBeforeHook` | `rules/react/rules_of_hooks.rs` | `enter_node` treats only Call/New/Member kinds as may-throw; type annotations are erased at runtime and cannot throw. | | `ConstructorAssignmentCollector` | `rules/typescript/class_literal_property_style.rs` | Assignment-expression hook; assignments cannot occur in type grammar. | | `ReturnStatementChecker` | `rules/typescript/explicit_function_return_type.rs` | `return`-statement hook only. | | `ExplicitTypesChecker` | `rules/typescript/explicit_module_boundary_types.rs` | Inspects types by direct field access (`.return_type.is_none()`, `is_const_type_reference()`), never by walking; its 3 cast overrides survive on `VisitJs`. The deleted walk into `new` type args could only produce spurious "missing argument type" reports from function-typed type arguments. | | `AssignmentVisitor` | `rules/typescript/no_unnecessary_parameter_property_assignment.rs` | Assignment hook; RHS casts are unwrapped via `get_inner_expression`, not walker descent. | | `JsxFinder` | `utils/react.rs` | JSX/createElement hooks; JSX cannot occur in type grammar. All consumers pass value-space function bodies. | | `ThisExpressionFinder` | `utils/this_expression.rs` | The `this` type is `TSThisType`, which never fires `visit_this_expression`; the only leak path is a `typeof this.X` qualified name, invalid at the entry scopes its two consumers scan, and nested functions are already cut off by the `visit_function` stub. | | `TestCase`, `State`, `RuleConfig` | `tasks/rulegen/src/main.rs` | Extract value-space data (test `code` strings, schema object literals) via manual recursion/structural matching; `as`/`satisfies` wrappers are unwrapped with `get_inner_expression`, and `VisitJs` still walks those. | Left on `Visit` (results would change): `consistent_function_scoping`, `branches_sharing_code`, `prefer_arrow_callback`, `object_shorthand`, `class_methods_use_this`, the two jest identifier collectors above — their identifier/`this` hooks fire inside type space today (`typeof this`, type references) and that input feeds reports or autofixes. Also `no_loop_func`'s `NestedFunctionFinder`/`UnsafeReferenceFinder`, which share a `V: Visit<'a>` generic driver with one of them. --- 🤖 This PR was written with the assistance of Claude Code (Claude Fable 5).