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

Skip to content

Conversation

@vladimir-ivanov
Copy link
Contributor

@vladimir-ivanov vladimir-ivanov commented Sep 20, 2025

Summary

Adds is_meaningful_read property to ClassMemberReference

This change introduces an is_meaningful_read field on the ClassMemberReference struct.
The purpose of this property is to distinguish between meaningful and non-meaningful (useless) reads, so that rules like no_unused_class_properties can reason more precisely about property usage.
• Applicability:
• The flag is relevant only for reads.
• Writes do not use this distinction, so for all writes, is_meaningful_read is set to None.
• Reads, however, will have Some(true) or Some(false).

A meaningful read is a property access whose value is actually consumed in the program’s logic.
Examples of meaningful reads:

return this.foo;        // value is returned
console.log(this.bar);  // value is passed as an argument
if (this.flag) {  }    // value participates in a condition

full list of what counts as meaningul read context:

&[
    JsSyntaxKind::JS_RETURN_STATEMENT,
    JsSyntaxKind::JS_CALL_ARGUMENTS,
    JsSyntaxKind::JS_CONDITIONAL_EXPRESSION,
    JsSyntaxKind::JS_LOGICAL_EXPRESSION,
    JsSyntaxKind::JS_THROW_STATEMENT,
    JsSyntaxKind::JS_AWAIT_EXPRESSION,
    JsSyntaxKind::JS_YIELD_EXPRESSION,
    JsSyntaxKind::JS_UNARY_EXPRESSION,
    JsSyntaxKind::JS_TEMPLATE_EXPRESSION,
    JsSyntaxKind::JS_CALL_EXPRESSION, // callee
    JsSyntaxKind::JS_NEW_EXPRESSION,
    JsSyntaxKind::JS_IF_STATEMENT,
    JsSyntaxKind::JS_SWITCH_STATEMENT,
    JsSyntaxKind::JS_FOR_STATEMENT,
    JsSyntaxKind::JS_FOR_IN_STATEMENT,
    JsSyntaxKind::JS_FOR_OF_STATEMENT,
    JsSyntaxKind::JS_BINARY_EXPRESSION,
];

Example of non meaningful reads:

this.counter++;         // read is only part of an increment/decrement
this.value = 42;        // assignment target is read syntactically but not meaningfully

Next step is to simplify no_unused_class_properties to use SemanticClass and extract ONLY reads that have is_meaningful_read set to true.

part of #7499

@changeset-bot
Copy link

changeset-bot bot commented Sep 20, 2025

⚠️ No Changeset found

Latest commit: 270801c

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@github-actions github-actions bot added A-Linter Area: linter L-JavaScript Language: JavaScript and super languages labels Sep 20, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 20, 2025

Walkthrough

Adds a public AccessKind enum (Write, MeaningfulRead, TrivialRead) and a pub access_kind: AccessKind field to ClassMemberReference. Exports unions AnyCandidateForUsedInExpressionNode and AnyJsUpdateExpression. Propagates and computes access_kind across reads and writes (destructuring, static members, pre/post update expressions), introduces get_read_access_kind and is_used_in_expression_context, updates traversal to carry the new unions, and extends tests and helpers to assert read/write kinds.

Possibly related PRs

Suggested reviewers

  • ematipico
  • dyc3

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly describes the primary change of adding an is_meaningful_read flag to semantic class reads within biome-js-analyze, accurately reflecting the main feature introduced in the PR.
Description Check ✅ Passed The description offers a clear, on-topic summary of the new is_meaningful_read field, its rationale, usage contexts, examples, and next steps, all of which directly map to the code modifications and PR objectives.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

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 @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
crates/biome_js_analyze/src/services/semantic_class.rs (1)

18-18: Redundant import: std::option::Option is unnecessary.

The Option type is already in the prelude and doesn't need explicit importing.

-use std::option::Option;
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8a4f4fc and 4a49f32.

📒 Files selected for processing (1)
  • crates/biome_js_analyze/src/services/semantic_class.rs (30 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
🧬 Code graph analysis (1)
crates/biome_js_analyze/src/services/semantic_class.rs (1)
crates/biome_rowan/src/ast/mod.rs (1)
  • cast_ref (142-151)
⏰ 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 (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: autofix
  • GitHub Check: Check Dependencies
  • GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Documentation
  • GitHub Check: Test Node.js API
  • GitHub Check: Bench (biome_html_formatter)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Bench (biome_graphql_formatter)
  • GitHub Check: Bench (biome_json_analyze)
  • GitHub Check: Bench (biome_html_parser)
  • GitHub Check: Bench (biome_module_graph)
  • GitHub Check: Bench (biome_configuration)
  • GitHub Check: Bench (biome_json_formatter)
  • GitHub Check: Bench (biome_css_parser)
  • GitHub Check: Bench (biome_graphql_parser)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Bench (biome_json_parser)
  • GitHub Check: Bench (biome_css_analyze)
  • GitHub Check: Bench (biome_css_formatter)
  • GitHub Check: Bench (biome_js_formatter)
🔇 Additional comments (14)
crates/biome_js_analyze/src/services/semantic_class.rs (14)

5-13: LGTM: Clean import additions for the new functionality.

The new imports for AnyJsObjectBindingPatternMember, JsPostUpdateExpression, and JsPreUpdateExpression are appropriately added to support the meaningful read tracking feature.


133-133: Well-designed extension to the data model.

The addition of is_meaningful_read: Option<bool> to ClassMemberReference provides clear semantics: Some(true) for meaningful reads, Some(false) for non-meaningful reads, and None for writes. This is a solid design choice.


146-152: Excellent type system design with union types.

The introduction of MeaningfulReadNode and AnyJsUpdateExpression unions provides a clean abstraction for nodes that can participate in meaningful read analysis. The type composition is logical and well-structured.


362-364: Correct meaningful read classification for variable initialisers.

Marking the right-hand side of variable statements as meaningful reads (Some(true)) is appropriate since these expressions are being evaluated for their values.


420-420: Consistent write reference handling.

The systematic use of None for is_meaningful_read across all write operations (array assignments, object assignments, destructuring patterns) maintains consistent semantics where writes are not considered meaningful reads.

Also applies to: 441-443, 458-460, 488-490, 505-507


721-723: Clear meaningful read semantics in assignments.

The distinction between compound assignments (Some(true) for reads) and regular assignments (None for writes) correctly reflects the semantic difference between operations that consume values versus those that only store them.

Also applies to: 749-751


772-802: Sophisticated handling of update expressions.

The implementation correctly models update expressions as producing both a read and a write reference, with the read being contextually determined by is_meaningful_read. The separation into distinct ClassMemberReference instances is the right approach.


872-874: Clean public API design.

The public is_meaningful_read function provides a clean interface for external consumers whilst encapsulating the internal logic in is_used_in_expression_context.


876-907: Robust expression context analysis with proper safeguards.

The implementation correctly handles parentheses skipping and includes appropriate loop limits to prevent infinite recursion. The expression context detection covers the key scenarios where reads are meaningful.


919-921: Excellent test structure enhancement.

The updated test case structure with (name, is_meaningful_read) tuples provides comprehensive validation of the new functionality whilst maintaining clarity.


1003-1003: Verify test expectations for object binding patterns.

The tests expect Some(false) for object binding pattern reads, but these might actually be meaningful in contexts like const { foo } = this; return foo;. Consider whether these should be Some(true) instead.

Based on the web search results, object destructuring in JavaScript "makes it possible to unpack values from arrays, or properties from objects, into distinct variables" and is primarily used for "extracting property values and assigning them to a variable".

The key insight is that destructuring is fundamentally about extracting and binding values to variables, not necessarily about consuming those values in a meaningful expression context. In const { foo } = this;, the foo is being extracted and bound to a new variable, which is different from using foo in an expression like return foo or console.log(foo).

The test expectations of Some(false) for object binding patterns appear to be correct, as the destructuring operation itself is not a "meaningful read" in the expression evaluation sense—it's a binding operation that creates variables for later use.

Also applies to: 1016-1016, 1053-1053, 1067-1067


1184-1195: Excellent context-aware meaningful read detection in tests.

The test cases brilliantly demonstrate the nuanced meaningful read detection: update expressions in simple statements are Some(false), but the same expressions in conditional or return contexts correctly show Some(true). This validates the is_used_in_expression_context logic.

Also applies to: 1211-1220


1254-1371: Comprehensive test coverage for the new API.

The dedicated test module for is_meaningful_read provides thorough validation across various expression contexts. The test structure efficiently extracts meaningful nodes and validates the expected behaviour patterns.


637-640: No action needed—declare_node_union! provides From<AnyJsObjectBindingPatternMember> for MeaningfulReadNode, so declarator.into() correctly converts the node for is_meaningful_read.

@codspeed-hq
Copy link

codspeed-hq bot commented Sep 20, 2025

CodSpeed Performance Report

Merging #7543 will not alter performance

Comparing vladimir-ivanov:feat/semantic_class_is_meaningful_read (270801c) with main (5c3d542)

Summary

✅ 133 untouched

@vladimir-ivanov
Copy link
Contributor Author

also also thinking of alternative names, if we want this functionality at all of course:
is_effective_read,
is_semantically_read
is_consumed_read
is_actual_read
...

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

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)

129-134: Adding is_meaningful_read to a Hash/Eq type changes set semantics; dedupe will now treat the same reference with different flags as different entries.

This will produce duplicate reads for the same member/range when observed in different contexts (true/false). Recommend excluding is_meaningful_read from identity and hashing.

-#[derive(Debug, Clone, Hash, Eq, PartialEq)]
+#[derive(Debug, Clone)]
 pub struct ClassMemberReference {
   pub name: Text,
   pub range: TextRange,
   pub is_meaningful_read: Option<bool>,
 }
+
+impl PartialEq for ClassMemberReference {
+    fn eq(&self, other: &Self) -> bool {
+        self.name == other.name && self.range == other.range
+    }
+}
+
+impl Eq for ClassMemberReference {}
+
+impl Hash for ClassMemberReference {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        self.name.hash(state);
+        self.range.hash(state);
+    }
+}

706-724: Expand compound-assignment operator coverage
The current match only handles |=, &=, /=, *=, %=, += and ??=; you must also include the remaining assignment operators (-=, <<=, >>=, >>>=, ^=, **=, ||=, &&=) or replace the matches! with a helper like is_compound_assignment_operator(kind).

🧹 Nitpick comments (7)
crates/biome_js_analyze/src/services/semantic_class.rs (7)

5-13: Tidy imports; remove redundant Option, add Hasher for custom Eq/Hash.

Option is in prelude; importing it is unnecessary. You’ll also need Hasher if you adopt the custom Eq/Hash suggestion below.

- use std::option::Option;
+ use std::hash::{Hash, Hasher};

Also applies to: 18-18


362-364: Don’t mark aliasing this as a meaningful read.

These entries feed alias tracking, not class-member reads. Mark as None to avoid confusion if they ever leak.

-                        // right hand side of a js variable statement is meaningful read
-                        is_meaningful_read: Some(true),
+                        // Alias registration only; not a class-member read.
+                        is_meaningful_read: None,

636-641: Object binding: extract the actual property name and a stable range.

Using to_trimmed_text() and the node’s full range can capture “foo: bar” rather than just “foo” and will vary across shapes, hurting dedupe and UX. Prefer extracting the property key token and its range (and handle shorthand/aliased/rest cases explicitly).

Would you like me to draft a helper to normalise “object binding member -> (property_name, range)”, covering property, shorthand, and rest?


772-803: LGTM on update read/write split; tiny nit on duplication.

You rebuild two ClassMemberReferences from name; consider inserting name.clone() directly for the write to avoid recomputing fields.


876-886: skip_parentheses walks down; it should walk up.

As written, it rarely strips anything. Walk ancestors while the node is a parenthesised expr.

-fn skip_parentheses(mut node: JsSyntaxNode) -> JsSyntaxNode {
-    while let Some(child) = node.first_child() {
-        if child.kind() == JsSyntaxKind::JS_PARENTHESIZED_EXPRESSION {
-            node = child; // move ownership
-        } else {
-            break;
-        }
-    }
-    node
-}
+fn skip_parentheses(mut node: JsSyntaxNode) -> JsSyntaxNode {
+    // Move up past any ParenthesizedExpression wrappers.
+    while node.kind() == JsSyntaxKind::JS_PARENTHESIZED_EXPRESSION {
+        if let Some(parent) = node.parent() {
+            node = parent;
+        } else {
+            break;
+        }
+    }
+    node
+}

887-907: Broaden meaningful-context detection; handle RHS of =, variable/field initialisers, and unary contexts.

This fixes cases like a = this.x, field = this.x, and if (!this.x).

