mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat(linter): implement eslint(eqeqeq) rule (#124)
* feat(linter): implement `eslint(eqeqeq)` rule * chore: improve comment * better errors * regenerate snapshots * add nursery lint category
This commit is contained in:
parent
b3dfb0ab51
commit
2423257573
3 changed files with 135 additions and 0 deletions
|
|
@ -1,3 +1,4 @@
|
|||
mod eq_eq_eq;
|
||||
mod for_direction;
|
||||
mod no_array_constructor;
|
||||
mod no_debugger;
|
||||
|
|
@ -8,6 +9,7 @@ mod deepscan {
|
|||
}
|
||||
|
||||
pub use deepscan::uninvoked_array_callback::UninvokedArrayCallback;
|
||||
pub use eq_eq_eq::EqEqEq;
|
||||
pub use for_direction::ForDirection;
|
||||
pub use no_array_constructor::NoArrayConstructor;
|
||||
pub use no_debugger::NoDebugger;
|
||||
|
|
@ -18,6 +20,7 @@ use crate::{context::LintContext, rule::Rule, rule::RuleMeta, AstNode};
|
|||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref RULES: Vec<RuleEnum> = vec![
|
||||
RuleEnum::EqEqEq(EqEqEq::default()),
|
||||
RuleEnum::NoDebugger(NoDebugger::default()),
|
||||
RuleEnum::NoEmpty(NoEmpty::default()),
|
||||
RuleEnum::NoArrayConstructor(NoArrayConstructor::default()),
|
||||
|
|
@ -30,6 +33,7 @@ lazy_static::lazy_static! {
|
|||
#[derive(Debug, Clone)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub enum RuleEnum {
|
||||
EqEqEq(EqEqEq),
|
||||
NoDebugger(NoDebugger),
|
||||
NoEmpty(NoEmpty),
|
||||
NoArrayConstructor(NoArrayConstructor),
|
||||
|
|
@ -41,6 +45,7 @@ pub enum RuleEnum {
|
|||
impl RuleEnum {
|
||||
pub const fn name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::EqEqEq(_) => EqEqEq::NAME,
|
||||
Self::NoDebugger(_) => NoDebugger::NAME,
|
||||
Self::NoEmpty(_) => NoEmpty::NAME,
|
||||
Self::NoArrayConstructor(_) => NoArrayConstructor::NAME,
|
||||
|
|
@ -52,6 +57,9 @@ impl RuleEnum {
|
|||
|
||||
pub fn read_json(&self, maybe_value: Option<serde_json::Value>) -> Self {
|
||||
match self {
|
||||
Self::EqEqEq(_) => {
|
||||
Self::EqEqEq(maybe_value.map(EqEqEq::from_configuration).unwrap_or_default())
|
||||
}
|
||||
Self::NoDebugger(_) => Self::NoDebugger(
|
||||
maybe_value.map(NoDebugger::from_configuration).unwrap_or_default(),
|
||||
),
|
||||
|
|
@ -75,6 +83,7 @@ impl RuleEnum {
|
|||
|
||||
pub fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
||||
match self {
|
||||
Self::EqEqEq(rule) => rule.run(node, ctx),
|
||||
Self::NoDebugger(rule) => rule.run(node, ctx),
|
||||
Self::NoEmpty(rule) => rule.run(node, ctx),
|
||||
Self::NoArrayConstructor(rule) => rule.run(node, ctx),
|
||||
|
|
|
|||
93
crates/oxc_linter/src/rules/eq_eq_eq.rs
Normal file
93
crates/oxc_linter/src/rules/eq_eq_eq.rs
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
use oxc_ast::{
|
||||
ast::{BinaryOperator, Expression, UnaryOperator},
|
||||
AstKind, Span,
|
||||
};
|
||||
use oxc_diagnostics::{
|
||||
miette::{self, Diagnostic},
|
||||
thiserror::Error,
|
||||
};
|
||||
use oxc_macros::declare_oxc_lint;
|
||||
|
||||
use crate::{autofix::Fix, context::LintContext, rule::Rule, AstNode};
|
||||
|
||||
#[derive(Debug, Error, Diagnostic)]
|
||||
#[error("eslint(eqeqeq): Expected {1:?} and instead saw {0:?}")]
|
||||
#[diagnostic(severity(warning), help("Prefer strict {1} operator"))]
|
||||
struct EqEqEqDiagnostic(&'static str, &'static str, #[label] pub Span);
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct EqEqEq;
|
||||
|
||||
declare_oxc_lint!(
|
||||
/// ### What it does
|
||||
/// Requires the use of the === and !== operators
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Using non-strict equality operators leads to hard to track bugs due to type coercion.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```javascript
|
||||
/// let a = []
|
||||
/// let b = false
|
||||
/// a == b
|
||||
/// ```
|
||||
EqEqEq,
|
||||
nursery
|
||||
);
|
||||
|
||||
fn to_strict_operator(operator: BinaryOperator) -> BinaryOperator {
|
||||
match operator {
|
||||
BinaryOperator::Equality => BinaryOperator::StrictEquality,
|
||||
BinaryOperator::Inequality => BinaryOperator::StrictInequality,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
impl Rule for EqEqEq {
|
||||
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
||||
let AstKind::BinaryExpression(binary_expr) = node.get().kind() else { return };
|
||||
if !matches!(binary_expr.operator, BinaryOperator::Equality | BinaryOperator::Inequality) {
|
||||
return;
|
||||
}
|
||||
|
||||
let is_valid_comparison = match (&binary_expr.left, &binary_expr.right) {
|
||||
(Expression::UnaryExpression(unary_expr), _)
|
||||
| (_, Expression::UnaryExpression(unary_expr)) => {
|
||||
matches!(unary_expr.operator, UnaryOperator::Typeof)
|
||||
}
|
||||
(lhs, rhs) => {
|
||||
(lhs.is_null() || rhs.is_null())
|
||||
|| lhs.is_literal_expression() && rhs.is_literal_expression()
|
||||
}
|
||||
};
|
||||
|
||||
if !is_valid_comparison {
|
||||
let operator = binary_expr.operator.as_str();
|
||||
let prefered_operator = to_strict_operator(binary_expr.operator).as_str();
|
||||
ctx.diagnostic(EqEqEqDiagnostic(operator, prefered_operator, binary_expr.span));
|
||||
ctx.fix(Fix::new(prefered_operator, binary_expr.span));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
use crate::tester::Tester;
|
||||
|
||||
let pass = vec![
|
||||
("typeof foo == 'undefined'", None),
|
||||
("'hello' != 'world'", None),
|
||||
("0 == 0", None),
|
||||
("true == true", None),
|
||||
("foo == null", None),
|
||||
];
|
||||
|
||||
let fail = vec![
|
||||
("a == b", None),
|
||||
("foo == true", None),
|
||||
("bananas != 1", None),
|
||||
("value == undefined", None),
|
||||
];
|
||||
|
||||
Tester::new(EqEqEq::NAME, pass, fail).test_and_snapshot();
|
||||
}
|
||||
33
crates/oxc_linter/src/snapshots/eq_eq_eq.snap
Normal file
33
crates/oxc_linter/src/snapshots/eq_eq_eq.snap
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
source: crates/oxc_linter/src/tester.rs
|
||||
expression: eq_eq_eq
|
||||
---
|
||||
|
||||
⚠ eslint(eqeqeq): Expected "===" and instead saw "=="
|
||||
╭─[eq_eq_eq.tsx:1:1]
|
||||
1 │ a == b
|
||||
· ──────
|
||||
╰────
|
||||
help: Prefer strict === operator
|
||||
|
||||
⚠ eslint(eqeqeq): Expected "===" and instead saw "=="
|
||||
╭─[eq_eq_eq.tsx:1:1]
|
||||
1 │ foo == true
|
||||
· ───────────
|
||||
╰────
|
||||
help: Prefer strict === operator
|
||||
|
||||
⚠ eslint(eqeqeq): Expected "!==" and instead saw "!="
|
||||
╭─[eq_eq_eq.tsx:1:1]
|
||||
1 │ bananas != 1
|
||||
· ────────────
|
||||
╰────
|
||||
help: Prefer strict !== operator
|
||||
|
||||
⚠ eslint(eqeqeq): Expected "===" and instead saw "=="
|
||||
╭─[eq_eq_eq.tsx:1:1]
|
||||
1 │ value == undefined
|
||||
· ──────────────────
|
||||
╰────
|
||||
help: Prefer strict === operator
|
||||
|
||||
Loading…
Reference in a new issue