Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

siketyan
Copy link
Member

@siketyan siketyan commented Sep 1, 2025

Summary

This pull request integrates Grit/JS plugins as syntax visitors that runs on the analyser. Previously, plugins are used to run separately, after all phases are run. To support querying AST nodes in JS plugins, we need to convert a plugin into an AST visitor and run it together with any other rules.

As a plus, I also integrated suppressions for plugins to other analyser suppressions, and range suppression are now supported for plugin diagnostics.

Test Plan

Existing tests should pass.

Docs

N/A

@siketyan siketyan self-assigned this Sep 1, 2025
Copy link

changeset-bot bot commented Sep 1, 2025

🦋 Changeset detected

Latest commit: 2d9f693

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 1, 2025

Walkthrough

Analyzer plugins are now language-aware and operate per erased syntax node (AnySyntaxNode) instead of whole parses. AnalyzerPlugin gained language() and query(); PluginTargetLanguage and PluginVisitor<L> were added. PluginVisitor traverses syntax with WalkEvent, prunes subtrees by range, evaluates matching nodes, converts diagnostics into SignalEntry with SignalRuleKey::Plugin, and enqueues signals. The analyzer runtime no longer stores plugins; JS/CSS analyzers register PluginVisitor::new_unchecked as Syntax-phase visitors. Suppression tracking and signal entries were extended to distinguish rule vs plugin suppressions. biome_rowan exposes the erased AnySyntaxNode type.

Possibly related PRs

Suggested reviewers

  • arendjr
  • dyc3
  • ematipico

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2d9f693 and 633093b.

📒 Files selected for processing (3)
  • crates/biome_analyze/src/analyzer_plugin.rs (2 hunks)
  • crates/biome_analyze/src/matcher.rs (2 hunks)
  • crates/biome_analyze/src/registry.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • crates/biome_analyze/src/analyzer_plugin.rs
  • crates/biome_analyze/src/registry.rs
  • crates/biome_analyze/src/matcher.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). (10)
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Test Node.js API
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Bench (biome_css_formatter)
  • GitHub Check: Bench (biome_css_parser)
  • GitHub Check: Bench (biome_css_analyze)
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: autofix
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

‼️ IMPORTANT
Auto-reply has been disabled for this repository in the CodeRabbit settings. The CodeRabbit bot will not respond to your replies unless it is explicitly tagged.

  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions github-actions bot added A-Core Area: core A-Project Area: project A-Linter Area: linter L-JavaScript Language: JavaScript and super languages L-CSS Language: CSS L-Grit Language: GritQL labels Sep 1, 2025
Copy link

codspeed-hq bot commented Sep 1, 2025

CodSpeed Performance Report

Merging #7369 will not alter performance

Comparing siketyan:refactor/plugin-analyzer-visitor (633093b) with main (4416573)

Summary

✅ 133 untouched benchmarks

@siketyan siketyan force-pushed the refactor/plugin-analyzer-visitor branch from f28badf to ee27873 Compare September 1, 2025 16:04
@siketyan siketyan force-pushed the refactor/plugin-analyzer-visitor branch from ee27873 to 15d3550 Compare September 1, 2025 16:11
@siketyan siketyan requested review from a team September 1, 2025 16:11
@siketyan siketyan marked this pull request as ready for review September 1, 2025 16:11
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: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/biome_analyze/src/matcher.rs (1)

148-159: Handle the Plugin variant in suppressions and reporting

  • In crates/biome_analyze/src/lib.rs around line 410, extend the match &entry.rule for suppressions to include a SignalRuleKey::Plugin(id) => … branch (or wildcard) so plugin-emitted signals aren’t silently ignored.
  • In the reporting logic around line 460, similarly handle SignalRuleKey::Plugin(id)—e.g. format or filter by the plugin key—rather than only matching on Rule(_).
🧹 Nitpick comments (10)
crates/biome_grit_patterns/src/grit_query.rs (1)

56-58: Guard the “compiled-for-this-language” invariant

from_node compiles patterns against a specific lang, while execute reads self.language. Because the field is now pub, external mutation after construction can desynchronise the compiled pattern from the execution context. Consider keeping the field private and exposing a getter to make it read-only.

Apply within this hunk:

-    /// Target language for the query.
-    pub language: GritTargetLanguage,
+    /// Target language for the query. Set at compile time; do not mutate after construction.
+    language: GritTargetLanguage,

Then add an accessor within the existing impl GritQuery:

impl GritQuery {
    /// Returns the target language used during compilation.
    pub fn language(&self) -> &GritTargetLanguage {
        &self.language
    }
}
crates/biome_analyze/src/matcher.rs (1)

141-146: Prefer Arc for plugin keys to avoid repeated allocations.

Plugin signals will likely reuse the same plugin id many times. Arc reduces cloning churn versus Box.

Apply:

-#[derive(Clone, Debug)]
-pub enum SignalRuleKey {
-    Rule(RuleKey),
-    Plugin(Box<str>),
-}
+#[derive(Clone, Debug)]
+pub enum SignalRuleKey {
+    Rule(RuleKey),
+    // Shared across many signals from the same plugin
+    Plugin(std::sync::Arc<str>),
+}

Optionally, add Display for nicer logging:

impl core::fmt::Display for SignalRuleKey {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            SignalRuleKey::Rule(k) => k.fmt(f),
            SignalRuleKey::Plugin(id) => f.write_str(id),
        }
    }
}
crates/biome_js_analyze/src/lib.rs (1)

144-155: Shrink the unsafe scope and keep it surgical.

Move the language check outside the unsafe block so only new_unchecked is unsafe. Same effect, less spooky action at a distance.

Apply:

-    for plugin in plugins {
-        // SAFETY: The plugin target language is correctly checked here.
-        unsafe {
-            if plugin.language() == PluginTargetLanguage::JavaScript {
-                analyzer.add_visitor(
-                    Phases::Syntax,
-                    Box::new(PluginVisitor::new_unchecked(plugin.clone())),
-                )
-            }
-        }
-    }
+    for plugin in plugins {
+        if plugin.language() == PluginTargetLanguage::JavaScript {
+            // SAFETY: The plugin target language was checked above.
+            let visitor = unsafe { PluginVisitor::new_unchecked(plugin.clone()) };
+            analyzer.add_visitor(Phases::Syntax, Box::new(visitor));
+        }
+    }

Minor thought: plugin visitors are appended after built-in visitors. That’s fine given heap-ordered emission, but shout if you want a stable “plugins-first/last” policy and we can wire it explicitly.

crates/biome_css_analyze/src/lib.rs (1)

118-129: Narrow the unsafe scope to the new_unchecked call.

     for plugin in plugins {
-        // SAFETY: The plugin target language is correctly checked here.
-        unsafe {
-            if plugin.language() == PluginTargetLanguage::Css {
-                analyzer.add_visitor(
-                    Phases::Syntax,
-                    Box::new(PluginVisitor::new_unchecked(plugin.clone())),
-                )
-            }
-        }
+        if plugin.language() == PluginTargetLanguage::Css {
+            // SAFETY: The plugin target language is correctly checked here.
+            let visitor = unsafe { PluginVisitor::new_unchecked(plugin.clone()) };
+            analyzer.add_visitor(Phases::Syntax, Box::new(visitor));
+        }
     }
crates/biome_rowan/src/syntax/node.rs (2)

1210-1214: Fix doc reference to the erased node type.
The comment mentions ErasedSyntaxNode, but the type is AnySyntaxNode.

-/// Marker trait to prevent unrelated types to be contained in the [`crate::ErasedSyntaxNode`] struct.
+/// Marker trait restricting which types can be stored inside [`AnySyntaxNode`].

1215-1234: Stronger type safety: store dyn AsSyntaxNode instead of dyn Any.
This enforces at the type level that only syntax nodes are boxed (aligns with the doc) while preserving the same downcast ergonomics.

-#[derive(Debug)]
-pub struct AnySyntaxNode {
-    raw: Box<dyn Any>,
-}
+#[derive(Debug)]
+pub struct AnySyntaxNode {
+    raw: Box<dyn AsSyntaxNode>,
+}
@@
 impl AnySyntaxNode {
     #[inline]
     pub fn downcast_ref<T: AsSyntaxNode>(&self) -> Option<&T> {
-        self.raw.downcast_ref()
+        // AsSyntaxNode: Any, so we can downcast via Any
+        (self.raw.as_ref() as &dyn Any).downcast_ref()
     }
 }
