From 93c6e6dd5ef26ca047ef2e3d4cea3f51d5ea4f89 Mon Sep 17 00:00:00 2001 From: Cam McHenry Date: Wed, 28 Oct 2026 22:28:48 -0400 Subject: [PATCH] perf(linter/unicorn/no-useless-fallback-in-spread): narrow AST dispatch (#28361) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Start from spread elements and retain the existing object-spread and parentheses checks. Reduce fixture callbacks from 12,119 to 454. --- Stack created with GitHub Stacks CLI • Give Feedback 💬 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/generated/rule_runner_impls.rs | 2 +- .../unicorn/no_useless_fallback_in_spread.rs | 19 ++++++++++--------- .../track_linter_timings/linter_timings.snap | 2 +- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/crates/oxc_linter/src/generated/rule_runner_impls.rs b/crates/oxc_linter/src/generated/rule_runner_impls.rs index 12437243b0..1fc38f6df0 100644 --- a/crates/oxc_linter/src/generated/rule_runner_impls.rs +++ b/crates/oxc_linter/src/generated/rule_runner_impls.rs @@ -3561,7 +3561,7 @@ impl RuleRunner for crate::rules::unicorn::no_useless_fallback_in_spread::NoUselessFallbackInSpread { const NODE_TYPES: Option<&AstTypesBitset> = - Some(&AstTypesBitset::from_types(&[AstType::LogicalExpression])); + Some(&AstTypesBitset::from_types(&[AstType::SpreadElement])); const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } diff --git a/crates/oxc_linter/src/rules/unicorn/no_useless_fallback_in_spread.rs b/crates/oxc_linter/src/rules/unicorn/no_useless_fallback_in_spread.rs index a5b598b9bf..d498a4219e 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_useless_fallback_in_spread.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_useless_fallback_in_spread.rs @@ -47,7 +47,12 @@ declare_oxc_lint!( impl Rule for NoUselessFallbackInSpread { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { - let AstKind::LogicalExpression(logical_expression) = node.kind() else { + let AstKind::SpreadElement(spread_element) = node.kind() else { + return; + }; + let Expression::LogicalExpression(logical_expression) = + spread_element.argument.without_parentheses() + else { return; }; @@ -69,14 +74,6 @@ impl Rule for NoUselessFallbackInSpread { return; }; - let AstKind::SpreadElement(spread_element) = parent.kind() else { - return; - }; - - let Some(parent) = outermost_paren_parent(parent, ctx) else { - return; - }; - if !matches!(parent.kind(), AstKind::ObjectExpression(_)) { return; } @@ -130,6 +127,8 @@ fn test() { "const object = call({}, ...(foo || {}))", r#"const object = {...(foo || {not: "empty"})}"#, "const object = {...(foo || {...{}})}", + "const object = {...((foo || {}) as object)}", + "const object = {...(foo || ({} as object))}", ]; let fail = vec![ @@ -162,6 +161,8 @@ fn test() { ]; let fix = vec![ + ("const object = {...((foo || {}))}", "const object = {...foo}"), + ("const object = {...(foo || {}), ...(bar ?? {})}", "const object = {...foo, ...bar}"), (r"const object = {...(foo || {})}", r"const object = {...foo}"), (r"const object = {...(foo?.bar || {})}", r"const object = {...foo?.bar}"), (r"const object = {...(foo() || {})}", r"const object = {...foo()}"), diff --git a/tasks/track_linter_timings/linter_timings.snap b/tasks/track_linter_timings/linter_timings.snap index cde02ba698..8bf06f84c7 100644 --- a/tasks/track_linter_timings/linter_timings.snap +++ b/tasks/track_linter_timings/linter_timings.snap @@ -574,7 +574,7 @@ unicorn/no-unreadable-array-destructuring | 276 unicorn/no-unreadable-iife | 62606 unicorn/no-useless-collection-argument | 1421 unicorn/no-useless-error-capture-stack-trace | 62606 -unicorn/no-useless-fallback-in-spread | 12119 +unicorn/no-useless-fallback-in-spread | 454 unicorn/no-useless-iterator-to-array | 65257 unicorn/no-useless-length-check | 12119 unicorn/no-useless-promise-resolve-reject | 62606