Skip to content

Commit 4021165

Browse files
authored
fix(lint/noUnassignedVariables): handle JSX ref attribute assignments (#7000)
1 parent c65a689 commit 4021165

File tree

4 files changed

+47
-1
lines changed

4 files changed

+47
-1
lines changed

.changeset/five-ghosts-wave.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@biomejs/biome": patch
3+
---
4+
5+
Fixed [#6795](https://github.com/biomejs/biome/issues/6795): `noUnassignedVariables` now correctly recognizes variables used in JSX `ref` attributes.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/* should not generate diagnostics */
2+
export const Component = () => {
3+
let value;
4+
return <div ref={value} />
5+
}
6+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
source: crates/biome_js_analyze/tests/spec_tests.rs
3+
expression: valid.jsx
4+
---
5+
# Input
6+
```jsx
7+
/* should not generate diagnostics */
8+
export const Component = () => {
9+
let value;
10+
return <div ref={value} />
11+
}
12+
13+
```

crates/biome_js_semantic/src/events.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ use biome_js_syntax::{
66
AnyJsIdentifierUsage, JsDirective, JsLanguage, JsSyntaxKind, JsSyntaxNode, TextRange,
77
TsTypeParameterName, inner_string_text,
88
};
9-
use biome_js_syntax::{AnyJsImportClause, AnyJsNamedImportSpecifier, AnyTsType};
9+
use biome_js_syntax::{
10+
AnyJsImportClause, AnyJsNamedImportSpecifier, AnyJsxAttributeName, AnyTsType, JsxAttribute,
11+
};
1012
use biome_rowan::TextSize;
1113
use biome_rowan::{AstNode, SyntaxNodeOptionExt, TokenText, syntax::Preorder};
1214
use rustc_hash::FxHashMap;
@@ -709,6 +711,18 @@ impl SemanticEventExtractor {
709711
}
710712
}
711713

714+
fn is_inside_jsx_ref_attribute(node: &JsSyntaxNode) -> bool {
715+
node.ancestors()
716+
.find_map(JsxAttribute::cast)
717+
.and_then(|attr| attr.name().ok())
718+
.is_some_and(|attr_name| match attr_name {
719+
AnyJsxAttributeName::JsxName(jsx_name) => jsx_name
720+
.value_token()
721+
.is_ok_and(|token| token.text_trimmed() == "ref"),
722+
_ => false,
723+
})
724+
}
725+
712726
fn enter_identifier_usage(&mut self, node: AnyJsIdentifierUsage) {
713727
let range = node.syntax().text_trimmed_range();
714728
let Ok(name_token) = node.value_token() else {
@@ -728,6 +742,14 @@ impl SemanticEventExtractor {
728742
);
729743
return;
730744
};
745+
746+
if parent.kind() == JS_IDENTIFIER_EXPRESSION
747+
&& Self::is_inside_jsx_ref_attribute(&parent)
748+
{
749+
self.push_reference(BindingName::Value(name), Reference::Write(range));
750+
return;
751+
}
752+
731753
match parent.kind() {
732754
JS_EXPORT_NAMED_SHORTHAND_SPECIFIER | JS_EXPORT_NAMED_SPECIFIER => {
733755
self.push_reference(

0 commit comments

Comments
 (0)