|
1 | 1 | use oxc_ast::{ |
2 | 2 | AstKind, |
3 | | - ast::{Expression, match_member_expression}, |
| 3 | + ast::{Expression, UnaryOperator, match_member_expression}, |
4 | 4 | }; |
5 | 5 | use oxc_diagnostics::OxcDiagnostic; |
6 | 6 | use oxc_macros::declare_oxc_lint; |
@@ -121,28 +121,66 @@ impl Rule for PreferNumberProperties { |
121 | 121 | AstKind::IdentifierReference(ident_ref) |
122 | 122 | if ctx.is_reference_to_global_variable(ident_ref) => |
123 | 123 | { |
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(_))) |
133 | 129 | { |
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 | + } |
141 | 180 | } |
142 | | - _ => fixer.insert_text_before(&ident_ref.span, "Number."), |
143 | 181 | }; |
144 | 182 |
|
145 | | - if ident_ref.name.as_str() == "isNaN" || ident_ref.name.as_str() == "isFinite" { |
| 183 | + if ident_name == "isNaN" || ident_name == "isFinite" { |
146 | 184 | ctx.diagnostic_with_dangerous_fix( |
147 | 185 | prefer_number_properties_diagnostic(ident_ref.span, &ident_ref.name), |
148 | 186 | fixer, |
@@ -480,6 +518,41 @@ function inner() { |
480 | 518 | ("class Foo3 {[NaN] = 1}", "class Foo3 {[Number.NaN] = 1}", None), |
481 | 519 | ("class Foo2 {[NaN] = 1}", "class Foo2 {[Number.NaN] = 1}", None), |
482 | 520 | ("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 | + ), |
483 | 556 | ]; |
484 | 557 |
|
485 | 558 | Tester::new(PreferNumberProperties::NAME, PreferNumberProperties::PLUGIN, pass, fail) |
|
0 commit comments