-fn is_used_in_expression_context(node: &MeaningfulReadNode) -> Option<bool> {
-    let mut current: JsSyntaxNode = node.syntax().clone();
-
-    // Limit the number of parent traversals to avoid deep recursion
-    for _ in 0..8 {
-        if let Some(parent) = current.parent() {
-            let parent = skip_parentheses(parent); // strip parentheses
-            match parent.kind() {
-                JsSyntaxKind::JS_RETURN_STATEMENT
-                | JsSyntaxKind::JS_CALL_ARGUMENTS
-                | JsSyntaxKind::JS_CONDITIONAL_EXPRESSION
-                | JsSyntaxKind::JS_LOGICAL_EXPRESSION
-                | JsSyntaxKind::JS_BINARY_EXPRESSION => return Some(true),
-                _ => current = parent,
-            }
-        } else {
-            break;
-        }
-    }
-    Some(false)
-}
+fn is_used_in_expression_context(node: &MeaningfulReadNode) -> Option<bool> {
+    let mut current: JsSyntaxNode = node.syntax().clone();
+    let mut steps: u8 = 0;
+    while let Some(mut parent) = current.parent() {
+        steps = steps.saturating_add(1);
+        if steps > 64 {
+            break;
+        }
+        parent = skip_parentheses(parent);
+        match parent.kind() {
+            JsSyntaxKind::JS_RETURN_STATEMENT
+            | JsSyntaxKind::JS_CALL_ARGUMENTS
+            | JsSyntaxKind::JS_CONDITIONAL_EXPRESSION
+            | JsSyntaxKind::JS_LOGICAL_EXPRESSION
+            | JsSyntaxKind::JS_BINARY_EXPRESSION
+            | JsSyntaxKind::JS_UNARY_EXPRESSION => return Some(true),
+            JsSyntaxKind::JS_ASSIGNMENT_EXPRESSION => {
+                if let Some(assign) = JsAssignmentExpression::cast_ref(&parent) {
+                    if let Ok(rhs) = assign.right() {
+                        if rhs.syntax().text_range().contains_range(current.text_range()) {
+                            return Some(true);
+                        }
+                    }
+                }
+                current = parent;
+            }
+            JsSyntaxKind::JS_VARIABLE_DECLARATOR => {
+                // `const x = this.y;` alone isn’t meaningful (usage unknown).
+                return Some(false);
+            }
+            JsSyntaxKind::JS_PROPERTY_CLASS_MEMBER => {
+                // `field = this.y;` in a class field initialiser is meaningful.
+                return Some(true);
+            }
+            _ => current = parent,
+        }
+    }
+    Some(false)
+}

1254-1372: Add a few focused tests to pin semantics.

  • class C { m(){ const a = this.x; } }this.x should be not meaningful (false).
  • class C { m(){ a = this.x; } } → meaningful (true).
  • class C { m(){ if (!this.x){} } } → meaningful (true).
  • class C { field = this.x; } → meaningful (true).

I can push these cases into is_meaningful_read_tests and the body collectors.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4a49f32 and 8194040.

📒 Files selected for processing (1)
  • crates/biome_js_analyze/src/services/semantic_class.rs (30 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
🧬 Code graph analysis (1)
crates/biome_js_analyze/src/services/semantic_class.rs (1)
crates/biome_rowan/src/ast/mod.rs (1)
  • cast_ref (142-151)
⏰ 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). (16)
  • GitHub Check: Test Node.js API
  • GitHub Check: Bench (biome_html_formatter)
  • GitHub Check: Bench (biome_json_formatter)
  • GitHub Check: Bench (biome_module_graph)
  • GitHub Check: Bench (biome_json_analyze)
  • GitHub Check: Bench (biome_html_parser)
  • GitHub Check: Bench (biome_graphql_parser)
  • GitHub Check: Bench (biome_css_analyze)
  • GitHub Check: Bench (biome_graphql_formatter)
  • GitHub Check: Bench (biome_json_parser)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Bench (biome_css_formatter)
  • GitHub Check: Bench (biome_css_parser)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: autofix
🔇 Additional comments (2)
crates/biome_js_analyze/src/services/semantic_class.rs (2)

146-152: Public API surface: confirm visibility of new unions.

If these are meant only for analyse internals, consider narrowing to pub(crate); if they’re part of the public API for rule authors, pub is fine. Please confirm intended audience.


592-599: Nice: explicit handling of pre/post updates.

Good call to split read/write and annotate meaning on the read side.

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 (2)
crates/biome_js_analyze/src/services/semantic_class.rs (2)

631-640: Object binding reads: use the property key’s span and name (not the whole member)

For { key: alias } = this, we should record key, and the range of the key token, not the entire binding member text. Current code will store "key: alias" and its span, which breaks identity and downstream matching.

-                reads.insert(ClassMemberReference {
-                    name: declarator.clone().to_trimmed_text(),
-                    range: declarator.clone().syntax().text_trimmed_range(),
-                    is_meaningful_read: is_meaningful_read(&declarator.clone().into()),
-                });
+                if let Some((name, range)) = extract_object_binding_member_key(&declarator) {
+                    reads.insert(ClassMemberReference {
+                        name,
+                        range,
+                        is_meaningful_read: is_meaningful_read(
+                            &MeaningfulReadNode::from(declarator.clone())
+                        ),
+                    });
+                }

Helper to add (outside this hunk):

fn extract_object_binding_member_key(
    member: &AnyJsObjectBindingPatternMember,
) -> Option<(Text, TextRange)> {
    if let Some(prop) = member.as_js_object_binding_pattern_property() {
        let key = prop.key().ok()?; // identifier/literal member name
        return Some((key.to_trimmed_text(), key.syntax().text_trimmed_range()));
    }
    if let Some(shorthand) = member.as_js_object_binding_pattern_shorthand_property() {
        let ident = shorthand.identifier().ok()?; // accessor may differ; use the key token
        return Some((ident.to_trimmed_text(), ident.syntax().text_trimmed_range()));
    }
    None // ignore rest
}

705-723: Compound assignment detection misses operators (e.g. -=, <<=, >>>=, ^=, |=, &&=, ||=, ??=)

Rather than whitelisting, treat any assignment with an operator other than = as a read+write.

-        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()
+            && let Some(operand) = left.as_any_js_assignment()
+            && operator.kind() != JsSyntaxKind::EQ
             && let Some(name) = ThisPatternResolver::extract_this_member_reference(
                 operand.as_js_static_member_assignment(),
                 scoped_this_references,
                 // nodes inside assignment expressions are considered meaningful reads e.g. this.x += 1;
                 Some(true),
             )
🧹 Nitpick comments (5)
crates/biome_js_analyze/src/services/semantic_class.rs (5)

18-18: Drop redundant import of Option

Option is in the prelude; the explicit import is noise.

-use std::option::Option;

129-134: Clarify tri‑state with an enum rather than Option

None means “not a read”, Some(true|false) are semantics. An explicit enum (e.g., ReadMeaningfulness::{Meaningful, NotMeaningful}) improves readability and avoids Boolean traps. If changing the type is too wide for this PR, at least add a doc‑comment documenting the tri‑state.


889-906: Make context climb unbounded (with a sane cap) and include awaited/yielded uses

Eight levels can be shallow in real code; also, awaited/yielded reads are meaningful.

-    // Limit the number of parent traversals to avoid deep recursion
-    for _ in 0..8 {
-        if let Some(parent) = current.parent() {
-            let parent = skip_parentheses(parent); // strip parentheses
-            match parent.kind() {
-                JsSyntaxKind::JS_RETURN_STATEMENT
-                | JsSyntaxKind::JS_CALL_ARGUMENTS
-                | JsSyntaxKind::JS_CONDITIONAL_EXPRESSION
-                | JsSyntaxKind::JS_LOGICAL_EXPRESSION
-                | JsSyntaxKind::JS_BINARY_EXPRESSION => return Some(true),
-                _ => current = parent,
-            }
-        } else {
-            break;
-        }
-    }
+    let mut hops = 0usize;
+    while let Some(parent) = current.parent() {
+        let parent = skip_parentheses(parent); // strip parentheses
+        match parent.kind() {
+            JsSyntaxKind::JS_RETURN_STATEMENT
+            | JsSyntaxKind::JS_CALL_ARGUMENTS
+            | JsSyntaxKind::JS_CONDITIONAL_EXPRESSION
+            | JsSyntaxKind::JS_LOGICAL_EXPRESSION
+            | JsSyntaxKind::JS_BINARY_EXPRESSION
+            | JsSyntaxKind::JS_AWAIT_EXPRESSION
+            | JsSyntaxKind::JS_YIELD_EXPRESSION => return Some(true),
+            _ => current = parent,
+        }
+        hops += 1;
+        if hops > 32 { break; }
+    }
     Some(false)

1099-1162: Add coverage for other compound ops and aliasing in object bindings

Please add cases for -=, *=, **=, <<=, >>=, >>>=, |=, ^=, &&=, ||=, ??= and for { key: alias } = this to ensure we record key with the key’s span.

I can draft the tests if you like.


871-874: Minor API naming nit

If you keep the boolean, consider is_effective_read or is_semantically_read (as per PR description) – both read more naturally than “meaningful”.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8194040 and a46488b.

📒 Files selected for processing (1)
  • crates/biome_js_analyze/src/services/semantic_class.rs (30 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
🧬 Code graph analysis (1)
crates/biome_js_analyze/src/services/semantic_class.rs (1)
crates/biome_rowan/src/ast/mod.rs (1)
  • cast_ref (142-151)
⏰ 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). (9)
  • GitHub Check: Documentation
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: Check Dependencies
  • GitHub Check: Test Node.js API
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: Bench (biome_js_parser)
🔇 Additional comments (4)
crates/biome_js_analyze/src/services/semantic_class.rs (4)

146-152: Public unions look good

The MeaningfulReadNode and AnyJsUpdateExpression unions are a sensible surface for the helper.


671-675: Use the member’s range, not the whole this.foo span

         reads.insert(ClassMemberReference {
             name: member.to_trimmed_text(),
-            range: static_member.syntax().text_trimmed_range(),
+            range: member.syntax().text_trimmed_range(),
             is_meaningful_read: is_meaningful_read(&static_member.into()),
         });

839-844: Same normalisation for property initialisers

Record the member token span, not the entire static member expression.

         reads.insert(ClassMemberReference {
             name,
-            range: static_member.syntax().text_trimmed_range(),
+            range: member.syntax().text_trimmed_range(),
             is_meaningful_read: is_meaningful_read(&MeaningfulReadNode::from(
                 static_member.clone(),
             )),
         });

361-363: LGTM: initialiser reads marked meaningful

Treating RHS of const alias = this; as meaningful is aligned with the intended signal.

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 (1)
crates/biome_js_analyze/src/services/semantic_class.rs (1)

705-723: Compound-assignment detection is incomplete — treat any non-'=' operator as compound

Explicit-kind matching omits several compound operators (e.g. '-=', '^=', '<<=', '>>=', '>>>='). Replace the matches list with a generic check: operator.text_trimmed().text() != "=".

-        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()
+            && let Some(operand) = left.as_any_js_assignment()
+            // any assignment operator that isn't "=" is compound
+            && operator.text_trimmed().text() != "="
             && let Some(name) = ThisPatternResolver::extract_this_member_reference(
                 operand.as_js_static_member_assignment(),
                 scoped_this_references,
                 // nodes inside assignment expressions are considered meaningful reads e.g. this.x += 1;
                 Some(true),
             )

Add a quick test for -= / |= / ^= / <<= / >>= / >>>=?

🧹 Nitpick comments (5)
crates/biome_js_analyze/src/services/semantic_class.rs (5)

18-18: Drop the redundant import

Option is in the prelude; this use adds noise.

-use std::option::Option;

146-152: Scope the new unions conservatively

Unless you intend to stabilise these types, prefer pub(crate) to avoid leaking API surface.

-declare_node_union! {
-    pub MeaningfulReadNode = AnyJsUpdateExpression | AnyJsObjectBindingPatternMember | JsStaticMemberExpression
-}
+declare_node_union! {
+    pub(crate) MeaningfulReadNode = AnyJsUpdateExpression | AnyJsObjectBindingPatternMember | JsStaticMemberExpression
+}
 
-declare_node_union! {
-    pub AnyJsUpdateExpression = JsPreUpdateExpression | JsPostUpdateExpression
-}
+declare_node_union! {
+    pub(crate) AnyJsUpdateExpression = JsPreUpdateExpression | JsPostUpdateExpression
+}

361-363: Alias capture doesn’t need a meaningful-read flag

These entries model this aliases, not class‑member reads; setting Some(true) is confusing and unused downstream.

-                        // right hand side of a js variable statement is meaningful read
-                        is_meaningful_read: Some(true),
+                        // alias capture; not a class-member read
+                        is_meaningful_read: None,

635-640: Use the property token’s range for object bindings

Recording the whole binding member span can make identities unstable (defaults, renames). Use the identifier/private-name token range instead.

-                reads.insert(ClassMemberReference {
-                    name: declarator.clone().to_trimmed_text(),
-                    range: declarator.clone().syntax().text_trimmed_range(),
-                    is_meaningful_read: is_meaningful_read(&declarator.clone().into()),
-                });
+                if let Some(prop) = declarator.clone().as_js_object_binding_pattern_property() {
+                    if let Ok(name) = prop.name() {
+                        reads.insert(ClassMemberReference {
+                            name: name.to_trimmed_text(),
+                            range: name.syntax().text_trimmed_range(),
+                            is_meaningful_read: is_meaningful_read(&declarator.clone().into()),
+                        });
+                    }
+                }

887-907: Unbounded, context walk; drop the arbitrary depth cap

Eight levels is brittle. Walk ancestors until a statement boundary or root; keep your current context checks.

