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>
This commit is contained in:
Cam McHenryandCopilot authored and GitHub committed 2026-10-28 23:07:02 -04:00
1 parent 73e877c43c
commit fd5edb71b5
1 file changed
+11 -7
+11 -7
View File
@@ -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<ModuleRecord>)],
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<ModuleRecord>)], 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();