From 7a1243ebeee715ee182813a284cf8196eb6de27e Mon Sep 17 00:00:00 2001 From: Cam McHenry Date: Wed, 28 Oct 2026 22:28:45 -0400 Subject: [PATCH] perf(linter/unicorn/no-invalid-fetch-options): narrow AST dispatch (#28354) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Start from object literals used directly as the options argument, preserving existing wrapper and property behavior. Reduce fixture callbacks from 64,027 to 14,125. --- 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 +- .../rules/unicorn/no_invalid_fetch_options.rs | 54 +++++++++---------- .../track_linter_timings/linter_timings.snap | 2 +- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/crates/oxc_linter/src/generated/rule_runner_impls.rs b/crates/oxc_linter/src/generated/rule_runner_impls.rs index 21349411ce..bf1cd6b3cf 100644 --- a/crates/oxc_linter/src/generated/rule_runner_impls.rs +++ b/crates/oxc_linter/src/generated/rule_runner_impls.rs @@ -3384,7 +3384,7 @@ impl RuleRunner for crate::rules::unicorn::no_instanceof_builtins::NoInstanceofB impl RuleRunner for crate::rules::unicorn::no_invalid_fetch_options::NoInvalidFetchOptions { const NODE_TYPES: Option<&AstTypesBitset> = - Some(&AstTypesBitset::from_types(&[AstType::CallExpression, AstType::NewExpression])); + Some(&AstTypesBitset::from_types(&[AstType::ObjectExpression])); const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } diff --git a/crates/oxc_linter/src/rules/unicorn/no_invalid_fetch_options.rs b/crates/oxc_linter/src/rules/unicorn/no_invalid_fetch_options.rs index 86d09259e0..e4ffc74f60 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_invalid_fetch_options.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_invalid_fetch_options.rs @@ -2,18 +2,17 @@ use std::borrow::Cow; use crate::{LintContext, ast_util::is_new_expression, rule::Rule}; use cow_utils::CowUtils; -use oxc_allocator::ArenaBox; use oxc_ast::{ AstKind, ast::{ - Argument, Expression, FormalParameter, ObjectExpression, ObjectPropertyKind, PropertyKey, - TSLiteral, TSLiteralType, TSType, TSTypeAnnotation, TemplateLiteral, + Expression, FormalParameter, ObjectExpression, ObjectPropertyKind, PropertyKey, TSLiteral, + TSLiteralType, TSType, TSTypeAnnotation, TemplateLiteral, }, }; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_semantic::AstNode; -use oxc_span::Span; +use oxc_span::{GetSpan, Span}; use oxc_str::CompactStr; fn no_invalid_fetch_options_diagnostic(span: Span, method: &str) -> OxcDiagnostic { @@ -62,30 +61,25 @@ declare_oxc_lint!( impl Rule for NoInvalidFetchOptions { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { - match node.kind() { - AstKind::CallExpression(call_expr) => { - if !call_expr.callee.is_specific_id("fetch") || call_expr.arguments.len() < 2 { - return; - } - - if let Argument::ObjectExpression(expr) = &call_expr.arguments[1] - && let Some((method_name, body_span)) = is_invalid_fetch_options(expr, ctx) - { - ctx.diagnostic(no_invalid_fetch_options_diagnostic(body_span, &method_name)); - } + let AstKind::ObjectExpression(expr) = node.kind() else { + return; + }; + let arguments = match ctx.nodes().parent_kind(node.id()) { + AstKind::CallExpression(call_expr) if call_expr.callee.is_specific_id("fetch") => { + &call_expr.arguments } - AstKind::NewExpression(new_expr) => { - if !is_new_expression(new_expr, &["Request"], Some(2), None) { - return; - } - - if let Argument::ObjectExpression(expr) = &new_expr.arguments[1] - && let Some((method_name, body_span)) = is_invalid_fetch_options(expr, ctx) - { - ctx.diagnostic(no_invalid_fetch_options_diagnostic(body_span, &method_name)); - } + AstKind::NewExpression(new_expr) + if is_new_expression(new_expr, &["Request"], Some(2), None) => + { + &new_expr.arguments } - _ => {} + _ => return, + }; + if arguments.get(1).is_none_or(|argument| argument.span() != expr.span) { + return; + } + if let Some((method_name, body_span)) = is_invalid_fetch_options(expr, ctx) { + ctx.diagnostic(no_invalid_fetch_options_diagnostic(body_span, &method_name)); } } } @@ -94,7 +88,7 @@ impl Rule for NoInvalidFetchOptions { const UNKNOWN_METHOD_NAME: Cow<'static, str> = Cow::Borrowed("UNKNOWN"); fn is_invalid_fetch_options<'a>( - obj_expr: &'a ArenaBox<'_, ObjectExpression<'_>>, + obj_expr: &'a ObjectExpression<'_>, ctx: &'a LintContext<'_>, ) -> Option<(Cow<'a, str>, Span)> { // fetch and Request method defaults to "GET" @@ -299,6 +293,12 @@ fn test() { "new not_Request(url, {body})", "fetch({body}, url)", "new Request({body}, url)", + "fetch(url, {}, {body})", + "new Request(url, {}, {body})", + "fetch(url, ({body}))", + "new Request(url, ({body}))", + "fetch(url, {body} as RequestInit)", + "fetch(url, {options: {body}})", r#"fetch(url, {[body]: "foo=bar"})"#, r#"new Request(url, {[body]: "foo=bar"})"#, r#"fetch(url, {body: "foo=bar", body: undefined});"#, diff --git a/tasks/track_linter_timings/linter_timings.snap b/tasks/track_linter_timings/linter_timings.snap index a3b80d482a..734915f3c3 100644 --- a/tasks/track_linter_timings/linter_timings.snap +++ b/tasks/track_linter_timings/linter_timings.snap @@ -548,7 +548,7 @@ unicorn/no-hex-escape | 63282 unicorn/no-immediate-mutation | 24802 unicorn/no-instanceof-array | 20604 unicorn/no-instanceof-builtins | 20604 -unicorn/no-invalid-fetch-options | 64027 +unicorn/no-invalid-fetch-options | 14125 unicorn/no-invalid-remove-event-listener | 62606 unicorn/no-length-as-slice-end | 62606 unicorn/no-lonely-if | 13190