-
-
Notifications
You must be signed in to change notification settings - Fork 723
feat(biome-js-analyze): class member references service #7428
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(biome-js-analyze): class member references service #7428
Conversation
|
WalkthroughRemoves the standalone Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🧰 Additional context used📓 Path-based instructions (3)crates/biome_*_{syntax,parser,formatter,analyze,factory,semantic}/**📄 CodeRabbit inference engine (CLAUDE.md)
Files:
crates/biome_*/**📄 CodeRabbit inference engine (CLAUDE.md)
Files:
**/*.rs📄 CodeRabbit inference engine (CONTRIBUTING.md)
Files:
🧠 Learnings (1)📓 Common learnings
🧬 Code graph analysis (1)crates/biome_js_analyze/src/services/semantic_class.rs (1)
⏰ 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)
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal). Please share your feedback with us on this Discord post. Comment |
need help on this one please, run method is not getting executed. |
Could you elaborate a little more on the problems you're having? At a glance it looks correct. What have you tried? What are you expecting? I noticed that you implement your own |
f2169f2
to
968bc3f
Compare
CodSpeed Performance ReportMerging #7428 will degrade performances by 7.12%Comparing Summary
Benchmarks breakdown
Footnotes |
65b73ab
to
7260163
Compare
unsure if I need to raise a ticket and do a changeset. Assuming the changesets are for end users only, we don't need one? please confirm. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
crates/biome_js_analyze/src/lint/style/use_readonly_class_properties.rs (2)
138-179
: Fix borrow pattern and remove O(n²) clones inrun
- Destructuring a reference with
let ClassMemberReferences { writes, .. } = &...
doesn’t work as written; take a reference to the set instead.- Current pipeline clones
constructor_params
andwrites
repeatedly; precompute name sets and filter once.Apply this diff:
@@ - let ClassMemberReferences { writes, .. } = ctx.class_member_references(); + let writes = &ctx.class_member_references().writes; @@ - let constructor_params: Vec<_> = - collect_non_readonly_constructor_parameters(&members, private_only); + let constructor_params: Vec<_> = + collect_non_readonly_constructor_parameters(&members, private_only); + + // Precompute names for faster membership checks + let write_names: std::collections::HashSet<_> = + writes.iter().map(|r| r.name.clone()).collect(); + let ctor_names: std::collections::HashSet<_> = constructor_params + .iter() + .filter_map(|n| extract_property_or_param_range_and_text(n).map(|t| t.text)) + .collect(); @@ - constructor_params - .clone() - .into_iter() - .chain( - non_readonly_class_property_members.filter(|class_property_member| { - !constructor_params.clone().into_iter().any(|node| { - node.to_trimmed_text() == class_property_member.to_trimmed_text() - }) - }), - ) - .filter_map(|prop_or_param| { - if writes - .clone() - .into_iter() - .any(|ClassMemberReference { name, .. }| { - if let Some(TextAndRange { text, .. }) = - extract_property_or_param_range_and_text(&prop_or_param.clone()) - { - return name.eq(&text); - } - - false - }) - { - None - } else { - Some(prop_or_param.clone()) - } - }) + constructor_params + .into_iter() + .filter_map(|node| { + extract_property_or_param_range_and_text(&node) + .map(|t| (!write_names.contains(&t.text)).then_some(node)) + .flatten() + }) + .chain( + non_readonly_class_property_members.filter_map(|member| { + extract_property_or_param_range_and_text(&member).map(|t| { + (!ctor_names.contains(&t.text) && !write_names.contains(&t.text)) + .then_some(member) + }).flatten() + }), + ) .collect::<Vec<_>>() .into_boxed_slice()
268-268
: User-facing text: “modifier”, not “decorator”The fix adds a
readonly
modifier. Let’s avoid confusing users.- markup! { "Add "<Emphasis>"readonly"</Emphasis>" decorator." }.to_owned(), + markup! { "Add "<Emphasis>"readonly"</Emphasis>" modifier." }.to_owned(),
🧹 Nitpick comments (5)
crates/biome_js_analyze/src/lint/style/use_readonly_class_properties.rs (2)
20-20
: Minor import tidy-up for new HashSet usageIf you take the refactor above, pull
HashSet
into scope.-use std::iter::once; +use std::{collections::HashSet, iter::once};
328-330
: Doc nit: duplicated word and clearer phrasingSmall clean-up; makes the comment easier to scan.
-/// Collects all all mutable (non-readonly) constructor parameters from a given class member list. If private_only is true, it only includes parameters with private visibility. -/// It returns a Vec<PropOrParam> representing these parameters, which are candidates for being marked as readonly. +/// Collects all mutable (non-`readonly`) constructor parameters from a given class member list. +/// If `private_only` is true, only parameters with private visibility are included. +/// Returns a `Vec<AnyPropertyMember>` of parameters that are candidates for `readonly`.crates/biome_js_analyze/src/services/semantic_class.rs (3)
653-657
: Use the member token range for precisionDiagnostic ranges are sharper if we point at the property name, not the whole
this.member
expression.- reads.insert(ClassMemberReference { - name: member.to_trimmed_text(), - range: static_member.syntax().text_trimmed_range(), - }); + let name_text = member.to_trimmed_text(); + reads.insert(ClassMemberReference { + range: member.syntax().text_trimmed_range(), + name: name_text, + });
686-698
: Consider covering more compound assignment operatorsYou handle many, but miss some common ones (e.g.
-=
and shifts/xor). Low effort to extend while we’re here.&& matches!( operator.kind(), JsSyntaxKind::PIPE2EQ | JsSyntaxKind::AMP2EQ + | JsSyntaxKind::CARET_EQ | JsSyntaxKind::SLASHEQ | JsSyntaxKind::STAREQ | JsSyntaxKind::PERCENTEQ | JsSyntaxKind::PLUSEQ + | JsSyntaxKind::MINUSEQ + | JsSyntaxKind::LT2EQ + | JsSyntaxKind::GT2EQ + | JsSyntaxKind::GT3EQ | JsSyntaxKind::QUESTION2EQ )
1-13
: Small import hygieneYou can drop a couple of unused imports after the refactors (if any remain post-diff). Let rustfmt/clippy guide this.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
crates/biome_js_analyze/src/lib.rs
(0 hunks)crates/biome_js_analyze/src/lint/style/use_readonly_class_properties.rs
(4 hunks)crates/biome_js_analyze/src/services/mod.rs
(1 hunks)crates/biome_js_analyze/src/services/semantic_class.rs
(6 hunks)
💤 Files with no reviewable changes (1)
- crates/biome_js_analyze/src/lib.rs
🧰 Additional context used
📓 Path-based instructions (3)
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/services/mod.rs
crates/biome_js_analyze/src/lint/style/use_readonly_class_properties.rs
crates/biome_js_analyze/src/services/semantic_class.rs
crates/biome_*/**
📄 CodeRabbit inference engine (CLAUDE.md)
Place core crates under /crates/biome_*/
Files:
crates/biome_js_analyze/src/services/mod.rs
crates/biome_js_analyze/src/lint/style/use_readonly_class_properties.rs
crates/biome_js_analyze/src/services/semantic_class.rs
**/*.rs
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Format all Rust source files before committing (just f)
Files:
crates/biome_js_analyze/src/services/mod.rs
crates/biome_js_analyze/src/lint/style/use_readonly_class_properties.rs
crates/biome_js_analyze/src/services/semantic_class.rs
🧠 Learnings (8)
📚 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 : If a rule provides a code action, declare fix_kind in declare_lint_rule!
Applied to files:
crates/biome_js_analyze/src/lint/style/use_readonly_class_properties.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 : When porting a lint rule, set sources in declare_lint_rule! (RuleSource::Eslint(...).same() or .inspired())
Applied to files:
crates/biome_js_analyze/src/lint/style/use_readonly_class_properties.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 : Set the language field in declare_lint_rule! to the most specific applicable language (js/jsx/ts/tsx)
Applied to files:
crates/biome_js_analyze/src/lint/style/use_readonly_class_properties.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 : When returning a code action, pass category via ctx.action_category(ctx.category(), ctx.group()) and applicability via ctx.metadata().applicability()
Applied to files:
crates/biome_js_analyze/src/lint/style/use_readonly_class_properties.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/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/style/use_readonly_class_properties.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 : Use rule_category!() when creating RuleDiagnostic instead of dynamic string categories
Applied to files:
crates/biome_js_analyze/src/lint/style/use_readonly_class_properties.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 : In declare_lint_rule! metadata, set version: "next"
Applied to files:
crates/biome_js_analyze/src/lint/style/use_readonly_class_properties.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 : When deprecating a rule, add deprecated: "<reason>" to declare_lint_rule!
Applied to files:
crates/biome_js_analyze/src/lint/style/use_readonly_class_properties.rs
🧬 Code graph analysis (1)
crates/biome_js_analyze/src/services/semantic_class.rs (1)
crates/biome_rowan/src/ast/mod.rs (2)
cast_ref
(142-151)unwrap_cast
(199-211)
⏰ 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). (18)
- GitHub Check: Test Node.js API
- GitHub Check: Bench (biome_configuration)
- GitHub Check: Bench (biome_package)
- GitHub Check: Bench (biome_module_graph)
- GitHub Check: Bench (biome_css_analyze)
- GitHub Check: Bench (biome_json_analyze)
- GitHub Check: Bench (biome_json_parser)
- GitHub Check: Bench (biome_html_parser)
- GitHub Check: Bench (biome_json_formatter)
- GitHub Check: Bench (biome_graphql_parser)
- GitHub Check: Bench (biome_js_analyze)
- GitHub Check: autofix
- GitHub Check: Bench (biome_js_formatter)
- GitHub Check: Bench (biome_graphql_formatter)
- GitHub Check: Bench (biome_html_formatter)
- GitHub Check: Bench (biome_css_parser)
- GitHub Check: Bench (biome_js_parser)
- GitHub Check: Bench (biome_css_formatter)
🔇 Additional comments (5)
crates/biome_js_analyze/src/services/mod.rs (1)
6-6
: Publicly exposingsemantic_class
looks rightModule placement is consistent with the existing structure. No further action.
crates/biome_js_analyze/src/lint/style/use_readonly_class_properties.rs (1)
116-124
: Double-check rule metadata (version and language scope)
- Our guidance usually sets
version: "next"
when introducing/porting a rule.- Consider whether this should also run for
tsx
(classes appear there too). If yes, adjustlanguage
accordingly.Would you confirm the repo’s current convention for new/changed rules and whether we should broaden from
"ts"
to also cover"tsx"
?crates/biome_js_analyze/src/services/semantic_class.rs (3)
35-48
: Service initialisation throughFromServices
is solidNice: clear error if the service is missing, and cloning the references keeps things immutable for consumers.
50-54
: Phase declaration is correctBinding
SemanticClassServices
toPhases::Semantic
aligns with how the service is consumed.
833-1157
: Tests are thorough and readableGood spread across patterns; these will catch regressions in the collectors.
There was a problem hiding this 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 (1)
crates/biome_js_analyze/src/services/semantic_class.rs (1)
190-196
: Don’t record arbitrary static members in property initialisers; requirethis
We currently count any
a.b
in a class field initialiser as a read. Gate it onthis
.- } else if let Some(static_member_expression) = - expression.as_js_static_member_expression() - { - return collect_class_property_reads_from_static_member( - static_member_expression, - ); - } + } else if let Some(static_member_expression) = + expression.as_js_static_member_expression() + { + if let Ok(obj) = static_member_expression.object() + && obj.as_js_this_expression().is_some() + { + return collect_class_property_reads_from_static_member( + static_member_expression, + ); + } + }And double-guard inside the helper:
fn collect_class_property_reads_from_static_member( static_member: &JsStaticMemberExpression, ) -> Option<ClassMemberReferences> { let mut reads = HashSet::new(); let writes = HashSet::new(); + // Only consider `this.prop` / `this.#prop` + if let Ok(object) = static_member.object() { + if object.as_js_this_expression().is_none() { + return None; + } + } + if let Ok(member) = static_member.member() { let name = member.to_trimmed_text(); reads.insert(ClassMemberReference { name, range: static_member.syntax().text_trimmed_range(), }); } Some(ClassMemberReferences { reads, writes }) }Also applies to: 794-809
♻️ Duplicate comments (4)
crates/biome_js_analyze/src/services/semantic_class.rs (4)
299-299
: Nix the commented debug printlnIt’s noise in the visitor.
- // println!("leave node in ThisScopeVisitor {:?}", node);
71-79
: Emit the Syntax-phase match so rules’ run() actually executesYou’re collecting refs but never emitting a Syntax-phase match. Call ctx.match_query when entering a class. This fixes the “run not executed” issue.
- fn visit(&mut self, event: &WalkEvent<JsSyntaxNode>, _ctx: VisitorContext<JsLanguage>) { + fn visit(&mut self, event: &WalkEvent<JsSyntaxNode>, ctx: VisitorContext<JsLanguage>) { if let WalkEvent::Enter(node) = event && let Some(js_class_declaration) = JsClassDeclaration::cast_ref(node) { + // Emit the Syntax-phase query match for this class + ctx.match_query(node.clone()); let class_member_list = js_class_declaration.members(); let refs = class_member_references(&class_member_list); self.references.reads.extend(refs.reads); self.references.writes.extend(refs.writes); } }#!/bin/bash # Expect: match_query is called inside ClassMemberReferencesVisitor; no SemanticClassVisitor present. rg -nP -C2 'impl\s+Visitor\s+for\s+ClassMemberReferencesVisitor|match_query\(' crates/biome_js_analyze/src/services/semantic_class.rs rg -nP -C1 'SemanticClassVisitor' crates/biome_js_analyze/src/services/semantic_class.rs || true
99-114
: Delete the redundant Semantic-phase visitor and its registrationWith Syntax-phase matching in place, this visitor just confuses things and risks double work.
-struct SemanticClassVisitor; - -impl Visitor for SemanticClassVisitor { - type Language = JsLanguage; - - fn visit(&mut self, event: &WalkEvent<JsSyntaxNode>, mut ctx: VisitorContext<JsLanguage>) { - match event { - WalkEvent::Enter(node) => { - if JsClassDeclaration::can_cast(node.kind()) { - ctx.match_query(node.clone()); - } - } - WalkEvent::Leave(_) => {} - }; - } -} @@ fn build_visitor(analyzer: &mut impl AddVisitor<JsLanguage>, root: &AnyJsRoot) { analyzer.add_visitor(Phases::Syntax, || ClassMemberReferencesVisitor::new(root)); - analyzer.add_visitor(Phases::Semantic, || SemanticClassVisitor); }Also applies to: 126-129
19-28
: Track references per class (don’t mix all classes together)The service currently aggregates reads/writes for the whole root. That breaks when multiple classes exist. Store refs keyed by class range (or node key) and expose a query by range, aligning with earlier feedback.
-#[derive(Clone)] -pub struct SemanticClassServices { - class_member_references: ClassMemberReferences, -} +#[derive(Clone)] +pub struct SemanticClassServices { + class_member_references: std::collections::BTreeMap<TextRange, ClassMemberReferences>, +} @@ -impl SemanticClassServices { - pub fn class_member_references(&self) -> &ClassMemberReferences { - &self.class_member_references - } -} +impl SemanticClassServices { + pub fn class_member_references(&self, range: TextRange) -> Option<&ClassMemberReferences> { + self.class_member_references.get(&range) + } +} @@ -#[derive(Debug, Clone)] -pub struct SemanticClassModel { - pub references: ClassMemberReferences, -} +#[derive(Debug, Clone)] +pub struct SemanticClassModel { + pub references_by_class: std::collections::BTreeMap<TextRange, ClassMemberReferences>, +} @@ let service: &SemanticClassModel = services.get_service().ok_or_else(|| { ServicesDiagnostic::new(rule_key.rule_name(), &["SemanticClassModel"]) })?; Ok(Self { - class_member_references: service.references.clone(), + class_member_references: service.references_by_class.clone(), }) } } @@ -pub struct ClassMemberReferencesVisitor { - references: ClassMemberReferences, -} +pub struct ClassMemberReferencesVisitor { + by_class: std::collections::BTreeMap<TextRange, ClassMemberReferences>, +} @@ impl ClassMemberReferencesVisitor { pub(crate) fn new(_root: &AnyJsRoot) -> Self { Self { - references: ClassMemberReferences::default(), + by_class: Default::default(), } } } @@ - { - let class_member_list = js_class_declaration.members(); - let refs = class_member_references(&class_member_list); - self.references.reads.extend(refs.reads); - self.references.writes.extend(refs.writes); + { + let class_member_list = js_class_declaration.members(); + let refs = class_member_references(&class_member_list); + let class_range = js_class_declaration.syntax().text_trimmed_range(); + let entry = self.by_class.entry(class_range).or_default(); + entry.reads.extend(refs.reads); + entry.writes.extend(refs.writes); } @@ - ctx.services.insert_service(SemanticClassModel { - references: self.references, - }); + ctx.services.insert_service(SemanticClassModel { + references_by_class: self.by_class, + });If you prefer NodeKey over TextRange for stability, happy to sketch that too.
Also applies to: 30-48, 56-66, 82-86
🧹 Nitpick comments (5)
crates/biome_js_analyze/src/services/semantic_class.rs (5)
647-656
: Prefer the member’s range over the whole expressionThis makes diagnostics crisper (highlights
foo
, notthis.foo
).- reads.insert(ClassMemberReference { - name: member.to_trimmed_text(), - range: static_member.syntax().text_trimmed_range(), - }); + reads.insert(ClassMemberReference { + name: member.to_trimmed_text(), + range: member.syntax().text_trimmed_range(), + });
682-701
: Cover all compound assignments (or centralise the check)The operator list misses some tokens (e.g.
-=
and shifts). Either expand it fully or add a helper like “is_compound_assignment”.Would you like me to propose a complete token set based on JsSyntaxKind?
2-7
: Remove unused union and import
AnyPropertyMember
isn’t used. Trim it and theTsPropertyParameter
import.- JsVariableDeclarator, TextRange, TsPropertyParameter, + JsVariableDeclarator, TextRange, @@ -declare_node_union! { - pub AnyPropertyMember = JsPropertyClassMember | TsPropertyParameter -} +// (removed unused AnyPropertyMember)Also applies to: 152-155
56-66
: Minor: Visitor can be DefaultRoot isn’t used; consider
impl Default
+analyzer.add_visitor(..., ClassMemberReferencesVisitor::default)
.Happy to wire it if you prefer.
10-13
: Naming: singular ‘Service’ reads betterMost codebases use FooService (singular). Consider
SemanticClassService
for consistency withSemanticModel
.If you want, I’ll prep a follow-up PR-wide rename.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
crates/biome_js_analyze/src/services/semantic_class.rs
(5 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
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/services/semantic_class.rs
crates/biome_*/**
📄 CodeRabbit inference engine (CLAUDE.md)
Place core crates under /crates/biome_*/
Files:
crates/biome_js_analyze/src/services/semantic_class.rs
**/*.rs
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Format all Rust source files before committing (just f)
Files:
crates/biome_js_analyze/src/services/semantic_class.rs
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-09-10T08:05:22.867Z
Learning: Applies to crates/biome_analyze/crates/biome_js_analyze/lib/src/{lint,assist}/**/*.rs : When banning globals (e.g., `noConsoleLog`), check the semantic model to avoid false positives from locally shadowed bindings
🧬 Code graph analysis (1)
crates/biome_js_analyze/src/services/semantic_class.rs (1)
crates/biome_rowan/src/ast/mod.rs (2)
cast_ref
(142-151)unwrap_cast
(199-211)
⏰ 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: Bench (biome_json_formatter)
- GitHub Check: Bench (biome_html_formatter)
- GitHub Check: Bench (biome_package)
- GitHub Check: Bench (biome_module_graph)
- GitHub Check: Test Node.js API
- GitHub Check: Bench (biome_graphql_parser)
- GitHub Check: Bench (biome_html_parser)
- GitHub Check: Bench (biome_configuration)
- GitHub Check: Documentation
- GitHub Check: Bench (biome_graphql_formatter)
- GitHub Check: Bench (biome_json_analyze)
- GitHub Check: Test (depot-ubuntu-24.04-arm-16)
- GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
- GitHub Check: Bench (biome_css_parser)
- GitHub Check: Bench (biome_json_parser)
- GitHub Check: Bench (biome_css_formatter)
- GitHub Check: Bench (biome_js_formatter)
- GitHub Check: Bench (biome_js_analyze)
- GitHub Check: Bench (biome_css_analyze)
- GitHub Check: Bench (biome_js_parser)
- GitHub Check: Test (depot-windows-2022-16)
- GitHub Check: Check Dependencies
- GitHub Check: Lint project (depot-windows-2022)
- GitHub Check: autofix
There was a problem hiding this 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 (2)
crates/biome_js_analyze/src/services/semantic_class.rs (2)
617-632
: Use the member token range for reads (consistency with writes)
Reads currently record the entirethis.foo
span. Writes use the member token’s range. Align reads to the member’s range for consistency and better diagnostics.if let Some(static_member) = JsStaticMemberExpression::cast_ref(node) && let Ok(object) = static_member.object() && is_this_reference(&object, scoped_this_references) && let Ok(member) = static_member.member() { reads.insert(ClassMemberReference { name: member.to_trimmed_text(), - range: static_member.syntax().text_trimmed_range(), + range: member.syntax().text_trimmed_range(), }); }
651-676
: Detect all compound assignments, not a hand-picked subset
Limiting to a small set of operators misses cases like-=
or**=
. Simpler and safer: treat any assignment operator other than=
as compound.- if let Ok(operator) = assignment.operator_token() - && let Some(operand) = left.as_any_js_assignment() - && matches!( - operator.kind(), - JsSyntaxKind::PIPE2EQ - | JsSyntaxKind::AMP2EQ - | JsSyntaxKind::SLASHEQ - | JsSyntaxKind::STAREQ - | JsSyntaxKind::PERCENTEQ - | JsSyntaxKind::PLUSEQ - | JsSyntaxKind::QUESTION2EQ - ) + if let Ok(operator) = assignment.operator_token() + && operator.kind() != JsSyntaxKind::EQ + && let Some(operand) = left.as_any_js_assignment() && let Some(name) = ThisPatternResolver::extract_this_member_reference( operand.as_js_static_member_assignment(), scoped_this_references, ) { reads.insert(name.clone()); }Add tests for
-=
/**=
/<<=
to avoid regressions.
♻️ Duplicate comments (1)
crates/biome_js_analyze/src/services/semantic_class.rs (1)
19-37
: Naming: align with existing service/model conventions
Current split reads as “Services” (plural) holding a “Model”. ConsiderClassMemberReferencesService
(service) +ClassMemberReferencesModel
(model), or adopt reviewer’s suggestion to mirrorSemanticModel
.
🧹 Nitpick comments (5)
crates/biome_js_analyze/src/services/semantic_class.rs (5)
577-599
: Only record the bound property name (avoidkey: alias
text)
declarator.to_trimmed_text()
will include aliases (foo: local
). Extract the property key instead so the recorded read is the class field name.- for declarator in binding.properties() { - if let Some(declarator) = declarator.ok() - && is_this_reference(&expression, scoped_this_references) - { - reads.insert(ClassMemberReference { - name: declarator.to_trimmed_text(), - range: declarator.syntax().text_trimmed_range(), - }); - } - } + for prop in binding.properties() { + if let Some(prop) = prop.ok() + && is_this_reference(&expression, scoped_this_references) + && let Some(p) = prop.as_js_object_binding_pattern_property() + && let Ok(key) = p.key() + { + reads.insert(ClassMemberReference { + name: key.to_trimmed_text(), + range: key.syntax().text_trimmed_range(), + }); + } + }
319-344
: Alias discovery misses nested block declarations
collect_local_this_references
only scans top-level statements, soconst self = this;
inside a block is ignored. Consider descending into blocks to catch aliases with block scope.
274-274
: Remove debug noise
Stray commentedprintln!
left behind.- // println!("leave node in ThisScopeVisitor {:?}", node);
127-129
: Dead union?
AnyPropertyMember
isn’t used. If truly unused, drop it and theTsPropertyParameter
import.
807-1131
: Add a few missing tests
Please add tests for:
- Compound ops
-=
,**=
,<<=
to validate the generalised check.- Object binding with aliasing (
const { foo: local } = this
) to ensure onlyfoo
is recorded.Happy to draft the cases if useful.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
crates/biome_js_analyze/src/lint/style/use_readonly_class_properties.rs
(4 hunks)crates/biome_js_analyze/src/services/semantic_class.rs
(5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- crates/biome_js_analyze/src/lint/style/use_readonly_class_properties.rs
🧰 Additional context used
📓 Path-based instructions (3)
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/services/semantic_class.rs
crates/biome_*/**
📄 CodeRabbit inference engine (CLAUDE.md)
Place core crates under /crates/biome_*/
Files:
crates/biome_js_analyze/src/services/semantic_class.rs
**/*.rs
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Format all Rust source files before committing (just f)
Files:
crates/biome_js_analyze/src/services/semantic_class.rs
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-09-10T08:05:22.867Z
Learning: Applies to crates/biome_analyze/crates/biome_js_analyze/lib/src/{lint,assist}/**/*.rs : When banning globals (e.g., `noConsoleLog`), check the semantic model to avoid false positives from locally shadowed bindings
🧬 Code graph analysis (1)
crates/biome_js_analyze/src/services/semantic_class.rs (1)
crates/biome_rowan/src/ast/mod.rs (1)
unwrap_cast
(199-211)
⏰ 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: Test Node.js API
- GitHub Check: Documentation
- GitHub Check: Test (depot-windows-2022-16)
- GitHub Check: Lint project (depot-windows-2022)
- GitHub Check: Check Dependencies
- GitHub Check: Test (depot-ubuntu-24.04-arm-16)
- GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
- GitHub Check: Bench (biome_css_parser)
- GitHub Check: Bench (biome_configuration)
- GitHub Check: Bench (biome_html_formatter)
- GitHub Check: Bench (biome_graphql_parser)
- GitHub Check: Bench (biome_module_graph)
- GitHub Check: Bench (biome_graphql_formatter)
- GitHub Check: Bench (biome_json_analyze)
- GitHub Check: Bench (biome_json_formatter)
- GitHub Check: Bench (biome_package)
- GitHub Check: Bench (biome_js_formatter)
- GitHub Check: Bench (biome_js_analyze)
- GitHub Check: Bench (biome_css_formatter)
- GitHub Check: Bench (biome_css_analyze)
- GitHub Check: Bench (biome_html_parser)
- GitHub Check: Bench (biome_json_parser)
- GitHub Check: Bench (biome_js_parser)
- GitHub Check: autofix
🔇 Additional comments (2)
crates/biome_js_analyze/src/services/semantic_class.rs (2)
65-80
: Syntax-phase matching fixed — good callEmitting
ctx.match_query
onJsClassDeclaration
in the Syntax phase and inserting the stateless model infinish
looks correct. This should unblock rules’run
.
739-761
: Behavioural sanity check across entry points
Constructor, method/property initialisers, and on-demand model all converge onvisit_references_in_body
/class_member_references
. Looks cohesive; tests cover these paths.Also applies to: 517-533, 138-194
fn build_visitor(analyzer: &mut impl AddVisitor<JsLanguage>, _root: &AnyJsRoot) { | ||
analyzer.add_visitor(Phases::Syntax, || ClassMemberReferencesVisitor {}); | ||
analyzer.add_visitor(Phases::Semantic, || ClassMemberReferencesVisitor {}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Register the visitor only once (Syntax phase)
Registering the same visitor for both Syntax and Semantic phases is redundant and can double-run finish()
. Keep only the Syntax-phase registration.
fn build_visitor(analyzer: &mut impl AddVisitor<JsLanguage>, _root: &AnyJsRoot) {
- analyzer.add_visitor(Phases::Syntax, || ClassMemberReferencesVisitor {});
- analyzer.add_visitor(Phases::Semantic, || ClassMemberReferencesVisitor {});
+ analyzer.add_visitor(Phases::Syntax, || ClassMemberReferencesVisitor {});
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
fn build_visitor(analyzer: &mut impl AddVisitor<JsLanguage>, _root: &AnyJsRoot) { | |
analyzer.add_visitor(Phases::Syntax, || ClassMemberReferencesVisitor {}); | |
analyzer.add_visitor(Phases::Semantic, || ClassMemberReferencesVisitor {}); | |
} | |
fn build_visitor(analyzer: &mut impl AddVisitor<JsLanguage>, _root: &AnyJsRoot) { | |
analyzer.add_visitor(Phases::Syntax, || ClassMemberReferencesVisitor {}); | |
} |
🤖 Prompt for AI Agents
In crates/biome_js_analyze/src/services/semantic_class.rs around lines 101 to
104, the ClassMemberReferencesVisitor is being registered for both
Phases::Syntax and Phases::Semantic which is redundant and can cause its
finish() to run twice; remove the Phases::Semantic registration and keep only
the Phases::Syntax registration so the visitor is added once during the syntax
phase.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we need both, if Semantic phase is not registered, it does not seem to make any matches.
Guessing the other option will be to split ClassMemberReferenceVisitor into two, - one for Syntax to register the service only and another for Semantic to register the node query
@ematipico please confirm ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The bot is actually correct
Check how the semantic model
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried both, maybe I am missing something, but bot is wrong. removing Semantic only - fails, removing Syntax only also fails.
splitting into two visitors works - only thing that I can make work.
need to read the internals and do more debugging to understand why it is not working.
Leave it with me. thanks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All right, sounds good. Maybe reply to the bot and tell it why it's incorrect, so next time won't bother
@ematipico planning to use this service if/when merged for my next pr, all the reads / writes are valuable for consistency in no_unused_private_properties for instance |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great refactor! Thank you. Happy to merge once everything is green. Also, keep an eye on performance, I recall there was a small regression
what exactly is the code on regression that I am looking at please - is it now actually running the rules themselves? will look tonight at it. thanks |
crates/biome_js_analyze/benches/js_analyzer.rs thanks, it says it there actually :-) |
…ed as valid updates and no errors triggered
a6da844
to
4d87dad
Compare
There was a problem hiding this 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 (3)
crates/biome_js_analyze/src/services/semantic_class.rs (3)
274-274
: Remove the debug print statement.- // println!("leave node in ThisScopeVisitor {:?}", node);
33-37
: Model doesn't store any class data.The
SemanticClassModel
is empty and can't provide class member references for specific classes. Rules need to query references by class range/identity.Store class references in the model:
#[derive(Debug, Clone)] -pub struct SemanticClassModel {} +pub struct SemanticClassModel { + class_references: FxHashMap<TextRange, ClassMemberReferences>, +} impl SemanticClassModel { - pub fn class_member_references(&self, members: &JsClassMemberList) -> ClassMemberReferences { - class_member_references(members) + pub fn class_member_references(&self, class_range: TextRange) -> Option<&ClassMemberReferences> { + self.class_references.get(&class_range) } }
77-79
: The service should be created once per root, not once per class.The current approach inserts a new
SemanticClassModel {}
for every class encountered. This will overwrite previous instances, and rules won't be able to access data from other classes in the same file.Move the service creation to happen once per file:
fn finish(self: Box<Self>, ctx: VisitorFinishContext<JsLanguage>) { - ctx.services.insert_service(SemanticClassModel {}); + // Service should be created once per root with collected data + // Consider storing class references in the visitor and creating + // the model here with all collected data }
🧹 Nitpick comments (1)
crates/biome_js_analyze/src/services/semantic_class.rs (1)
83-83
: Naming: ConsiderSemanticClass
→ClassSemanticQuery
.Given the existing feedback about naming and the pattern of other services, a more descriptive name might help distinguish this from general class handling.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
crates/biome_js_analyze/src/lib.rs
(0 hunks)crates/biome_js_analyze/src/lint/style/use_readonly_class_properties.rs
(4 hunks)crates/biome_js_analyze/src/services/mod.rs
(1 hunks)crates/biome_js_analyze/src/services/semantic_class.rs
(21 hunks)
💤 Files with no reviewable changes (1)
- crates/biome_js_analyze/src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (2)
- crates/biome_js_analyze/src/services/mod.rs
- crates/biome_js_analyze/src/lint/style/use_readonly_class_properties.rs
🧰 Additional context used
📓 Path-based instructions (3)
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/services/semantic_class.rs
crates/biome_*/**
📄 CodeRabbit inference engine (CLAUDE.md)
Place core crates under /crates/biome_*/
Files:
crates/biome_js_analyze/src/services/semantic_class.rs
**/*.rs
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Format all Rust source files before committing (just f)
Files:
crates/biome_js_analyze/src/services/semantic_class.rs
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-09-10T08:05:22.867Z
Learning: Applies to crates/biome_analyze/crates/biome_js_analyze/lib/src/{lint,assist}/**/*.rs : When banning globals (e.g., `noConsoleLog`), check the semantic model to avoid false positives from locally shadowed bindings
🧬 Code graph analysis (1)
crates/biome_js_analyze/src/services/semantic_class.rs (1)
crates/biome_rowan/src/ast/mod.rs (1)
unwrap_cast
(199-211)
⏰ 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: Bench (biome_package)
- GitHub Check: Bench (biome_js_formatter)
- GitHub Check: Test Node.js API
- GitHub Check: Bench (biome_html_formatter)
- GitHub Check: Bench (biome_html_parser)
- GitHub Check: Bench (biome_configuration)
- GitHub Check: Bench (biome_graphql_parser)
- GitHub Check: Bench (biome_css_formatter)
- GitHub Check: Bench (biome_js_analyze)
- GitHub Check: Bench (biome_json_analyze)
- GitHub Check: Bench (biome_json_parser)
- GitHub Check: autofix
- GitHub Check: Bench (biome_module_graph)
- GitHub Check: Bench (biome_json_formatter)
- GitHub Check: Bench (biome_js_parser)
- GitHub Check: Bench (biome_graphql_formatter)
- GitHub Check: Bench (biome_css_analyze)
- GitHub Check: Bench (biome_css_parser)
- GitHub Check: Test (depot-windows-2022-16)
- GitHub Check: Lint project (depot-windows-2022)
- GitHub Check: Documentation
- GitHub Check: Check Dependencies
- GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
- GitHub Check: Test (depot-ubuntu-24.04-arm-16)
…ed as valid updates and no errors triggered
merging, there is nothing different from before, apart of the introduction of a service, looks like the perf degradation is purely because of that. @ematipico please let me know if I missed anything, but I can not see it. |
Summary
moves class_member_references fn into a dedicated semantic class service
closes #7460