diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index abcfbaeec9607..d52689596b7fd 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -260,6 +260,7 @@ mod unicorn { pub mod no_lonely_if; pub mod no_magic_array_flat_depth; pub mod no_negated_condition; + pub mod no_negation_in_equality_check; pub mod no_nested_ternary; pub mod no_new_array; pub mod no_new_buffer; @@ -619,6 +620,7 @@ oxc_macros::declare_all_lint_rules! { unicorn::no_lonely_if, unicorn::no_magic_array_flat_depth, unicorn::no_negated_condition, + unicorn::no_negation_in_equality_check, unicorn::no_nested_ternary, unicorn::no_new_array, unicorn::no_new_buffer, diff --git a/crates/oxc_linter/src/rules/unicorn/no_negation_in_equality_check.rs b/crates/oxc_linter/src/rules/unicorn/no_negation_in_equality_check.rs new file mode 100644 index 0000000000000..1ea39e6646571 --- /dev/null +++ b/crates/oxc_linter/src/rules/unicorn/no_negation_in_equality_check.rs @@ -0,0 +1,135 @@ +use oxc_ast::{ast::Expression, AstKind}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; +use oxc_syntax::operator::{BinaryOperator, UnaryOperator}; + +use crate::{context::LintContext, rule::Rule, AstNode}; + +fn no_negation_in_equality_check_diagnostic( + span0: Span, + current_operator: BinaryOperator, + suggested_operator: BinaryOperator, +) -> OxcDiagnostic { + OxcDiagnostic::warn( + "eslint-plugin-unicorn(no-negation-in-equality-check): Negated expression is not allowed in equality check.", + ) + .with_help(format!("Remove the negation operator and use '{}' instead of '{}'.", suggested_operator.as_str(), current_operator.as_str())) + .with_label(span0) +} + +#[derive(Debug, Default, Clone)] +pub struct NoNegationInEqualityCheck; + +declare_oxc_lint!( + /// ### What it does + /// + /// Disallow negated expressions on the left of (in)equality checks. + /// + /// ### Why is this bad? + /// + /// A negated expression on the left of an (in)equality check is likely a mistake from trying to negate the whole condition. + /// + /// ### Example + /// ```javascript + /// // Bad + /// + /// if (!foo === bar) {} + /// + /// if (!foo !== bar) {} + /// + /// // Good + /// + /// if (foo !== bar) {} + /// + /// if (!(foo === bar)) {} + /// ``` + NoNegationInEqualityCheck, + correctness, +); + +impl Rule for NoNegationInEqualityCheck { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + if let AstKind::BinaryExpression(binary_expr) = node.kind() { + let Expression::UnaryExpression(left_unary_expr) = &binary_expr.left else { + return; + }; + + if left_unary_expr.operator != UnaryOperator::LogicalNot { + return; + } + + if let Expression::UnaryExpression(left_nested_unary_expr) = &left_unary_expr.argument { + if left_nested_unary_expr.operator == UnaryOperator::LogicalNot { + return; + } + } + + if !binary_expr.operator.is_equality() { + return; + } + + let Some(suggested_operator) = binary_expr.operator.equality_inverse_operator() else { + return; + }; + + ctx.diagnostic(no_negation_in_equality_check_diagnostic( + left_unary_expr.span, + binary_expr.operator, + suggested_operator, + )); + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + "!foo instanceof bar", + "+foo === bar", + "!(foo === bar)", + "!!foo === bar", + "!!!foo === bar", + "foo === !bar", + ]; + + let fail = vec![ + "!foo === bar", + "!foo !== bar", + "!foo == bar", + "!foo != bar", + " + function x() { + return!foo === bar; + } + ", + " + function x() { + return! + foo === bar; + throw! + foo === bar; + } + ", + " + foo + !(a) === b + ", + " + foo + ![a, b].join('') === c + ", + " + foo + ! [a, b].join('') === c + ", + " + foo + !/* comment */[a, b].join('') === c + ", + ]; + + Tester::new(NoNegationInEqualityCheck::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/no_negation_in_equality_check.snap b/crates/oxc_linter/src/snapshots/no_negation_in_equality_check.snap new file mode 100644 index 0000000000000..54037e54510be --- /dev/null +++ b/crates/oxc_linter/src/snapshots/no_negation_in_equality_check.snap @@ -0,0 +1,93 @@ +--- +source: crates/oxc_linter/src/tester.rs +--- + ⚠ eslint-plugin-unicorn(no-negation-in-equality-check): Negated expression is not allowed in equality check. + ╭─[no_negation_in_equality_check.tsx:1:1] + 1 │ !foo === bar + · ──── + ╰──── + help: Remove the negation operator and use '!==' instead of '==='. + + ⚠ eslint-plugin-unicorn(no-negation-in-equality-check): Negated expression is not allowed in equality check. + ╭─[no_negation_in_equality_check.tsx:1:1] + 1 │ !foo !== bar + · ──── + ╰──── + help: Remove the negation operator and use '===' instead of '!=='. + + ⚠ eslint-plugin-unicorn(no-negation-in-equality-check): Negated expression is not allowed in equality check. + ╭─[no_negation_in_equality_check.tsx:1:1] + 1 │ !foo == bar + · ──── + ╰──── + help: Remove the negation operator and use '!=' instead of '=='. + + ⚠ eslint-plugin-unicorn(no-negation-in-equality-check): Negated expression is not allowed in equality check. + ╭─[no_negation_in_equality_check.tsx:1:1] + 1 │ !foo != bar + · ──── + ╰──── + help: Remove the negation operator and use '==' instead of '!='. + + ⚠ eslint-plugin-unicorn(no-negation-in-equality-check): Negated expression is not allowed in equality check. + ╭─[no_negation_in_equality_check.tsx:3:14] + 2 │ function x() { + 3 │ return!foo === bar; + · ──── + 4 │ } + ╰──── + help: Remove the negation operator and use '!==' instead of '==='. + + ⚠ eslint-plugin-unicorn(no-negation-in-equality-check): Negated expression is not allowed in equality check. + ╭─[no_negation_in_equality_check.tsx:3:14] + 2 │ function x() { + 3 │ ╭─▶ return! + 4 │ ╰─▶ foo === bar; + 5 │ throw! + ╰──── + help: Remove the negation operator and use '!==' instead of '==='. + + ⚠ eslint-plugin-unicorn(no-negation-in-equality-check): Negated expression is not allowed in equality check. + ╭─[no_negation_in_equality_check.tsx:5:13] + 4 │ foo === bar; + 5 │ ╭─▶ throw! + 6 │ ╰─▶ foo === bar; + 7 │ } + ╰──── + help: Remove the negation operator and use '!==' instead of '==='. + + ⚠ eslint-plugin-unicorn(no-negation-in-equality-check): Negated expression is not allowed in equality check. + ╭─[no_negation_in_equality_check.tsx:3:7] + 2 │ foo + 3 │ !(a) === b + · ──── + 4 │ + ╰──── + help: Remove the negation operator and use '!==' instead of '==='. + + ⚠ eslint-plugin-unicorn(no-negation-in-equality-check): Negated expression is not allowed in equality check. + ╭─[no_negation_in_equality_check.tsx:3:7] + 2 │ foo + 3 │ ![a, b].join('') === c + · ──────────────── + 4 │ + ╰──── + help: Remove the negation operator and use '!==' instead of '==='. + + ⚠ eslint-plugin-unicorn(no-negation-in-equality-check): Negated expression is not allowed in equality check. + ╭─[no_negation_in_equality_check.tsx:3:7] + 2 │ foo + 3 │ ! [a, b].join('') === c + · ───────────────── + 4 │ + ╰──── + help: Remove the negation operator and use '!==' instead of '==='. + + ⚠ eslint-plugin-unicorn(no-negation-in-equality-check): Negated expression is not allowed in equality check. + ╭─[no_negation_in_equality_check.tsx:3:7] + 2 │ foo + 3 │ !/* comment */[a, b].join('') === c + · ───────────────────────────── + 4 │ + ╰──── + help: Remove the negation operator and use '!==' instead of '==='.