-    // Limit the number of parent traversals to avoid deep recursion
-    for _ in 0..8 {
-        if let Some(parent) = current.parent() {
-            let parent = skip_parentheses(parent); // strip parentheses
+    // Walk until a statement boundary or root
+    while let Some(parent0) = current.parent() {
+        let parent = skip_parentheses(parent0); // strip enclosing (...)
             match parent.kind() {
                 JsSyntaxKind::JS_RETURN_STATEMENT
                 | JsSyntaxKind::JS_CALL_ARGUMENTS
                 | JsSyntaxKind::JS_CONDITIONAL_EXPRESSION
                 | JsSyntaxKind::JS_LOGICAL_EXPRESSION
                 | JsSyntaxKind::JS_BINARY_EXPRESSION => return Some(true),
                 _ => current = parent,
             }
-        } else {
-            break;
-        }
-    }
+    }
     Some(false)

Consider also treating contexts like await, yield, unary ops, and template expressions as meaningful. Happy to sketch tests.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a46488b and 91a9189.

📒 Files selected for processing (1)
  • crates/biome_js_analyze/src/services/semantic_class.rs (30 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
🧬 Code graph analysis (1)
crates/biome_js_analyze/src/services/semantic_class.rs (1)
crates/biome_rowan/src/ast/mod.rs (1)
  • cast_ref (142-151)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (24)
  • GitHub Check: Documentation
  • GitHub Check: Bench (biome_html_parser)
  • GitHub Check: Bench (biome_graphql_formatter)
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Bench (biome_configuration)
  • GitHub Check: Bench (biome_graphql_parser)
  • GitHub Check: Bench (biome_package)
  • GitHub Check: Bench (biome_json_formatter)
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: Bench (biome_module_graph)
  • GitHub Check: Bench (biome_html_formatter)
  • GitHub Check: Test Node.js API
  • GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Bench (biome_css_analyze)
  • GitHub Check: Bench (biome_json_parser)
  • GitHub Check: Bench (biome_css_parser)
  • GitHub Check: Check Dependencies
  • GitHub Check: Bench (biome_json_analyze)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Bench (biome_css_formatter)
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: autofix
🔇 Additional comments (5)
crates/biome_js_analyze/src/services/semantic_class.rs (5)

129-134: Confirm Hash/Eq semantics with the new field

Including is_meaningful_read in Eq/Hash means the same name/range may exist twice (true vs false). If that’s intentional (per‑site classification), all good; if not, exclude it from equality or normalise before set insertion.


771-801: Update-expression handling looks good

Read/write split and meaningfulness via context are handled neatly.


671-675: Normalise to the member’s token range (not this.foo span)

This keeps read identities stable across contexts.

         reads.insert(ClassMemberReference {
             name: member.to_trimmed_text(),
-            range: static_member.syntax().text_trimmed_range(),
+            range: member.syntax().text_trimmed_range(),
             is_meaningful_read: is_meaningful_read(&static_member.into()),
         });

839-845: Same range normalisation for property initialisers

Use the member token’s range.

     if let Ok(member) = static_member.member() {
         let name = member.to_trimmed_text();
         reads.insert(ClassMemberReference {
             name,
-            range: static_member.syntax().text_trimmed_range(),
+            range: member.syntax().text_trimmed_range(),
             is_meaningful_read: is_meaningful_read(&MeaningfulReadNode::from(
                 static_member.clone(),
             )),
         });
     }

875-885: Parenthesis skipping walks the wrong way

You need to walk up through enclosing (...), not descend.

-/// If the node is a parenthesized expression, return the inner expression,
-/// otherwise return the node itself.
-fn skip_parentheses(node: JsSyntaxNode) -> JsSyntaxNode {
-    if node.kind() == JsSyntaxKind::JS_PARENTHESIZED_EXPRESSION {
-        // Assume the first child is the expression inside parentheses
-        if let Some(inner) = node.first_child() {
-            return inner;
-        }
-    }
-    node
-}
+/// Walk up past chains of enclosing parenthesised expressions.
+fn skip_parentheses(mut node: JsSyntaxNode) -> JsSyntaxNode {
+    while node.kind() == JsSyntaxKind::JS_PARENTHESIZED_EXPRESSION {
+        if let Some(parent) = node.parent() {
+            node = parent;
+        } else {
+            break;
+        }
+    }
+    node
+}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
crates/biome_js_analyze/src/services/semantic_class.rs (1)

887-907: Context detection looks reasonable but could be more robust.

The is_used_in_expression_context function covers key expression contexts. However, the arbitrary limit of 8 parent traversals and the specific node kinds might miss some edge cases.

Consider expanding the meaningful expression contexts to include:

  • Array/object literal contexts
  • Template literal expressions
  • Spread syntax contexts
                JsSyntaxKind::JS_RETURN_STATEMENT
                | JsSyntaxKind::JS_CALL_ARGUMENTS
                | JsSyntaxKind::JS_CONDITIONAL_EXPRESSION
                | JsSyntaxKind::JS_LOGICAL_EXPRESSION
-                | JsSyntaxKind::JS_BINARY_EXPRESSION => return Some(true),
+                | JsSyntaxKind::JS_BINARY_EXPRESSION
+                | JsSyntaxKind::JS_ARRAY_EXPRESSION
+                | JsSyntaxKind::JS_OBJECT_EXPRESSION => return Some(true),
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 91a9189 and 74bd9f5.

📒 Files selected for processing (1)
  • crates/biome_js_analyze/src/services/semantic_class.rs (30 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
🧬 Code graph analysis (1)
crates/biome_js_analyze/src/services/semantic_class.rs (1)
crates/biome_rowan/src/ast/mod.rs (1)
  • cast_ref (142-151)
⏰ 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_html_formatter)
  • GitHub Check: Bench (biome_json_parser)
  • GitHub Check: Bench (biome_configuration)
  • GitHub Check: Bench (biome_module_graph)
  • GitHub Check: Bench (biome_json_formatter)
  • GitHub Check: Bench (biome_package)
  • GitHub Check: Bench (biome_graphql_parser)
  • GitHub Check: Bench (biome_graphql_formatter)
  • GitHub Check: Bench (biome_json_analyze)
  • GitHub Check: Bench (biome_html_parser)
  • GitHub Check: Bench (biome_css_analyze)
  • GitHub Check: Bench (biome_css_parser)
  • GitHub Check: Bench (biome_css_formatter)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: Bench (biome_js_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: Lint project (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: autofix
  • GitHub Check: Test Node.js API
🔇 Additional comments (9)
crates/biome_js_analyze/src/services/semantic_class.rs (9)

133-134: Feature looks solid, but note the field addition could affect serialisation.

The additional is_meaningful_read field on ClassMemberReference extends the public API. If this struct is used in serialised contexts (like language server protocol), ensure backwards compatibility is maintained.


146-153: Clean union type definitions for the meaningful read feature.

Well-structured type unions that properly distinguish update expressions and member accesses. The type system extension looks appropriate for the semantic analysis needs.


591-598: Streamlined pattern matching for update expressions.

Replacing the previous verbose pattern matching with the new AnyJsUpdateExpression union simplifies the code whilst maintaining the same functionality.


635-640: Mind the range inconsistency here.

Using the full declarator syntax range instead of just the member's range conflicts with the established pattern elsewhere in the codebase.

Apply this diff to align with the range normalisation pattern:

                reads.insert(ClassMemberReference {
-                    name: declarator.clone().to_trimmed_text(),
-                    range: declarator.clone().syntax().text_trimmed_range(),
+                    name: declarator.clone().to_trimmed_text(),
+                    range: declarator.clone().to_trimmed_text().text_range(),
                    is_meaningful_read: is_meaningful_read(&declarator.clone().into()),
                });

672-676: Range normalisation needed here as flagged before.

Apply this diff to fix the range:

        reads.insert(ClassMemberReference {
            name: member.to_trimmed_text(),
-            range: static_member.syntax().text_trimmed_range(),
+            range: member.syntax().text_trimmed_range(),
            is_meaningful_read: is_meaningful_read(&static_member.into()),
        });

789-801: Excellent refactor for update expressions.

The restructuring properly creates distinct read and write references for increment/decrement operations, with the read getting meaningful-read analysis and the write being marked as None (since writes aren't meaningful reads by definition).


841-844: Same range issue in property initialisers.

Apply this diff:

        reads.insert(ClassMemberReference {
            name,
-            range: static_member.syntax().text_trimmed_range(),
+            range: static_member.member()?.syntax().text_trimmed_range(),
            is_meaningful_read: is_meaningful_read(&MeaningfulReadNode::from(
                static_member.clone(),
            )),
        });

871-885: Parenthesis skipping goes the wrong way.

The function should walk up the tree, not down:

-fn skip_parentheses(node: JsSyntaxNode) -> JsSyntaxNode {
-    if node.kind() == JsSyntaxKind::JS_PARENTHESIZED_EXPRESSION {
-        // Assume the first child is the expression inside parentheses
-        if let Some(inner) = node.first_child() {
-            return inner;
-        }
-    }
-    node
-}
+fn skip_parentheses(mut node: JsSyntaxNode) -> JsSyntaxNode {
+    while let Some(parent) = node.parent() {
+        if parent.kind() == JsSyntaxKind::JS_PARENTHESIZED_EXPRESSION {
+            node = parent;
+        } else {
+            break;
+        }
+    }
+    node
+}

919-922: Test structure accommodates the new meaningful-read flag nicely.

The expanded test structure with Option<bool> tuples for expected reads and writes provides comprehensive coverage for the new functionality.

Copy link
Member

@ematipico ematipico left a comment

Choose a reason for hiding this comment

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

The PR fails to explain the business logic of the code, which means it makes it impossible to review it. I addressed some coding problems, however the whole "meaningful reads" must be explained in the PR.

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_js_analyze/src/services/semantic_class.rs (1)

129-134: Document and double‑check hash/eq semantics for is_meaningful_read

Public field lacks docs and now participates in Hash/Eq, so the same member at the same range can appear twice in reads with different flags. If that’s intentional, document it; if not, exclude the flag from equality or normalise before insert.

 #[derive(Debug, Clone, Hash, Eq, PartialEq)]
 pub struct ClassMemberReference {
     pub name: Text,
     pub range: TextRange,
-    pub is_meaningful_read: Option<bool>,
+    /// Whether this reference is a meaningful read (Some(true)),
+    /// a non‑meaningful read (Some(false)), or N/A for writes (None).
+    /// Note: This field participates in Hash/Eq; identical name/range with different
+    /// values will be treated as distinct set entries.
+    pub is_meaningful_read: Option<bool>,
 }
🧹 Nitpick comments (5)
crates/biome_js_analyze/src/services/semantic_class.rs (5)

18-18: Drop redundant Option import

Option is in the prelude; importing std::option::Option is noise.

-use std::option::Option;

361-363: Don’t tag this aliases as “meaningful reads”

These entries model aliasing, not class member reads. Set to None to avoid leaking semantics into alias tracking.

-                        // right hand side of a js variable statement is meaningful read
-                        is_meaningful_read: Some(true),
+                        // N/A for aliasing; this is not a class member read
+                        is_meaningful_read: None,

419-420: Explain “Only applicable to writes.”

Add a short rationale (reads come from RHS/context; assignment patterns represent LHS writes).

-    /// Only applicable to writes.
+    /// Only applicable to writes: array/object assignment patterns contribute LHS targets,
+    /// which are write sites for class members.

Also applies to: 468-470


772-801: Update expressions: mark read/write cleanly, but avoid cloning the whole node

Looks correct semantically. Minor nit: you can pass &js_update_expression without .clone() when building the MeaningfulReadNode.

-            is_meaningful_read: is_meaningful_read(&MeaningfulReadNode::from(
-                js_update_expression.clone(),
-            )),
+            is_meaningful_read: is_meaningful_read(&MeaningfulReadNode::from(js_update_expression)),

1016-1017: Tests: great coverage; please add callee/new/await/yield/throw cases

Given the expanded semantics, add cases for meaningful reads as call callees, new, await, yield, throw, and template expressions.

Also applies to: 1067-1067, 1114-1116, 1131-1132, 1184-1196, 1211-1221

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 74bd9f5 and 02090f1.

📒 Files selected for processing (1)
  • crates/biome_js_analyze/src/services/semantic_class.rs (30 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
🧬 Code graph analysis (1)
crates/biome_js_analyze/src/services/semantic_class.rs (1)
crates/biome_rowan/src/ast/mod.rs (1)
  • cast_ref (142-151)
⏰ 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: Bench (biome_configuration)
  • GitHub Check: Bench (biome_css_parser)
  • GitHub Check: Bench (biome_html_formatter)
  • GitHub Check: Bench (biome_html_parser)
  • GitHub Check: Bench (biome_json_parser)
  • GitHub Check: Bench (biome_module_graph)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Bench (biome_graphql_parser)
  • GitHub Check: Bench (biome_css_formatter)
  • GitHub Check: Bench (biome_json_analyze)
  • GitHub Check: Bench (biome_graphql_formatter)
  • GitHub Check: Bench (biome_css_analyze)
  • GitHub Check: Bench (biome_json_formatter)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Documentation
  • GitHub Check: Check Dependencies
  • GitHub Check: autofix
🔇 Additional comments (6)
crates/biome_js_analyze/src/services/semantic_class.rs (6)

591-598: Nice: update‑expression coverage wired into the walk

Hook looks correct and low‑cost. 👍


672-675: Range normalisation: good fix

Using the member token’s range keeps identities stable across contexts. Thanks for adjusting.


839-844: Use member token range, not the whole this.foo span (again)

This still uses the full static member span. Use the member token’s trimmed range for consistency with other sites.

-        reads.insert(ClassMemberReference {
-            name,
-            range: static_member.syntax().text_trimmed_range(),
-            is_meaningful_read: is_meaningful_read(&MeaningfulReadNode::from(
-                static_member.clone(),
-            )),
-        });
+        if let Ok(member) = static_member.member() {
+            reads.insert(ClassMemberReference {
+                name,
+                range: member.syntax().text_trimmed_range(),
+                is_meaningful_read: is_meaningful_read(&MeaningfulReadNode::from(static_member.clone())),
+            });
+        }

636-639: Use property key (not binding name) for object-pattern reads

File: crates/biome_js_analyze/src/services/semantic_class.rs (lines 636–639)

to_trimmed_text() returns the binding (alias) in patterns like { foo: alias }; extract the property key (handle computed keys) so recorded reads reflect the source property. Suggested diff:

-                reads.insert(ClassMemberReference {
-                    name: declarator.clone().to_trimmed_text(),
-                    range: declarator.clone().syntax().text_trimmed_range(),
-                    is_meaningful_read: is_meaningful_read(&declarator.clone().into()),
-                });
+                if let Some(prop) = declarator.clone().as_js_object_binding_pattern_property() {
+                    let key = prop.key().ok()?;
+                    let name = key.to_trimmed_text();
+                    reads.insert(ClassMemberReference {
+                        name,
+                        range: key.syntax().text_trimmed_range(),
+                        is_meaningful_read: is_meaningful_read(&MeaningfulReadNode::from(declarator.clone())),
+                    });
+                }

Add unit tests for { foo: alias } = this and computed-key cases like { ["f"+"oo"]: bar } = this.


146-152: Rename union to AnyMeaningfulReadNode — verify no name collisions

File: crates/biome_js_analyze/src/services/semantic_class.rs (lines 146–152). The ripgrep you ran returned no matches; absence of matches isn't proof — confirm there are no existing AnyJsUpdateExpression or MeaningfulReadNode declarations in the repo before renaming.

-declare_node_union! {
-    pub MeaningfulReadNode = AnyJsUpdateExpression | AnyJsObjectBindingPatternMember | JsStaticMemberExpression
-}
+declare_node_union! {
+    pub AnyMeaningfulReadNode = AnyJsUpdateExpression | AnyJsObjectBindingPatternMember | JsStaticMemberExpression
+}

705-722: Complete compound assignment operator list
The current matches! only covers ||=, &&=, /=, *=, %=, +=, ?= but omits -=, |=, ^=, <<=, >>=, >>>=. Expand to include JsSyntaxKind::MINUSEQ, PIPEEQ, CARETEQ, LTLTEQ, GTGTEQ, GTGTGTEQ, or switch to matching the semantic binary‐operator enum.

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_js_analyze/src/services/semantic_class.rs (1)

838-846: Use the member’s range here as well (normalisation bug)

This still uses the whole this.foo span. For consistency with other paths, use the member token’s range.

Apply:

-    if let Ok(member) = static_member.member() {
-        let name = member.to_trimmed_text();
-        reads.insert(ClassMemberReference {
-            name,
-            range: static_member.syntax().text_trimmed_range(),
-            is_meaningful_read: is_meaningful_read(&AnyMeaningfulReadNode::from(
-                static_member.clone(),
-            )),
-        });
-    }
+    if let Ok(member) = static_member.member() {
+        let name = member.to_trimmed_text();
+        reads.insert(ClassMemberReference {
+            name,
+            range: member.syntax().text_trimmed_range(),
+            is_meaningful_read: is_meaningful_read(&AnyMeaningfulReadNode::from(
+                static_member.clone(),
+            )),
+        });
+    }
🧹 Nitpick comments (4)
crates/biome_js_analyze/src/services/semantic_class.rs (4)

18-18: Drop the explicit Option import

Option is in the prelude; the explicit use std::option::Option; is redundant.

Apply:

- use std::option::Option;

133-135: Document the tri‑state clearly (reads vs writes)

Good addition. Please make the contract explicit: “Some(true/false) only for reads; None for writes.” Also add a brief note on “meaningful” with 2–3 examples to avoid ambiguity.


638-641: Avoid repeated clones; compute once

Minor nit: avoid multiple .clone() calls on declarator.

Apply:

-                reads.insert(ClassMemberReference {
-                    name: declarator.clone().to_trimmed_text(),
-                    range: declarator.clone().syntax().text_trimmed_range(),
-                    is_meaningful_read: is_meaningful_read(&declarator.clone().into()),
-                });
+                let decl = declarator.clone();
+                reads.insert(ClassMemberReference {
+                    name: decl.to_trimmed_text(),
+                    range: decl.syntax().text_trimmed_range(),
+                    is_meaningful_read: is_meaningful_read(&decl.into()),
+                });

1309-1364: Add missing meaningful‑context tests

Please extend with cases for callee/new/await/throw/unary/template/switch/for and parenthesised forms. This will guard the widened matcher.

Happy to draft the extra cases if you prefer me to push a patch.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 02090f1 and e2f7c93.

📒 Files selected for processing (1)
  • crates/biome_js_analyze/src/services/semantic_class.rs (31 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)
📚 Learning: 2025-09-10T08:05:22.867Z
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

Applied to files:

  • crates/biome_js_analyze/src/services/semantic_class.rs
🧬 Code graph analysis (1)
crates/biome_js_analyze/src/services/semantic_class.rs (1)
crates/biome_rowan/src/ast/mod.rs (1)
  • cast_ref (142-151)
🪛 GitHub Actions: Benchmarks
crates/biome_js_analyze/src/services/semantic_class.rs

[error] 878-878: Rust compiler error: the trait bound "&SyntaxNode: Into<SyntaxNode>" is not satisfied in biome_js_analyze::semantic_class at line 878. Code uses 'AnyJsExpression::cast(node.syntax().into())', which requires an Into<SyntaxNode> implementation for '&SyntaxNode'.

🪛 GitHub Actions: autofix.ci
crates/biome_js_analyze/src/services/semantic_class.rs

[error] 878-878: the trait bound &SyntaxNode<JsLanguage>: Into<SyntaxNode<JsLanguage>> is not satisfied. Could not compile biome_js_analyze. The compiler error indicates a type mismatch in semantic_class.rs at the specified line.

🪛 GitHub Actions: Pull request Node.js
crates/biome_js_analyze/src/services/semantic_class.rs

[error] 878-878: the trait bound &SyntaxNode<JsLanguage>: Into<SyntaxNode<JsLanguage>> is not satisfied

🔇 Additional comments (6)
crates/biome_js_analyze/src/services/semantic_class.rs (6)

148-154: Type naming: align with internal convention

Earlier feedback suggested AnyMeaningfulNode. Consider renaming AnyMeaningfulReadNode accordingly before release to avoid public API churn.


673-677: Nice: range now uses the member token

Switching to member.syntax().text_trimmed_range() fixes identity stability for reads. Thanks for addressing this.


774-803: Update/read split for ++/-- looks correct

Reads get meaningful flag via the surrounding context, writes are None. LGTM.


1165-1188: Tests: ++/-- context expectations look right

Good coverage across plain, conditional and return contexts.


884-900: Remove arbitrary parent limit; widen contexts and skip parentheses upwards

The 8‑level cap can cause false negatives; also contexts miss throw/await/yield/unary/template/callee/new and control‑flow conditions. Walk until the root and check more kinds.

Apply:

-    // Limit the number of parent traversals to avoid deep recursion
-    for _ in 0..8 {
-        if let Some(parent) = current.parent() {
-            match parent.kind() {
-                JsSyntaxKind::JS_RETURN_STATEMENT
-                | JsSyntaxKind::JS_CALL_ARGUMENTS
-                | JsSyntaxKind::JS_CONDITIONAL_EXPRESSION
-                | JsSyntaxKind::JS_LOGICAL_EXPRESSION
-                | JsSyntaxKind::JS_BINARY_EXPRESSION => return Some(true),
-                _ => current = parent,
-            }
-        } else {
-            break;
-        }
-    }
-    Some(false)
+    while let Some(parent) = current.parent() {
+        // Walk past chains of parenthesised expressions
+        let mut p = parent;
+        while p.kind() == JsSyntaxKind::JS_PARENTHESIZED_EXPRESSION {
+            if let Some(pp) = p.parent() { p = pp; } else { break; }
+        }
+        match p.kind() {
+            // Value is consumed in these positions
+            JsSyntaxKind::JS_RETURN_STATEMENT
+            | JsSyntaxKind::JS_THROW_STATEMENT
+            | JsSyntaxKind::JS_AWAIT_EXPRESSION
+            | JsSyntaxKind::JS_YIELD_EXPRESSION
+            | JsSyntaxKind::JS_CONDITIONAL_EXPRESSION
+            | JsSyntaxKind::JS_LOGICAL_EXPRESSION
+            | JsSyntaxKind::JS_BINARY_EXPRESSION
+            | JsSyntaxKind::JS_UNARY_EXPRESSION
+            | JsSyntaxKind::JS_TEMPLATE_EXPRESSION
+            | JsSyntaxKind::JS_CALL_ARGUMENTS
+            | JsSyntaxKind::JS_CALL_EXPRESSION   // callee: this.fn()
+            | JsSyntaxKind::JS_NEW_EXPRESSION
+            | JsSyntaxKind::JS_IF_STATEMENT
+            | JsSyntaxKind::JS_SWITCH_STATEMENT
+            | JsSyntaxKind::JS_FOR_STATEMENT
+            | JsSyntaxKind::JS_FOR_IN_STATEMENT
+            | JsSyntaxKind::JS_FOR_OF_STATEMENT => return Some(true),
+            _ => current = p,
+        }
+    }
+    Some(false)

Please add tests for:

  • this.fn() (callee), new (this.Ctor)()
  • await this.x, throw this.x, -(this.x), `${this.x}`
  • switch (this.x) {}, for (; this.x;) {}, and parenthesised forms like return (this.x).

704-725: Compound-assignment detection incomplete — widen the operator check

Current match only covers: PIPE2EQ, AMP2EQ, SLASHEQ, STAREQ, PERCENTEQ, PLUSEQ, QUESTION2EQ. Add the missing '-=' and shift-assignment kinds from the JsSyntaxKind enum (confirm exact names in the JS syntax crate) or replace the match with a generic check (e.g. operator.kind() != JsSyntaxKind::EQ or an is_compound_assignment() helper).

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_js_analyze/src/services/semantic_class.rs (1)

707-724: Compound assignments: cover all operators (or detect “not =”).

The current match omits several operators (e.g., -=, |=, ^=, <<=, >>=, >>>=, **=). This will miss legitimate reads (false negatives).

-        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 // treat any non-`=` assignment as a read
+            && let Some(operand) = left.as_any_js_assignment()
🧹 Nitpick comments (3)
crates/biome_js_analyze/src/services/semantic_class.rs (3)

5-13: Drop explicit Option import (in prelude).

use std::option::Option; is redundant. Keeps imports lean.

- use std::option::Option;

Also applies to: 18-18


877-912: Meaningful-context detector: remove traversal cap, peel parentheses, add while/do‑while.

The 8‑level cap can miss deep nests; prefer an unbounded parent walk. Also include JS_WHILE_STATEMENT and JS_DO_WHILE_STATEMENT. Peeling parentheses via omit_parentheses() avoids extra hops.

-fn is_used_in_expression_context(node: &AnyMeaningfulReadNode) -> Option<bool> {
-    let mut current: JsSyntaxNode = if let Some(expression) = AnyJsExpression::cast(node.syntax().clone()) {
-        expression.syntax().clone() // get JsSyntaxNode
-    } else {
-        node.syntax().clone() // fallback to the node itself
-    };
-
-    // Limit the number of parent traversals to avoid deep recursion
-    for _ in 0..8 {
-        if let Some(parent) = current.parent() {
-            match parent.kind() {
+fn is_used_in_expression_context(node: &AnyMeaningfulReadNode) -> Option<bool> {
+    let mut current = if let Some(expr) = AnyJsExpression::cast_ref(node.syntax()) {
+        expr.omit_parentheses().syntax().clone()
+    } else {
+        node.syntax().clone()
+    };
+
+    while let Some(parent) = current.parent() {
+        match parent.kind() {
             JsSyntaxKind::JS_RETURN_STATEMENT
             | JsSyntaxKind::JS_CALL_ARGUMENTS
             | JsSyntaxKind::JS_CONDITIONAL_EXPRESSION
             | JsSyntaxKind::JS_LOGICAL_EXPRESSION
             | JsSyntaxKind::JS_THROW_STATEMENT
             | JsSyntaxKind::JS_AWAIT_EXPRESSION
             | JsSyntaxKind::JS_YIELD_EXPRESSION
             | JsSyntaxKind::JS_UNARY_EXPRESSION
             | JsSyntaxKind::JS_TEMPLATE_EXPRESSION
             | JsSyntaxKind::JS_CALL_EXPRESSION // (callee)
             | JsSyntaxKind::JS_NEW_EXPRESSION
             | JsSyntaxKind::JS_IF_STATEMENT
             | JsSyntaxKind::JS_SWITCH_STATEMENT
             | JsSyntaxKind::JS_FOR_STATEMENT
             | JsSyntaxKind::JS_FOR_IN_STATEMENT
             | JsSyntaxKind::JS_FOR_OF_STATEMENT
+            | JsSyntaxKind::JS_WHILE_STATEMENT
+            | JsSyntaxKind::JS_DO_WHILE_STATEMENT
             | JsSyntaxKind::JS_BINARY_EXPRESSION => return Some(true),
-                _ => current = parent,
-            }
-        } else {
-            break;
-        }
-    }
-    Some(false)
+            _ => current = parent,
+        }
+    }
+    Some(false)
}

Question: should initialisers be considered meaningful? e.g., const t = this.x; If yes, include JS_VARIABLE_DECLARATOR/JS_INITIALIZER_CLAUSE too.


235-235: Nit: “Cannot”, not “Can not”.

Micro copy tweak.

-// Can not implement `Visitor` directly because it requires a new ctx that can not be created here
+// Cannot implement `Visitor` directly because it requires a new ctx that cannot be created here
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e2f7c93 and 7113c25.

📒 Files selected for processing (1)
  • crates/biome_js_analyze/src/services/semantic_class.rs (31 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)
📚 Learning: 2025-09-10T08:05:22.867Z
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

Applied to files:

  • crates/biome_js_analyze/src/services/semantic_class.rs
🧬 Code graph analysis (1)
crates/biome_js_analyze/src/services/semantic_class.rs (1)
crates/biome_rowan/src/ast/mod.rs (1)
  • cast_ref (142-151)
⏰ 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). (8)
  • GitHub Check: Documentation
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: Check Dependencies
  • GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Test Node.js API
  • GitHub Check: autofix
🔇 Additional comments (4)
crates/biome_js_analyze/src/services/semantic_class.rs (4)

148-154: Type name: confirm convention (“AnyMeaningfulNode” vs “AnyMeaningfulReadNode”).

Previous feedback suggested “AnyMeaningfulNode” as the internal convention. If we’re intentionally diverging, fine—just confirm.


774-803: Pre/post updates: good split into read + write.

Capturing both edges and deferring meaningfulness to the update node context is spot on.


841-846: Use the member token’s range (not the whole this.foo) for reads.

Keeps identity stable and consistent with other paths.

-        reads.insert(ClassMemberReference {
-            name,
-            range: static_member.syntax().text_trimmed_range(),
-            is_meaningful_read: is_meaningful_read(&AnyMeaningfulReadNode::from(
-                static_member.clone(),
-            )),
-        });
+        if let Ok(member) = static_member.member() {
+            reads.insert(ClassMemberReference {
+                name,
+                range: member.syntax().text_trimmed_range(),
+                is_meaningful_read: is_meaningful_read(&AnyMeaningfulReadNode::from(
+                    static_member.clone(),
+                )),
+            });
+        }

1340-1417: Tests: add coverage for missed contexts and operators.

  • While/do‑while: while (this.c) {} and do { } while (this.d).
  • Compound ops: -=, |=, ^=, <<=, >>=, >>>=, **=.
  • Object binding aliasing: {a: b} = this should record read of a.

@vladimir-ivanov vladimir-ivanov force-pushed the feat/semantic_class_is_meaningful_read branch from 1f81ab0 to cb241c8 Compare September 21, 2025 12:18
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
crates/biome_js_analyze/src/services/semantic_class.rs (2)

235-235: Remove the misleading comment

This visitor doesn't implement the Visitor trait directly because it creates contexts manually, not due to recursion concerns.

-// Can not implement `Visitor` directly because it requires a new ctx that can not be created here
+// Manually implements visitor pattern for direct control over traversal context

885-887: Clarify traversal limit comment

This is iterative parent traversal, not recursion. The limit prevents potential infinite loops in malformed syntax trees.

-    // Limit the number of parent traversals to avoid deep recursion
+    // Limit parent traversals to prevent potential infinite loops
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7113c25 and cb241c8.

📒 Files selected for processing (1)
  • crates/biome_js_analyze/src/services/semantic_class.rs (31 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)
📚 Learning: 2025-09-10T08:05:22.867Z
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

Applied to files:

  • crates/biome_js_analyze/src/services/semantic_class.rs
🧬 Code graph analysis (1)
crates/biome_js_analyze/src/services/semantic_class.rs (1)
crates/biome_rowan/src/ast/mod.rs (1)
  • cast_ref (142-151)
⏰ 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). (5)
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: Test Node.js API
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: Bench (biome_js_parser)
🔇 Additional comments (13)
crates/biome_js_analyze/src/services/semantic_class.rs (13)

5-13: Imports and namespace clean-up

Nice addition of the meaningful-read types, all looks correct.


133-136: Good documentation clarification

The documentation now clearly explains what meaningful reads are and when None applies.


148-155: Consistent naming with BiomJS conventions

Perfect, the AnyMeaningfulReadNode follows the established naming pattern and the AnyJsUpdateExpression union is clean.


363-365: Consistent meaningful-read application

Good application of meaningful reads to variable initialisers.


421-461: Clear documentation for write-only operations

The comments correctly explain that array assignment patterns only apply to writes.


638-641: Object binding normalisation issue persists

Using declarator.to_trimmed_text() for object bindings like {a: b} yields "a: b" instead of just "a". Need to extract the property key.


675-677: Range normalisation looks good

Using member.syntax().text_trimmed_range() correctly captures just the property identifier.


722-724: Meaningful read logic for compound assignments

Correct implementation—compound assignments like += do indeed involve meaningful reads.


773-803: Update expression handling improvements

The refactored approach using the union type is much cleaner. The meaningful-read logic correctly distinguishes between the read and write portions.


873-914: Expression context detection expanded

Good expansion of expression contexts, covering more meaningful-read scenarios. The limit of 8 parent traversals is reasonable for preventing infinite loops.


878-883: Fix the cast issue

The current attempt to cast via Into will fail. Use cast_ref or clone the syntax node directly.

-    let mut current: JsSyntaxNode =
-        if let Some(expression) = AnyJsExpression::cast(node.syntax().clone()) {
-            expression.syntax().clone() // get JsSyntaxNode
-        } else {
-            node.syntax().clone() // fallback to the node itself
-        };
+    let mut current = if let Some(expression) = AnyJsExpression::cast(node.syntax().clone()) {
+        expression.omit_parentheses().syntax().clone()
+    } else {
+        node.syntax().clone()
+    };

925-975: Test infrastructure improvements

The new helper functions with meaningful-read expectations are well-structured and comprehensive.


1260-1423: Comprehensive meaningful-read test coverage

Excellent test coverage across all expression contexts. The test structure is clear and validates both node names and meaningful-read flags correctly.

@vladimir-ivanov vladimir-ivanov marked this pull request as draft September 21, 2025 14:10
@vladimir-ivanov vladimir-ivanov marked this pull request as ready for review September 21, 2025 14:31
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
crates/biome_js_analyze/src/services/semantic_class.rs (2)

148-155: Union types follow naming conventions correctly.

The nodes follow Biome's naming conventions and serve the intended purpose. However, the AnyCandidateForUsedInExpressionNode name feels unwieldy—could consider shortening to AnyMeaningfulReadCandidate in future.


876-877: Minor documentation polish needed.

The comment has a typo: "NOt limited" should be "Not limited".

-/// NOt limited to `this` references. Can be used for any node.
+/// Not limited to `this` references. Can be used for any node.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cb241c8 and 6e05104.

📒 Files selected for processing (1)
  • crates/biome_js_analyze/src/services/semantic_class.rs (31 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)
📚 Learning: 2025-09-10T08:05:22.867Z
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

Applied to files:

  • crates/biome_js_analyze/src/services/semantic_class.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)
  • to_trimmed_text (223-225)
⏰ 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). (22)
  • GitHub Check: Bench (biome_module_graph)
  • GitHub Check: Bench (biome_configuration)
  • GitHub Check: Bench (biome_json_analyze)
  • GitHub Check: Bench (biome_html_parser)
  • GitHub Check: Bench (biome_html_formatter)
  • GitHub Check: Bench (biome_graphql_formatter)
  • GitHub Check: Bench (biome_graphql_parser)
  • 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_json_formatter)
  • GitHub Check: Bench (biome_json_parser)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Test Node.js API
  • GitHub Check: autofix
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Documentation
  • GitHub Check: Check Dependencies
🔇 Additional comments (12)
crates/biome_js_analyze/src/services/semantic_class.rs (12)

133-136: Clear, well-documented addition to the API.

The field and its documentation are clear and correctly indicate the different values: Some(true) for meaningful reads, Some(false) for non-meaningful reads, and None for writes.


421-443: Consistent meaningful read annotation for writes.

All write references correctly set is_meaningful_read to None. The consistency is appreciated.

Also applies to: 471-490, 505-507, 526-526, 538-538, 546-546, 725-725, 752-753, 790-790


593-600: Correct handling of update expressions.

The refactor to use AnyJsUpdateExpression is correct and the cast_ref approach is proper. Good job addressing the past review feedback.


637-643: Object binding pattern still uses wrong range source.

The range is still taken from the entire declarator rather than extracting the property key. This creates inconsistency in ClassMemberReference ranges.

Apply the fix suggested in the past review to extract the property key's range instead of the full declarator range.


677-679: Range now correctly uses member, not entire static expression.

Good fix addressing the past review feedback. The range now correctly points to just the member identifier.


775-805: Update expression handling is thorough and correct.

The logic correctly creates both read and write references for update expressions, with appropriate is_meaningful_read values. The use of AnyCandidateForUsedInExpressionNode is consistent with other parts of the code.


842-848: Property initialiser range should use member token.

This still uses the entire static member expression range instead of just the member token range, creating inconsistency.

Apply the suggested fix from past reviews to use the member's range instead of the full static member expression range.


875-915: Expression context detection is comprehensive.

The function now covers all the meaningful expression contexts mentioned in the PR objectives. The traversal limit of 8 levels seems reasonable to prevent performance issues. Good work expanding the syntax kinds to include all meaningful contexts.


880-885: AnyJsExpression cast approach is correct.

The fix for the compile error using clone() instead of .into() is appropriate and follows Rust idioms correctly.


926-1260: Comprehensive test coverage for meaningful reads.

The tests thoroughly cover all scenarios with clear expectations for both reads and writes. The helper functions assert_reads and assert_writes are well-structured. The test cases validate the core functionality of meaningful read detection.


1262-1442: Thorough testing of expression contexts.

The is_used_in_expression_context_tests module provides excellent coverage of all meaningful expression contexts. The test structure with extract_all_nodes and run_test_cases is clean and maintainable.


362-365: Resolved — callers handle is_meaningful_read correctly

All usages are local to crates/biome_js_analyze/src/services/semantic_class.rs and treat is_meaningful_read as Option (Some(...) or None); unit tests assert the expected Option values, so no change required.

@vladimir-ivanov vladimir-ivanov force-pushed the feat/semantic_class_is_meaningful_read branch from 5760170 to fde4f87 Compare September 21, 2025 14:43
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
crates/biome_js_analyze/src/services/semantic_class.rs (1)

362-365: Simplify the meaningful read calculation.

The cast to AnyCandidateForUsedInExpressionNode and then calling is_used_in_expression_context seems overly complex for a simple boolean return.

-                        is_meaningful_read: Some(is_used_in_expression_context(
-                            &AnyCandidateForUsedInExpressionNode::from(id),
-                        )),
+                        is_meaningful_read: Some(is_used_in_expression_context(
+                            &id.into(),
+                        )),
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6e05104 and fde4f87.

📒 Files selected for processing (1)
  • crates/biome_js_analyze/src/services/semantic_class.rs (31 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)
📚 Learning: 2025-09-10T08:05:22.867Z
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

Applied to files:

  • crates/biome_js_analyze/src/services/semantic_class.rs
🧬 Code graph analysis (1)
crates/biome_js_analyze/src/services/semantic_class.rs (1)
crates/biome_rowan/src/ast/mod.rs (1)
  • cast_ref (142-151)
⏰ 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). (22)
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Documentation
  • GitHub Check: Check Dependencies
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: Test Node.js API
  • GitHub Check: Bench (biome_configuration)
  • GitHub Check: Bench (biome_json_analyze)
  • GitHub Check: Bench (biome_graphql_formatter)
  • GitHub Check: Bench (biome_html_formatter)
  • GitHub Check: Bench (biome_css_analyze)
  • GitHub Check: Bench (biome_module_graph)
  • GitHub Check: Bench (biome_graphql_parser)
  • GitHub Check: Bench (biome_css_formatter)
  • GitHub Check: Bench (biome_html_parser)
  • GitHub Check: Bench (biome_json_parser)
  • GitHub Check: Bench (biome_json_formatter)
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: autofix
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Bench (biome_css_parser)
  • GitHub Check: Bench (biome_js_analyze)
