diff --git a/crates/oxc_ast/src/ast/js.rs b/crates/oxc_ast/src/ast/js.rs index d0738be1b..9bd36a925 100644 --- a/crates/oxc_ast/src/ast/js.rs +++ b/crates/oxc_ast/src/ast/js.rs @@ -163,6 +163,20 @@ impl<'a> Expression<'a> { } } + #[must_use] + pub fn is_specific_member_access(&'a self, object: &str, property: &str) -> bool { + match self.get_inner_expression() { + Expression::MemberExpression(expr) => expr.is_specific_member_access(object, property), + Expression::ChainExpression(chain) => { + let ChainElement::MemberExpression(expr) = &chain.expression else { + return false; + }; + expr.is_specific_member_access(object, property) + } + _ => false, + } + } + #[must_use] pub fn get_inner_expression(&self) -> &Expression<'a> { match self { @@ -432,6 +446,13 @@ impl<'a> MemberExpression<'a> { MemberExpression::PrivateFieldExpression(_) => None, } } + + /// Whether it is a static member access `object.property` + #[must_use] + pub fn is_specific_member_access(&'a self, object: &str, property: &str) -> bool { + self.object().is_specific_id(object) + && self.static_property_name().is_some_and(|p| p == property) + } } #[derive(Debug, PartialEq, Hash)] diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 8e953751b..bda9a33b9 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -13,4 +13,5 @@ oxc_macros::declare_all_lint_rules! { no_mixed_operators, no_constant_binary_expression, deepscan::uninvoked_array_callback, + use_isnan, } diff --git a/crates/oxc_linter/src/rules/use_isnan.rs b/crates/oxc_linter/src/rules/use_isnan.rs new file mode 100644 index 000000000..5d109c9eb --- /dev/null +++ b/crates/oxc_linter/src/rules/use_isnan.rs @@ -0,0 +1,488 @@ +use oxc_ast::{ + ast::{Argument, ChainElement, Expression}, + AstKind, GetSpan, Span, +}; +use oxc_diagnostics::{ + miette::{self, Diagnostic}, + thiserror::Error, +}; +use oxc_macros::declare_oxc_lint; + +use crate::{context::LintContext, rule::Rule, AstNode}; + +#[derive(Debug, Error, Diagnostic)] +enum UseIsnanDiagnostic { + #[error("eslint(use-isnan): Requires calls to isNaN() when checking for NaN")] + #[diagnostic(severity(warning), help("Use the isNaN function to compare with NaN."))] + ComparisinWithNaN(#[label] Span), + #[error("eslint(use-isnan): Requires calls to isNaN() when checking for NaN")] + #[diagnostic( + severity(warning), + help( + "'switch(NaN)' can never match a case clause. Use Number.isNaN instead of the switch." + ) + )] + SwitchNaN(#[label] Span), + #[error("eslint(use-isnan): Requires calls to isNaN() when checking for NaN")] + #[diagnostic( + severity(warning), + help("'case NaN' can never match. Use Number.isNaN before the switch.") + )] + CaseNaN(#[label] Span), + #[error("eslint(use-isnan): Requires calls to isNaN() when checking for NaN")] + #[diagnostic(severity(warning), help("Array prototype method '{0}' cannot find NaN."))] + IndexOfNaN(&'static str, #[label] Span), +} + +#[derive(Debug, Clone)] +pub struct UseIsnan { + /// Whether to disallow NaN in switch cases and discriminants + enforce_for_switch_case: bool, + /// Whether to disallow NaN as arguments of `indexOf` and `lastIndexOf` + enforce_for_index_of: bool, +} + +impl Default for UseIsnan { + fn default() -> Self { + Self { enforce_for_switch_case: true, enforce_for_index_of: false } + } +} + +declare_oxc_lint!( + /// ### What it does + /// Disallows checking against NaN without using isNaN() call. + /// + /// ### Why is this bad? + /// In JavaScript, NaN is a special value of the Number type. + /// It’s used to represent any of the “not-a-number” values represented + /// by the double-precision 64-bit format as specified by the IEEE Standard + /// for Binary Floating-Point Arithmetic. + /// + /// Because NaN is unique in JavaScript by not being equal to anything, including itself, + /// the results of comparisons to NaN are confusing: + /// - NaN === NaN or NaN == NaN evaluate to false + /// - NaN !== NaN or NaN != NaN evaluate to true + /// + /// Therefore, use Number.isNaN() or global isNaN() functions to test whether a value is NaN. + /// + /// ### Example + /// ```javascript + /// foo == NaN; + /// foo === NaN; + /// foo <= NaN; + /// foo > NaN; + /// ``` + UseIsnan, + correctness +); + +impl Rule for UseIsnan { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + match node.get().kind() { + AstKind::BinaryExpression(expr) if expr.operator.is_compare() || expr.operator.is_equality() => { + if is_nan_identifier(&expr.left) { + ctx.diagnostic(UseIsnanDiagnostic::ComparisinWithNaN(expr.left.span())); + } + if is_nan_identifier(&expr.right) { + ctx.diagnostic(UseIsnanDiagnostic::ComparisinWithNaN(expr.right.span())); + } + } + AstKind::SwitchCase(case) if self.enforce_for_switch_case => { + if let Some(test) = &case.test && is_nan_identifier(test) { + ctx.diagnostic(UseIsnanDiagnostic::CaseNaN(test.span())); + } + } + AstKind::SwitchStatement(switch) if self.enforce_for_switch_case => { + if is_nan_identifier(&switch.discriminant) { + ctx.diagnostic(UseIsnanDiagnostic::SwitchNaN(switch.discriminant.span())); + } + } + AstKind::CallExpression(call) if self.enforce_for_index_of => { + // Match target array prototype methods whose only argument is NaN + if let Some(method) = is_target_callee(&call.callee) + && call.arguments.len() == 1 && let Some(Argument::Expression(expr)) = &call.arguments.first() && is_nan_identifier(expr) { + ctx.diagnostic(UseIsnanDiagnostic::IndexOfNaN(method, expr.span())); + } + } + _ => (), + } + } + + fn from_configuration(value: serde_json::Value) -> Self { + let (enforce_for_switch_case, enforce_for_index_of) = + value.get(0).map_or((true, false), |config| { + ( + config + .get("enforceForSwitchCase") + .and_then(serde_json::Value::as_bool) + .unwrap_or(true), + config + .get("enforceForIndexOf") + .and_then(serde_json::Value::as_bool) + .unwrap_or_default(), + ) + }); + + Self { enforce_for_switch_case, enforce_for_index_of } + } +} + +fn is_nan_identifier<'a>(expr: &'a Expression<'a>) -> bool { + expr.is_specific_id("NaN") || expr.is_specific_member_access("Number", "NaN") +} + +/// If callee is calling the `indexOf` or `lastIndexOf` function. +fn is_target_callee<'a>(callee: &'a Expression<'a>) -> Option<&'static str> { + const TARGET_METHODS: [&str; 2] = ["indexOf", "lastIndexOf"]; + let callee = callee.get_inner_expression(); + match callee { + Expression::MemberExpression(expr) => expr.static_property_name().and_then(|property| { + TARGET_METHODS.iter().find(|method| **method == property).copied() + }), + Expression::ChainExpression(chain) => { + if let ChainElement::MemberExpression(expr) = &chain.expression { + expr.static_property_name().and_then(|property| { + TARGET_METHODS.iter().find(|method| **method == property).copied() + }) + } else { + None + } + } + _ => None, + } +} + +#[test] +#[allow(clippy::too_many_lines)] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + ("var x = NaN;", None), + ("isNaN(NaN) === true;", None), + ("isNaN(123) !== true;", None), + ("Number.isNaN(NaN) === true;", None), + ("Number.isNaN(123) !== true;", None), + ("foo(NaN + 1);", None), + ("foo(1 + NaN);", None), + ("foo(NaN - 1)", None), + ("foo(1 - NaN)", None), + ("foo(NaN * 2)", None), + ("foo(2 * NaN)", None), + ("foo(NaN / 2)", None), + ("foo(2 / NaN)", None), + ("var x; if (x = NaN) { }", None), + ("var x = Number.NaN;", None), + ("isNaN(Number.NaN) === true;", None), + ("Number.isNaN(Number.NaN) === true;", None), + ("foo(Number.NaN + 1);", None), + ("foo(1 + Number.NaN);", None), + ("foo(Number.NaN - 1)", None), + ("foo(1 - Number.NaN)", None), + ("foo(Number.NaN * 2)", None), + ("foo(2 * Number.NaN)", None), + ("foo(Number.NaN / 2)", None), + ("foo(2 / Number.NaN)", None), + ("var x; if (x = Number.NaN) { }", None), + ("x === Number[NaN];", None), + ( + "switch(NaN) { case foo: break; }", + Some(serde_json::json!([{ "enforceForSwitchCase": false }])), + ), + ( + "switch(foo) { case NaN: break; }", + Some(serde_json::json!([{ "enforceForSwitchCase": false }])), + ), + ( + "switch(NaN) { case NaN: break; }", + Some(serde_json::json!([{ "enforceForSwitchCase": false }])), + ), + ( + "switch(foo) { case bar: break; case NaN: break; default: break; }", + Some(serde_json::json!([{ "enforceForSwitchCase": false }])), + ), + ("switch(foo) {}", Some(serde_json::json!([{ "enforceForSwitchCase": true }]))), + ( + "switch(foo) { case bar: NaN; }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ( + "switch(foo) { default: NaN; }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ("switch(Nan) {}", Some(serde_json::json!([{ "enforceForSwitchCase": true }]))), + ( + "switch('NaN') { default: break; }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ("switch(foo(NaN)) {}", Some(serde_json::json!([{ "enforceForSwitchCase": true }]))), + ("switch(foo.NaN) {}", Some(serde_json::json!([{ "enforceForSwitchCase": true }]))), + ( + "switch(foo) { case Nan: break }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ( + "switch(foo) { case 'NaN': break }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ( + "switch(foo) { case foo(NaN): break }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ( + "switch(foo) { case foo.NaN: break }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ( + "switch(foo) { case bar: break; case 1: break; default: break; }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ( + "switch(Number.NaN) { case foo: break; }", + Some(serde_json::json!([{ "enforceForSwitchCase": false }])), + ), + ( + "switch(foo) { case Number.NaN: break; }", + Some(serde_json::json!([{ "enforceForSwitchCase": false }])), + ), + ( + "switch(NaN) { case Number.NaN: break; }", + Some(serde_json::json!([{ "enforceForSwitchCase": false }])), + ), + ( + "switch(foo) { case bar: break; case Number.NaN: break; default: break; }", + Some(serde_json::json!([{ "enforceForSwitchCase": false }])), + ), + ( + "switch(foo) { case bar: Number.NaN; }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ( + "switch(foo) { default: Number.NaN; }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ("switch(Number.Nan) {}", Some(serde_json::json!([{ "enforceForSwitchCase": true }]))), + ( + "switch('Number.NaN') { default: break; }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ("switch(foo(Number.NaN)) {}", Some(serde_json::json!([{ "enforceForSwitchCase": true }]))), + ("switch(foo.Number.NaN) {}", Some(serde_json::json!([{ "enforceForSwitchCase": true }]))), + ( + "switch(foo) { case Number.Nan: break }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ( + "switch(foo) { case 'Number.NaN': break }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ( + "switch(foo) { case foo(Number.NaN): break }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ( + "switch(foo) { case foo.Number.NaN: break }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ("foo.indexOf(NaN)", None), + ("foo.lastIndexOf(NaN)", None), + ("foo.indexOf(Number.NaN)", None), + ("foo.lastIndexOf(Number.NaN)", None), + ("foo.indexOf(NaN)", Some(serde_json::json!([{}]))), + ("foo.lastIndexOf(NaN)", Some(serde_json::json!([{}]))), + ("foo.indexOf(NaN)", Some(serde_json::json!([{ "enforceForIndexOf": false }]))), + ("foo.lastIndexOf(NaN)", Some(serde_json::json!([{ "enforceForIndexOf": false }]))), + ("indexOf(NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("lastIndexOf(NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("new foo.indexOf(NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo.bar(NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo.IndexOf(NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo[indexOf](NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo[lastIndexOf](NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("indexOf.foo(NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo.indexOf()", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo.lastIndexOf()", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo.indexOf(a)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo.lastIndexOf(Nan)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo.indexOf(a, NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo.lastIndexOf(NaN, b)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo.indexOf(a, b)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo.lastIndexOf(NaN, NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo.indexOf(...NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo.lastIndexOf(NaN())", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo.indexOf(Number.NaN)", Some(serde_json::json!([{}]))), + ("foo.lastIndexOf(Number.NaN)", Some(serde_json::json!([{}]))), + ("foo.indexOf(Number.NaN)", Some(serde_json::json!([{ "enforceForIndexOf": false }]))), + ("foo.lastIndexOf(Number.NaN)", Some(serde_json::json!([{ "enforceForIndexOf": false }]))), + ("indexOf(Number.NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("lastIndexOf(Number.NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("new foo.indexOf(Number.NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo.bar(Number.NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo.IndexOf(Number.NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo[indexOf](Number.NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo[lastIndexOf](Number.NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("indexOf.foo(Number.NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo.lastIndexOf(Number.Nan)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo.indexOf(a, Number.NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ( + "foo.lastIndexOf(Number.NaN, b)", + Some(serde_json::json!([{ "enforceForIndexOf": true }])), + ), + ( + "foo.lastIndexOf(Number.NaN, NaN)", + Some(serde_json::json!([{ "enforceForIndexOf": true }])), + ), + ("foo.indexOf(...Number.NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo.lastIndexOf(Number.NaN())", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ]; + + let fail = vec![ + ("123 == NaN;", None), + ("123 === NaN;", None), + ("NaN === \"abc\";", None), + ("NaN == \"abc\";", None), + ("123 != NaN;", None), + ("123 !== NaN;", None), + ("NaN !== \"abc\";", None), + ("NaN != \"abc\";", None), + ("NaN < \"abc\";", None), + ("\"abc\" < NaN;", None), + ("NaN > \"abc\";", None), + ("\"abc\" > NaN;", None), + ("NaN <= \"abc\";", None), + ("\"abc\" <= NaN;", None), + ("NaN >= \"abc\";", None), + ("\"abc\" >= NaN;", None), + ("123 == Number.NaN;", None), + ("123 === Number.NaN;", None), + ("Number.NaN === \"abc\";", None), + ("Number.NaN == \"abc\";", None), + ("123 != Number.NaN;", None), + ("123 !== Number.NaN;", None), + ("Number.NaN !== \"abc\";", None), + ("Number.NaN != \"abc\";", None), + ("Number.NaN < \"abc\";", None), + ("\"abc\" < Number.NaN;", None), + ("Number.NaN > \"abc\";", None), + ("\"abc\" > Number.NaN;", None), + ("Number.NaN <= \"abc\";", None), + ("\"abc\" <= Number.NaN;", None), + ("Number.NaN >= \"abc\";", None), + ("\"abc\" >= Number.NaN;", None), + ("x === Number?.NaN;", None), + ("x === Number['NaN'];", None), + ("switch(NaN) { case foo: break; }", None), + ("switch(foo) { case NaN: break; }", None), + ("switch(NaN) { case foo: break; }", Some(serde_json::json!([{}]))), + ("switch(foo) { case NaN: break; }", Some(serde_json::json!([{}]))), + ("switch(NaN) {}", Some(serde_json::json!([{ "enforceForSwitchCase": true }]))), + ( + "switch(NaN) { case foo: break; }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ( + "switch(NaN) { default: break; }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ( + "switch(NaN) { case foo: break; default: break; }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ("switch(foo) { case NaN: }", Some(serde_json::json!([{ "enforceForSwitchCase": true }]))), + ( + "switch(foo) { case NaN: break; }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ( + "switch(foo) { case (NaN): break; }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ( + "switch(foo) { case bar: break; case NaN: break; default: break; }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ( + "switch(foo) { case bar: case NaN: default: break; }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ( + "switch(foo) { case bar: break; case NaN: break; case baz: break; case NaN: break; }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ( + "switch(NaN) { case NaN: break; }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ("switch(Number.NaN) { case foo: break; }", None), + ("switch(foo) { case Number.NaN: break; }", None), + ("switch(Number.NaN) { case foo: break; }", Some(serde_json::json!([{}]))), + ("switch(foo) { case Number.NaN: break; }", Some(serde_json::json!([{}]))), + ("switch(Number.NaN) {}", Some(serde_json::json!([{ "enforceForSwitchCase": true }]))), + ( + "switch(Number.NaN) { case foo: break; }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ( + "switch(Number.NaN) { default: break; }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ( + "switch(Number.NaN) { case foo: break; default: break; }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ( + "switch(foo) { case Number.NaN: }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ( + "switch(foo) { case Number.NaN: break; }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ( + "switch(foo) { case (Number.NaN): break; }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ( + "switch(foo) { case bar: break; case Number.NaN: break; default: break; }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ( + "switch(foo) { case bar: case Number.NaN: default: break; }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ( + "switch(foo) { case bar: break; case NaN: break; case baz: break; case Number.NaN: break; }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ( + "switch(Number.NaN) { case Number.NaN: break; }", + Some(serde_json::json!([{ "enforceForSwitchCase": true }])), + ), + ("foo.indexOf(NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo.lastIndexOf(NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo['indexOf'](NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo['lastIndexOf'](NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo().indexOf(NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo.bar.lastIndexOf(NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo.indexOf?.(NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo?.indexOf(NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("(foo?.indexOf)(NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo.indexOf(Number.NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo.lastIndexOf(Number.NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo['indexOf'](Number.NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ( + "foo['lastIndexOf'](Number.NaN)", + Some(serde_json::json!([{ "enforceForIndexOf": true }])), + ), + ("foo().indexOf(Number.NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ( + "foo.bar.lastIndexOf(Number.NaN)", + Some(serde_json::json!([{ "enforceForIndexOf": true }])), + ), + ("foo.indexOf?.(Number.NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("foo?.indexOf(Number.NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ("(foo?.indexOf)(Number.NaN)", Some(serde_json::json!([{ "enforceForIndexOf": true }]))), + ]; + + Tester::new(UseIsnan::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/use_isnan.snap b/crates/oxc_linter/src/snapshots/use_isnan.snap new file mode 100644 index 000000000..82e36a0b0 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/use_isnan.snap @@ -0,0 +1,608 @@ +--- +source: crates/oxc_linter/src/tester.rs +assertion_line: 53 +expression: use_isnan +--- + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ 123 == NaN; + · ─── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ 123 === NaN; + · ─── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ NaN === "abc"; + · ─── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ NaN == "abc"; + · ─── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ 123 != NaN; + · ─── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ 123 !== NaN; + · ─── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ NaN !== "abc"; + · ─── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ NaN != "abc"; + · ─── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ NaN < "abc"; + · ─── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ "abc" < NaN; + · ─── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ NaN > "abc"; + · ─── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ "abc" > NaN; + · ─── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ NaN <= "abc"; + · ─── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ "abc" <= NaN; + · ─── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ NaN >= "abc"; + · ─── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ "abc" >= NaN; + · ─── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ 123 == Number.NaN; + · ────────── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ 123 === Number.NaN; + · ────────── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ Number.NaN === "abc"; + · ────────── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ Number.NaN == "abc"; + · ────────── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ 123 != Number.NaN; + · ────────── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ 123 !== Number.NaN; + · ────────── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ Number.NaN !== "abc"; + · ────────── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ Number.NaN != "abc"; + · ────────── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ Number.NaN < "abc"; + · ────────── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ "abc" < Number.NaN; + · ────────── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ Number.NaN > "abc"; + · ────────── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ "abc" > Number.NaN; + · ────────── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ Number.NaN <= "abc"; + · ────────── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ "abc" <= Number.NaN; + · ────────── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ Number.NaN >= "abc"; + · ────────── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ "abc" >= Number.NaN; + · ────────── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ x === Number?.NaN; + · ─────────── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ x === Number['NaN']; + · ───────────── + ╰──── + help: Use the isNaN function to compare with NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(NaN) { case foo: break; } + · ─── + ╰──── + help: 'switch(NaN)' can never match a case clause. Use Number.isNaN instead of the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(foo) { case NaN: break; } + · ─── + ╰──── + help: 'case NaN' can never match. Use Number.isNaN before the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(NaN) { case foo: break; } + · ─── + ╰──── + help: 'switch(NaN)' can never match a case clause. Use Number.isNaN instead of the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(foo) { case NaN: break; } + · ─── + ╰──── + help: 'case NaN' can never match. Use Number.isNaN before the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(NaN) {} + · ─── + ╰──── + help: 'switch(NaN)' can never match a case clause. Use Number.isNaN instead of the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(NaN) { case foo: break; } + · ─── + ╰──── + help: 'switch(NaN)' can never match a case clause. Use Number.isNaN instead of the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(NaN) { default: break; } + · ─── + ╰──── + help: 'switch(NaN)' can never match a case clause. Use Number.isNaN instead of the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(NaN) { case foo: break; default: break; } + · ─── + ╰──── + help: 'switch(NaN)' can never match a case clause. Use Number.isNaN instead of the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(foo) { case NaN: } + · ─── + ╰──── + help: 'case NaN' can never match. Use Number.isNaN before the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(foo) { case NaN: break; } + · ─── + ╰──── + help: 'case NaN' can never match. Use Number.isNaN before the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(foo) { case (NaN): break; } + · ───── + ╰──── + help: 'case NaN' can never match. Use Number.isNaN before the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(foo) { case bar: break; case NaN: break; default: break; } + · ─── + ╰──── + help: 'case NaN' can never match. Use Number.isNaN before the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(foo) { case bar: case NaN: default: break; } + · ─── + ╰──── + help: 'case NaN' can never match. Use Number.isNaN before the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(foo) { case bar: break; case NaN: break; case baz: break; case NaN: break; } + · ─── + ╰──── + help: 'case NaN' can never match. Use Number.isNaN before the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(foo) { case bar: break; case NaN: break; case baz: break; case NaN: break; } + · ─── + ╰──── + help: 'case NaN' can never match. Use Number.isNaN before the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(NaN) { case NaN: break; } + · ─── + ╰──── + help: 'switch(NaN)' can never match a case clause. Use Number.isNaN instead of the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(NaN) { case NaN: break; } + · ─── + ╰──── + help: 'case NaN' can never match. Use Number.isNaN before the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(Number.NaN) { case foo: break; } + · ────────── + ╰──── + help: 'switch(NaN)' can never match a case clause. Use Number.isNaN instead of the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(foo) { case Number.NaN: break; } + · ────────── + ╰──── + help: 'case NaN' can never match. Use Number.isNaN before the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(Number.NaN) { case foo: break; } + · ────────── + ╰──── + help: 'switch(NaN)' can never match a case clause. Use Number.isNaN instead of the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(foo) { case Number.NaN: break; } + · ────────── + ╰──── + help: 'case NaN' can never match. Use Number.isNaN before the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(Number.NaN) {} + · ────────── + ╰──── + help: 'switch(NaN)' can never match a case clause. Use Number.isNaN instead of the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(Number.NaN) { case foo: break; } + · ────────── + ╰──── + help: 'switch(NaN)' can never match a case clause. Use Number.isNaN instead of the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(Number.NaN) { default: break; } + · ────────── + ╰──── + help: 'switch(NaN)' can never match a case clause. Use Number.isNaN instead of the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(Number.NaN) { case foo: break; default: break; } + · ────────── + ╰──── + help: 'switch(NaN)' can never match a case clause. Use Number.isNaN instead of the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(foo) { case Number.NaN: } + · ────────── + ╰──── + help: 'case NaN' can never match. Use Number.isNaN before the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(foo) { case Number.NaN: break; } + · ────────── + ╰──── + help: 'case NaN' can never match. Use Number.isNaN before the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(foo) { case (Number.NaN): break; } + · ──────────── + ╰──── + help: 'case NaN' can never match. Use Number.isNaN before the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(foo) { case bar: break; case Number.NaN: break; default: break; } + · ────────── + ╰──── + help: 'case NaN' can never match. Use Number.isNaN before the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(foo) { case bar: case Number.NaN: default: break; } + · ────────── + ╰──── + help: 'case NaN' can never match. Use Number.isNaN before the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(foo) { case bar: break; case NaN: break; case baz: break; case Number.NaN: break; } + · ─── + ╰──── + help: 'case NaN' can never match. Use Number.isNaN before the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(foo) { case bar: break; case NaN: break; case baz: break; case Number.NaN: break; } + · ────────── + ╰──── + help: 'case NaN' can never match. Use Number.isNaN before the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(Number.NaN) { case Number.NaN: break; } + · ────────── + ╰──── + help: 'switch(NaN)' can never match a case clause. Use Number.isNaN instead of the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ switch(Number.NaN) { case Number.NaN: break; } + · ────────── + ╰──── + help: 'case NaN' can never match. Use Number.isNaN before the switch. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ foo.indexOf(NaN) + · ─── + ╰──── + help: Array prototype method 'indexOf' cannot find NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ foo.lastIndexOf(NaN) + · ─── + ╰──── + help: Array prototype method 'lastIndexOf' cannot find NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ foo['indexOf'](NaN) + · ─── + ╰──── + help: Array prototype method 'indexOf' cannot find NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ foo['lastIndexOf'](NaN) + · ─── + ╰──── + help: Array prototype method 'lastIndexOf' cannot find NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ foo().indexOf(NaN) + · ─── + ╰──── + help: Array prototype method 'indexOf' cannot find NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ foo.bar.lastIndexOf(NaN) + · ─── + ╰──── + help: Array prototype method 'lastIndexOf' cannot find NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ foo.indexOf?.(NaN) + · ─── + ╰──── + help: Array prototype method 'indexOf' cannot find NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ foo?.indexOf(NaN) + · ─── + ╰──── + help: Array prototype method 'indexOf' cannot find NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ (foo?.indexOf)(NaN) + · ─── + ╰──── + help: Array prototype method 'indexOf' cannot find NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ foo.indexOf(Number.NaN) + · ────────── + ╰──── + help: Array prototype method 'indexOf' cannot find NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ foo.lastIndexOf(Number.NaN) + · ────────── + ╰──── + help: Array prototype method 'lastIndexOf' cannot find NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ foo['indexOf'](Number.NaN) + · ────────── + ╰──── + help: Array prototype method 'indexOf' cannot find NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ foo['lastIndexOf'](Number.NaN) + · ────────── + ╰──── + help: Array prototype method 'lastIndexOf' cannot find NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ foo().indexOf(Number.NaN) + · ────────── + ╰──── + help: Array prototype method 'indexOf' cannot find NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ foo.bar.lastIndexOf(Number.NaN) + · ────────── + ╰──── + help: Array prototype method 'lastIndexOf' cannot find NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ foo.indexOf?.(Number.NaN) + · ────────── + ╰──── + help: Array prototype method 'indexOf' cannot find NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ foo?.indexOf(Number.NaN) + · ────────── + ╰──── + help: Array prototype method 'indexOf' cannot find NaN. + + ⚠ eslint(use-isnan): Requires calls to isNaN() when checking for NaN + ╭─[use_isnan.tsx:1:1] + 1 │ (foo?.indexOf)(Number.NaN) + · ────────── + ╰──── + help: Array prototype method 'indexOf' cannot find NaN. +