+
Skip to content

Conversation

arendjr
Copy link
Contributor

@arendjr arendjr commented Sep 9, 2025

Summary

Greatly improved performance of noImportCycles by eliminating allocations.

In one repository, the total runtime of Biome with only noImportCycles enabled went from ~23s down to ~4s.

Test Plan

Manually tested on the unleash repository.

Docs

N/A

Copy link

changeset-bot bot commented Sep 9, 2025

🦋 Changeset detected

Latest commit: 46a6eaa

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 13 packages
Name Type
@biomejs/biome Patch
@biomejs/cli-win32-x64 Patch
@biomejs/cli-win32-arm64 Patch
@biomejs/cli-darwin-x64 Patch
@biomejs/cli-darwin-arm64 Patch
@biomejs/cli-linux-x64 Patch
@biomejs/cli-linux-arm64 Patch
@biomejs/cli-linux-x64-musl Patch
@biomejs/cli-linux-arm64-musl Patch
@biomejs/wasm-web Patch
@biomejs/wasm-bundler Patch
@biomejs/wasm-nodejs Patch
@biomejs/backend-jsonrpc Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Contributor

coderabbitai bot commented Sep 9, 2025

Walkthrough

Adds a changeset to bump the patch version of @biomejs/biome and note a performance improvement for noImportCycles. In the linter rule implementation, switches internal path handling to biome_resolver::ResolvedPath, updates stack element types accordingly, and changes the Rule state and find_cycle return types from boxed slices/strings to Vec. Adjusts path collection and traversal logic to use the new path representation, and imports ResolvedPath.

Suggested labels

A-Project

Suggested reviewers

  • ematipico

Pre-merge checks (3 passed)

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title precisely identifies the key change of improving the performance of the noImportCycles linter rule and follows the conventional “perf(linter): …” prefix. It is concise, clear, and focused on the main impact without unnecessary detail.
Description Check ✅ Passed The description clearly summarises the performance improvements achieved, the specific rule affected, and includes a test plan, making it directly related to the changes in the PR. It appropriately outlines the context and validation steps without veering off-topic.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added A-Linter Area: linter L-JavaScript Language: JavaScript and super languages labels Sep 9, 2025