@@
 impl<L: Language + 'static> From<SyntaxNode<L>> for AnySyntaxNode {
     fn from(value: SyntaxNode<L>) -> Self {
-        Self {
-            raw: Box::new(value),
-        }
+        Self { raw: Box::new(value) }
     }
 }

If #[derive(Debug)] ever complains about the trait object, we can add a tiny manual Debug impl that prints the inner type name. Happy to provide it.

crates/biome_analyze/src/analyzer_plugin.rs (1)

47-55: Provide a safe constructor alongside new_unchecked

The unsafe ctor is easy to misuse. Offer a safe new(plugin) -> Option<Self> (or Result) that checks plugin.language() matches L to prevent accidental cross-language registration.

 pub unsafe fn new_unchecked(plugin: Arc<Box<dyn AnalyzerPlugin>>) -> Self {
     let query = plugin.query().into_iter().map(L::Kind::from_raw).collect();

     Self {
         query,
         plugin,
         skip_subtree: None,
     }
 }
+
+pub fn new(plugin: Arc<Box<dyn AnalyzerPlugin>>, expected: PluginTargetLanguage) -> Option<Self> {
+    if plugin.language() != expected {
+        return None;
+    }
+    // SAFETY: Language checked above.
+    Some(unsafe { Self::new_unchecked(plugin) })
+}
crates/biome_analyze/src/suppressions.rs (3)

191-197: New range-suppression plugin fields look good

The added fields model plugin-specific range suppressions cleanly. Minor nit: prefer initialising booleans explicitly to false rather than Default::default() for readability.

-            suppress_all_plugins: Default::default(),
+            suppress_all_plugins: false,

Also applies to: 208-211


620-633: Consider reporting “already suppressed” for plugin suppressions too

already_suppressed(...) only checks rule filters; it won’t attribute a plugin line/range suppression as redundant when a top-level plugin suppression already applies. For parity with rules (nicer UX), extend this to consult plugin suppressions as well.

Here’s a minimal change:

-    fn already_suppressed(
-        &self,
-        filter: Option<&RuleFilter>,
-        range: &TextRange,
-    ) -> Option<TextRange> {
-        filter.and_then(|filter| {
-            self.top_level_suppression
-                .has_filter(filter)
-                .then_some(self.top_level_suppression.comment_range)
-                .or(self
-                    .range_suppressions
-                    .matches_filter_in_range(filter, range))
-        })
-    }
+    fn already_suppressed(
+        &self,
+        filter: Option<&RuleFilter>,
+        range: &TextRange,
+        plugin_name: Option<&str>,
+    ) -> Option<TextRange> {
+        // Rule-based overshadowing
+        if let Some(filter) = filter {
+            if self.top_level_suppression.has_filter(filter) {
+                return Some(self.top_level_suppression.comment_range);
+            }
+            if let Some(r) = self.range_suppressions.matches_filter_in_range(filter, range) {
+                return Some(r);
+            }
+        }
+        // Plugin-based overshadowing
+        if let Some(name) = plugin_name {
+            if self.top_level_suppression.suppressed_plugin(name) {
+                return Some(self.top_level_suppression.comment_range);
+            }
+            if let Some(r) = self.range_suppressions.matches_plugin_in_range(name, range) {
+                return Some(r);
+            }
+        }
+        None
+    }

Add this helper to RangeSuppressions:

+    pub(crate) fn matches_plugin_in_range(
+        &self,
+        plugin: &str,
+        position: &TextRange,
+    ) -> Option<TextRange> {
+        for s in self.suppressions.iter().rev() {
+            if s.suppression_range.contains_range(*position)
+                && (s.suppress_all_plugins || s.suppressed_plugins.contains(plugin))
+            {
+                return Some(s.suppression_range);
+            }
+        }
+        None
+    }

And pass plugin_name.as_deref() from callers where you compute already_suppressed.

Happy to wire this across the three call sites if you’d like a patch.


703-712: Docs: suppression example typo

The example reads “// lint/biome-ignore plugin/my-plugin”. Should be “// biome-ignore lint/plugin/my-plugin”.

-    /// A suppression disabling a plugin eg. `// lint/biome-ignore plugin/my-plugin`
+    /// A suppression disabling a plugin, e.g. `// biome-ignore lint/plugin/my-plugin`
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3999330 and 15d3550.

⛔ Files ignored due to path filters (5)
  • Cargo.lock is excluded by !**/*.lock and included by **
  • crates/biome_js_analyze/tests/plugin/preferObjectSpreadSuppression.grit.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/plugin/preferObjectSpreadSuppressionAll.grit.snap is excluded by !**/*.snap and included by **
  • crates/biome_service/src/snapshots/biome_service__workspace__tests__plugins_are_loaded_and_used_during_analysis.snap is excluded by !**/*.snap and included by **
  • crates/biome_service/src/snapshots/biome_service__workspace__tests__plugins_may_use_invalid_span.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (14)
  • crates/biome_analyze/src/analyzer_plugin.rs (2 hunks)
  • crates/biome_analyze/src/lib.rs (5 hunks)
  • crates/biome_analyze/src/matcher.rs (3 hunks)
  • crates/biome_analyze/src/registry.rs (2 hunks)
  • crates/biome_analyze/src/suppressions.rs (6 hunks)
  • crates/biome_css_analyze/src/lib.rs (2 hunks)
  • crates/biome_grit_patterns/src/grit_query.rs (1 hunks)
  • crates/biome_js_analyze/src/lib.rs (2 hunks)
  • crates/biome_plugin_loader/Cargo.toml (1 hunks)
  • crates/biome_plugin_loader/src/analyzer_grit_plugin.rs (2 hunks)
  • crates/biome_plugin_loader/src/analyzer_js_plugin.rs (4 hunks)
  • crates/biome_rowan/src/lib.rs (1 hunks)
  • crates/biome_rowan/src/syntax.rs (1 hunks)
  • crates/biome_rowan/src/syntax/node.rs (2 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{rs,toml}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Format Rust and TOML files before committing (use just f/just format).

Files:

  • crates/biome_js_analyze/src/lib.rs
  • crates/biome_rowan/src/lib.rs
  • crates/biome_rowan/src/syntax.rs
  • crates/biome_plugin_loader/Cargo.toml
  • crates/biome_grit_patterns/src/grit_query.rs
  • crates/biome_analyze/src/registry.rs
  • crates/biome_plugin_loader/src/analyzer_grit_plugin.rs
  • crates/biome_rowan/src/syntax/node.rs
  • crates/biome_css_analyze/src/lib.rs
  • crates/biome_plugin_loader/src/analyzer_js_plugin.rs
  • crates/biome_analyze/src/analyzer_plugin.rs
  • crates/biome_analyze/src/matcher.rs
  • crates/biome_analyze/src/lib.rs
  • crates/biome_analyze/src/suppressions.rs
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/lib.rs
  • crates/biome_css_analyze/src/lib.rs
crates/biome_*/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place core crates under /crates/biome_*/

Files:

  • crates/biome_js_analyze/src/lib.rs
  • crates/biome_rowan/src/lib.rs
  • crates/biome_rowan/src/syntax.rs
  • crates/biome_plugin_loader/Cargo.toml
  • crates/biome_grit_patterns/src/grit_query.rs
  • crates/biome_analyze/src/registry.rs
  • crates/biome_plugin_loader/src/analyzer_grit_plugin.rs
  • crates/biome_rowan/src/syntax/node.rs
  • crates/biome_css_analyze/src/lib.rs
  • crates/biome_plugin_loader/src/analyzer_js_plugin.rs
  • crates/biome_analyze/src/analyzer_plugin.rs
  • crates/biome_analyze/src/matcher.rs
  • crates/biome_analyze/src/lib.rs
  • crates/biome_analyze/src/suppressions.rs
crates/**/Cargo.toml

📄 CodeRabbit inference engine (CONTRIBUTING.md)

crates/**/Cargo.toml: In internal crates, use workspace = true for internal dependencies.
Use path dependencies for dev-dependencies in internal crates.

Files:

  • crates/biome_plugin_loader/Cargo.toml
{Cargo.toml,crates/**/Cargo.toml}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Keep the version field consistent across all crates during release.

Files:

  • crates/biome_plugin_loader/Cargo.toml
