Skip to content

Commit 99e105f

Browse files
fix(linter): correct autofix in unicorn/prefer-number-properties for Infinity (#12445)
Previously, the autofix would replace global `Infinity` with `Number.Infinity`, which is invalid because `Number.Infinity` is undefined. ```js let a = Infinity; // let a = Number.POSITIVE_INFINITY; let b = -Infinity; // let b = Number.NEGATIVE_INFINITY; ```
1 parent 0b539e3 commit 99e105f

File tree

1 file changed

+92
-19
lines changed

1 file changed

+92
-19
lines changed

crates/oxc_linter/src/rules/unicorn/prefer_number_properties.rs

Lines changed: 92 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use oxc_ast::{
22
AstKind,
3-
ast::{Expression, match_member_expression},
3+
ast::{Expression, UnaryOperator, match_member_expression},
44
};
55
use oxc_diagnostics::OxcDiagnostic;
66
use oxc_macros::declare_oxc_lint;
@@ -121,28 +121,66 @@ impl Rule for PreferNumberProperties {
121121
AstKind::IdentifierReference(ident_ref)
122122
if ctx.is_reference_to_global_variable(ident_ref) =>
123123
{
124-
if (ident_ref.name.as_str() == "NaN" && self.check_nan)
125-
|| (ident_ref.name.as_str() == "Infinity" && self.check_infinity)
126-
|| (matches!(
127-
ident_ref.name.as_str(),
128-
"isNaN" | "isFinite" | "parseFloat" | "parseInt"
129-
) && matches!(
130-
ctx.nodes().parent_kind(node.id()),
131-
AstKind::ObjectProperty(_)
132-
))
124+
let ident_name = ident_ref.name.as_str();
125+
if (ident_name == "NaN" && self.check_nan)
126+
|| (ident_name == "Infinity" && self.check_infinity)
127+
|| (matches!(ident_name, "isNaN" | "isFinite" | "parseFloat" | "parseInt")
128+
&& matches!(ctx.nodes().parent_kind(node.id()), AstKind::ObjectProperty(_)))
133129
{
134-
let fixer = |fixer: RuleFixer<'_, 'a>| match ctx.nodes().parent_kind(node.id())
135-
{
136-
AstKind::ObjectProperty(object_property) if object_property.shorthand => {
137-
fixer.insert_text_before(
138-
&ident_ref.span,
139-
format!("{}: Number.", ident_ref.name.as_str()),
140-
)
130+
let mut replacement = "Number.POSITIVE_INFINITY";
131+
let mut replace_span = ident_ref.span;
132+
let fixer = |fixer: RuleFixer<'_, 'a>| {
133+
if ident_name == "Infinity" {
134+
let unary_expr =
135+
ctx.nodes().ancestor_kinds(node.id()).find_map(|ancestor| {
136+
if let AstKind::UnaryExpression(unary_expr) = ancestor {
137+
Some(unary_expr)
138+
} else {
139+
None
140+
}
141+
});
142+
143+
if let Some(unary_expr) = unary_expr {
144+
replacement = if matches!(
145+
unary_expr.operator,
146+
UnaryOperator::UnaryNegation
147+
) {
148+
// -(Infinity) -> Number.NEGATIVE_INFINITY
149+
// +(Infinity) -> +(Number.POSITIVE_INFINITY)
150+
replace_span = unary_expr.span;
151+
"Number.NEGATIVE_INFINITY"
152+
} else {
153+
"Number.POSITIVE_INFINITY"
154+
};
155+
}
156+
}
157+
match ctx.nodes().parent_kind(node.id()) {
158+
AstKind::ObjectProperty(object_property)
159+
if object_property.shorthand =>
160+
{
161+
if ident_name == "Infinity" {
162+
fixer.insert_text_after(
163+
&ident_ref.span,
164+
format!(": {replacement}"),
165+
)
166+
} else {
167+
fixer.insert_text_before(
168+
&ident_ref.span,
169+
format!("{}: Number.", ident_ref.name.as_str()),
170+
)
171+
}
172+
}
173+
_ => {
174+
if ident_name == "Infinity" {
175+
fixer.replace(replace_span, replacement)
176+
} else {
177+
fixer.insert_text_before(&ident_ref.span, "Number.")
178+
}
179+
}
141180
}
142-
_ => fixer.insert_text_before(&ident_ref.span, "Number."),
143181
};
144182

145-
if ident_ref.name.as_str() == "isNaN" || ident_ref.name.as_str() == "isFinite" {
183+
if ident_name == "isNaN" || ident_name == "isFinite" {
146184
ctx.diagnostic_with_dangerous_fix(
147185
prefer_number_properties_diagnostic(ident_ref.span, &ident_ref.name),
148186
fixer,
@@ -480,6 +518,41 @@ function inner() {
480518
("class Foo3 {[NaN] = 1}", "class Foo3 {[Number.NaN] = 1}", None),
481519
("class Foo2 {[NaN] = 1}", "class Foo2 {[Number.NaN] = 1}", None),
482520
("class Foo {[NaN] = 1}", "class Foo {[Number.NaN] = 1}", None),
521+
(
522+
"const foo = Infinity;",
523+
"const foo = Number.POSITIVE_INFINITY;",
524+
Some(json!([{"checkInfinity":true}])),
525+
),
526+
(
527+
"const foo = -Infinity;",
528+
"const foo = Number.NEGATIVE_INFINITY;",
529+
Some(json!([{"checkInfinity":true}])),
530+
),
531+
(
532+
"const foo = -(Infinity);",
533+
"const foo = Number.NEGATIVE_INFINITY;",
534+
Some(json!([{"checkInfinity":true}])),
535+
),
536+
(
537+
"const foo = -((Infinity));",
538+
"const foo = Number.NEGATIVE_INFINITY;",
539+
Some(json!([{"checkInfinity":true}])),
540+
),
541+
(
542+
"let a = { Infinity, }",
543+
"let a = { Infinity: Number.POSITIVE_INFINITY, }",
544+
Some(json!([{"checkInfinity":true}])),
545+
),
546+
(
547+
"let a = (Infinity)",
548+
"let a = (Number.POSITIVE_INFINITY)",
549+
Some(json!([{"checkInfinity":true}])),
550+
),
551+
(
552+
"let a = +(Infinity)",
553+
"let a = +(Number.POSITIVE_INFINITY)",
554+
Some(json!([{"checkInfinity":true}])),
555+
),
483556
];
484557

485558
Tester::new(PreferNumberProperties::NAME, PreferNumberProperties::PLUGIN, pass, fail)

0 commit comments

Comments
 (0)