if let Some(next_module_info) = ctx.module_info_for_path(path) {
stack.push((path.as_str().into(), module_info));
stack.push((resolved_path.clone(), module_info));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this one was the main culprit, because path.as_str().into() would do an allocation to store as a Box<str>, but resolved_path wraps an Arc, so cloning it is cheap.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
.changeset/cynical-cyclops-cycles.md (1)

5-7: Add a docs link to the rule.

When referencing a rule in a changeset, include a link to its documentation page. Keeps release notes navigable.

Suggested tweak:

-Greatly improved performance of `noImportCycles` by eliminating allocations.
+Greatly improved performance of [`noImportCycles`](<link-to-rule-docs>) by eliminating allocations.
crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs (2)

158-160: Prefer a boxed slice for rule state to avoid Vec capacity overhead.

State = Vec<String> grows for collection then carries unused capacity into the diagnostic. A Box<[String]> trims that, and matches prevailing style for payloads passed to diagnostics.

Apply:

-    type State = Vec<String>;
+    type State = Box<[String]>;
@@
-) -> Option<Vec<String>> {
+) -> Option<Box<[String]>> {
@@
-                let paths = Some(start_path.to_string())
-                    .into_iter()
-                    .chain(
-                        stack
-                            .iter()
-                            .filter_map(|(path, _)| path.as_path())
-                            .map(ToString::to_string),
-                    )
-                    .chain(Some(path.to_string()))
-                    .collect();
+                let paths: Box<[String]> = std::iter::once(start_path.to_string())
+                    .chain(
+                        stack
+                            .iter()
+                            .filter_map(|(path, _)| path.as_path())
+                            .map(|p| p.to_string()),
+                    )
+                    .chain(std::iter::once(path.to_string()))
+                    .collect();
                 return Some(paths);

Also applies to: 231-267


256-265: Minor: avoid ToString::to_string and Option::into_iter ceremony.

Slightly cleaner and just as fast to use std::iter::once and a closure.

If you keep Vec<String>:

-                let paths = Some(start_path.to_string())
-                    .into_iter()
+                let paths = std::iter::once(start_path.to_string())
                     .chain(
                         stack
                             .iter()
                             .filter_map(|(path, _)| path.as_path())
-                            .map(ToString::to_string),
+                            .map(|p| p.to_string()),
                     )
-                    .chain(Some(path.to_string()))
+                    .chain(std::iter::once(path.to_string()))
                     .collect();
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aa8cea3 and 46a6eaa.

📒 Files selected for processing (2)
  • .changeset/cynical-cyclops-cycles.md (1 hunks)
  • crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs (4 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
.changeset/*.md

📄 CodeRabbit inference engine (CONTRIBUTING.md)

.changeset/*.md: In changeset files, only use #### or ##### headers; avoid other header levels
Changeset descriptions should use past tense for what you did (e.g., "Added...")
Describe current Biome behavior in present tense within changesets (e.g., "Biome now supports...")
For bug fixes in changesets, start with a link to the issue (e.g., "Fixed #1234: ...")
When referencing rules or assists in changesets, include links to their documentation pages
Include a minimal code block in the changeset when applicable to demonstrate the change
End every sentence in the changeset description with a period

Files:

  • .changeset/cynical-cyclops-cycles.md
crates/biome_*_{syntax,parser,formatter,analyze,factory,semantic}/**

📄 CodeRabbit inference engine (CLAUDE.md)

Maintain the per-language crate structure: biome_{lang}_{syntax,parser,formatter,analyze,factory,semantic}

Files:

  • crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs
crates/biome_*/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place core crates under /crates/biome_*/

Files:

  • crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs
**/*.rs

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Format all Rust source files before committing (just f)

Files:

  • crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs
🧠 Learnings (12)
📓 Common learnings
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-09-05T09:13:58.901Z
Learning: Applies to crates/biome_analyze/crates/biome_js_analyze/**/src/**/*.rs : In declare_lint_rule! metadata, set version: "next"
📚 Learning: 2025-09-05T09:13:58.901Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-09-05T09:13:58.901Z
Learning: Applies to crates/biome_analyze/crates/biome_js_analyze/lib/src/lint/nursery/**/*.rs : Place all new rules in the nursery group under biome_js_analyze/lib/src/lint/nursery

Applied to files:

  • crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs
📚 Learning: 2025-08-11T11:50:12.090Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:50:12.090Z
Learning: Applies to crates/biome_js_type_info/src/**/*.rs : Represent links between types using TypeReference (not Arc) to avoid cross-module retention and recursive structures; store type data in linear vectors

Applied to files:

  • crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs
📚 Learning: 2025-08-11T11:53:15.299Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_service/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:53:15.299Z
Learning: Use the `noImportCycles` rule to observe project layout and module graph state during debugging

Applied to files:

  • crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs
📚 Learning: 2025-08-11T11:50:12.090Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:50:12.090Z
Learning: Applies to crates/biome_js_type_info/{src,biome_module_graph/src}/**/*.rs : When pattern-matching on ResolvedTypeData via as_raw_data(), ensure any nested TypeReferences are subsequently resolved using the correct ResolverId; never use the raw data with a resolver without applying the right ResolverId to avoid panics

Applied to files:

  • crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs
📚 Learning: 2025-08-11T11:50:12.090Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:50:12.090Z
Learning: Applies to crates/biome_js_type_info/{src,resolver,biome_module_graph/src}/**/*.rs : Implement and use type resolution via the TypeResolver trait; resolvers own TypeStore vectors and provide fast by-id and hashed lookups

Applied to files:

  • crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs
📚 Learning: 2025-09-05T09:13:58.901Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-09-05T09:13:58.901Z
Learning: Applies to crates/biome_analyze/crates/biome_js_analyze/**/src/**/*.rs : Prefer Box<[_]> over Vec<_> for Signals when emitting multiple diagnostics

Applied to files:

  • crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs
📚 Learning: 2025-09-05T09:13:58.901Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-09-05T09:13:58.901Z
Learning: Applies to crates/biome_analyze/crates/biome_js_analyze/**/src/**/*.rs : Avoid deep indentation and unwrap/expect in CST navigation; prefer ?, map, filter, and_then

Applied to files:

  • crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs
📚 Learning: 2025-09-05T09:13:58.901Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-09-05T09:13:58.901Z
Learning: Applies to crates/biome_analyze/crates/biome_js_analyze/**/src/**/*.rs : Avoid unnecessary string allocations in rules; compare against &str or TokenText instead of calling to_string()

Applied to files:

  • crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs
📚 Learning: 2025-08-11T11:50:12.090Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:50:12.090Z
Learning: Applies to crates/biome_js_type_info/biome_module_graph/src/js_module_info/scoped_resolver.rs : Full inference resolves TypeReference::Import across modules into TypeReference::Resolved using the module graph; do not cache full-inference results

Applied to files:

  • crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs
📚 Learning: 2025-09-05T09:13:58.901Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-09-05T09:13:58.901Z
Learning: Applies to crates/biome_analyze/crates/biome_rule_options/lib/**/*.rs : Prefer Box<[Box<str>]> over Vec<String> for lists of strings in options for memory efficiency

Applied to files:

  • crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs
📚 Learning: 2025-08-11T11:50:12.090Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:50:12.090Z
Learning: Applies to crates/biome_js_type_info/biome_module_graph/src/js_module_info/collector.rs : Thin (module-level) inference may resolve qualifiers to local declarations or globals (TypeReference::Resolved), mark imported bindings as TypeReference::Import, and set unresolved to TypeReference::Unknown; it must not look beyond the current module

Applied to files:

  • crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (24)
  • GitHub Check: Documentation
  • GitHub Check: Test Node.js API
  • GitHub Check: Bench (biome_package)
  • GitHub Check: Bench (biome_html_formatter)
  • GitHub Check: Check Dependencies
  • GitHub Check: Bench (biome_json_formatter)
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Bench (biome_configuration)
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Bench (biome_graphql_formatter)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Bench (biome_css_formatter)
  • GitHub Check: Bench (biome_json_analyze)
  • GitHub Check: Bench (biome_graphql_parser)
  • GitHub Check: Bench (biome_html_parser)
  • GitHub Check: Bench (biome_css_analyze)
  • GitHub Check: Bench (biome_css_parser)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Bench (biome_module_graph)
  • GitHub Check: Bench (biome_json_parser)
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: autofix
🔇 Additional comments (1)
crates/biome_js_analyze/src/lint/nursery/no_import_cycles.rs (1)

234-273: Nice DFS without recursion; good use of ResolvedPath to cut transient strings.

The iterative traversal plus a seen set looks solid and avoids the usual stack blow-ups. Using ResolvedPath on the stack pays off here.

Copy link

codspeed-hq bot commented Sep 9, 2025

CodSpeed Performance Report

Merging #7454 will not alter performance

Comparing arendjr:performant-cycle-detection (46a6eaa) with main (7139aad)1

Summary

✅ 133 untouched benchmarks

Footnotes

  1. No successful run was found on main (aa8cea3) during the generation of this report, so 7139aad was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@arendjr arendjr merged commit ac17183 into biomejs:main Sep 9, 2025
30 checks passed
@github-actions github-actions bot mentioned this pull request Sep 9, 2025
kedevked pushed a commit to kedevked/biome that referenced this pull request Sep 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Linter Area: linter L-JavaScript Language: JavaScript and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载