🔇 Additional comments (13)
crates/biome_js_analyze/src/services/semantic_class.rs (13)

5-13: LGTM! Comprehensive imports added for expanded functionality.

The new imports logically support the meaningful read detection and expanded node type handling introduced in this PR.


133-136: Clear documentation of is_meaningful_read field.

The field documentation effectively explains the purpose and applicability. The Option<bool> type makes sense: Some(true/false) for reads indicating meaningful usage, None for writes where the concept doesn't apply.


148-155: Node union declarations follow Biome conventions well.

These public unions properly implement the established naming pattern (Any*) and expose the necessary AST node types for the meaningful read detection system.


421-444: Good documentation for write-only contexts.

The comments clearly indicate these functions handle write references where meaningful read doesn't apply. The consistent use of None for is_meaningful_read is correct.

Also applies to: 471-491, 506-508


593-600: Clean handling of update expressions with proper type safety.

The unified handling of pre/post update expressions through the AnyJsUpdateExpression union is well-structured and maintains type safety.


678-679: Verify the range consistency fix.

The use of member.syntax().text_trimmed_range() instead of the full static member expression looks correct based on past review comments, ensuring consistent property identifier ranges.


724-726: Correct meaningful read annotation for compound assignments.

Compound assignment reads (like this.x += 1) are correctly marked as meaningful since the property value is consumed by the operation.


