feat(linter): Implement eslint:yoda (#7559)

In this PR, implement
[eslint:yoda](https://eslint.org/docs/latest/rules/yoda)

ref: https://github.com/oxc-project/oxc/issues/479
This commit is contained in:
tbashiyy 2024-12-05 00:58:21 +09:00 committed by GitHub
parent 690fc54cdb
commit bd9d38a9a0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 1807 additions and 7 deletions

View file

@ -502,6 +502,7 @@ impl<'a> ComputedMemberExpression<'a> {
{
Some(lit.quasis[0].value.raw.clone())
}
Expression::RegExpLiteral(lit) => Some(Atom::from(lit.raw)),
_ => None,
}
}

View file

@ -148,6 +148,7 @@ mod eslint {
pub mod unicode_bom;
pub mod use_isnan;
pub mod valid_typeof;
pub mod yoda;
}
mod typescript {
@ -640,6 +641,7 @@ oxc_macros::declare_all_lint_rules! {
eslint::unicode_bom,
eslint::use_isnan,
eslint::valid_typeof,
eslint::yoda,
import::default,
import::export,
import::first,

File diff suppressed because it is too large Load diff

View file

@ -299,7 +299,6 @@ fn test() {
"foo.slice(foo.length - 1 / 1)",
"[1, 2, 3].slice([1, 2, 3].length - 1)",
"foo[bar++].slice(foo[bar++].length - 1)",
"foo[`${bar}`].slice(foo[`${bar}`].length - 1)",
"function foo() {return [].slice.apply(arguments);}",
"String.prototype.toSpliced.call(foo, foo.length - 1)",
"String.prototype.with.call(foo, foo.length - 1)",

View file

@ -0,0 +1,599 @@
---
source: crates/oxc_linter/src/tester.rs
assertion_line: 356
snapshot_kind: text
---
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (x <= 'foo' || 'bar' < x) {}
· ──────────
╰────
help: Expected literal to be on the left side of <=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if ("red" == value) {}
· ──────────────
╰────
help: Expected literal to be on the right side of ==.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (true === value) {}
· ──────────────
╰────
help: Expected literal to be on the right side of ===.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (5 != value) {}
· ──────────
╰────
help: Expected literal to be on the right side of !=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (5n != value) {}
· ───────────
╰────
help: Expected literal to be on the right side of !=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (null !== value) {}
· ──────────────
╰────
help: Expected literal to be on the right side of !==.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if ("red" <= value) {}
· ──────────────
╰────
help: Expected literal to be on the right side of <=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (`red` <= value) {}
· ──────────────
╰────
help: Expected literal to be on the right side of <=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (`red` <= `${foo}`) {}
· ─────────────────
╰────
help: Expected literal to be on the right side of <=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (`red` <= `${"red"}`) {}
· ───────────────────
╰────
help: Expected literal to be on the right side of <=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (true >= value) {}
· ─────────────
╰────
help: Expected literal to be on the right side of >=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:12]
1 │ var foo = (5 < value) ? true : false
· ─────────
╰────
help: Expected literal to be on the right side of <.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:26]
1 │ function foo() { return (null > value); }
· ────────────
╰────
help: Expected literal to be on the right side of >.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (-1 < str.indexOf(substr)) {}
· ────────────────────────
╰────
help: Expected literal to be on the right side of <.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (value == "red") {}
· ──────────────
╰────
help: Expected literal to be on the left side of ==.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (value == `red`) {}
· ──────────────
╰────
help: Expected literal to be on the left side of ==.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (value === true) {}
· ──────────────
╰────
help: Expected literal to be on the left side of ===.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (value === 5n) {}
· ────────────
╰────
help: Expected literal to be on the left side of ===.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (`${"red"}` <= `red`) {}
· ───────────────────
╰────
help: Expected literal to be on the left side of <=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:14]
1 │ if (a < 0 && 0 <= b && b < 1) {}
· ──────
╰────
help: Expected literal to be on the right side of <=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (0 <= a && a < 1 && b < 1) {}
· ──────
╰────
help: Expected literal to be on the right side of <=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (1 < a && a < 0) {}
· ─────
╰────
help: Expected literal to be on the right side of <.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:1]
1 │ 0 < a && a < 1
· ─────
╰────
help: Expected literal to be on the right side of <.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:18]
1 │ var a = b < 0 || 1 <= b;
· ──────
╰────
help: Expected literal to be on the right side of <=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (0 <= x && x < -1) {}
· ──────
╰────
help: Expected literal to be on the right side of <=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:10]
1 │ var a = (b < 0 && 0 <= b);
· ─────
╰────
help: Expected literal to be on the left side of <.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:10]
1 │ var a = (b < `0` && `0` <= b);
· ───────
╰────
help: Expected literal to be on the left side of <.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (`green` < x.y && x.y < `blue`) {}
· ─────────────
╰────
help: Expected literal to be on the right side of <.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (0 <= a[b] && a['b'] < 1) {}
· ─────────
╰────
help: Expected literal to be on the right side of <=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (0 <= a[b] && a[`b`] < 1) {}
· ─────────
╰────
help: Expected literal to be on the right side of <=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (`0` <= a[b] && a[`b`] < `1`) {}
· ───────────
╰────
help: Expected literal to be on the right side of <=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (0 <= a[b] && a.b < 1) {}
· ─────────
╰────
help: Expected literal to be on the right side of <=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (0 <= a[''] && a.b < 1) {}
· ──────────
╰────
help: Expected literal to be on the right side of <=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (0 <= a[''] && a[' '] < 1) {}
· ──────────
╰────
help: Expected literal to be on the right side of <=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (0 <= a[''] && a[null] < 1) {}
· ──────────
╰────
help: Expected literal to be on the right side of <=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (0 <= a[``] && a[null] < 1) {}
· ──────────
╰────
help: Expected literal to be on the right side of <=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (0 <= a[''] && a[b] < 1) {}
· ──────────
╰────
help: Expected literal to be on the right side of <=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (0 <= a[''] && a[b()] < 1) {}
· ──────────
╰────
help: Expected literal to be on the right side of <=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (0 <= a[``] && a[b()] < 1) {}
· ──────────
╰────
help: Expected literal to be on the right side of <=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (0 <= a[b()] && a[b()] < 1) {}
· ───────────
╰────
help: Expected literal to be on the right side of <=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (0 <= a.null && a[/(?<zero>0)/] <= 1) {}
· ───────────
╰────
help: Expected literal to be on the right side of <=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (3 == a) {}
· ──────
╰────
help: Expected literal to be on the right side of ==.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ foo(3 === a);
· ───────
╰────
help: Expected literal to be on the right side of ===.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ foo(a === 3);
· ───────
╰────
help: Expected literal to be on the left side of ===.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ foo(a === `3`);
· ─────────
╰────
help: Expected literal to be on the left side of ===.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (0 <= x && x < 1) {}
· ──────
╰────
help: Expected literal to be on the right side of <=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:14]
1 │ if ( /* a */ 0 /* b */ < /* c */ foo /* d */ ) {}
· ───────────────────────
╰────
help: Expected literal to be on the right side of <.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:14]
1 │ if ( /* a */ foo /* b */ > /* c */ 0 /* d */ ) {}
· ───────────────────────
╰────
help: Expected literal to be on the left side of >.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (foo()===1) {}
· ─────────
╰────
help: Expected literal to be on the left side of ===.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (foo() === 1) {}
· ───────────────
╰────
help: Expected literal to be on the left side of ===.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:8]
1 │ while (0 === (a));
· ─────────
╰────
help: Expected literal to be on the right side of ===.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:8]
1 │ while (0 === (a = b));
· ─────────────
╰────
help: Expected literal to be on the right side of ===.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:8]
1 │ while ((a) === 0);
· ─────────
╰────
help: Expected literal to be on the left side of ===.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:8]
1 │ while ((a = b) === 0);
· ─────────────
╰────
help: Expected literal to be on the left side of ===.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (((((((((((foo)))))))))) === ((((((5)))))));
· ─────────────────────────────────────────
╰────
help: Expected literal to be on the left side of ===.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:24]
1 │ function *foo() { yield(1) < a }
· ───────
╰────
help: Expected literal to be on the right side of <.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:24]
1 │ function *foo() { yield((1)) < a }
· ─────────
╰────
help: Expected literal to be on the right side of <.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:25]
1 │ function *foo() { yield 1 < a }
· ─────
╰────
help: Expected literal to be on the right side of <.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:28]
1 │ function *foo() { yield/**/1 < a }
· ─────
╰────
help: Expected literal to be on the right side of <.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:24]
1 │ function *foo() { yield(1) < ++a }
· ─────────
╰────
help: Expected literal to be on the right side of <.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:24]
1 │ function *foo() { yield(1) < (a) }
· ─────────
╰────
help: Expected literal to be on the right side of <.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:3]
1 │ x=1 < a
· ─────
╰────
help: Expected literal to be on the right side of <.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:24]
1 │ function *foo() { yield++a < 1 }
· ───────
╰────
help: Expected literal to be on the left side of <.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:24]
1 │ function *foo() { yield(a) < 1 }
· ───────
╰────
help: Expected literal to be on the left side of <.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:25]
1 │ function *foo() { yield a < 1 }
· ─────
╰────
help: Expected literal to be on the left side of <.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:28]
1 │ function *foo() { yield/**/a < 1 }
· ─────
╰────
help: Expected literal to be on the left side of <.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:24]
1 │ function *foo() { yield++a < (1) }
· ─────────
╰────
help: Expected literal to be on the left side of <.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:3]
1 │ x=a < 1
· ─────
╰────
help: Expected literal to be on the left side of <.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:1]
1 │ 0 < f()in obj
· ───────
╰────
help: Expected literal to be on the right side of <.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:1]
1 │ 1 > x++instanceof foo
· ───────
╰────
help: Expected literal to be on the right side of >.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:1]
1 │ x < ('foo')in bar
· ───────────
╰────
help: Expected literal to be on the left side of <.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:1]
1 │ false <= ((x))in foo
· ──────────────
╰────
help: Expected literal to be on the right side of <=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:1]
1 │ x >= (1)instanceof foo
· ────────
╰────
help: Expected literal to be on the left side of >=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:1]
1 │ false <= ((x)) in foo
· ──────────────
╰────
help: Expected literal to be on the right side of <=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:1]
1 │ x >= 1 instanceof foo
· ──────
╰────
help: Expected literal to be on the left side of >=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:1]
1 │ x >= 1/**/instanceof foo
· ──────
╰────
help: Expected literal to be on the left side of >=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:2]
1 │ (x >= 1)instanceof foo
· ──────
╰────
help: Expected literal to be on the left side of >=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:1]
1 │ (x) >= (1)instanceof foo
· ──────────
╰────
help: Expected literal to be on the left side of >=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:1]
1 │ 1 > x===foo
· ─────
╰────
help: Expected literal to be on the right side of >.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:1]
1 │ 1 > x
· ─────
╰────
help: Expected literal to be on the right side of >.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:22]
1 │ if (`green` < x.y && x.y < `blue`) {}
· ────────────
╰────
help: Expected literal to be on the left side of <.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:16]
1 │ if('a' <= x && x < 'b') {}
· ───────
╰────
help: Expected literal to be on the left side of <.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if ('b' <= x && x < 'a') {}
· ────────
╰────
help: Expected literal to be on the right side of <=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:4]
1 │ if('a' <= x && x < 1) {}
· ────────
╰────
help: Expected literal to be on the right side of <=.
⚠ eslint(yoda): Require or disallow "Yoda" conditions
╭─[yoda.tsx:1:5]
1 │ if (0 < a && b < max) {}
· ─────
╰────
help: Expected literal to be on the right side of <.

View file

@ -7,6 +7,7 @@ use oxc_ast::{
AstKind,
};
use oxc_semantic::AstNode;
use oxc_span::cmp::ContentEq;
use oxc_syntax::operator::LogicalOperator;
pub use self::boolean::*;
@ -177,6 +178,20 @@ pub fn is_same_reference(left: &Expression, right: &Expression, ctx: &LintContex
(Expression::StringLiteral(left_str), Expression::StringLiteral(right_str)) => {
return left_str.value == right_str.value;
}
(Expression::StringLiteral(string_lit), Expression::TemplateLiteral(template_lit))
| (Expression::TemplateLiteral(template_lit), Expression::StringLiteral(string_lit)) => {
return template_lit.is_no_substitution_template()
&& string_lit.value == template_lit.quasi().unwrap();
}
(Expression::TemplateLiteral(left_str), Expression::TemplateLiteral(right_str)) => {
return left_str.quasis.content_eq(&right_str.quasis)
&& left_str.expressions.len() == right_str.expressions.len()
&& left_str
.expressions
.iter()
.zip(right_str.expressions.iter())
.all(|(left, right)| is_same_reference(left, right, ctx));
}
(Expression::NumericLiteral(left_num), Expression::NumericLiteral(right_num)) => {
return left_num.raw == right_num.raw;
}
@ -266,12 +281,35 @@ pub fn is_same_member_expression(
MemberExpression::ComputedMemberExpression(right),
) = (left, right)
{
if !is_same_reference(
left.expression.get_inner_expression(),
right.expression.get_inner_expression(),
ctx,
) {
return false;
// TODO(camc314): refactor this to go through `is_same_reference` and introduce some sort of `context` to indicate how the two values should be compared.
match (&left.expression, &right.expression) {
// x['/regex/'] === x[/regex/]
// x[/regex/] === x['/regex/']
(Expression::StringLiteral(string_lit), Expression::RegExpLiteral(regex_lit))
| (Expression::RegExpLiteral(regex_lit), Expression::StringLiteral(string_lit)) => {
if string_lit.value != regex_lit.raw {
return false;
}
}
// ex) x[`/regex/`] === x[/regex/]
// ex) x[/regex/] === x[`/regex/`]
(Expression::TemplateLiteral(template_lit), Expression::RegExpLiteral(regex_lit))
| (Expression::RegExpLiteral(regex_lit), Expression::TemplateLiteral(template_lit)) => {
if !(template_lit.is_no_substitution_template()
&& template_lit.quasi().unwrap() == regex_lit.raw)
{
return false;
}
}
_ => {
if !is_same_reference(
left.expression.get_inner_expression(),
right.expression.get_inner_expression(),
ctx,
) {
return false;
}
}
}
}