🧠 Learnings (20)
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/biome_js_analyze/lib/src/lint/nursery/*.rs : For new JavaScript lint rules generated by `just new-js-lintrule`, implement the rule in the generated file under `biome_js_analyze/lib/src/lint/nursery/`

Applied to files:

  • crates/biome_js_analyze/src/lib.rs
  • crates/biome_plugin_loader/src/analyzer_js_plugin.rs
📚 Learning: 2025-08-17T08:55:30.118Z
Learnt from: CR
PR: biomejs/biome#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-17T08:55:30.118Z
Learning: Applies to crates/biome_*_{syntax,parser,formatter,analyze,factory,semantic}/** : Maintain the per-language crate structure: biome_{lang}_{syntax,parser,formatter,analyze,factory,semantic}

Applied to files:

  • crates/biome_js_analyze/src/lib.rs
  • crates/biome_plugin_loader/Cargo.toml
  • crates/biome_css_analyze/src/lib.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/lib.rs
📚 Learning: 2025-08-17T08:57:34.751Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_parser/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:57:34.751Z
Learning: Applies to crates/biome_parser/xtask/codegen/*.ungram : Union node names must start with Any* (e.g., AnyHtmlAttribute)

Applied to files:

  • crates/biome_rowan/src/syntax.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Use the generic Format trait and FormatNode for AST nodes when implementing the formatter

Applied to files:

  • crates/biome_rowan/src/syntax.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : When a token is mandatory and present in the AST, use the AST-provided token (e.g., node.l_paren_token().format()) instead of hardcoded tokens

Applied to files:

  • crates/biome_rowan/src/syntax.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Import the FormatNode trait and implement it for your Node

Applied to files:

  • crates/biome_rowan/src/syntax.rs
📚 Learning: 2025-08-11T11:48:52.001Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:52.001Z
Learning: Applies to crates/biome_js_formatter/**/Cargo.toml : Add biome_js_formatter as a path dependency in Cargo.toml: biome_js_formatter = { version = "0.0.1", path = "../biome_js_formatter" }

Applied to files:

  • crates/biome_plugin_loader/Cargo.toml
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/Cargo.toml : Add the specified dev-dependencies (biome_formatter_test, biome_html_factory, biome_html_parser, biome_parser, biome_service, countme with feature enable, iai 0.1.1, quickcheck, quickcheck_macros, tests_macros) to Cargo.toml under [dev-dependencies]

Applied to files:

  • crates/biome_plugin_loader/Cargo.toml
📚 Learning: 2025-08-11T11:40:38.097Z
Learnt from: CR
PR: biomejs/biome#0
File: CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:40:38.097Z
Learning: Applies to crates/**/Cargo.toml : In internal crates, use `workspace = true` for internal dependencies.

Applied to files:

  • crates/biome_plugin_loader/Cargo.toml
📚 Learning: 2025-08-17T08:57:34.751Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_parser/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:57:34.751Z
Learning: Applies to crates/biome_parser/crates/biome_*_syntax/** : Create a new crate named biome_<language>_syntax under crates/

Applied to files:

  • crates/biome_plugin_loader/Cargo.toml
📚 Learning: 2025-08-11T11:40:38.097Z
Learnt from: CR
PR: biomejs/biome#0
File: CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:40:38.097Z
Learning: Applies to Cargo.toml : Centralize shared dependencies in the root Cargo.toml using workspace dependencies.

Applied to files:

  • crates/biome_plugin_loader/Cargo.toml
📚 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: Applies to crates/biome_service/src/workspace.rs : Implement the Workspace trait in src/workspace.rs

Applied to files:

  • crates/biome_plugin_loader/Cargo.toml
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/**/lib/src/**/nursery/**/*.rs : Use the local `rule_category!()` macro in diagnostics for the rule’s category, not string-parsed categories

Applied to files:

  • crates/biome_plugin_loader/src/analyzer_grit_plugin.rs
  • crates/biome_analyze/src/analyzer_plugin.rs
  • crates/biome_analyze/src/lib.rs
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/src/lib.rs : Place the plumbing traits and impls (AsFormat, IntoFormat, FormattedIterExt, and their Iterator adapters) in the biome_html_formatter crate’s lib.rs

Applied to files:

  • crates/biome_rowan/src/syntax/node.rs
📚 Learning: 2025-08-11T11:46:05.836Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_diagnostics/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:46:05.836Z
Learning: Applies to crates/biome_diagnostics/**/*.rs : Specify category and severity using #[diagnostic(...)] on the type or derive them from fields

Applied to files:

  • crates/biome_analyze/src/analyzer_plugin.rs
📚 Learning: 2025-08-11T11:46:05.836Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_diagnostics/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:46:05.836Z
Learning: Applies to crates/biome_diagnostics/**/*.rs : Types implementing Diagnostic must also implement Debug (e.g., use #[derive(Debug, Diagnostic)])

Applied to files:

  • crates/biome_analyze/src/analyzer_plugin.rs
📚 Learning: 2025-08-11T11:46:05.836Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_diagnostics/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:46:05.836Z
Learning: Applies to crates/biome_diagnostics/**/*.rs : #[derive(Diagnostic)] can be used on enums only if every variant contains a diagnostic type

Applied to files:

  • crates/biome_analyze/src/analyzer_plugin.rs
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/biome_rule_options/lib/**/*.rs : Define per-rule options in the `biome_rule_options` crate under `lib/`, with serde- and schemars-compatible derives and `#[serde(rename_all = "camelCase", deny_unknown_fields, default)]`

Applied to files:

  • crates/biome_analyze/src/matcher.rs
📚 Learning: 2025-08-11T11:46:05.836Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_diagnostics/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:46:05.836Z
Learning: Applies to crates/biome_diagnostics/crates/biome_diagnostics_categories/src/categories.rs : When declaring a new diagnostic category, register it in crates/biome_diagnostics_categories/src/categories.rs

Applied to files:

  • crates/biome_analyze/src/lib.rs
🧬 Code graph analysis (8)
crates/biome_js_analyze/src/lib.rs (1)
crates/biome_analyze/src/analyzer_plugin.rs (1)
  • new_unchecked (47-55)
crates/biome_analyze/src/registry.rs (1)
crates/biome_analyze/src/matcher.rs (1)
  • rule (113-115)
crates/biome_plugin_loader/src/analyzer_grit_plugin.rs (3)
crates/biome_analyze/src/analyzer_plugin.rs (3)
  • language (19-19)
  • query (21-21)
  • evaluate (23-23)
crates/biome_plugin_loader/src/analyzer_js_plugin.rs (3)
  • language (76-78)
  • query (80-85)
  • evaluate (87-122)
crates/biome_grit_patterns/src/grit_context.rs (2)
  • language (127-129)
  • parse (362-372)
crates/biome_rowan/src/syntax/node.rs (2)
crates/biome_analyze/src/registry.rs (2)
  • TypeId (179-179)
  • TypeId (233-233)
crates/biome_analyze/src/matcher.rs (1)
  • downcast_ref (58-60)
crates/biome_css_analyze/src/lib.rs (1)
crates/biome_analyze/src/analyzer_plugin.rs (1)
  • new_unchecked (47-55)
crates/biome_plugin_loader/src/analyzer_js_plugin.rs (2)
crates/biome_analyze/src/analyzer_plugin.rs (3)
  • language (19-19)
  • query (21-21)
  • evaluate (23-23)
crates/biome_plugin_loader/src/analyzer_grit_plugin.rs (3)
  • language (46-51)
  • query (53-63)
  • evaluate (65-120)
crates/biome_analyze/src/analyzer_plugin.rs (3)
crates/biome_plugin_loader/src/analyzer_js_plugin.rs (4)
  • fmt (52-56)
  • language (76-78)
  • query (80-85)
  • evaluate (87-122)
crates/biome_rowan/src/syntax/node.rs (10)
  • fmt (778-811)
  • fmt (815-817)
  • fmt (917-919)
  • kind (67-69)
  • kind (1048-1054)
  • kind (1125-1125)
  • kind (1129-1131)
  • kind (1135-1137)
  • kind (1184-1186)
  • new (1152-1154)
crates/biome_plugin_loader/src/analyzer_grit_plugin.rs (6)
  • language (46-51)
  • query (53-63)
  • evaluate (65-120)
  • node (69-70)
  • node (72-73)
  • None (103-103)
crates/biome_analyze/src/matcher.rs (2)
crates/biome_analyze/src/registry.rs (3)
  • RuleKey (441-441)
  • new (298-300)
  • new (382-455)
crates/biome_analyze/src/context.rs (1)
  • RuleKey (42-42)
⏰ 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). (25)
  • GitHub Check: Test Node.js API
  • GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Documentation
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: Check Dependencies
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Bench (biome_configuration)
  • GitHub Check: Bench (biome_json_analyze)
  • GitHub Check: Bench (biome_html_formatter)
  • GitHub Check: Bench (biome_graphql_formatter)
  • GitHub Check: Bench (biome_package)
  • GitHub Check: Bench (biome_css_parser)
  • GitHub Check: Bench (biome_html_parser)
  • GitHub Check: Bench (biome_module_graph)
  • GitHub Check: Bench (biome_graphql_parser)
  • GitHub Check: Parser conformance
  • GitHub Check: Bench (biome_css_formatter)
  • GitHub Check: Bench (biome_css_analyze)
  • GitHub Check: Bench (biome_json_formatter)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Bench (biome_json_parser)
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: autofix
🔇 Additional comments (26)
crates/biome_grit_patterns/src/grit_query.rs (2)

