From 676ee9930514321251e8834820947c52e62509f0 Mon Sep 17 00:00:00 2001 From: camchenry <1514176+camchenry@users.noreply.github.com> Date: Wed, 15 Oct 2025 09:09:12 +0000 Subject: [PATCH] perf(linter): run `no-class-assign` on class nodes instead of symbols (#14578) Enables skipping by node type. Marginally faster for files that don't contain a class. --- .../src/generated/rule_runner_impls.rs | 5 +-- .../src/rules/eslint/no_class_assign.rs | 36 +++++++++++++------ 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/crates/oxc_linter/src/generated/rule_runner_impls.rs b/crates/oxc_linter/src/generated/rule_runner_impls.rs index 62426597f3e1a..32bd52da7d7e2 100644 --- a/crates/oxc_linter/src/generated/rule_runner_impls.rs +++ b/crates/oxc_linter/src/generated/rule_runner_impls.rs @@ -200,8 +200,9 @@ impl RuleRunner for crate::rules::eslint::no_case_declarations::NoCaseDeclaratio } impl RuleRunner for crate::rules::eslint::no_class_assign::NoClassAssign { - const NODE_TYPES: Option<&AstTypesBitset> = None; - const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::RunOnSymbol; + const NODE_TYPES: Option<&AstTypesBitset> = + Some(&AstTypesBitset::from_types(&[AstType::Class])); + const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } impl RuleRunner for crate::rules::eslint::no_compare_neg_zero::NoCompareNegZero { diff --git a/crates/oxc_linter/src/rules/eslint/no_class_assign.rs b/crates/oxc_linter/src/rules/eslint/no_class_assign.rs index fe4bb841fa328..1759a3c293ee0 100644 --- a/crates/oxc_linter/src/rules/eslint/no_class_assign.rs +++ b/crates/oxc_linter/src/rules/eslint/no_class_assign.rs @@ -1,6 +1,7 @@ +use oxc_ast::{AstKind, ast::BindingIdentifier}; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; -use oxc_semantic::SymbolId; +use oxc_semantic::AstNode; use oxc_span::Span; use crate::{context::LintContext, rule::Rule}; @@ -82,17 +83,27 @@ declare_oxc_lint!( ); impl Rule for NoClassAssign { - fn run_on_symbol(&self, symbol_id: SymbolId, ctx: &LintContext<'_>) { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + let AstKind::Class(class) = node.kind() else { + return; + }; + + let Some(symbol_id) = class.id.as_ref().map(BindingIdentifier::symbol_id) else { + return; + }; + let symbol_table = ctx.scoping(); - if symbol_table.symbol_flags(symbol_id).is_class() { - for reference in symbol_table.get_resolved_references(symbol_id) { - if reference.is_write() { - ctx.diagnostic(no_class_assign_diagnostic( - symbol_table.symbol_name(symbol_id), - symbol_table.symbol_span(symbol_id), - ctx.semantic().reference_span(reference), - )); - } + // This should always be considered a class (since we got it from a class declaration), + // but we check in debug mode just to be sure. + debug_assert!(symbol_table.symbol_flags(symbol_id).is_class()); + + for reference in symbol_table.get_resolved_references(symbol_id) { + if reference.is_write() { + ctx.diagnostic(no_class_assign_diagnostic( + symbol_table.symbol_name(symbol_id), + symbol_table.symbol_span(symbol_id), + ctx.semantic().reference_span(reference), + )); } } } @@ -119,6 +130,9 @@ fn test() { ("if (foo) { class A {} } else { class A {} } A = 1;", None), // Sequence expression ("(class A {}, A = 1)", None), + // Class expressions + ("let A = class { }; A = 1;", None), + ("let A = class B { }; A = 1;", None), ]; let fail = vec![