From fd5edb71b5e38d2478f6fe44938586ad1379684f Mon Sep 17 00:00:00 2001 From: Cam McHenry Date: Wed, 28 Oct 2026 23:07:02 -0400 Subject: [PATCH] perf(linter/import/no-cycle): reuse module records in diagnostic stack (#27480) Instead of storing copies of paths, we store a reference to the original module record, which has the path we need. By doing this, we don't need to repeatedly clone lots of paths, but we now need to do a lot more reference counting, but I think it's a good tradeoff. Running on `vscode`: Metric | Before | After | Change -- | -- | -- | -- Allocation calls | 19,051,530 | 16,807,784 | -11.8% Allocated bytes | 1,700,223,977 B | 1,506,926,944 B | -11.4% Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- crates/oxc_linter/src/rules/import/no_cycle.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/crates/oxc_linter/src/rules/import/no_cycle.rs b/crates/oxc_linter/src/rules/import/no_cycle.rs index 49c1a0f567..4614ac3b5b 100644 --- a/crates/oxc_linter/src/rules/import/no_cycle.rs +++ b/crates/oxc_linter/src/rules/import/no_cycle.rs @@ -1,6 +1,6 @@ use std::{ ffi::OsStr, - path::{Component, Path, PathBuf}, + path::{Component, Path}, sync::Arc, }; @@ -20,7 +20,11 @@ use crate::{ rule::{DefaultRuleConfig, Rule}, }; -fn no_cycle_diagnostic(span: Span, stack: &[(CompactStr, PathBuf)], cwd: &Path) -> OxcDiagnostic { +fn no_cycle_diagnostic( + span: Span, + stack: &[(CompactStr, Arc)], + cwd: &Path, +) -> OxcDiagnostic { let cycle_description = format_cycle(stack, cwd); OxcDiagnostic::warn("Dependency cycle detected") .with_help("Refactor to remove the cycle. Consider extracting shared code into a separate module that both files can import.") @@ -38,10 +42,11 @@ fn self_referencing_cycle_diagnostic(span: Span, is_import: bool) -> OxcDiagnost .with_label(span.primary_label("this module references itself")) } -fn format_cycle(stack: &[(CompactStr, PathBuf)], cwd: &Path) -> String { +fn format_cycle(stack: &[(CompactStr, Arc)], cwd: &Path) -> String { let mut lines = Vec::with_capacity(stack.len() * 2 + 1); - for (i, (specifier, path)) in stack.iter().enumerate() { + for (i, (specifier, module_record)) in stack.iter().enumerate() { + let path = &module_record.resolved_absolute_path; let relative_path = path .strip_prefix(cwd) .unwrap_or(path) @@ -169,8 +174,7 @@ impl Rule for NoCycle { let requested_module = module_record.requested_modules[&key][0]; let span = requested_module.span; - let mut stack = - vec![(key.clone(), loaded_module_record.resolved_absolute_path.clone())]; + let mut stack = vec![(key.clone(), Arc::clone(&loaded_module_record))]; if loaded_module_record.resolved_absolute_path == *needle { ctx.diagnostic(self_referencing_cycle_diagnostic(span, requested_module.is_import)); @@ -182,7 +186,7 @@ impl Rule for NoCycle { .filter(|(key, val), parent| self.should_traverse_module(key, val, parent)) .event(|event, (key, val), _| match event { ModuleGraphVisitorEvent::Enter => { - stack.push((key.clone(), val.resolved_absolute_path.clone())); + stack.push((key.clone(), Arc::clone(val))); } ModuleGraphVisitorEvent::Leave => { stack.pop();