56-58: LGTM: exposing language for routing fits the new visitor model

Making language readable from outside will simplify plugin wiring. Nice one.


56-58: No external writes to GritQuery.language detected. Only occurrence of .language = is in the Rule::language builder in crates/biome_analyze/src/rule.rs, not on GritQuery, and no struct literals set language elsewhere.

crates/biome_rowan/src/syntax.rs (1)

10-13: LGTM: re-exporting AnySyntaxNode.

Clean public surfacing; no behavioural changes. Ship it.

crates/biome_rowan/src/lib.rs (1)

46-52: LGTM: public API now exposes AnySyntaxNode.

Consistent with the syntax module export; minimal risk.

crates/biome_analyze/src/matcher.rs (2)

214-218: Tests import update: all good.

Importing SignalRuleKey into tests aligns with the new API.


249-255: Test signal construction updated correctly.

Wrapping the RuleKey in SignalRuleKey::Rule is spot on.

crates/biome_js_analyze/src/lib.rs (1)

8-10: LGTM: imports for PluginTargetLanguage and PluginVisitor.

Matches the new analyser plugin API surface.

crates/biome_css_analyze/src/lib.rs (1)

17-19: LGTM: import wiring matches the new plugin visitor API.
No concerns here.

crates/biome_analyze/src/registry.rs (2)

5-5: LGTM: switch to SignalRuleKey import.
Consistent with the new matcher keying.


441-445: Manual verification required: automated searches didn’t locate any literal SignalEntry { rule: … } constructions. Please manually confirm every SignalEntry instantiation uses SignalRuleKey::Rule (for rules) or the Plugin variant as intended.

crates/biome_rowan/src/syntax/node.rs (1)

11-11: LGTM: added Any import.
Required for the erasure/downcast bits below.

crates/biome_plugin_loader/src/analyzer_js_plugin.rs (3)

75-86: Language + query wiring looks good

Targets JavaScript and limits evaluation to JS roots. Matches the visitor’s expectations.


174-189: Threaded test is sound

Exercises thread-local loading and per-file evaluation; assertions are crisp.


87-122: Preserve AST context or defer explicitly

  • Include minimal node metadata (e.g. kind and range) in the JS entrypoint call—or document intentional deferral in the PR description and open a tracking issue.
  • Tag pulled diagnostics with the plugin’s name via .map(|d| d.subcategory(plugin_name.clone())) to avoid anonymous subcategories.

Would you like a prototype passing (path, kind, start, end) as extra JS args?

crates/biome_analyze/src/analyzer_plugin.rs (2)

19-24: Trait surface aligns with the new visitor model

Language + query metadata and per-node evaluation are a solid API for syntax-time plugins.


87-92: Range pruning: double-check ordering(range).is_ne() does what you intend

Ensure this treats “disjoint” ranges only; overlapping-but-not-containing nodes shouldn’t be skipped if that’s not desired. If the semantics are ambiguous, prefer an explicit disjoint check.

crates/biome_plugin_loader/src/analyzer_grit_plugin.rs (3)

46-63: Language/query mapping looks correct

Grit → {JS,CSS} targeting and root-kind query are consistent with the visitor.


80-119: Diagnostic shaping is solid

Carries logs, enforces spans, and annotates with plugin name; reads well.


127-179: Helper is tidy

register_diagnostic handles message/severity nicely; defaults are sensible.

crates/biome_analyze/src/suppressions.rs (3)

225-231: Thread plugin_name through push_suppression: OK

Plumbing the optional plugin name into range suppressions is sensible and aligns with line/top-level handling. No issues spotted.

Also applies to: 239-245


342-357: New suppress_plugin() helper: LGTM

Efficient backwards scan and correct did_suppress_signal marking. No concerns.


552-560: Range push now passes plugin_name: OK

Good cohesion with the updated RangeSuppressions::push_suppression.

crates/biome_analyze/src/lib.rs (4)

30-32: Re-exports and SignalRuleKey import: LGTM

Public surface reflects the new plugin visitor model, and the import is scoped correctly.

Also applies to: 39-39


199-201: Consistent, helpful “unused suppression” notes

Nice touch linking to the suppression that actually applied. No issues.

Also applies to: 221-223


403-430: Top-level and range suppression handling for plugins looks correct

The new match on SignalRuleKey integrates plugin suppressions cleanly with existing rule paths.


455-481: Line suppression path covers plugins; consider one edge case

Logic for instances (rules) vs plugins is sound. One behavioural question: should // biome-ignore-start lint/plugin without a plugin name be able to close a plugin-specific block at // biome-ignore-end lint/plugin (no name)? At present, a RangeEnd without a plugin name only closes an “all plugins” start. If the grammar doesn’t allow names on -end, we might want to relax the end-side matching to close the most recent plugin-suppression regardless of name.

Would you confirm the intended syntax? If you want the relaxed behaviour, I can propose a small tweak to the PLUGIN_LINT_RULE_FILTER end-match to fall back to the most recent non-ended plugin range when plugin_name is None.

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: 1

♻️ Duplicate comments (1)
crates/biome_analyze/src/analyzer_plugin.rs (1)

110-116: Good fix: use span() accessor instead of field access

Using diagnostic.span() avoids visibility/compat pitfalls flagged earlier. Nice one.

🧹 Nitpick comments (4)
crates/biome_analyze/src/analyzer_plugin.rs (4)

18-24: Prefer Arc over Arc<Box> to avoid double indirection

The extra Box adds an unnecessary allocation and pointer chase. If ABI/public API allows, consider simplifying to Arc for AnalyzerPluginSlice/Vec and fields that store plugins.


32-36: Struct field also inherits the double indirection

PluginVisitor.plugin mirrors the Arc<Box<...>> pattern. If you adopt Arc, update this field accordingly to keep things consistent and lean.


43-55: Make construction safe (or guarded) by checking the plugin’s target language

new_unchecked relies on callers to pair the correct language with the plugin. A mismatch silently no-ops (query kinds won’t match) and is painful to debug. Offer a safe constructor that validates plugin.language().

