From 92deebfded11ea5badf66d3a9dc45fc133923a1e Mon Sep 17 00:00:00 2001 From: Cam McHenry Date: Fri, 30 Oct 2026 12:26:33 -0400 Subject: [PATCH] perf(linter): reduce no-unmodified-loop-condition allocations (#28478) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use smallvec to avoid frequent heap allocs of small `Vec`s. On local fixtures: Metric | Before → After -- | -- Allocations | 5,338 → 2,707 (−49.3%) Allocated bytes | 406,280 → 268,840 (−33.8%) Maximum peak bytes | 1,868 → 1,772 (−5.1%) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../eslint/no_unmodified_loop_condition.rs | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/no_unmodified_loop_condition.rs b/crates/oxc_linter/src/rules/eslint/no_unmodified_loop_condition.rs index d0373defa2..07a02c6033 100644 --- a/crates/oxc_linter/src/rules/eslint/no_unmodified_loop_condition.rs +++ b/crates/oxc_linter/src/rules/eslint/no_unmodified_loop_condition.rs @@ -2,6 +2,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; use schemars::JsonSchema; use serde::Deserialize; +use smallvec::SmallVec; use oxc_allocator::GetAddress; use oxc_ast::{ @@ -145,6 +146,8 @@ impl LoopInfo { } } +type ConditionSymbols = SmallVec<[(SymbolId, NodeId, bool); 2]>; + impl NoUnmodifiedLoopCondition { fn check_loop_condition<'a>( &self, @@ -155,18 +158,17 @@ impl NoUnmodifiedLoopCondition { let mut collector = ConditionSymbolsCollector::new(ctx, self.check_conditional_expressions); collector.visit_expression(condition); - let mut standalone_symbols: Vec<(SymbolId, NodeId, bool)> = vec![]; + let mut standalone_symbols = ConditionSymbols::new(); let mut standalone_seen: FxHashSet = FxHashSet::default(); - let mut grouped_symbols: FxHashMap> = - FxHashMap::default(); + let mut grouped_symbols: FxHashMap = FxHashMap::default(); let mut grouped_seen: FxHashMap> = FxHashMap::default(); - let mut group_order: Vec = vec![]; + let mut group_order: SmallVec<[Span; 2]> = SmallVec::new(); for symbol in collector.symbols { if let Some(group_span) = symbol.group_span { if let std::collections::hash_map::Entry::Vacant(entry) = grouped_symbols.entry(group_span) { - entry.insert(vec![]); + entry.insert(SmallVec::new()); group_order.push(group_span); } let seen = grouped_seen.entry(group_span).or_default(); @@ -187,7 +189,7 @@ impl NoUnmodifiedLoopCondition { } let mut modified_cache: FxHashMap = FxHashMap::default(); - let mut diagnostics: Vec = vec![]; + let mut diagnostics: SmallVec<[NodeId; 2]> = SmallVec::new(); let mut reported_symbols: FxHashSet = FxHashSet::default(); for (symbol_id, reference_node_id, report_independently) in standalone_symbols { @@ -330,8 +332,8 @@ struct ConditionSymbolsCollector<'a, 'ctx> { ctx: &'ctx LintContext<'a>, check_conditional_expressions: bool, conditional_expression_depth: u32, - symbols: Vec, - group_stack: Vec, + symbols: SmallVec<[ConditionSymbolInfo; 2]>, + group_stack: SmallVec<[Span; 4]>, dynamic_groups: FxHashSet, } @@ -341,8 +343,8 @@ impl<'a, 'ctx> ConditionSymbolsCollector<'a, 'ctx> { ctx, check_conditional_expressions, conditional_expression_depth: 0, - symbols: vec![], - group_stack: vec![], + symbols: SmallVec::new(), + group_stack: SmallVec::new(), dynamic_groups: FxHashSet::default(), } }