diff --git a/crates/swc/tests/errors/lints/no-compare-neg-zero/default/.swcrc b/crates/swc/tests/errors/lints/no-compare-neg-zero/default/.swcrc new file mode 100644 index 00000000000..3cc056ea5fe --- /dev/null +++ b/crates/swc/tests/errors/lints/no-compare-neg-zero/default/.swcrc @@ -0,0 +1,9 @@ +{ + "jsc": { + "lints": { + "no-compare-neg-zero": [ + "error" + ] + } + } +} \ No newline at end of file diff --git a/crates/swc/tests/errors/lints/no-compare-neg-zero/default/input.js b/crates/swc/tests/errors/lints/no-compare-neg-zero/default/input.js new file mode 100644 index 00000000000..38387a2e0d3 --- /dev/null +++ b/crates/swc/tests/errors/lints/no-compare-neg-zero/default/input.js @@ -0,0 +1,16 @@ +x === -0; +-0 === x; +x == -0; +-0 == x; +x > -0; +-0 > x; +x >= -0; +-0 >= x; +x < -0; +-0 < x; +x <= -0; +-0 <= x; +-0 === -0 + +x == '-0' +Object.is(x, -0) diff --git a/crates/swc/tests/errors/lints/no-compare-neg-zero/default/output.swc-stderr b/crates/swc/tests/errors/lints/no-compare-neg-zero/default/output.swc-stderr new file mode 100644 index 00000000000..44ae85c6fc9 --- /dev/null +++ b/crates/swc/tests/errors/lints/no-compare-neg-zero/default/output.swc-stderr @@ -0,0 +1,78 @@ + + x Do not use the '===' to compare against -0 + ,---- + 1 | x === -0; + : ^^^^^^^^ + `---- + + x Do not use the '===' to compare against -0 + ,---- + 2 | -0 === x; + : ^^^^^^^^ + `---- + + x Do not use the '==' to compare against -0 + ,---- + 3 | x == -0; + : ^^^^^^^ + `---- + + x Do not use the '==' to compare against -0 + ,---- + 4 | -0 == x; + : ^^^^^^^ + `---- + + x Do not use the '>' to compare against -0 + ,---- + 5 | x > -0; + : ^^^^^^ + `---- + + x Do not use the '>' to compare against -0 + ,---- + 6 | -0 > x; + : ^^^^^^ + `---- + + x Do not use the '>=' to compare against -0 + ,---- + 7 | x >= -0; + : ^^^^^^^ + `---- + + x Do not use the '>=' to compare against -0 + ,---- + 8 | -0 >= x; + : ^^^^^^^ + `---- + + x Do not use the '<' to compare against -0 + ,---- + 9 | x < -0; + : ^^^^^^ + `---- + + x Do not use the '<' to compare against -0 + ,---- + 10 | -0 < x; + : ^^^^^^ + `---- + + x Do not use the '<=' to compare against -0 + ,---- + 11 | x <= -0; + : ^^^^^^^ + `---- + + x Do not use the '<=' to compare against -0 + ,---- + 12 | -0 <= x; + : ^^^^^^^ + `---- + + x Do not use the '===' to compare against -0 + ,---- + 13 | -0 === -0 + : ^^^^^^^^^ + `---- diff --git a/crates/swc_ecma_lints/src/config.rs b/crates/swc_ecma_lints/src/config.rs index 0f67faede41..efbf8e4276c 100644 --- a/crates/swc_ecma_lints/src/config.rs +++ b/crates/swc_ecma_lints/src/config.rs @@ -191,4 +191,8 @@ pub struct LintConfig { #[cfg(feature = "non_critical_lints")] #[serde(default, alias = "preferConst")] pub prefer_const: RuleConfig<()>, + + #[cfg(feature = "non_critical_lints")] + #[serde(default, alias = "noCompareNegZero")] + pub no_compare_neg_zero: RuleConfig<()>, } diff --git a/crates/swc_ecma_lints/src/rules/mod.rs b/crates/swc_ecma_lints/src/rules/mod.rs index cbebc1fbb69..f0dcb67b4be 100644 --- a/crates/swc_ecma_lints/src/rules/mod.rs +++ b/crates/swc_ecma_lints/src/rules/mod.rs @@ -20,6 +20,7 @@ pub(crate) mod non_critical_lints { pub mod eqeqeq; pub mod no_alert; pub mod no_bitwise; + pub mod no_compare_neg_zero; pub mod no_console; pub mod no_debugger; pub mod no_empty_function; @@ -166,6 +167,10 @@ pub fn all(lint_params: LintParams) -> Vec> { rules.extend(no_var::no_var(&lint_config.no_var)); rules.extend(prefer_const::prefer_const(&lint_config.prefer_const)); + + rules.extend(no_compare_neg_zero::no_compare_neg_zero( + &lint_config.no_compare_neg_zero, + )); } rules diff --git a/crates/swc_ecma_lints/src/rules/no_compare_neg_zero.rs b/crates/swc_ecma_lints/src/rules/no_compare_neg_zero.rs new file mode 100644 index 00000000000..c12d0e50e9b --- /dev/null +++ b/crates/swc_ecma_lints/src/rules/no_compare_neg_zero.rs @@ -0,0 +1,84 @@ +use swc_common::{errors::HANDLER, Span}; +use swc_ecma_ast::*; +use swc_ecma_visit::{Visit, VisitWith}; + +use crate::{ + config::{LintRuleReaction, RuleConfig}, + rule::{visitor_rule, Rule}, + rules::utils::unwrap_seqs_and_parens, +}; + +pub fn no_compare_neg_zero(config: &RuleConfig<()>) -> Option> { + let rule_reaction = config.get_rule_reaction(); + + match rule_reaction { + LintRuleReaction::Off => None, + _ => Some(visitor_rule(NoCompareNegZero::new(rule_reaction))), + } +} + +#[derive(Debug, Default)] +struct NoCompareNegZero { + expected_reaction: LintRuleReaction, +} + +impl NoCompareNegZero { + fn new(expected_reaction: LintRuleReaction) -> Self { + Self { expected_reaction } + } + + fn emit_report(&self, op: BinaryOp, span: Span) { + let message = format!("Do not use the '{}' to compare against -0", op); + + HANDLER.with(|handler| match self.expected_reaction { + LintRuleReaction::Error => { + handler.struct_span_err(span, &message).emit(); + } + LintRuleReaction::Warning => { + handler.struct_span_warn(span, &message).emit(); + } + _ => {} + }); + } + + fn is_neg_zero(&self, expr: &Expr) -> bool { + if let Expr::Unary(UnaryExpr { + op: op!(unary, "-"), + arg, + .. + }) = unwrap_seqs_and_parens(expr) + { + if let Expr::Lit(Lit::Num(Number { value, .. })) = unwrap_seqs_and_parens(arg.as_ref()) + { + return *value == 0f64; + } + } + + false + } + + fn check(&self, bin_expr: &BinExpr) { + if let op!("===") + | op!("==") + | op!("!==") + | op!("!=") + | op!("<") + | op!("<=") + | op!(">") + | op!(">=") = bin_expr.op + { + if self.is_neg_zero(bin_expr.left.as_ref()) || self.is_neg_zero(bin_expr.right.as_ref()) + { + self.emit_report(bin_expr.op, bin_expr.span); + } + } + } +} + +impl Visit for NoCompareNegZero { + fn visit_bin_expr(&mut self, bin_expr: &BinExpr) { + self.check(bin_expr); + + bin_expr.visit_children_with(self); + } +}