Patch sketch (adds a safe new and keeps new_unchecked for internal use):

 impl<L> PluginVisitor<L>
 where
     L: Language + 'static,
     L::Kind: Eq + Hash,
 {
+    /// Creates a syntax visitor from the plugin, validating the target language.
+    pub fn new(plugin: Arc<Box<dyn AnalyzerPlugin>>, expected: PluginTargetLanguage) -> Result<Self, PluginTargetLanguage> {
+        let actual = plugin.language();
+        if actual != expected {
+            return Err(actual);
+        }
+        // SAFETY: `actual == expected` guarantees the language pairing.
+        Ok(unsafe { Self::new_unchecked(plugin) })
+    }
+
     /// Creates a syntax visitor from the plugin.
     ///
     /// # Safety
     /// Do not forget to check the plugin is targeted for the language `L`.
     pub unsafe fn new_unchecked(plugin: Arc<Box<dyn AnalyzerPlugin>>) -> Self {
         let query = plugin.query().into_iter().map(L::Kind::from_raw).collect();

87-93: No change needed: ordering(range).is_ne() correctly skips only disjoint nodes.

  • Optional: for clearer intent, you may use node.text_range_with_trivia().intersect(range).is_none() (be aware it treats boundary-touching as overlap).
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 15d3550 and 6b66586.

📒 Files selected for processing (2)
  • crates/biome_analyze/src/analyzer_plugin.rs (2 hunks)
  • crates/biome_analyze/src/suppressions.rs (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/biome_analyze/src/suppressions.rs
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{rs,toml}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Format Rust and TOML files before committing (use just f/just format).

Files:

  • crates/biome_analyze/src/analyzer_plugin.rs
crates/biome_*/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place core crates under /crates/biome_*/

Files:

  • crates/biome_analyze/src/analyzer_plugin.rs
🧠 Learnings (4)
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/**/lib/src/**/nursery/**/*.rs : Use the local `rule_category!()` macro in diagnostics for the rule’s category, not string-parsed categories

Applied to files:

  • crates/biome_analyze/src/analyzer_plugin.rs
📚 Learning: 2025-08-11T11:46:05.836Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_diagnostics/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:46:05.836Z
Learning: Applies to crates/biome_diagnostics/**/*.rs : Specify category and severity using #[diagnostic(...)] on the type or derive them from fields

Applied to files:

  • crates/biome_analyze/src/analyzer_plugin.rs
📚 Learning: 2025-08-11T11:46:05.836Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_diagnostics/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:46:05.836Z
Learning: Applies to crates/biome_diagnostics/**/*.rs : Types implementing Diagnostic must also implement Debug (e.g., use #[derive(Debug, Diagnostic)])

Applied to files:

  • crates/biome_analyze/src/analyzer_plugin.rs
📚 Learning: 2025-08-11T11:46:05.836Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_diagnostics/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:46:05.836Z
Learning: Applies to crates/biome_diagnostics/**/*.rs : #[derive(Diagnostic)] can be used on enums only if every variant contains a diagnostic type

Applied to files:

  • crates/biome_analyze/src/analyzer_plugin.rs
🧬 Code graph analysis (1)
crates/biome_analyze/src/analyzer_plugin.rs (3)
crates/biome_plugin_loader/src/analyzer_js_plugin.rs (4)
  • fmt (52-56)
  • language (76-78)
  • query (80-85)
  • evaluate (87-122)
crates/biome_rowan/src/syntax/node.rs (12)
  • fmt (778-811)
  • fmt (815-817)
  • fmt (917-919)
  • skip_subtree (944-946)
  • skip_subtree (999-1001)
  • kind (67-69)
  • kind (1048-1054)
  • kind (1125-1125)
  • kind (1129-1131)
  • kind (1135-1137)
  • kind (1184-1186)
  • new (1152-1154)
crates/biome_plugin_loader/src/analyzer_grit_plugin.rs (6)
  • language (46-51)
  • query (53-63)
  • evaluate (65-120)
  • node (69-70)
  • node (72-73)
  • None (103-103)
⏰ 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). (23)
  • GitHub Check: Bench (biome_configuration)
  • GitHub Check: Bench (biome_module_graph)
  • GitHub Check: Bench (biome_html_formatter)
  • GitHub Check: Bench (biome_package)
  • GitHub Check: Bench (biome_graphql_formatter)
  • GitHub Check: Bench (biome_html_parser)
  • GitHub Check: Bench (biome_graphql_parser)
  • GitHub Check: Bench (biome_json_formatter)
  • GitHub Check: Bench (biome_css_analyze)
  • GitHub Check: Bench (biome_json_analyze)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Bench (biome_json_parser)
  • GitHub Check: Bench (biome_css_formatter)
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Bench (biome_css_parser)
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Documentation
  • GitHub Check: Check Dependencies
  • GitHub Check: Parser conformance
  • GitHub Check: Test Node.js API
  • GitHub Check: autofix

@siketyan siketyan force-pushed the refactor/plugin-analyzer-visitor branch from 6b66586 to 9f09a10 Compare September 1, 2025 17:28
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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/biome_analyze/src/suppressions.rs (1)

107-109: Fix: cover() return value is ignored in TopLevelSuppression.expand_range

You’re dropping the result of TextRange::cover; the top-level range never grows.

-    pub(crate) fn expand_range(&mut self, range: TextRange) {
-        self.range.cover(range);
-    }
+    pub(crate) fn expand_range(&mut self, range: TextRange) {
+        self.range = self.range.cover(range);
+    }
🧹 Nitpick comments (4)
crates/biome_analyze/src/suppressions.rs (4)

191-197: Docs/style nit: wording consistency

  • “Set to true if this comment suppress all plugins” → “suppresses”.
  • Consider aligning field naming with TopLevelSuppression (“plugins” vs “suppressed_plugins”) for symmetry.

208-211: Minor: prefer explicit false over Default::default() for bool

Clearer at a glance and avoids type inference surprises.

-            suppress_all_plugins: Default::default(),
+            suppress_all_plugins: false,

229-233: Avoid plugin name allocations: pass &str through the pipeline

No need to allocate a String here; accept Option<&str> and only allocate when inserting into the set.

-    pub(crate) fn push_suppression(
+    pub(crate) fn push_suppression(
         &mut self,
         suppression: &AnalyzerSuppression,
         filter: Option<RuleFilter<'static>>,
-        plugin_name: Option<String>,
+        plugin_name: Option<&str>,
         text_range: TextRange,
         already_suppressed: Option<TextRange>,
     ) -> Result<(), AnalyzerSuppressionDiagnostic> {
@@
-                Some(PLUGIN_LINT_RULE_FILTER) => {
-                    if let Some(plugin_name) = plugin_name {
-                        range_suppression.suppressed_plugins.insert(plugin_name);
-                    } else {
+                Some(PLUGIN_LINT_RULE_FILTER) => {
+                    if let Some(plugin_name) = plugin_name {
+                        range_suppression
+                            .suppressed_plugins
+                            .insert(plugin_name.to_string());
+                    } else {
                         range_suppression.suppress_all_plugins = true;
-                    }
+                    }
                 }

Also applies to: 239-245


551-559: Plumb plugin name by reference to avoid copies

Follow-up to the earlier suggestion: pass &str into RangeSuppressions::push_suppression.

-                self.range_suppressions.push_suppression(
+                self.range_suppressions.push_suppression(
                     suppression,
                     filter,
-                    plugin_name,
+                    plugin_name.as_deref(),
                     comment_range,
                     already_suppressed,
                 )

Optionally, consider marking plugin-level “already suppressed” to match rule-level behaviour:

  • Add a helper (outside the changed hunk):
impl RangeSuppressions {
    pub(crate) fn matches_plugin_in_range(
        &self,
        plugin_name: &str,
        position: &TextRange,
    ) -> Option<TextRange> {
        for s in self.suppressions.iter().rev() {
            if s.suppression_range.contains_range(*position)
                && (s.suppress_all_plugins || s.suppressed_plugins.contains(plugin_name))
            {
                return Some(s.suppression_range);
            }
        }
        None
    }
}
  • Then, in push_suppression (before matching on suppression.variant), enrich already_suppressed for plugin suppressions:
let mut already_suppressed = self.already_suppressed(filter.as_ref(), &comment_range);
if matches!(filter, Some(PLUGIN_LINT_RULE_FILTER)) {
    if self.top_level_suppression.suppress_all_plugins
        || plugin_name
            .as_ref()
            .is_some_and(|n| self.top_level_suppression.suppressed_plugin(n))
    {
        already_suppressed = Some(self.top_level_suppression.comment_range);
    } else if let Some(name) = plugin_name.as_deref() {
        if let Some(r) = self
            .range_suppressions
            .matches_plugin_in_range(name, &comment_range)
        {
            already_suppressed = Some(r);
        }
    }
}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6b66586 and 9f09a10.

📒 Files selected for processing (2)
  • crates/biome_analyze/src/analyzer_plugin.rs (2 hunks)
  • crates/biome_analyze/src/suppressions.rs (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/biome_analyze/src/analyzer_plugin.rs
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{rs,toml}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Format Rust and TOML files before committing (use just f/just format).

Files:

  • crates/biome_analyze/src/suppressions.rs
crates/biome_*/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place core crates under /crates/biome_*/

Files:

  • crates/biome_analyze/src/suppressions.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). (25)
  • GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Documentation
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Check Dependencies
  • GitHub Check: Bench (biome_module_graph)
  • GitHub Check: Bench (biome_css_analyze)
  • GitHub Check: Bench (biome_configuration)
  • GitHub Check: Bench (biome_package)
  • GitHub Check: Bench (biome_json_analyze)
  • GitHub Check: Bench (biome_html_formatter)
  • GitHub Check: Bench (biome_json_formatter)
  • GitHub Check: Bench (biome_graphql_parser)
  • GitHub Check: Bench (biome_html_parser)
  • GitHub Check: Bench (biome_graphql_formatter)
  • GitHub Check: Bench (biome_css_parser)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Bench (biome_css_formatter)
  • GitHub Check: Bench (biome_json_parser)
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: Test Node.js API
  • GitHub Check: autofix
  • GitHub Check: Parser conformance
🔇 Additional comments (1)
crates/biome_analyze/src/suppressions.rs (1)

341-355: LGTM: clean, mirrors suppress_rule and marks did_suppress_signal

No concerns here.

@siketyan siketyan force-pushed the refactor/plugin-analyzer-visitor branch from cf945b4 to 19d8cc4 Compare September 1, 2025 17:40
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

♻️ Duplicate comments (2)
crates/biome_analyze/src/suppressions.rs (2)

259-267: Unify diagnostic wording: use “biome-ignore-*” consistently

Elsewhere you use “biome-ignore-*”. Let’s align this message too.

-                    markup!{"Found a "<Emphasis>"biome-range-end"</Emphasis>" suppression without a "<Emphasis>"biome-range-start"</Emphasis>" suppression. This is invalid"}
+                    markup!{"Found a "<Emphasis>"biome-ignore-end"</Emphasis>" suppression without a "<Emphasis>"biome-ignore-start"</Emphasis>" suppression. This is invalid"}

270-295: Bug: generic biome-ignore-end drops state instead of closing the last open suppression

Popping and returning early skips marking is_ended and doesn’t extend the range to include the end comment. Close the most recent open suppression instead, mirroring the other branches.

-            let range_suppression = match filter {
-                None => {
-                    self.suppressions.pop();
-                    return Ok(());
-                }
+            let range_suppression = match filter {
+                None => self
+                    .suppressions
+                    .iter_mut()
+                    .rev()
+                    .find(|s| !s.is_ended),
                 Some(PLUGIN_LINT_RULE_FILTER) => self
                     .suppressions
                     .iter_mut()
                     .rev()
                     .filter(|s| !s.is_ended)
                     .find(|s| match &plugin_name {
                         Some(plugin_name) => s.suppressed_plugins.contains(plugin_name),
                         None => s.suppress_all_plugins,
                     }),
                 Some(filter) => self
                     .suppressions
                     .iter_mut()
                     .rev()
                     .filter(|s| !s.is_ended)
                     .find(|s| {
                         s.filters_by_category
                             .get(&suppression.category)
                             .is_some_and(|filters| filters.contains(&filter))
                     }),
             };
🧹 Nitpick comments (3)
crates/biome_analyze/src/suppressions.rs (3)

191-197: Naming consistency + doc nit

Range uses suppressed_plugins while top-level uses plugins. Consider aligning names for symmetry. Also fix the grammar in the doc string.

-    /// Set to true if this comment suppress all plugins
+    /// Set to true if this comment suppresses all plugins

208-211: Prefer explicit false over Default::default() for bools

Tiny readability win; keeps intent obvious.

-            suppressed_plugins: Default::default(),
-            suppress_all_plugins: Default::default(),
+            suppressed_plugins: Default::default(),
+            suppress_all_plugins: false,

107-109: Bug: cover() result is ignored for top-level expand

This never updates range. Assign the result.

-        self.range.cover(range);
+        self.range = self.range.cover(range);
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9f09a10 and 19d8cc4.

📒 Files selected for processing (1)
  • crates/biome_analyze/src/suppressions.rs (6 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{rs,toml}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Format Rust and TOML files before committing (use just f/just format).

Files:

  • crates/biome_analyze/src/suppressions.rs
crates/biome_*/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place core crates under /crates/biome_*/

Files:

  • crates/biome_analyze/src/suppressions.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). (23)
  • GitHub Check: Test Node.js API
  • GitHub Check: Documentation
  • GitHub Check: Check Dependencies
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: Bench (biome_html_formatter)
  • GitHub Check: Bench (biome_module_graph)
  • GitHub Check: Bench (biome_configuration)
  • GitHub Check: Bench (biome_html_parser)
  • GitHub Check: Bench (biome_graphql_formatter)
  • GitHub Check: Bench (biome_graphql_parser)
  • GitHub Check: Parser conformance
  • GitHub Check: Bench (biome_json_formatter)
  • GitHub Check: Bench (biome_json_parser)
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: Bench (biome_json_analyze)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Bench (biome_css_analyze)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Bench (biome_css_formatter)
  • GitHub Check: Bench (biome_css_parser)
  • GitHub Check: autofix
🔇 Additional comments (5)
crates/biome_analyze/src/suppressions.rs (5)

239-245: LGTM: plugin range-start handling

Correctly records specific plugin vs “all plugins”. Nicely done.


296-301: LGTM: end-range now expands suppression_range

Correctly marks ended and covers the end-comment span.


341-355: LGTM: plugin suppression matching

Linear scan from the back with did_suppress_signal toggle is appropriate for typical counts.


551-559: LGTM: plumbs plugin_name into range suppressions

Signature alignment looks correct with the updated push.


229-231: Verify all push_suppression call sites
Ripgrep returned no matches; please manually ensure every invocation properly passes the new plugin_name parameter.

Copy link
Contributor

@arendjr arendjr left a comment

Choose a reason for hiding this comment

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

Amazing work, thank you!

@arendjr
Copy link
Contributor

arendjr commented Sep 2, 2025

You might want to add a changeset still for the improved suppressions with plugins?

Comment on lines +87 to +89
if let Some(range) = ctx.range
&& node.text_range_with_trivia().ordering(range).is_ne()
{
Copy link
Member

Choose a reason for hiding this comment

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

What do we do here?

Copy link
Member Author

@siketyan siketyan Sep 3, 2025

Choose a reason for hiding this comment

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

Actually I just copied it from SyntaxVisitor, and honestly I am not sure what do we do here :(

let node = match event {
WalkEvent::Enter(node) => node,
WalkEvent::Leave(node) => {
if let Some(skip_subtree) = &self.skip_subtree
&& skip_subtree == node
{
self.skip_subtree = None;
}
return;
}
};
if self.skip_subtree.is_some() {
return;
}
if let Some(range) = ctx.range
&& node.text_range_with_trivia().ordering(range).is_ne()
{
self.skip_subtree = Some(node.clone());
return;
}

Copy link
Contributor

Choose a reason for hiding this comment

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

It appears that ctx can provide a range within which diagnostics should be pulled, instead of running analysis on the entire tree. So this check is just to make sure that the node being checked is within that range.


/// Opaque struct for [`SyntaxNode`] without the `L: Language` constraint.
#[derive(Debug)]
pub struct AnySyntaxNode {
Copy link
Contributor

@arendjr arendjr Sep 3, 2025

Choose a reason for hiding this comment

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

What is the advantage of using AnySyntaxNode over the SendNode type we already have? In the Grit plugin we can see that the nodes are directly converted to a SendNode anyway, so why don't we just use SendNode everywhere instead of introducing a new type here?

Nevermind, figured out the answer: Because SendNode can only be reliably used for root nodes :)

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes I introduced AnySyntaxNode because it's safe to use with any descendants but !Send. I am not sure whether the limitation of SendNode can be lifted and merged into AnySyntaxNode.

@siketyan siketyan requested a review from ematipico September 3, 2025 16:14
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
crates/biome_plugin_loader/src/analyzer_js_plugin.rs (1)

88-123: Propagate plugin name into diagnostics for suppression matching

To enable biome-ignore lint/plugin/ suppressions for JS plugins, tag emitted diagnostics with the plugin name as subcategory (e.g. file-stem), similar to the Grit path.

-    fn evaluate(&self, _node: AnySyntaxNode, path: Arc<Utf8PathBuf>) -> Vec<RuleDiagnostic> {
+    fn evaluate(&self, _node: AnySyntaxNode, path: Arc<Utf8PathBuf>) -> Vec<RuleDiagnostic> {
+        let plugin_name = self
+            .path
+            .file_stem()
+            .map(|s| s.to_string())
+            .unwrap_or_else(|| "anonymous".to_string());
         let mut plugin = match self
             .loaded
             .get_mut_or_try_init(|| load_plugin(self.fs.clone(), &self.path))
         {
             Ok(plugin) => plugin,
             Err(err) => {
                 return vec![RuleDiagnostic::new(
                     category!("plugin"),
                     None::<TextRange>,
                     markup!("Could not load the plugin: "<Error>{err.to_string()}</Error>),
                 )];
             }
         };
 
         let plugin = plugin.deref_mut();
 
         // TODO: pass the AST to the plugin
         plugin
             .ctx
             .call_function(
                 &plugin.entrypoint,
                 &JsValue::undefined(),
                 &[JsValue::String(JsString::from(path.as_str()))],
             )
             .map_or_else(
                 |err| {
                     vec![RuleDiagnostic::new(
                         category!("plugin"),
                         None::<TextRange>,
                         markup!("Plugin errored: "<Error>{err.to_string()}</Error>),
                     )]
                 },
-                |_| plugin.ctx.pull_diagnostics(),
+                |_| {
+                    plugin
+                        .ctx
+                        .pull_diagnostics()
+                        .into_iter()
+                        .map(|d| d.subcategory(plugin_name.clone()))
+                        .collect()
+                },
             )
     }

If the runtime already tags diagnostics with a subcategory, feel free to ignore; otherwise this aligns JS plugins with the new suppression model. I can follow up with a small PR if you prefer.

crates/biome_analyze/src/suppressions.rs (1)

107-109: Bug: top-level suppression range never expands (cover() result is ignored)

TextRange::cover returns a new range; you’re dropping it, so range stays default. This breaks accurate highlighting/accounting of top-level suppression span.

     pub(crate) fn expand_range(&mut self, range: TextRange) {
-        self.range.cover(range);
+        self.range = self.range.cover(range);
     }
♻️ Duplicate comments (3)
crates/biome_analyze/src/matcher.rs (1)

147-151: Nice DX win implementing From

This matches previous feedback and smooths call-sites converting to SignalRuleKey.

crates/biome_analyze/src/analyzer_plugin.rs (1)

107-116: Avoid “anonymous” plugin keys; ensure stable identity for per-plugin suppressions

Falling back to "anonymous" breaks per-plugin suppression for JS plugins (subcategory isn’t set there). Either make plugin identity explicit on the trait, or ensure JS plugins set a stable subcategory. This was raised before; still unresolved here.

Option A (preferred, explicit key on the trait):

@@
 pub trait AnalyzerPlugin: Debug + Send + Sync {
     fn language(&self) -> PluginTargetLanguage;
 
     fn query(&self) -> Vec<RawSyntaxKind>;
 
     fn evaluate(&self, node: AnySyntaxNode, path: Arc<Utf8PathBuf>) -> Vec<RuleDiagnostic>;
+
+    /// Stable identity used for suppressions and reporting.
+    fn key(&self) -> std::borrow::Cow<'static, str>;
 }
@@
-                let name = diagnostic
-                    .subcategory
-                    .clone()
-                    .unwrap_or_else(|| "anonymous".into());
+                let name = diagnostic
+                    .subcategory
+                    .clone()
+                    .unwrap_or_else(|| (&*self.plugin).key().into_owned());

Option B (minimal churn): in crates/biome_plugin_loader/src/analyzer_js_plugin.rs, map pulled diagnostics to set a stable subcategory (e.g. plugin file stem) before returning:

let name = self
    .path
    .file_stem()
    .map(|s| s.to_string())
    .unwrap_or_else(|| "anonymous".to_string());

plugin
    .ctx
    .pull_diagnostics()
    .into_iter()
    .map(|d| d.subcategory(name.clone()))
    .collect()

Run to confirm no “anonymous” subcategories remain for JS plugins:

#!/bin/bash
rg -n --type=rust -C2 'pull_diagnostics\(\).*collect' crates/biome_plugin_loader | sed -n '1,200p'
rg -n --type=rust -C2 'subcategory\(' crates/biome_plugin_loader/src/analyzer_js_plugin.rs || echo "No subcategory set in JS plugin loader"
crates/biome_analyze/src/suppressions.rs (1)

278-283: Generic biome-ignore-end shouldn’t pop and drop state

Popping removes the suppression entirely, which prevents “unused range suppression” diagnostics and loses context. Instead, close the latest open suppression (any kind), mark it ended, and extend the range to include the end comment.

-                None => {
-                    self.suppressions.pop();
-                    return Ok(());
-                }
+                None => self
+                    .suppressions
+                    .iter_mut()
+                    .rev()
+                    .find(|s| !s.is_ended),
@@
-            if let Some(existing_suppression) = range_suppression {
+            if let Some(existing_suppression) = range_suppression {
                 // Mark this as ended and expand it by the text range of this comment
                 existing_suppression.is_ended = true;
                 existing_suppression.suppression_range =
                     existing_suppression.suppression_range.cover(text_range);
             } else {
🧹 Nitpick comments (10)
crates/biome_analyze/src/matcher.rs (2)

141-151: Good abstraction; consider deriving Eq/Hash and a Display impl

SignalRuleKey cleanly captures both core rules and plugins. To ease use in maps/sets and logging, derive Eq/Hash and add Display.

-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, PartialEq, Eq, Hash)]
 pub enum SignalRuleKey {
     Rule(RuleKey),
     Plugin(Box<str>),
 }
+
+impl std::fmt::Display for SignalRuleKey {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            SignalRuleKey::Rule(k) => k.fmt(f),
+            SignalRuleKey::Plugin(name) => write!(f, "plugin/{name}"),
+        }
+    }
+}

153-165: Documentation nit: include plugins in the field doc

The field comment still says “rule”; it now also represents plugin diagnostics.

-    /// Unique identifier for the rule that emitted this signal
+    /// Unique identifier for the rule or plugin that emitted this signal
     pub rule: SignalRuleKey,
crates/biome_css_analyze/tests/plugin/useLowercaseColorsSuppression.grit (1)

1-9: Confirm plugin name used for suppressions

Suppressions in the CSS fixture target lint/plugin/useLowercaseColorsSuppression. Ensure this rule’s emitted diagnostics use that exact plugin name (e.g. file-stem) as the subcategory; otherwise the suppressions won’t match. If the Grit DSL supports an explicit name, consider declaring it to make this deterministic.

crates/biome_css_analyze/tests/plugin/useLowercaseColorsSuppression.css (1)

13-16: Minor consistency: drop trailing semicolon in directive

One directive has a trailing semicolon inside the comment; others don’t. For consistency:

-    /* biome-ignore lint/plugin/anotherPlugin: reason; */
+    /* biome-ignore lint/plugin/anotherPlugin: reason */
crates/biome_plugin_loader/src/analyzer_js_plugin.rs (2)

76-86: Scoped query to JS roots is fine; keep an eye on granularity

Limiting to AnyJsRoot ensures evaluate runs once per file. The TODO about granular queries from JS plugins is spot on; wiring that later will avoid running whole-file plugins unnecessarily.


105-112: Follow-up: plumb AST to JS plugins

Leaving a TODO is fine for this PR. Next step: serialise a minimal AST facade (or pass a node handle) so plugins can truly act as syntax visitors and avoid re-parsing or relying on global state.

crates/biome_analyze/src/analyzer_plugin.rs (3)

89-95: Range pruning: use intersection for clarity

The ordering check is harder to parse. Using an explicit intersection reads clearer and avoids subtle ordering semantics.

-        if let Some(range) = ctx.range
-            && node.text_range_with_trivia().ordering(range).is_ne()
-        {
+        if let Some(range) = ctx.range
+            && node.text_range_with_trivia().intersect(range).is_none()
+        {
             self.skip_subtree = Some(node.clone());
             return;
         }

49-57: Make the unsafe constructor safer in debug builds

Add a debug assertion that the plugin language matches L to catch misuse early. If you can’t derive PluginTargetLanguage from L here, place this assert at the call sites that know the language.


32-38: Avoid double indirection for plugins

Using Arc<Box<dyn AnalyzerPlugin>> is an unnecessary extra hop. Prefer Arc<dyn AnalyzerPlugin> across the API when feasible.

crates/biome_analyze/src/lib.rs (1)

73-76: Docstring is outdated; plugins now run as visitors

This still claims plugins “do not support the same visitor pattern” which is no longer true after this PR. Please update to avoid confusing readers.

Suggested rewording:

  • “The analyzer also supports plugins as syntax visitors. They respect the same suppression comments and report signals in the same format.”
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 19d8cc4 and 2d9f693.

⛔ Files ignored due to path filters (1)
  • crates/biome_css_analyze/tests/plugin/useLowercaseColorsSuppression.grit.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (9)
  • .changeset/fresh-terms-carry.md (1 hunks)
  • crates/biome_analyze/src/analyzer_plugin.rs (2 hunks)
  • crates/biome_analyze/src/lib.rs (5 hunks)
  • crates/biome_analyze/src/matcher.rs (3 hunks)
  • crates/biome_analyze/src/registry.rs (2 hunks)
  • crates/biome_analyze/src/suppressions.rs (6 hunks)
  • crates/biome_css_analyze/tests/plugin/useLowercaseColorsSuppression.css (1 hunks)
  • crates/biome_css_analyze/tests/plugin/useLowercaseColorsSuppression.grit (1 hunks)
  • crates/biome_plugin_loader/src/analyzer_js_plugin.rs (4 hunks)
✅ Files skipped from review due to trivial changes (1)
  • .changeset/fresh-terms-carry.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/biome_analyze/src/registry.rs
🧰 Additional context used
📓 Path-based instructions (4)
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_css_analyze/tests/plugin/useLowercaseColorsSuppression.css
  • crates/biome_css_analyze/tests/plugin/useLowercaseColorsSuppression.grit
crates/biome_*/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place core crates under /crates/biome_*/

Files:

  • crates/biome_css_analyze/tests/plugin/useLowercaseColorsSuppression.css
  • crates/biome_css_analyze/tests/plugin/useLowercaseColorsSuppression.grit
  • crates/biome_analyze/src/matcher.rs
  • crates/biome_analyze/src/analyzer_plugin.rs
  • crates/biome_analyze/src/lib.rs
  • crates/biome_plugin_loader/src/analyzer_js_plugin.rs
  • crates/biome_analyze/src/suppressions.rs
**/tests/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place test files under a tests/ directory in each crate

Files:

  • crates/biome_css_analyze/tests/plugin/useLowercaseColorsSuppression.css
  • crates/biome_css_analyze/tests/plugin/useLowercaseColorsSuppression.grit
**/*.{rs,toml}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Format Rust and TOML files before committing (use just f/just format).

Files:

  • crates/biome_analyze/src/matcher.rs
  • crates/biome_analyze/src/analyzer_plugin.rs
  • crates/biome_analyze/src/lib.rs
  • crates/biome_plugin_loader/src/analyzer_js_plugin.rs
  • crates/biome_analyze/src/suppressions.rs
🧠 Learnings (8)
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/biome_js_analyze/tests/quick_test.rs : Use `biome_js_analyze/tests/quick_test.rs` for quick, ad-hoc testing; un-ignore the test and adjust the rule filter as needed

Applied to files:

  • crates/biome_css_analyze/tests/plugin/useLowercaseColorsSuppression.grit
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/biome_js_analyze/lib/src/lint/nursery/*.rs : For new JavaScript lint rules generated by `just new-js-lintrule`, implement the rule in the generated file under `biome_js_analyze/lib/src/lint/nursery/`

Applied to files:

  • crates/biome_css_analyze/tests/plugin/useLowercaseColorsSuppression.grit
  • crates/biome_plugin_loader/src/analyzer_js_plugin.rs
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/biome_rule_options/lib/**/*.rs : Define per-rule options in the `biome_rule_options` crate under `lib/`, with serde- and schemars-compatible derives and `#[serde(rename_all = "camelCase", deny_unknown_fields, default)]`

Applied to files:

  • crates/biome_analyze/src/matcher.rs
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/**/lib/src/**/nursery/**/*.rs : Use the local `rule_category!()` macro in diagnostics for the rule’s category, not string-parsed categories

Applied to files:

  • crates/biome_analyze/src/analyzer_plugin.rs
  • crates/biome_analyze/src/lib.rs
📚 Learning: 2025-08-11T11:46:05.836Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_diagnostics/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:46:05.836Z
Learning: Applies to crates/biome_diagnostics/**/*.rs : Specify category and severity using #[diagnostic(...)] on the type or derive them from fields

Applied to files:

  • crates/biome_analyze/src/analyzer_plugin.rs
📚 Learning: 2025-08-11T11:46:05.836Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_diagnostics/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:46:05.836Z
Learning: Applies to crates/biome_diagnostics/**/*.rs : Types implementing Diagnostic must also implement Debug (e.g., use #[derive(Debug, Diagnostic)])

Applied to files:

  • crates/biome_analyze/src/analyzer_plugin.rs
📚 Learning: 2025-08-11T11:46:05.836Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_diagnostics/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:46:05.836Z
Learning: Applies to crates/biome_diagnostics/**/*.rs : #[derive(Diagnostic)] can be used on enums only if every variant contains a diagnostic type

Applied to files:

  • crates/biome_analyze/src/analyzer_plugin.rs
📚 Learning: 2025-08-11T11:46:05.836Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_diagnostics/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:46:05.836Z
Learning: Applies to crates/biome_diagnostics/crates/biome_diagnostics_categories/src/categories.rs : When declaring a new diagnostic category, register it in crates/biome_diagnostics_categories/src/categories.rs

Applied to files:

  • crates/biome_analyze/src/lib.rs
🧬 Code graph analysis (4)
crates/biome_analyze/src/matcher.rs (2)
crates/biome_analyze/src/registry.rs (3)
  • RuleKey (441-441)
  • new (298-300)
  • new (382-455)
crates/biome_analyze/src/context.rs (1)
  • RuleKey (42-42)
crates/biome_analyze/src/analyzer_plugin.rs (5)
crates/biome_plugin_loader/src/analyzer_js_plugin.rs (4)
  • fmt (52-56)
  • language (76-78)
  • query (80-86)
  • evaluate (88-123)
crates/biome_rowan/src/syntax/node.rs (11)
  • fmt (778-811)
  • fmt (815-817)
  • fmt (917-919)
  • kind (67-69)
  • kind (1048-1054)
  • kind (1125-1125)
  • kind (1129-1131)
  • kind (1135-1137)
  • kind (1184-1186)
  • new (1152-1154)
  • default (923-928)
crates/biome_plugin_loader/src/analyzer_grit_plugin.rs (6)
  • language (46-51)
  • query (53-63)
  • evaluate (65-120)
  • node (69-70)
  • node (72-73)
  • None (103-103)
crates/biome_rowan/src/syntax.rs (3)
  • node (100-102)
  • from_raw (40-40)
  • new (74-76)
crates/biome_analyze/src/registry.rs (2)
  • new (298-300)
  • new (382-455)
crates/biome_analyze/src/lib.rs (1)
crates/biome_analyze/src/matcher.rs (1)
  • rule (113-115)
crates/biome_plugin_loader/src/analyzer_js_plugin.rs (3)
crates/biome_analyze/src/analyzer_plugin.rs (3)
  • language (19-19)
  • query (21-21)
  • evaluate (23-23)
crates/biome_plugin_loader/src/analyzer_grit_plugin.rs (3)
  • language (46-51)
  • query (53-63)
  • evaluate (65-120)
crates/biome_rowan/src/syntax/node.rs (7)
  • kind (67-69)
  • kind (1048-1054)
  • kind (1125-1125)
  • kind (1129-1131)
  • kind (1135-1137)
  • kind (1184-1186)
  • new (1152-1154)
⏰ 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). (23)
  • GitHub Check: Bench (biome_json_parser)
  • GitHub Check: Bench (biome_package)
  • GitHub Check: Bench (biome_json_analyze)
  • GitHub Check: Bench (biome_configuration)
  • GitHub Check: Bench (biome_css_analyze)
  • GitHub Check: Bench (biome_graphql_formatter)
  • GitHub Check: Bench (biome_module_graph)
  • GitHub Check: Bench (biome_graphql_parser)
  • GitHub Check: Bench (biome_html_parser)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Bench (biome_html_formatter)
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Bench (biome_css_formatter)
  • GitHub Check: Bench (biome_css_parser)
  • GitHub Check: Bench (biome_json_formatter)
  • GitHub Check: Documentation
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Check Dependencies
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Test Node.js API
  • GitHub Check: Parser conformance
  • GitHub Check: autofix
🔇 Additional comments (6)
crates/biome_analyze/src/matcher.rs (2)

258-258: Tests updated correctly to use into()

This keeps the test faithful to the new API while remaining readable.


153-165: All SignalEntry call-sites now use SignalRuleKey; no bare RuleKey initialisers remain.

crates/biome_plugin_loader/src/analyzer_js_plugin.rs (1)

175-176: Test updates look good

Passing parse.syntax().into() matches the new evaluate signature and keeps the concurrency test meaningful.

Also applies to: 189-190

crates/biome_analyze/src/lib.rs (1)

410-431: Suppression matching for plugins looks good

The split between Rule vs Plugin with top-level and range suppressions is clear and consistent with the new model.

crates/biome_analyze/src/suppressions.rs (2)

349-366: Range plugin suppression helper: nice and tidy

Good encapsulation; the semantics align with line/top-level plugin suppression.


97-101: Cross-type equality confirmed
impl PartialEq<RuleKey> for RuleFilter<'static> at crates/biome_analyze/src/matcher.rs:132 ensures f == filter compiles.

@siketyan siketyan merged commit b1f8cbd into biomejs:main Sep 4, 2025
31 checks passed
@github-actions github-actions bot mentioned this pull request Sep 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Core Area: core A-Linter Area: linter A-Project Area: project L-CSS Language: CSS L-Grit Language: GritQL L-JavaScript Language: JavaScript and super languages
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants