Skip to content

Commit 7cd564d

Browse files
committed
refactor(lint/useLiteralKeys): handle numeric keys
1 parent de36f84 commit 7cd564d

File tree

5 files changed

+86
-30
lines changed

5 files changed

+86
-30
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@biomejs/biome": minor
3+
---
4+
5+
[useLiteralKeys](https://biomejs.dev/linter/rules/use-literal-keys/) now handles numeric keys and is declared as being the same rule as the ESLint [no-useless-computed-key](https://eslint.org/docs/latest/rules/no-useless-computed-key) rule.

crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/biome_js_analyze/src/lint/complexity/use_literal_keys.rs

Lines changed: 47 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ use biome_diagnostics::Severity;
77
use biome_js_factory::make;
88
use biome_js_syntax::{
99
AnyJsAssignment, AnyJsComputedMember, AnyJsMemberExpression, AnyJsName, AnyJsObjectMemberName,
10-
AnyTsEnumMemberName, JsComputedMemberName, JsSyntaxKind, T,
10+
AnyTsEnumMemberName, JsComputedMemberName, JsSyntaxKind, JsSyntaxToken, T, inner_string_text,
11+
static_value::StaticValue,
1112
};
1213
use biome_rowan::{AstNode, BatchMutationExt, SyntaxNodeOptionExt, TextRange, declare_node_union};
1314
use biome_unicode_table::is_js_ident;
@@ -50,6 +51,7 @@ declare_lint_rule! {
5051
language: "js",
5152
sources: &[
5253
RuleSource::Eslint("dot-notation"),
54+
RuleSource::Eslint("no-useless-computed-key"),
5355
RuleSource::EslintTypeScript("dot-notation")
5456
],
5557
recommended: true,
@@ -60,7 +62,7 @@ declare_lint_rule! {
6062

6163
impl Rule for UseLiteralKeys {
6264
type Query = Ast<AnyJsMember>;
63-
type State = (TextRange, String, bool);
65+
type State = (TextRange, JsSyntaxToken, bool);
6466
type Signals = Option<Self::State>;
6567
type Options = ();
6668

@@ -75,29 +77,38 @@ impl Rule for UseLiteralKeys {
7577
}
7678
};
7779
let value = inner_expression.as_static_value()?;
78-
let value = value.as_string_constant()?;
79-
// `{["__proto__"]: null }` and `{"__proto__": null}`/`{"__proto__": null}`
80-
// have different semantic.
81-
// The first is a regular property.
82-
// The second is a special property that changes the object prototype.
83-
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto
84-
if is_computed_member_name && value == "__proto__" {
85-
return None;
86-
}
87-
// A computed property `["something"]` can always be simplified to a string literal "something",
88-
// unless it is a template literal inside that contains unescaped new line characters:
89-
//
90-
// const a = {
91-
// [`line1
92-
// line2`]: true
93-
// }
94-
//
95-
if (is_computed_member_name && !has_unescaped_new_line(value)) || is_js_ident(value) {
96-
return Some((
97-
inner_expression.range(),
98-
value.to_string(),
99-
is_computed_member_name,
100-
));
80+
//let value = value.as_string_constant()?;
81+
match value {
82+
StaticValue::Number(token) => {
83+
if is_computed_member_name {
84+
return Some((inner_expression.range(), token, is_computed_member_name));
85+
}
86+
}
87+
StaticValue::String(token) => {
88+
let value = inner_string_text(&token);
89+
// `{["__proto__"]: null }` and `{"__proto__": null}`/`{"__proto__": null}`
90+
// have different semantic.
91+
// The first is a regular property.
92+
// The second is a special property that changes the object prototype.
93+
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto
94+
if is_computed_member_name && value == "__proto__" {
95+
return None;
96+
}
97+
// A computed property `["something"]` can always be simplified to a string literal "something",
98+
// unless it is a template literal inside that contains unescaped new line characters:
99+
//
100+
// const a = {
101+
// [`line1
102+
// line2`]: true
103+
// }
104+
//
105+
if (is_computed_member_name && !has_unescaped_new_line(&value))
106+
|| is_js_ident(&value)
107+
{
108+
return Some((inner_expression.range(), token, is_computed_member_name));
109+
}
110+
}
111+
_ => {}
101112
}
102113
None
103114
}
@@ -121,13 +132,14 @@ impl Rule for UseLiteralKeys {
121132
))
122133
}
123134

124-
fn action(ctx: &RuleContext<Self>, (_, identifier, _): &Self::State) -> Option<JsRuleAction> {
135+
fn action(ctx: &RuleContext<Self>, (_, token, _): &Self::State) -> Option<JsRuleAction> {
125136
let node = ctx.query();
126137
let mut mutation = ctx.root().begin();
127138
match node {
128139
AnyJsMember::AnyJsComputedMember(node) => {
140+
let identifier = inner_string_text(token);
129141
let object = node.object().ok()?;
130-
let member = make::js_name(make::ident(identifier));
142+
let member = make::js_name(make::ident(&identifier));
131143
let dot_token = node
132144
.optional_chain_token()
133145
.unwrap_or_else(|| make::token(T![.]));
@@ -158,10 +170,15 @@ impl Rule for UseLiteralKeys {
158170
}
159171
}
160172
AnyJsMember::JsComputedMemberName(member) => {
161-
let name_token = if ctx.as_preferred_quote().is_double() {
162-
make::js_string_literal(identifier)
173+
let name_token = if token.kind() == JsSyntaxKind::JS_NUMBER_LITERAL {
174+
token.clone()
163175
} else {
164-
make::js_string_literal_single_quotes(identifier)
176+
let identifier = inner_string_text(token);
177+
if ctx.as_preferred_quote().is_double() {
178+
make::js_string_literal(&identifier)
179+
} else {
180+
make::js_string_literal_single_quotes(&identifier)
181+
}
165182
};
166183
if member.syntax().parent().kind() == Some(JsSyntaxKind::TS_ENUM_MEMBER) {
167184
let literal_enum_member_name = make::ts_literal_enum_member_name(name_token);

crates/biome_js_analyze/tests/specs/complexity/useLiteralKeys/invalid.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,7 @@ a = {
4545
};
4646
a = {
4747
[`line1\nline2`]: true,
48+
};
49+
a = {
50+
[0]: 0,
4851
};

crates/biome_js_analyze/tests/specs/complexity/useLiteralKeys/invalid.js.snap

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ a = {
5252
a = {
5353
[`line1\nline2`]: true,
5454
};
55+
a = {
56+
[0]: 0,
57+
};
5558
```
5659

5760
# Diagnostics
@@ -710,6 +713,7 @@ invalid.js:47:4 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━
710713
> 47 │ [`line1\nline2`]: true,
711714
│ ^^^^^^^^^^^^^^
712715
48 │ };
716+
49a = {
713717
714718
i Unsafe fix: Use a literal key instead.
715719
@@ -718,6 +722,25 @@ invalid.js:47:4 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━
718722
47 │ - ··[`line1\nline2`]:·true,
719723
47 │ + ··"line1\nline2"true,
720724
48 48 │ };
725+
49 49a = {
726+
727+
728+
```
729+
730+
```
731+
invalid.js:50:4 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
732+
733+
i The computed expression can be simplified to a string literal.
734+
735+
48 │ };
736+
49a = {
737+
> 50 │ [0]: 0,
738+
│ ^
739+
51 │ };
740+
741+
i Unsafe fix: Use a literal key instead.
721742
743+
50 │ ··[0]:·0,
744+
- -
722745
723746
```

0 commit comments

Comments
 (0)