775-805: Clean separation of read/write semantics in updates.

The update expression handling properly creates separate read and write references with appropriate meaningful read annotations. The write gets None, the read gets the context-based evaluation.


875-915: Well-documented context detection with appropriate syntax kinds.

The function documentation clearly explains the purpose, and the expanded set of syntax kinds covers the meaningful contexts outlined in the PR description. The traversal depth limit prevents excessive recursion.


880-885: Fix the syntax casting issue.

The AnyJsExpression::cast(node.syntax().clone()) approach correctly handles the type conversion after the previous compile error fix.


927-977: Comprehensive test helpers for meaningful read validation.

The test helper functions properly validate both the property names and their meaningful read annotations, providing clear error messages for debugging test failures.


1000-1046: Thorough test coverage for all reference types.

The test cases comprehensively cover object binding patterns, static member expressions, assignment expressions, and update expressions, with proper validation of meaningful read flags. The test cases include both direct this usage and alias scenarios.

Also applies to: 1048-1106, 1108-1172, 1174-1261


1262-1452: Excellent comprehensive testing of expression contexts.

The dedicated test module for is_used_in_expression_context covers all the meaningful expression contexts mentioned in the PR description, including edge cases like nested parentheses and complex logical expressions.

Comment on lines 890 to 892
// Limit the number of parent traversals to avoid deep recursion
for _ in 0..8 {
if let Some(parent) = current.parent() {
Copy link
Member

Choose a reason for hiding this comment

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

Yes, but it's just a loop. That's what you usually do to avoid a recursion. I still don't understand the limit. Ascending a tree is way cheaper than descending it, and we don't put limits when we descend a tree e.g. when we call preorder(). So it's fine removing it.

@vladimir-ivanov vladimir-ivanov force-pushed the feat/semantic_class_is_meaningful_read branch from 8ddcb1d to c7d4cec Compare September 23, 2025 18:26
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_js_analyze/src/services/semantic_class.rs (1)

1203-1239: Update remaining test expectations.

The pre/post update test cases also need AccessKind conversion.

Apply this diff:

                 expected_reads: vec![
-                    ("count", Some(false)),
-                    ("total", Some(false)),
-                    ("inIfCondition", Some(true)),
-                    ("inReturn", Some(true)),
+                    ("count", AccessKind::TrivialRead),
+                    ("total", AccessKind::TrivialRead),
+                    ("inIfCondition", AccessKind::MeaningfulRead),
+                    ("inReturn", AccessKind::MeaningfulRead),
                 ],
                 expected_writes: vec![
-                    ("count", None),
-                    ("total", None),
-                    ("inIfCondition", None),
-                    ("inReturn", None),
+                    ("count", AccessKind::Write),
+                    ("total", AccessKind::Write),
+                    ("inIfCondition", AccessKind::Write),
+                    ("inReturn", AccessKind::Write),
                 ],

                 expected_reads: vec![
-                    ("count", Some(false)),
-                    ("total", Some(false)),
-                    ("inReturnIncrement", Some(true)),
+                    ("count", AccessKind::TrivialRead),
+                    ("total", AccessKind::TrivialRead),
+                    ("inReturnIncrement", AccessKind::MeaningfulRead),
                 ],
                 expected_writes: vec![
-                    ("count", None),
-                    ("total", None),
-                    ("inReturnIncrement", None),
+                    ("count", AccessKind::Write),
+                    ("total", AccessKind::Write),
+                    ("inReturnIncrement", AccessKind::Write),
                 ],
🧹 Nitpick comments (3)
crates/biome_js_analyze/src/services/semantic_class.rs (3)

129-152: Add docs explaining when each variant applies.

The enum definition is comprehensive, but missing essential context about when each variant is used. From past review feedback, this needs clarification.

Apply this diff to improve the documentation:

 /// Represents how a class member is accessed within the code.
-/// Variants:
-///
-/// - `Write`:
-///   The member is being assigned to or mutated.
-///   Example: `this.count = 10;`
-///   This indicates the member's value/state changes at this point.
-///
-/// - `MeaningfulRead`:
-///   The member's value is retrieved and used in a way that affects program logic.
-///   Example: `if (this.enabled) { ... }` or `let x = this.value + 1;`
-///   These reads influence control flow or computation.
-///
-/// - `TrivialRead`:
-///   The member is accessed, but its value is not used in a way that
-///   meaningfully affects logic.
-///   Example: `this.value;` as a standalone expression, or a read that is optimized away.
-///   This is mostly for distinguishing "dead reads" from truly meaningful ones.
+/// This classification helps lint rules like `no_unused_class_properties` identify
+/// whether a property is genuinely unused or serves a meaningful purpose.
+///
+/// ## When variants apply:
+/// - **Write**: Any assignment, compound assignment, or mutation (e.g., `this.x = 1`, `this.y += 2`, `this.z++`)
+/// - **MeaningfulRead**: Property access where the value affects control flow or computation 
+///   (e.g., `return this.x`, `if (this.flag)`, `console.log(this.name)`)
+/// - **TrivialRead**: Property access that doesn't meaningfully affect program logic
+///   (e.g., standalone `this.value;` expressions without side effects)
 #[derive(Debug, Clone, Hash, Eq, PartialEq)]
 enum AccessKind {

171-177: Union seems over-engineered for its simple usage.

Looking at the usage patterns, this union serves mainly as a parameter type for two functions. The type name is also quite verbose.

Consider simplifying the approach:

-declare_node_union! {
-    pub AnyCandidateForUsedInExpressionNode = AnyJsUpdateExpression | AnyJsObjectBindingPatternMember | JsStaticMemberExpression | AnyJsBindingPattern
-}
+declare_node_union! {
+    pub AnyExpressionCandidate = AnyJsUpdateExpression | AnyJsObjectBindingPatternMember | JsStaticMemberExpression | AnyJsBindingPattern
+}

Or potentially inline the logic if the union is only used in a couple of places.


886-893: Function could be more concise.

The wrapper function adds little value over direct inlining.

Consider removing the wrapper:

-/// Determines the kind of read access for a given node.
-fn get_read_access_kind(node: &AnyCandidateForUsedInExpressionNode) -> AccessKind {
-    if is_used_in_expression_context(node) {
-        AccessKind::MeaningfulRead
-    } else {
-        AccessKind::TrivialRead
-    }
-}

And inline the logic at call sites:

-access_kind: get_read_access_kind(&declarator.clone().into()),
+access_kind: if is_used_in_expression_context(&declarator.clone().into()) {
+    AccessKind::MeaningfulRead
+} else {
+    AccessKind::TrivialRead
+},
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8ddcb1d and c7d4cec.

📒 Files selected for processing (1)
  • crates/biome_js_analyze/src/services/semantic_class.rs (31 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)
📚 Learning: 2025-09-10T08:05:22.867Z
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

Applied to files:

  • crates/biome_js_analyze/src/services/semantic_class.rs
🧬 Code graph analysis (1)
crates/biome_js_analyze/src/services/semantic_class.rs (1)
crates/biome_rowan/src/ast/mod.rs (1)
  • cast_ref (142-151)
⏰ 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: End-to-end tests
  • GitHub Check: Check Dependencies
  • GitHub Check: Documentation
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Bench (biome_configuration)
  • GitHub Check: Bench (biome_html_formatter)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Bench (biome_module_graph)
  • GitHub Check: Bench (biome_graphql_parser)
  • GitHub Check: Bench (biome_package)
  • GitHub Check: Bench (biome_html_parser)
  • GitHub Check: Bench (biome_graphql_formatter)
  • GitHub Check: Test Node.js API
  • GitHub Check: Bench (biome_json_formatter)
  • GitHub Check: Bench (biome_json_analyze)
  • GitHub Check: Bench (biome_css_analyze)
  • 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_parser)
  • GitHub Check: autofix
🔇 Additional comments (2)
crates/biome_js_analyze/src/services/semantic_class.rs (2)

899-926: Excellent contextual analysis improvement!

The function now properly handles parent traversal and covers all the meaningful expression contexts from the PR objectives. The logic is solid and comprehensive.


655-660: Object binding name extraction needs refinement.

The current approach uses to_trimmed_text() on the entire declarator, which can yield malformed names like "a: b" for object property destructuring.

Apply this diff to extract the property key properly:

             if let Some(declarator) = declarator.ok()
                 && is_this_reference(&expression, scoped_this_references)
             {
+                let (name_text, range) = match &declarator {
+                    AnyJsObjectBindingPatternMember::JsObjectBindingPatternProperty(prop) => {
+                        if let Ok(key) = prop.key() {
+                            (key.to_trimmed_text(), key.syntax().text_trimmed_range())
+                        } else {
+                            (declarator.to_trimmed_text(), declarator.syntax().text_trimmed_range())
+                        }
+                    },
+                    AnyJsObjectBindingPatternMember::JsObjectBindingPatternShorthandProperty(shorthand) => {
+                        if let Ok(name) = shorthand.name() {
+                            (name.to_trimmed_text(), name.syntax().text_trimmed_range())
+                        } else {
+                            (declarator.to_trimmed_text(), declarator.syntax().text_trimmed_range())
+                        }
+                    },
+                    _ => (declarator.to_trimmed_text(), declarator.syntax().text_trimmed_range())
+                };
                 reads.insert(ClassMemberReference {
-                    name: declarator.clone().to_trimmed_text(),
-                    range: declarator.clone().syntax().text_trimmed_range(),
+                    name: name_text,
+                    range,
                     access_kind: get_read_access_kind(&declarator.clone().into()),
                 });

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 (1)
crates/biome_js_analyze/src/services/semantic_class.rs (1)

646-664: Object binding: use the property key, not the whole declarator

Using to_trimmed_text() on the declarator yields strings like "a: b". Extract the key/shorthand identifier and use its range for stable identity.

-                reads.insert(ClassMemberReference {
-                    name: declarator.clone().to_trimmed_text(),
-                    range: declarator.clone().syntax().text_trimmed_range(),
-                    access_kind: get_read_access_kind(&declarator.clone().into()),
-                });
+                // Normalise to the property key (handles `{a: b}` and shorthand)
+                let (name_text, range) = if let Some(prop) =
+                    declarator.as_js_object_binding_pattern_property()
+                {
+                    if let Ok(key) = prop.key() {
+                        (key.to_trimmed_text(), key.syntax().text_trimmed_range())
+                    } else {
+                        (declarator.clone().to_trimmed_text(), declarator.clone().syntax().text_trimmed_range())
+                    }
+                } else if let Some(shorthand) =
+                    declarator.as_js_object_binding_pattern_shorthand_property()
+                {
+                    if let Ok(ident) = shorthand.name() {
+                        (ident.to_trimmed_text(), ident.syntax().text_trimmed_range())
+                    } else {
+                        (declarator.clone().to_trimmed_text(), declarator.clone().syntax().text_trimmed_range())
+                    }
+                } else {
+                    (declarator.clone().to_trimmed_text(), declarator.clone().syntax().text_trimmed_range())
+                };
+                reads.insert(ClassMemberReference {
+                    name: name_text,
+                    range,
+                    access_kind: get_read_access_kind(&declarator.clone().into()),
+                });
🧹 Nitpick comments (6)
crates/biome_js_analyze/src/services/semantic_class.rs (6)

129-152: AccessKind enum: solid API choice

Good separation of concerns; this will age well. Consider marking it #[non_exhaustive] to allow future variants without breaking semver.


385-388: Alias collection: access kind is incidental here

We don’t need meaningful/trivial classification for alias bindings; it’s metadata for alias tracking only. Harmless, but you could skip get_read_access_kind here.

-                        access_kind: get_read_access_kind(
-                            &AnyCandidateForUsedInExpressionNode::from(id),
-                        ),
+                        access_kind: AccessKind::TrivialRead,

895-922: Doc nit + tighten description

The sentence trails off (“requires more work e.g.”). Also, be explicit about “consumed by context”.

-/// Checks if the given node is used in an expression context
-/// (e.g., return, call arguments, conditionals, binary expressions).
-/// Not limited to `this` references. Can be used for any node, but requires more work e.g.
-/// Returns `true` if the read is meaningful, `false` otherwise.
+/// Returns true if the node’s value is consumed by its syntactic context
+/// (e.g. return/throw, callee/arguments, await/yield, control‑flow tests, unary/binary/logical,
+/// template, new); otherwise false.
+/// Not limited to `this` references; the union here reflects current supported nodes.

1131-1147: Assignment tests: good start — please add more operators

Add cases for '-=', '|=', '^=', '&=', shifts, and '**=' to guard the extended operator set.


1290-1378: Expression-context tests: helpful coverage

These directly validate the context classifier. Consider adding:

  • callee vs argument in the same snippet,
  • switch((this.x)) with extra parentheses,
  • new (this.Ctor)() with nesting.

18-18: Unnecessary import

use std::option::Option; is redundant; Option is in the prelude.

-use std::option::Option;
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c7d4cec and a47df2f.

📒 Files selected for processing (1)
  • crates/biome_js_analyze/src/services/semantic_class.rs (31 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)
📚 Learning: 2025-09-10T08:05:22.867Z
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

Applied to files:

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

171-177: Node unions look right

The unions cover update expressions, binding/object pattern members, and static member reads. Nicely scoped for the current use-cases.


612-619: Update expressions handling: nice and tight

Detecting both pre and post updates and classifying the read via the whole update expression context is spot on.


790-816: Update expressions: reuse the extracted name; OK

Clones then reuses name/range for the paired read. Looks fine and avoids re-parsing. Minor naming shadowing, but clear enough.


845-859: Range should be the member token, not the whole this.foo

Consistency matters for de-duplication and identity tracking. Use the member’s trimmed range.

-        let name = member.to_trimmed_text();
-        reads.insert(ClassMemberReference {
-            name,
-            range: static_member.syntax().text_trimmed_range(),
+        let name = member.to_trimmed_text();
+        reads.insert(ClassMemberReference {
+            name,
+            range: member.syntax().text_trimmed_range(),
             access_kind: get_read_access_kind(&AnyCandidateForUsedInExpressionNode::from(
                 static_member.clone(),
             )),
         });

886-893: get_read_access_kind: clean abstraction

Simple and readable; keeps call sites tidy.


1012-1021: Object binding tests: 👍

Covers this and alias reads; expectations line up with the new AccessKind.


1067-1077: Static member tests: 👍

Meaningful vs trivial contexts are exercised (call args vs bare expr). Nice.


1207-1230: Update expression tests: on point

Covers if/return vs bare updates; matches the intended semantics.


726-743: Cover all compound-assignment operators (verification needed)

Search in the sandbox returned no JsSyntaxKind EQ variants; cannot confirm token names. Ensure the match in crates/biome_js_analyze/src/services/semantic_class.rs (≈ lines 726–743) includes these kinds:

  • Arithmetic: PLUSEQ, MINUSEQ, STAREQ, STAR2EQ, SLASHEQ, PERCENTEQ
  • Bitwise: AMPEQ, PIPEEQ, CARETEQ
  • Shifts: LTLTEQ, GTGTEQ, GTGTGTEQ
  • Logical/nullish: AMP2EQ, PIPE2EQ, QUESTION2EQ

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 (1)
crates/biome_js_analyze/src/services/semantic_class.rs (1)

726-743: Treat any non-'=' assignment as compound — record a read

Current match in crates/biome_js_analyze/src/services/semantic_class.rs (~726–743) only covers PIPE2EQ, AMP2EQ, SLASHEQ, STAREQ, PERCENTEQ, PLUSEQ and QUESTION2EQ — missing -=, **=, <<=/>>=, &=, |=, ^=, etc., so meaningful reads are under‑reported. Prefer a semantic check:

if let Ok(op) = assignment.operator_token() {
if op.kind() != JsSyntaxKind::EQ {
// treat as compound assignment: produce a read
}
}

🧹 Nitpick comments (5)
crates/biome_js_analyze/src/services/semantic_class.rs (5)

148-152: Make AccessKind Copy to avoid clones.

Enum is small and used pervasively; deriving Copy removes .clone() noise and micro-allocs.

-#[derive(Debug, Clone, Hash, Eq, PartialEq)]
+#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
 pub enum AccessKind {

Follow-up: drop .clone() where AccessKind is copied (see Lines 557 and 565).


545-566: Drop unnecessary clones now that AccessKind is Copy.

-                            access_kind: access_kind.clone(),
+                            access_kind,
-                                    access_kind,
+                                    access_kind,

18-18: Remove redundant use std::option::Option;.

Option is in the prelude. This import is unnecessary.

-use std::option::Option;

142-147: Doc nit: fix wording for TrivialRead.

Current text says “distinguishing ... access_kind ones.” Replace with clearer phrasing.

-///   This is mostly for distinguishing "dead reads" from truly access_kind ones.
+///   Helps distinguish dead reads from genuinely consumed reads.

1292-1293: Remove stray comment.

// WE n looks like a leftover.

-        // WE n
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a47df2f and 44a1edc.

📒 Files selected for processing (1)
  • crates/biome_js_analyze/src/services/semantic_class.rs (31 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)
📚 Learning: 2025-09-10T08:05:22.867Z
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

Applied to files:

  • crates/biome_js_analyze/src/services/semantic_class.rs
🧬 Code graph analysis (1)
crates/biome_js_analyze/src/services/semantic_class.rs (1)
crates/biome_rowan/src/ast/mod.rs (1)
  • cast_ref (142-151)
⏰ 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). (14)
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Test Node.js API
  • GitHub Check: Bench (biome_json_analyze)
  • GitHub Check: Bench (biome_css_analyze)
  • GitHub Check: Bench (biome_html_formatter)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: Bench (biome_css_formatter)
  • GitHub Check: Bench (biome_json_formatter)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Bench (biome_css_parser)
  • GitHub Check: Bench (biome_json_parser)
  • GitHub Check: autofix
🔇 Additional comments (5)
crates/biome_js_analyze/src/services/semantic_class.rs (5)

692-696: LGTM on range and context.

Using the member token’s trimmed range and delegating meaningfulness to get_read_access_kind is consistent and correct.

Please ensure callee reads like this.fn() are covered by your tests (they are via JS_CALL_EXPRESSION, which looks good).


790-816: Update-expression handling looks correct.

  • Writes recorded via the assignment operand.
  • Read meaningfulness derived from the update-expression context.

895-923: Expression-context set looks aligned with the PR intent.

Covers return, call args/callee, conditionals, logical/binary, throw/await/yield, unary, template, new, and control-flow heads.

Consider adding tests for:

  • Parenthesised forms (e.g. return (this.x)).
  • new (this.Ctor)().
  • Template tag callee if applicable.

853-859: Use the member’s range, not the entire this.foo span, for property initialisers.

Current code records the whole static member expression range. For consistency, use the member token’s trimmed range.

-        reads.insert(ClassMemberReference {
-            name,
-            range: static_member.syntax().text_trimmed_range(),
-            access_kind: get_read_access_kind(&AnyCandidateForUsedInExpressionNode::from(
-                static_member.clone(),
-            )),
-        });
+        reads.insert(ClassMemberReference {
+            name,
+            range: static_member.member().ok()?.syntax().text_trimmed_range(),
+            access_kind: get_read_access_kind(&AnyCandidateForUsedInExpressionNode::from(
+                static_member.clone(),
+            )),
+        });

656-660: Fix object binding name/range: extract the property key, not the whole declarator.

Using to_trimmed_text() on the member yields strings like "a: b"; range also points to the whole property node. Normalise to the property key for accurate identity and stable ranges.

Apply:

-                reads.insert(ClassMemberReference {
-                    name: declarator.clone().to_trimmed_text(),
-                    range: declarator.clone().syntax().text_trimmed_range(),
-                    access_kind: get_read_access_kind(&declarator.clone().into()),
-                });
+                let (name_text, range) = if let Some(prop) =
+                    declarator.as_js_object_binding_pattern_property()
+                {
+                    if let Ok(key) = prop.key() {
+                        (key.to_trimmed_text(), key.syntax().text_trimmed_range())
+                    } else {
+                        (declarator.clone().to_trimmed_text(), declarator.clone().syntax().text_trimmed_range())
+                    }
+                } else if let Some(shorthand) =
+                    declarator.as_js_object_binding_pattern_shorthand_property()
+                {
+                    if let Ok(ident) = shorthand.name() {
+                        (ident.to_trimmed_text(), ident.syntax().text_trimmed_range())
+                    } else {
+                        (declarator.clone().to_trimmed_text(), declarator.clone().syntax().text_trimmed_range())
+                    }
+                } else {
+                    (declarator.clone().to_trimmed_text(), declarator.clone().syntax().text_trimmed_range())
+                };
+                reads.insert(ClassMemberReference {
+                    name: name_text,
+                    range,
+                    access_kind: get_read_access_kind(&declarator.clone().into()),
+                });

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

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)

726-742: Compound assignments: missing operators → false negatives

Covers only a subset of compound ops. -= |= ^= <<= >>= >>>= **= etc. won’t be classified as read+write.

Use a generic check for “not simple =” to future‑proof.

Apply:

-        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(op) = assignment.operator_token()
+            && op.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,
                 AccessKind::MeaningfulRead,
             )
🧹 Nitpick comments (1)
crates/biome_js_analyze/src/services/semantic_class.rs (1)

18-18: Redundant import

Option is in prelude; this use is unnecessary.

Apply:

-use std::option::Option;
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 44a1edc and 7ada3c7.

📒 Files selected for processing (1)
  • crates/biome_js_analyze/src/services/semantic_class.rs (31 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)
📚 Learning: 2025-09-10T08:05:22.867Z
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

Applied to files:

  • crates/biome_js_analyze/src/services/semantic_class.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). (5)
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Check Dependencies
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: Test Node.js API
🔇 Additional comments (2)
crates/biome_js_analyze/src/services/semantic_class.rs (2)

855-859: Static member read range should be the member token, not this.foo

Keeps identity stable and consistent with other paths.

Apply:

-        reads.insert(ClassMemberReference {
-            name,
-            range: static_member.syntax().text_trimmed_range(),
-            access_kind: get_read_access_kind(&AnyCandidateForUsedInExpressionNode::from(
-                static_member.clone(),
-            )),
-        });
+        reads.insert(ClassMemberReference {
+            name,
+            range: member.syntax().text_trimmed_range(),
+            access_kind: get_read_access_kind(&AnyCandidateForUsedInExpressionNode::from(
+                static_member.clone(),
+            )),
+        });

657-660: Object binding: normalise to property key (avoid “a: b” as name)

Using the whole declarator yields names/ranges like "a: b". Extract the key for both property and shorthand forms.

Apply:

-                reads.insert(ClassMemberReference {
-                    name: declarator.clone().to_trimmed_text(),
-                    range: declarator.clone().syntax().text_trimmed_range(),
-                    access_kind: get_read_access_kind(&declarator.clone().into()),
-                });
+                let (name_text, range) = if let Some(prop) =
+                    declarator.as_js_object_binding_pattern_property()
+                {
+                    if let Ok(key) = prop.key() {
+                        (key.to_trimmed_text(), key.syntax().text_trimmed_range())
+                    } else {
+                        (declarator.clone().to_trimmed_text(), declarator.clone().syntax().text_trimmed_range())
+                    }
+                } else if let Some(shorthand) =
+                    declarator.as_js_object_binding_pattern_shorthand_property()
+                {
+                    if let Ok(ident) = shorthand.name() {
+                        (ident.to_trimmed_text(), ident.syntax().text_trimmed_range())
+                    } else {
+                        (declarator.clone().to_trimmed_text(), declarator.clone().syntax().text_trimmed_range())
+                    }
+                } else {
+                    (declarator.clone().to_trimmed_text(), declarator.clone().syntax().text_trimmed_range())
+                };
+                reads.insert(ClassMemberReference {
+                    name: name_text,
+                    range,
+                    access_kind: get_read_access_kind(&declarator.clone().into()),
+                });

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 (1)
crates/biome_js_analyze/src/services/semantic_class.rs (1)

726-743: Detect all compound assignments (don’t miss -=, <<=, |=, etc.).

The match only covers a subset of compound operators. Safer: treat “not plain =” as compound.

Apply:

-        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()
+            && let Some(operand) = left.as_any_js_assignment()
+            && operator.kind() != JsSyntaxKind::EQ
             && let Some(name) = ThisPatternResolver::extract_this_member_reference(
                 operand.as_js_static_member_assignment(),
                 scoped_this_references,
-                AccessKind::MeaningfulRead,
+                AccessKind::MeaningfulRead,
             )

Optionally, add tests for -=, **=, <<=, >>=, >>>=, ^=, |=.

🧹 Nitpick comments (2)
crates/biome_js_analyze/src/services/semantic_class.rs (2)

18-18: Redundant import of Option.

Option is in the prelude; this use is unnecessary.

Apply:

-use std::option::Option;

244-256: Type reuse for this aliases is confusing.

ClassMemberReference is used to store alias identifiers (not class members). Consider a dedicated alias type (name + range) to avoid conflating concepts.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7ada3c7 and 7e9cd70.

📒 Files selected for processing (1)
  • crates/biome_js_analyze/src/services/semantic_class.rs (31 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)
📚 Learning: 2025-09-10T08:05:22.867Z
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

Applied to files:

  • crates/biome_js_analyze/src/services/semantic_class.rs
🧬 Code graph analysis (1)
crates/biome_js_analyze/src/services/semantic_class.rs (1)
crates/biome_rowan/src/ast/mod.rs (1)
  • cast_ref (142-151)
⏰ 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: Documentation
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: Check Dependencies
  • GitHub Check: Test Node.js API
  • GitHub Check: Bench (biome_configuration)
  • GitHub Check: Bench (biome_html_formatter)
  • GitHub Check: Bench (biome_module_graph)
  • GitHub Check: Bench (biome_css_analyze)
  • GitHub Check: Bench (biome_css_formatter)
  • GitHub Check: Bench (biome_graphql_formatter)
  • GitHub Check: Bench (biome_graphql_parser)
  • GitHub Check: Bench (biome_json_formatter)
  • GitHub Check: Bench (biome_json_parser)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Bench (biome_json_analyze)
  • GitHub Check: Bench (biome_html_parser)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: Bench (biome_css_parser)
  • GitHub Check: autofix
🔇 Additional comments (3)
crates/biome_js_analyze/src/services/semantic_class.rs (3)

741-742: Consistency: avoid hardcoding MeaningfulRead (reuse the classifier).

Prefer using the same classification helper used elsewhere for reads.

If you later extend AnyCandidateForUsedInExpressionNode to include assignment expressions, replace the hardcoded variant with get_read_access_kind for consistency.


853-859: Use the member token range, not the whole this.foo span, for initialisers.

Keep range identity consistent with other reads (only the property token).

Apply:

-        reads.insert(ClassMemberReference {
-            name,
-            range: static_member.syntax().text_trimmed_range(),
-            access_kind: get_read_access_kind(&AnyCandidateForUsedInExpressionNode::from(
-                static_member.clone(),
-            )),
-        });
+        reads.insert(ClassMemberReference {
+            name,
+            range: member.syntax().text_trimmed_range(),
+            access_kind: get_read_access_kind(&AnyCandidateForUsedInExpressionNode::from(
+                static_member.clone(),
+            )),
+        });

656-660: Normalise object binding names to the property key (avoid "a: b").

Using to_trimmed_text() on the whole member yields strings like "a: b". Extract the key/shorthand identifier instead so names and ranges refer to the property key only.

Apply:

-                reads.insert(ClassMemberReference {
-                    name: declarator.clone().to_trimmed_text(),
-                    range: declarator.clone().syntax().text_trimmed_range(),
-                    access_kind: get_read_access_kind(&declarator.clone().into()),
-                });
+                let (name_text, range) = if let Some(prop) =
+                    declarator.as_js_object_binding_pattern_property()
+                {
+                    if let Ok(key) = prop.key() {
+                        (key.to_trimmed_text(), key.syntax().text_trimmed_range())
+                    } else {
+                        (declarator.clone().to_trimmed_text(), declarator.clone().syntax().text_trimmed_range())
+                    }
+                } else if let Some(shorthand) =
+                    declarator.as_js_object_binding_pattern_shorthand_property()
+                {
+                    if let Ok(ident) = shorthand.name() {
+                        (ident.to_trimmed_text(), ident.syntax().text_trimmed_range())
+                    } else {
+                        (declarator.clone().to_trimmed_text(), declarator.clone().syntax().text_trimmed_range())
+                    }
+                } else {
+                    (declarator.clone().to_trimmed_text(), declarator.clone().syntax().text_trimmed_range())
+                };
+                reads.insert(ClassMemberReference {
+                    name: name_text,
+                    range,
+                    access_kind: get_read_access_kind(&declarator.clone().into()),
+                });

@vladimir-ivanov
Copy link
Contributor Author

@ematipico good for review, thanks

Copy link
Member

@ematipico ematipico left a comment

Choose a reason for hiding this comment

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

Nice work!

/// Not limited to `this` references.
/// It can be used for any node; additional cases may require extending the context checks.
fn is_used_in_expression_context(node: &AnyCandidateForUsedInExpressionNode) -> bool {
node.syntax().ancestors().any(|ancestor| {
Copy link
Member

Choose a reason for hiding this comment

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

Add skip(1), otherwise you risk considering the current node

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

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7e9cd70 and 270801c.

📒 Files selected for processing (1)
  • crates/biome_js_analyze/src/services/semantic_class.rs (31 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 (2)
📚 Learning: 2025-09-10T08:05:22.867Z
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

Applied to files:

  • crates/biome_js_analyze/src/services/semantic_class.rs
📚 Learning: 2025-09-12T08:06:16.650Z
Learnt from: ematipico
PR: biomejs/biome#7410
File: crates/biome_js_analyze/src/lint/nursery/no_jsx_props_bind.rs:103-149
Timestamp: 2025-09-12T08:06:16.650Z
Learning: In Biome AST traversal, `ancestors().skip(1)` is a reasonable pattern to skip the current node and prevent false positives when searching for ancestor nodes of the same kind.

Applied to files:

  • crates/biome_js_analyze/src/services/semantic_class.rs
🧬 Code graph analysis (1)
crates/biome_js_analyze/src/services/semantic_class.rs (1)
crates/biome_rowan/src/ast/mod.rs (1)
  • cast_ref (142-151)
⏰ 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_module_graph)
  • GitHub Check: Bench (biome_configuration)
  • GitHub Check: Bench (biome_css_formatter)
  • GitHub Check: Bench (biome_css_analyze)
  • GitHub Check: Bench (biome_package)
  • GitHub Check: Bench (biome_graphql_formatter)
  • GitHub Check: Bench (biome_json_analyze)
  • GitHub Check: Bench (biome_html_parser)
  • GitHub Check: Bench (biome_html_formatter)
  • GitHub Check: Bench (biome_graphql_parser)
  • GitHub Check: Bench (biome_json_formatter)
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Bench (biome_json_parser)
  • GitHub Check: Bench (biome_css_parser)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: autofix
🔇 Additional comments (1)
crates/biome_js_analyze/src/services/semantic_class.rs (1)

855-858: Use the member token’s range for diagnostics

Grabbing static_member.syntax().text_trimmed_range() stretches the span back to the whole this.foo, undoing the earlier normalisation and making downstream diagnostics jittery. Please keep the reference anchored on the member token itself.

-        range: static_member.syntax().text_trimmed_range(),
+        range: member.syntax().text_trimmed_range(),

Comment on lines +899 to +921
fn is_used_in_expression_context(node: &AnyCandidateForUsedInExpressionNode) -> bool {
node.syntax().ancestors().skip(1).any(|ancestor| {
matches!(
ancestor.kind(),
JsSyntaxKind::JS_RETURN_STATEMENT
| JsSyntaxKind::JS_CALL_ARGUMENTS
| JsSyntaxKind::JS_CONDITIONAL_EXPRESSION
| JsSyntaxKind::JS_LOGICAL_EXPRESSION
| JsSyntaxKind::JS_THROW_STATEMENT
| JsSyntaxKind::JS_AWAIT_EXPRESSION
| JsSyntaxKind::JS_YIELD_EXPRESSION
| JsSyntaxKind::JS_UNARY_EXPRESSION
| JsSyntaxKind::JS_TEMPLATE_EXPRESSION
| JsSyntaxKind::JS_CALL_EXPRESSION
| JsSyntaxKind::JS_NEW_EXPRESSION
| JsSyntaxKind::JS_IF_STATEMENT
| JsSyntaxKind::JS_SWITCH_STATEMENT
| JsSyntaxKind::JS_FOR_STATEMENT
| JsSyntaxKind::JS_FOR_IN_STATEMENT
| JsSyntaxKind::JS_FOR_OF_STATEMENT
| JsSyntaxKind::JS_BINARY_EXPRESSION
)
})
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Treat class field initialisers as meaningful reads

Right now class C { bar = this.foo; } classifies the this.foo read as trivial, because the walk never recognises the surrounding field initialiser. Once the linter filters on MeaningfulRead, foo will be reported as unused despite actively feeding bar. Let’s mark class field initialisers as meaningful by teaching the matcher about JsInitializerClause + JsPropertyClassMember.

-    node.syntax().ancestors().skip(1).any(|ancestor| {
-        matches!(
-            ancestor.kind(),
-            JsSyntaxKind::JS_RETURN_STATEMENT
-                | JsSyntaxKind::JS_CALL_ARGUMENTS
-                | JsSyntaxKind::JS_CONDITIONAL_EXPRESSION
-                | JsSyntaxKind::JS_LOGICAL_EXPRESSION
-                | JsSyntaxKind::JS_THROW_STATEMENT
-                | JsSyntaxKind::JS_AWAIT_EXPRESSION
-                | JsSyntaxKind::JS_YIELD_EXPRESSION
-                | JsSyntaxKind::JS_UNARY_EXPRESSION
-                | JsSyntaxKind::JS_TEMPLATE_EXPRESSION
-                | JsSyntaxKind::JS_CALL_EXPRESSION
-                | JsSyntaxKind::JS_NEW_EXPRESSION
-                | JsSyntaxKind::JS_IF_STATEMENT
-                | JsSyntaxKind::JS_SWITCH_STATEMENT
-                | JsSyntaxKind::JS_FOR_STATEMENT
-                | JsSyntaxKind::JS_FOR_IN_STATEMENT
-                | JsSyntaxKind::JS_FOR_OF_STATEMENT
-                | JsSyntaxKind::JS_BINARY_EXPRESSION
-        )
-    })
+    node
+        .syntax()
+        .ancestors()
+        .skip(1)
+        .any(|ancestor| match ancestor.kind() {
+            JsSyntaxKind::JS_INITIALIZER_CLAUSE => ancestor
+                .parent()
+                .map_or(false, |parent| parent.kind() == JsSyntaxKind::JS_PROPERTY_CLASS_MEMBER),
+            JsSyntaxKind::JS_RETURN_STATEMENT
+            | JsSyntaxKind::JS_CALL_ARGUMENTS
+            | JsSyntaxKind::JS_CONDITIONAL_EXPRESSION
+            | JsSyntaxKind::JS_LOGICAL_EXPRESSION
+            | JsSyntaxKind::JS_THROW_STATEMENT
+            | JsSyntaxKind::JS_AWAIT_EXPRESSION
+            | JsSyntaxKind::JS_YIELD_EXPRESSION
+            | JsSyntaxKind::JS_UNARY_EXPRESSION
+            | JsSyntaxKind::JS_TEMPLATE_EXPRESSION
+            | JsSyntaxKind::JS_CALL_EXPRESSION
+            | JsSyntaxKind::JS_NEW_EXPRESSION
+            | JsSyntaxKind::JS_IF_STATEMENT
+            | JsSyntaxKind::JS_SWITCH_STATEMENT
+            | JsSyntaxKind::JS_FOR_STATEMENT
+            | JsSyntaxKind::JS_FOR_IN_STATEMENT
+            | JsSyntaxKind::JS_FOR_OF_STATEMENT
+            | JsSyntaxKind::JS_BINARY_EXPRESSION => true,
+            _ => false,
+        })
📝 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.

Suggested change
fn is_used_in_expression_context(node: &AnyCandidateForUsedInExpressionNode) -> bool {
node.syntax().ancestors().skip(1).any(|ancestor| {
matches!(
ancestor.kind(),
JsSyntaxKind::JS_RETURN_STATEMENT
| JsSyntaxKind::JS_CALL_ARGUMENTS
| JsSyntaxKind::JS_CONDITIONAL_EXPRESSION
| JsSyntaxKind::JS_LOGICAL_EXPRESSION
| JsSyntaxKind::JS_THROW_STATEMENT
| JsSyntaxKind::JS_AWAIT_EXPRESSION
| JsSyntaxKind::JS_YIELD_EXPRESSION
| JsSyntaxKind::JS_UNARY_EXPRESSION
| JsSyntaxKind::JS_TEMPLATE_EXPRESSION
| JsSyntaxKind::JS_CALL_EXPRESSION
| JsSyntaxKind::JS_NEW_EXPRESSION
| JsSyntaxKind::JS_IF_STATEMENT
| JsSyntaxKind::JS_SWITCH_STATEMENT
| JsSyntaxKind::JS_FOR_STATEMENT
| JsSyntaxKind::JS_FOR_IN_STATEMENT
| JsSyntaxKind::JS_FOR_OF_STATEMENT
| JsSyntaxKind::JS_BINARY_EXPRESSION
)
})
fn is_used_in_expression_context(node: &AnyCandidateForUsedInExpressionNode) -> bool {
node
.syntax()
.ancestors()
.skip(1)
.any(|ancestor| match ancestor.kind() {
// Class field initializer: treat `this.foo` in `bar = this.foo` as meaningful.
JsSyntaxKind::JS_INITIALIZER_CLAUSE => ancestor
.parent()
.map_or(false, |parent| parent.kind() == JsSyntaxKind::JS_PROPERTY_CLASS_MEMBER),
JsSyntaxKind::JS_RETURN_STATEMENT
| JsSyntaxKind::JS_CALL_ARGUMENTS
| JsSyntaxKind::JS_CONDITIONAL_EXPRESSION
| JsSyntaxKind::JS_LOGICAL_EXPRESSION
| JsSyntaxKind::JS_THROW_STATEMENT
| JsSyntaxKind::JS_AWAIT_EXPRESSION
| JsSyntaxKind::JS_YIELD_EXPRESSION
| JsSyntaxKind::JS_UNARY_EXPRESSION
| JsSyntaxKind::JS_TEMPLATE_EXPRESSION
| JsSyntaxKind::JS_CALL_EXPRESSION
| JsSyntaxKind::JS_NEW_EXPRESSION
| JsSyntaxKind::JS_IF_STATEMENT
| JsSyntaxKind::JS_SWITCH_STATEMENT
| JsSyntaxKind::JS_FOR_STATEMENT
| JsSyntaxKind::JS_FOR_IN_STATEMENT
| JsSyntaxKind::JS_FOR_OF_STATEMENT
| JsSyntaxKind::JS_BINARY_EXPRESSION => true,
_ => false,
})
}
🤖 Prompt for AI Agents
In crates/biome_js_analyze/src/services/semantic_class.rs around lines 899 to
921, the matcher currently overlooks class field initialisers so reads like
`class C { bar = this.foo; }` are treated as trivial; update the ancestor kind
check to include JsInitializerClause and JsPropertyClassMember (i.e. add these
JsSyntaxKind variants to the matches! list) so that a node inside a class field
initializer is considered an expression context / meaningful read. Ensure both
variants are added to the same matches! arm and run tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants