From fb1de8faeab73b9ad5116c4a3b885e7ab5231b0d Mon Sep 17 00:00:00 2001 From: tbashiyy Date: Sun, 1 Dec 2024 15:09:34 +0900 Subject: [PATCH 1/4] impl eslint:yoda --- crates/oxc_ast/src/ast_impl/js.rs | 1 + crates/oxc_linter/src/rules.rs | 2 + crates/oxc_linter/src/rules/eslint/yoda.rs | 1160 +++++++++++++++++ .../rules/unicorn/prefer_negative_index.rs | 1 - .../oxc_linter/src/snapshots/eslint_yoda.snap | 599 +++++++++ crates/oxc_linter/src/utils/unicorn.rs | 74 +- 6 files changed, 1830 insertions(+), 7 deletions(-) create mode 100644 crates/oxc_linter/src/rules/eslint/yoda.rs create mode 100644 crates/oxc_linter/src/snapshots/eslint_yoda.snap diff --git a/crates/oxc_ast/src/ast_impl/js.rs b/crates/oxc_ast/src/ast_impl/js.rs index 9a4f86e899f5e..31968f3f4d4d4 100644 --- a/crates/oxc_ast/src/ast_impl/js.rs +++ b/crates/oxc_ast/src/ast_impl/js.rs @@ -502,6 +502,7 @@ impl<'a> ComputedMemberExpression<'a> { { Some(lit.quasis[0].value.raw.clone()) } + Expression::RegExpLiteral(lit) => Some(Atom::from(lit.raw)), _ => None, } } diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 5736533ae68d9..3326856202a63 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -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, diff --git a/crates/oxc_linter/src/rules/eslint/yoda.rs b/crates/oxc_linter/src/rules/eslint/yoda.rs new file mode 100644 index 0000000000000..8a1fe4146a7e7 --- /dev/null +++ b/crates/oxc_linter/src/rules/eslint/yoda.rs @@ -0,0 +1,1160 @@ +use oxc_ast::{ + ast::{ + BinaryExpression, BinaryOperator, Expression, LogicalExpression, LogicalOperator, + UnaryOperator, + }, + AstKind, +}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_ecmascript::ToBigInt; +use oxc_macros::declare_oxc_lint; +use oxc_span::{GetSpan, Span}; +use regex::Regex; + +use crate::{context::LintContext, rule::Rule, utils::is_same_reference, AstNode}; + +fn yoda_diagnostic(span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("Require or disallow \"Yoda\" conditions") + .with_help("Expected literal to be on the opposite side of the operator.") + .with_label(span) +} + +#[derive(Debug, Default, Clone)] +pub struct Yoda { + never: bool, + except_range: bool, + only_equality: bool, +} + +declare_oxc_lint!( + /// ## What it does + /// + /// Require or disallow "Yoda" conditions. + /// This rule aims to enforce consistent style of conditions which compare a variable to a literal value. The literal value must always come first in the comparison. + /// + /// ## Why is this bad? + /// + /// Yoda conditions are so named because the literal value of the condition comes first while the variable comes second. For example, the following is a Yoda condition: + /// ```js + /// if ("red" === color) { + // // ... + /// } + /// ``` + /// This is called a Yoda condition because it reads as, "if red equals the color", similar to the way the Star Wars character Yoda speaks. Compare to the other way of arranging the operands: + /// ```js + /// if (color === "red") { + /// // ... + /// } + /// ``` + /// This typically reads, "if the color equals red", which is arguably a more natural way to describe the comparison. + /// Proponents of Yoda conditions highlight that it is impossible to mistakenly use `=` instead of `==` because you cannot assign to a literal value. Doing so will cause a syntax error and you will be informed of the mistake early on. This practice was therefore very common in early programming where tools were not yet available. + /// Opponents of Yoda conditions point out that tooling has made us better programmers because tools will catch the mistaken use of `=` instead of `==` (ESLint will catch this for you). Therefore, they argue, the utility of the pattern doesn't outweigh the readability hit the code takes while using Yoda conditions. + /// + /// ## Options + /// This rule can take a string option: + /// * If it is the default `"never"`, then comparisons must never be Yoda conditions. + /// * If it is `"always"`, then the literal value must always come first. + /// The default `"never"` option can have exception options in an object literal: + /// * If the `"exceptRange"` property is `true`, the rule *allows* yoda conditions in range comparisons which are wrapped directly in parentheses, including the parentheses of an `if` or `while` condition. The default value is `false`. A *range* comparison tests whether a variable is inside or outside the range between two literal values. + /// * If the `"onlyEquality"` property is `true`, the rule reports yoda conditions *only* for the equality operators `==` and `===`. The default value is `false`. + /// The `onlyEquality` option allows a superset of the exceptions which `exceptRange` allows, thus both options are not useful together. + /// + /// ### never + /// Examples of **incorrect** code for the default `"never"` option: + /// ```js + /// if ("red" === color) { + /// // ... + /// } + /// if (`red` === color) { + /// // ... + /// } + /// if (`red` === `${color}`) { + /// // ... + /// } + /// + /// if (true == flag) { + /// // ... + /// } + /// + // if (5 > count) { + // // ... + // } + /// + // if (-1 < str.indexOf(substr)) { + // // ... + // } + /// + /// if (0 <= x && x < 1) { + /// // ... + /// } + /// ``` + /// + /// Examples of **correct** code for the default `"never"` option: + /// + /// ```js + /// if (5 & value) { + /// // ... + /// } + /// + /// if (value === "red") { + /// // ... + /// } + /// + /// if (value === `red`) { + /// // ... + /// } + /// + /// if (`${value}` === `red`) { + /// + /// } + /// ``` + /// + /// ### exceptRange + /// + /// Examples of **correct** code for the `"never", { "exceptRange": true }` options: + /// + /// ```js + /// function isReddish(color) { + /// return (color.hue < 60 || 300 < color.hue); + /// } + /// + /// if (x < -1 || 1 < x) { + /// // ... + /// } + /// + /// if (count < 10 && (0 <= rand && rand < 1)) { + /// // ... + /// } + /// + /// if (`blue` < x && x < `green`) { + /// // ... + /// } + /// + /// function howLong(arr) { + /// return (0 <= arr.length && arr.length < 10) ? "short" : "long"; + /// } + /// ``` + /// + /// ### onlyEquality + /// + /// Examples of **correct** code for the `"never", { "onlyEquality": true }` options: + /// + /// ```js + /// if (x < -1 || 9 < x) { + /// } + /// + /// if (x !== 'foo' && 'bar' != x) { + /// } + /// + /// if (x !== `foo` && `bar` != x) { + /// } + /// ``` + /// + /// ### always + /// + /// Examples of **incorrect** code for the `"always"` option: + /// + /// ```js + /// if (color == "blue") { + /// // ... + /// } + /// + /// if (color == `blue`) { + /// // ... + /// } + /// ``` + /// + /// Examples of **correct** code for the `"always"` option: + /// + /// ```js + /// if ("blue" == value) { + /// // ... + /// } + /// + /// if (`blue` == value) { + /// // ... + /// } + /// + /// if (`blue` == `${value}`) { + /// // ... + /// } + /// + /// if (-1 < str.indexOf(substr)) { + /// // ... + /// } + /// ``` + Yoda, + style, + fix +); + +impl Rule for Yoda { + fn from_configuration(value: serde_json::Value) -> Self { + let mut config = Self { never: true, except_range: false, only_equality: false }; + + let Some(arr) = value.as_array() else { + return config; + }; + + let option1 = arr.first().and_then(serde_json::Value::as_str); + let option2 = arr.get(1).and_then(serde_json::Value::as_object); + + if option1 == Some("always") { + config.never = false; + } + + if let Some(option2) = option2 { + if option2.get("exceptRange").and_then(serde_json::Value::as_bool) == Some(true) { + config.except_range = true; + } + if option2.get("onlyEquality").and_then(serde_json::Value::as_bool) == Some(true) { + config.only_equality = true; + } + } + + config + } + + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + let AstKind::BinaryExpression(expr) = node.kind() else { + return; + }; + + if let Some(parent_node) = ctx.nodes().parent_node(node.id()) { + if let AstKind::LogicalExpression(logical_expr) = parent_node.kind() { + let parent_logical_expr = ctx.nodes().parent_node(parent_node.id()); + + if self.except_range + && parent_logical_expr.is_some_and(|e| is_parenthesized(e)) + && is_range(logical_expr, ctx) + { + return; + } + } + }; + + if !expr.operator.is_equality() && !expr.operator.is_compare() { + return; + } + + if self.only_equality && !is_equality(expr) { + return; + } + + // never + if self.never && is_yoda(expr) { + do_diagnostic_with_fix(expr, ctx); + } + + // always + if !self.never && is_not_yoda(expr) { + do_diagnostic_with_fix(expr, ctx); + } + } +} + +fn is_yoda(expr: &BinaryExpression) -> bool { + is_literal_or_simple_template_literal(expr.left.get_inner_expression()) + && !is_literal_or_simple_template_literal(expr.right.get_inner_expression()) +} + +fn is_not_yoda(expr: &BinaryExpression) -> bool { + !is_literal_or_simple_template_literal(expr.left.get_inner_expression()) + && is_literal_or_simple_template_literal(expr.right.get_inner_expression()) +} + +fn do_diagnostic_with_fix(expr: &BinaryExpression, ctx: &LintContext) { + ctx.diagnostic_with_fix(yoda_diagnostic(expr.span), |fix| { + let flipped_operator = flip_operator(expr.operator); + + let left_str = ctx.source_range(expr.left.span()); + let right_str = ctx.source_range(expr.right.span()); + let flipped_operator_str = flipped_operator.as_str(); + + let operator_str = expr.operator.as_str(); + let source_str = ctx.source_range(expr.span); + let regex = Regex::new(operator_str).unwrap(); + let mut operator_position_start: u32 = 0; + let mut operator_position_end: u32 = 0; + for mat in regex.find_iter(source_str) { + let start = u32::try_from(mat.start()).unwrap(); + let end = u32::try_from(mat.end()).unwrap(); + + let is_inside_comments = ctx.comments().iter().any(|c| { + c.span.start <= start + expr.span.start && end + expr.span.start <= c.span.end + }); + + if !is_inside_comments { + operator_position_start = start + expr.span.start; + operator_position_end = end + expr.span.start; + break; + } + } + + let str_between_left_and_operator = + ctx.source_range(Span::new(expr.left.span().end, operator_position_start)); + let str_between_operator_and_right = + ctx.source_range(Span::new(operator_position_end, expr.right.span().start)); + + let left_start = expr.left.span().start; + let left_prev_token = if left_start > 0 && (expr.right.is_literal() || expr.right.is_identifier_reference() ) { + let token = ctx.source_range(Span::new(left_start - 1, left_start)); + match_token(token) + } else { + "" + }; + + let right_end = expr.right.span().end; + let source_size = u32::try_from(ctx.source_text().len()).unwrap(); + let right_next_token = if right_end < source_size && (expr.left.is_literal() || expr.left.is_identifier_reference()) { + let token = ctx.source_range(Span::new(right_end, right_end + 1)); + match_token(token) + } else { + "" + }; + + let replacement = format!( + "{left_prev_token}{right_str}{str_between_left_and_operator}{flipped_operator_str}{str_between_operator_and_right}{left_str}{right_next_token}" + ); + + fix.replace(expr.span, replacement) + }); +} + +fn match_token(token: &str) -> &str { + match token { + " " | "(" | ")" | "/" | "=" | ";" => "", + _ => " ", + } +} + +fn flip_operator(operator: BinaryOperator) -> BinaryOperator { + match operator { + BinaryOperator::LessThan => BinaryOperator::GreaterThan, + BinaryOperator::LessEqualThan => BinaryOperator::GreaterEqualThan, + BinaryOperator::GreaterThan => BinaryOperator::LessThan, + BinaryOperator::GreaterEqualThan => BinaryOperator::LessEqualThan, + _ => operator, + } +} + +fn is_equality(expr: &BinaryExpression) -> bool { + expr.operator == BinaryOperator::Equality || expr.operator == BinaryOperator::StrictEquality +} + +fn is_parenthesized(parent_logical_expr: &AstNode) -> bool { + let kind = parent_logical_expr.kind(); + + matches!(kind, AstKind::ParenthesizedExpression(_)) + || matches!(kind, AstKind::IfStatement(_)) + || matches!(kind, AstKind::WhileStatement(_)) + || matches!(kind, AstKind::DoWhileStatement(_)) +} + +fn is_range(expr: &LogicalExpression, ctx: &LintContext) -> bool { + let Expression::BinaryExpression(left) = &expr.left else { + return false; + }; + let Expression::BinaryExpression(right) = &expr.right else { + return false; + }; + + match left.operator { + BinaryOperator::LessThan | BinaryOperator::LessEqualThan => {} + _ => return false, + } + + match right.operator { + BinaryOperator::LessThan | BinaryOperator::LessEqualThan => {} + _ => return false, + } + + if expr.operator == LogicalOperator::And { + if !is_same_reference(&left.right, &right.left, ctx) { + return false; + } + + let left_left = &left.left; + let right_right = &right.right; + + let is_left_left_target_literal = is_target_literal(left_left); + let is_right_right_target_literal = is_target_literal(right_right); + + if !is_left_left_target_literal && !is_right_right_target_literal { + return false; + } + + if !is_left_left_target_literal || !is_right_right_target_literal { + return true; + } + + if let (Some(left_left), Some(right_right)) = + (get_string_literal(left_left), get_string_literal(right_right)) + { + return left_left <= right_right; + } + + if let (Some(left_left), Some(right_right)) = + (get_number(left_left), get_number(right_right)) + { + return left_left <= right_right; + } + + return false; + } + + if expr.operator == LogicalOperator::Or { + if !is_same_reference(&left.left, &right.right, ctx) { + return false; + } + + let left_right = &left.right; + let right_left = &right.left; + + let is_left_right_target_literal = is_target_literal(left_right); + let is_right_left_target_literal = is_target_literal(right_left); + + if !is_left_right_target_literal && !is_right_left_target_literal { + return false; + } + + if !is_left_right_target_literal || !is_right_left_target_literal { + return true; + } + + if let (Some(left_right), Some(right_left)) = + (get_string_literal(left_right), get_string_literal(right_left)) + { + return left_right <= right_left; + } + + if let (Some(left_right), Some(right_left)) = + (get_number(left_right), get_number(right_left)) + { + return left_right <= right_left; + } + + return false; + } + + false +} + +fn is_simple_template_literal(expr: &Expression) -> bool { + match expr { + Expression::TemplateLiteral(template) => template.quasis.len() == 1, + _ => false, + } +} + +fn is_literal_or_simple_template_literal(expr: &Expression) -> bool { + expr.is_literal() || is_number(expr) || is_simple_template_literal(expr) +} + +fn is_target_literal(expr: &Expression) -> bool { + get_string_literal(expr).is_some() || is_number(expr) +} + +fn get_string_literal<'a>(expr: &'a Expression) -> Option<&'a str> { + match expr { + Expression::StringLiteral(string) => Some(&string.value), + Expression::TemplateLiteral(template) => { + if template.quasis.len() != 1 { + return None; + } + + template.quasis.first().map(|e| e.value.raw.as_str()) + } + _ => None, + } +} + +fn is_number(expr: &Expression) -> bool { + match expr { + Expression::NumericLiteral(_) | Expression::BigIntLiteral(_) => true, + Expression::UnaryExpression(unary) => { + if unary.operator == UnaryOperator::UnaryNegation { + return is_number(&unary.argument); + } + false + } + _ => false, + } +} + +fn get_number(expr: &Expression) -> Option { + match expr { + Expression::NumericLiteral(numeric) => Some(numeric.value), + Expression::BigIntLiteral(big_int) => { + let big_int = big_int.to_big_int()?; + + let Ok(big_int) = big_int.to_string().parse::() else { + return None; + }; + + Some(big_int) + } + Expression::UnaryExpression(unary) => { + if unary.operator == UnaryOperator::UnaryNegation { + return get_number(&unary.argument).map(|num| -num); + } + + None + } + _ => None, + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + (r#"if (value === "red") {}"#, Some(serde_json::json!(["never"]))), + ("if (value === value) {}", Some(serde_json::json!(["never"]))), + ("if (value != 5) {}", Some(serde_json::json!(["never"]))), + ("if (5 & foo) {}", Some(serde_json::json!(["never"]))), + ("if (5 === 4) {}", Some(serde_json::json!(["never"]))), + ("if (value === `red`) {}", Some(serde_json::json!(["never"]))), // { "ecmaVersion": 2015 }, + ("if (`red` === `red`) {}", Some(serde_json::json!(["never"]))), // { "ecmaVersion": 2015 }, + ("if (`${foo}` === `red`) {}", Some(serde_json::json!(["never"]))), // { "ecmaVersion": 2015 }, + (r#"if (`${""}` === `red`) {}"#, Some(serde_json::json!(["never"]))), // { "ecmaVersion": 2015 }, + (r#"if (`${"red"}` === foo) {}"#, Some(serde_json::json!(["never"]))), // { "ecmaVersion": 2015 }, + ("if (b > `a` && b > `a`) {}", Some(serde_json::json!(["never"]))), // { "ecmaVersion": 2015 }, + (r#"if (`b` > `a` && "b" > "a") {}"#, Some(serde_json::json!(["never"]))), // { "ecmaVersion": 2015 }, + (r#"if ("blue" === value) {}"#, Some(serde_json::json!(["always"]))), + ("if (value === value) {}", Some(serde_json::json!(["always"]))), + ("if (4 != value) {}", Some(serde_json::json!(["always"]))), + ("if (foo & 4) {}", Some(serde_json::json!(["always"]))), + ("if (5 === 4) {}", Some(serde_json::json!(["always"]))), + ("if (`red` === value) {}", Some(serde_json::json!(["always"]))), // { "ecmaVersion": 2015 }, + ("if (`red` === `red`) {}", Some(serde_json::json!(["always"]))), // { "ecmaVersion": 2015 }, + ("if (`red` === `${foo}`) {}", Some(serde_json::json!(["always"]))), // { "ecmaVersion": 2015 }, + (r#"if (`red` === `${""}`) {}"#, Some(serde_json::json!(["always"]))), // { "ecmaVersion": 2015 }, + (r#"if (foo === `${"red"}`) {}"#, Some(serde_json::json!(["always"]))), // { "ecmaVersion": 2015 }, + ("if (`a` > b && `a` > b) {}", Some(serde_json::json!(["always"]))), // { "ecmaVersion": 2015 }, + (r#"if (`b` > `a` && "b" > "a") {}"#, Some(serde_json::json!(["always"]))), // { "ecmaVersion": 2015 }, + ( + r#"if ("a" < x && x < MAX ) {}"#, + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ("if (1 < x && x < MAX ) {}", Some(serde_json::json!(["never", { "exceptRange": true }]))), + ( + "if ('a' < x && x < MAX ) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (x < `x` || `x` <= x) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), // { "ecmaVersion": 2015 }, + ("if (0 < x && x <= 1) {}", Some(serde_json::json!(["never", { "exceptRange": true }]))), + ("if (0 <= x && x < 1) {}", Some(serde_json::json!(["always", { "exceptRange": true }]))), + ( + "if ('blue' < x.y && x.y < 'green') {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (0 < x[``] && x[``] < 100) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), // { "ecmaVersion": 2015 }, + ( + "if (0 < x[''] && x[``] < 100) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), // { "ecmaVersion": 2015 }, + ( + "if (a < 4 || (b[c[0]].d['e'] < 0 || 1 <= b[c[0]].d['e'])) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (0 <= x['y'] && x['y'] <= 100) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (a < 0 && (0 < b && b < 1)) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if ((0 < a && a < 1) && b < 0) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ("if (-1 < x && x < 0) {}", Some(serde_json::json!(["never", { "exceptRange": true }]))), + ( + "if (0 <= this.prop && this.prop <= 1) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (0 <= index && index < list.length) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (ZERO <= index && index < 100) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (value <= MIN || 10 < value) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (value <= 0 || MAX < value) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + r#"if (0 <= a.b && a["b"] <= 100) {}"#, + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (0 <= a.b && a[`b`] <= 100) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), // { "ecmaVersion": 2015 }, + ("if (-1n < x && x <= 1n) {}", Some(serde_json::json!(["never", { "exceptRange": true }]))), // { "ecmaVersion": 2020 }, + ( + "if (-1n <= x && x < 1n) {}", + Some(serde_json::json!(["always", { "exceptRange": true }])), + ), // { "ecmaVersion": 2020 }, + ( + "if (x < `1` || `1` < x) {}", + Some(serde_json::json!(["always", { "exceptRange": true }])), + ), // { "ecmaVersion": 2020 }, + ( + "if (1 <= a['/(?0)/'] && a[/(?0)/] <= 100) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), // { "ecmaVersion": 2018 }, + ( + "if (x <= `bar` || `foo` < x) {}", + Some(serde_json::json!(["always", { "exceptRange": true }])), + ), // { "ecmaVersion": 2015 }, + ( + "if ('a' < x && x < MAX ) {}", + Some(serde_json::json!(["always", { "exceptRange": true }])), + ), // { "ecmaVersion": 2015 }, + ("if ('a' < x && x < MAX ) {}", Some(serde_json::json!(["always"]))), // { "ecmaVersion": 2015 }, + ( + "if (MIN < x && x < 'a' ) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), // { "ecmaVersion": 2015 }, + ("if (MIN < x && x < 'a' ) {}", Some(serde_json::json!(["never"]))), // { "ecmaVersion": 2015 }, + ( + "if (`blue` < x.y && x.y < `green`) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), // { "ecmaVersion": 2015 }, + ( + "if (0 <= x[`y`] && x[`y`] <= 100) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), // { "ecmaVersion": 2015 }, + ( + r#"if (0 <= x[`y`] && x["y"] <= 100) {}"#, + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), // { "ecmaVersion": 2015 }, + ( + "if ('a' <= x && x < 'b') {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ("if (x < -1n || 1n <= x) {}", Some(serde_json::json!(["never", { "exceptRange": true }]))), // { "ecmaVersion": 2020 }, + ( + "if (x < -1n || 1n <= x) {}", + Some(serde_json::json!(["always", { "exceptRange": true }])), + ), // { "ecmaVersion": 2020 }, + ("if (1 < a && a <= 2) {}", Some(serde_json::json!(["never", { "exceptRange": true }]))), + ("if (x < -1 || 1 < x) {}", Some(serde_json::json!(["never", { "exceptRange": true }]))), + ( + "if (x <= 'bar' || 'foo' < x) {}", + Some(serde_json::json!(["always", { "exceptRange": true }])), + ), + ("if (x < 0 || 1 <= x) {}", Some(serde_json::json!(["never", { "exceptRange": true }]))), + ("if('a' <= x && x < MAX) {}", Some(serde_json::json!(["never", { "exceptRange": true }]))), + ( + "if (0 <= obj?.a && obj?.a < 1) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), // { "ecmaVersion": 2020 }, + ("if (0 < x && x <= 1) {}", Some(serde_json::json!(["never", { "onlyEquality": true }]))), + ( + "if (x !== 'foo' && 'foo' !== x) {}", + Some(serde_json::json!(["never", { "onlyEquality": true }])), + ), + ( + "if (x < 2 && x !== -3) {}", + Some(serde_json::json!(["always", { "onlyEquality": true }])), + ), + ( + "if (x !== `foo` && `foo` !== x) {}", + Some(serde_json::json!(["never", { "onlyEquality": true }])), + ), // { "ecmaVersion": 2015 }, + ( + "if (x < `2` && x !== `-3`) {}", + Some(serde_json::json!(["always", { "onlyEquality": true }])), + ), // { "ecmaVersion": 2015 } + ]; + + let fail = vec![ + ( + "if (x <= 'foo' || 'bar' < x) {}", + Some(serde_json::json!(["always", { "exceptRange": true }])), + ), + (r#"if ("red" == value) {}"#, Some(serde_json::json!(["never"]))), + ("if (true === value) {}", Some(serde_json::json!(["never"]))), + ("if (5 != value) {}", Some(serde_json::json!(["never"]))), + ("if (5n != value) {}", Some(serde_json::json!(["never"]))), // { "ecmaVersion": 2020 }, + ("if (null !== value) {}", Some(serde_json::json!(["never"]))), + (r#"if ("red" <= value) {}"#, Some(serde_json::json!(["never"]))), + ("if (`red` <= value) {}", Some(serde_json::json!(["never"]))), // { "ecmaVersion": 2015 }, + ("if (`red` <= `${foo}`) {}", Some(serde_json::json!(["never"]))), // { "ecmaVersion": 2015 }, + (r#"if (`red` <= `${"red"}`) {}"#, Some(serde_json::json!(["never"]))), // { "ecmaVersion": 2015 }, + ("if (true >= value) {}", Some(serde_json::json!(["never"]))), + ("var foo = (5 < value) ? true : false", Some(serde_json::json!(["never"]))), + ("function foo() { return (null > value); }", Some(serde_json::json!(["never"]))), + ("if (-1 < str.indexOf(substr)) {}", Some(serde_json::json!(["never"]))), + (r#"if (value == "red") {}"#, Some(serde_json::json!(["always"]))), + ("if (value == `red`) {}", Some(serde_json::json!(["always"]))), // { "ecmaVersion": 2015 }, + ("if (value === true) {}", Some(serde_json::json!(["always"]))), + ("if (value === 5n) {}", Some(serde_json::json!(["always"]))), // { "ecmaVersion": 2020 }, + (r#"if (`${"red"}` <= `red`) {}"#, Some(serde_json::json!(["always"]))), // { "ecmaVersion": 2015 }, + ( + "if (a < 0 && 0 <= b && b < 1) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (0 <= a && a < 1 && b < 1) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ("if (1 < a && a < 0) {}", Some(serde_json::json!(["never", { "exceptRange": true }]))), + ("0 < a && a < 1", Some(serde_json::json!(["never", { "exceptRange": true }]))), + ("var a = b < 0 || 1 <= b;", Some(serde_json::json!(["never", { "exceptRange": true }]))), + ("if (0 <= x && x < -1) {}", Some(serde_json::json!(["never", { "exceptRange": true }]))), + ( + "var a = (b < 0 && 0 <= b);", + Some(serde_json::json!(["always", { "exceptRange": true }])), + ), + ( + "var a = (b < `0` && `0` <= b);", + Some(serde_json::json!(["always", { "exceptRange": true }])), + ), // { "ecmaVersion": 2015 }, + ( + "if (`green` < x.y && x.y < `blue`) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), // { "ecmaVersion": 2015 }, + ( + "if (0 <= a[b] && a['b'] < 1) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (0 <= a[b] && a[`b`] < 1) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), // { "ecmaVersion": 2015 }, + ( + "if (`0` <= a[b] && a[`b`] < `1`) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), // { "ecmaVersion": 2015 }, + ( + "if (0 <= a[b] && a.b < 1) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (0 <= a[''] && a.b < 1) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (0 <= a[''] && a[' '] < 1) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (0 <= a[''] && a[null] < 1) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (0 <= a[``] && a[null] < 1) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), // { "ecmaVersion": 2015 }, + ( + "if (0 <= a[''] && a[b] < 1) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (0 <= a[''] && a[b()] < 1) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (0 <= a[``] && a[b()] < 1) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), // { "ecmaVersion": 2015 }, + ( + "if (0 <= a[b()] && a[b()] < 1) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (0 <= a.null && a[/(?0)/] <= 1) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), // { "ecmaVersion": 2018 }, + ("if (3 == a) {}", Some(serde_json::json!(["never", { "onlyEquality": true }]))), + ("foo(3 === a);", Some(serde_json::json!(["never", { "onlyEquality": true }]))), + ("foo(a === 3);", Some(serde_json::json!(["always", { "onlyEquality": true }]))), + ("foo(a === `3`);", Some(serde_json::json!(["always", { "onlyEquality": true }]))), // { "ecmaVersion": 2015 }, + ("if (0 <= x && x < 1) {}", None), + ("if ( /* a */ 0 /* b */ < /* c */ foo /* d */ ) {}", Some(serde_json::json!(["never"]))), + ("if ( /* a */ foo /* b */ > /* c */ 0 /* d */ ) {}", Some(serde_json::json!(["always"]))), + ("if (foo()===1) {}", Some(serde_json::json!(["always"]))), + ("if (foo() === 1) {}", Some(serde_json::json!(["always"]))), + ("while (0 === (a));", Some(serde_json::json!(["never"]))), + ("while (0 === (a = b));", Some(serde_json::json!(["never"]))), + ("while ((a) === 0);", Some(serde_json::json!(["always"]))), + ("while ((a = b) === 0);", Some(serde_json::json!(["always"]))), + ("if (((((((((((foo)))))))))) === ((((((5)))))));", Some(serde_json::json!(["always"]))), + ("function *foo() { yield(1) < a }", Some(serde_json::json!(["never"]))), // { "ecmaVersion": 2015 }, + ("function *foo() { yield((1)) < a }", Some(serde_json::json!(["never"]))), // { "ecmaVersion": 2015 }, + ("function *foo() { yield 1 < a }", Some(serde_json::json!(["never"]))), // { "ecmaVersion": 2015 }, + ("function *foo() { yield/**/1 < a }", Some(serde_json::json!(["never"]))), // { "ecmaVersion": 2015 }, + ("function *foo() { yield(1) < ++a }", Some(serde_json::json!(["never"]))), // { "ecmaVersion": 2015 }, + ("function *foo() { yield(1) < (a) }", Some(serde_json::json!(["never"]))), // { "ecmaVersion": 2015 }, + ("x=1 < a", Some(serde_json::json!(["never"]))), + ("function *foo() { yield++a < 1 }", Some(serde_json::json!(["always"]))), // { "ecmaVersion": 2015 }, + ("function *foo() { yield(a) < 1 }", Some(serde_json::json!(["always"]))), // { "ecmaVersion": 2015 }, + ("function *foo() { yield a < 1 }", Some(serde_json::json!(["always"]))), // { "ecmaVersion": 2015 }, + ("function *foo() { yield/**/a < 1 }", Some(serde_json::json!(["always"]))), // { "ecmaVersion": 2015 }, + ("function *foo() { yield++a < (1) }", Some(serde_json::json!(["always"]))), // { "ecmaVersion": 2015 }, + ("x=a < 1", Some(serde_json::json!(["always"]))), + ("0 < f()in obj", None), + ("1 > x++instanceof foo", Some(serde_json::json!(["never"]))), + ("x < ('foo')in bar", Some(serde_json::json!(["always"]))), + ("false <= ((x))in foo", Some(serde_json::json!(["never"]))), + ("x >= (1)instanceof foo", Some(serde_json::json!(["always"]))), + ("false <= ((x)) in foo", Some(serde_json::json!(["never"]))), + ("x >= 1 instanceof foo", Some(serde_json::json!(["always"]))), + ("x >= 1/**/instanceof foo", Some(serde_json::json!(["always"]))), + ("(x >= 1)instanceof foo", Some(serde_json::json!(["always"]))), + ("(x) >= (1)instanceof foo", Some(serde_json::json!(["always"]))), + ("1 > x===foo", Some(serde_json::json!(["never"]))), + ("1 > x", Some(serde_json::json!(["never"]))), + ( + "if (`green` < x.y && x.y < `blue`) {}", + Some(serde_json::json!(["always", { "exceptRange": true }])), + ), // { "ecmaVersion": 2015 }, + ("if('a' <= x && x < 'b') {}", Some(serde_json::json!(["always"]))), + ( + "if ('b' <= x && x < 'a') {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ("if('a' <= x && x < 1) {}", Some(serde_json::json!(["never", { "exceptRange": true }]))), + ("if (0 < a && b < max) {}", Some(serde_json::json!(["never", { "exceptRange": true }]))), + ]; + + let fix = vec![ + ( + "if (x <= 'foo' || 'bar' < x) {}", + "if ('foo' >= x || 'bar' < x) {}", + Some(serde_json::json!(["always", { "exceptRange": true }])), + ), + ( + r#"if ("red" == value) {}"#, + r#"if (value == "red") {}"#, + Some(serde_json::json!(["never"])), + ), + ("if (true === value) {}", "if (value === true) {}", Some(serde_json::json!(["never"]))), + ("if (5 != value) {}", "if (value != 5) {}", Some(serde_json::json!(["never"]))), + ("if (5n != value) {}", "if (value != 5n) {}", Some(serde_json::json!(["never"]))), + ("if (null !== value) {}", "if (value !== null) {}", Some(serde_json::json!(["never"]))), + ( + r#"if ("red" <= value) {}"#, + r#"if (value >= "red") {}"#, + Some(serde_json::json!(["never"])), + ), + ("if (`red` <= value) {}", "if (value >= `red`) {}", Some(serde_json::json!(["never"]))), + ( + "if (`red` <= `${foo}`) {}", + "if (`${foo}` >= `red`) {}", + Some(serde_json::json!(["never"])), + ), + ( + r#"if (`red` <= `${"red"}`) {}"#, + r#"if (`${"red"}` >= `red`) {}"#, + Some(serde_json::json!(["never"])), + ), + ("if (true >= value) {}", "if (value <= true) {}", Some(serde_json::json!(["never"]))), + ( + "var foo = (5 < value) ? true : false", + "var foo = (value > 5) ? true : false", + Some(serde_json::json!(["never"])), + ), + ( + "function foo() { return (null > value); }", + "function foo() { return (value < null); }", + Some(serde_json::json!(["never"])), + ), + ( + "if (-1 < str.indexOf(substr)) {}", + "if (str.indexOf(substr) > -1) {}", + Some(serde_json::json!(["never"])), + ), + ( + r#"if (value == "red") {}"#, + r#"if ("red" == value) {}"#, + Some(serde_json::json!(["always"])), + ), + ("if (value == `red`) {}", "if (`red` == value) {}", Some(serde_json::json!(["always"]))), + ("if (value === true) {}", "if (true === value) {}", Some(serde_json::json!(["always"]))), + ("if (value === 5n) {}", "if (5n === value) {}", Some(serde_json::json!(["always"]))), + ( + r#"if (`${"red"}` <= `red`) {}"#, + r#"if (`red` >= `${"red"}`) {}"#, + Some(serde_json::json!(["always"])), + ), + ( + "if (a < 0 && 0 <= b && b < 1) {}", + "if (a < 0 && b >= 0 && b < 1) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (0 <= a && a < 1 && b < 1) {}", + "if (a >= 0 && a < 1 && b < 1) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (1 < a && a < 0) {}", + "if (a > 1 && a < 0) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "0 < a && a < 1", + "a > 0 && a < 1", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "var a = b < 0 || 1 <= b;", + "var a = b < 0 || b >= 1;", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (0 <= x && x < -1) {}", + "if (x >= 0 && x < -1) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "var a = (b < 0 && 0 <= b);", + "var a = (0 > b && 0 <= b);", + Some(serde_json::json!(["always", { "exceptRange": true }])), + ), + ( + "var a = (b < `0` && `0` <= b);", + "var a = (`0` > b && `0` <= b);", + Some(serde_json::json!(["always", { "exceptRange": true }])), + ), + ( + "if (`green` < x.y && x.y < `blue`) {}", + "if (x.y > `green` && x.y < `blue`) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (0 <= a[b] && a['b'] < 1) {}", + "if (a[b] >= 0 && a['b'] < 1) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (0 <= a[b] && a[`b`] < 1) {}", + "if (a[b] >= 0 && a[`b`] < 1) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (`0` <= a[b] && a[`b`] < `1`) {}", + "if (a[b] >= `0` && a[`b`] < `1`) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (0 <= a[b] && a.b < 1) {}", + "if (a[b] >= 0 && a.b < 1) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (0 <= a[''] && a.b < 1) {}", + "if (a[''] >= 0 && a.b < 1) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (0 <= a[''] && a[' '] < 1) {}", + "if (a[''] >= 0 && a[' '] < 1) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (0 <= a[''] && a[null] < 1) {}", + "if (a[''] >= 0 && a[null] < 1) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (0 <= a[``] && a[null] < 1) {}", + "if (a[``] >= 0 && a[null] < 1) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (0 <= a[''] && a[b] < 1) {}", + "if (a[''] >= 0 && a[b] < 1) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (0 <= a[''] && a[b()] < 1) {}", + "if (a[''] >= 0 && a[b()] < 1) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (0 <= a[``] && a[b()] < 1) {}", + "if (a[``] >= 0 && a[b()] < 1) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (0 <= a[b()] && a[b()] < 1) {}", + "if (a[b()] >= 0 && a[b()] < 1) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (0 <= a.null && a[/(?0)/] <= 1) {}", + "if (a.null >= 0 && a[/(?0)/] <= 1) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (3 == a) {}", + "if (a == 3) {}", + Some(serde_json::json!(["never", { "onlyEquality": true }])), + ), + ( + "foo(3 === a);", + "foo(a === 3);", + Some(serde_json::json!(["never", { "onlyEquality": true }])), + ), + ( + "foo(a === 3);", + "foo(3 === a);", + Some(serde_json::json!(["always", { "onlyEquality": true }])), + ), + ( + "foo(a === `3`);", + "foo(`3` === a);", + Some(serde_json::json!(["always", { "onlyEquality": true }])), + ), + ("if (0 <= x && x < 1) {}", "if (x >= 0 && x < 1) {}", None), + ( + "if ( /* a */ 0 /* b */ < /* c */ foo /* d */ ) {}", + "if ( /* a */ foo /* b */ > /* c */ 0 /* d */ ) {}", + Some(serde_json::json!(["never"])), + ), + ( + "if ( /* a */ 0 /* < */ < /* < */ foo /* d */ ) {}", + "if ( /* a */ foo /* < */ > /* < */ 0 /* d */ ) {}", + Some(serde_json::json!(["never"])), + ), + ( + "if ( /* a */ foo /* b */ > /* c */ 0 /* d */ ) {}", + "if ( /* a */ 0 /* b */ < /* c */ foo /* d */ ) {}", + Some(serde_json::json!(["always"])), + ), + ("if (foo()===1) {}", "if (1===foo()) {}", Some(serde_json::json!(["always"]))), + ("if (foo() === 1) {}", "if (1 === foo()) {}", Some(serde_json::json!(["always"]))), + ("while (0 === (a));", "while ((a) === 0);", Some(serde_json::json!(["never"]))), + ("while (0 === (a = b));", "while ((a = b) === 0);", Some(serde_json::json!(["never"]))), + ("while ((a) === 0);", "while (0 === (a));", Some(serde_json::json!(["always"]))), + ("while ((a = b) === 0);", "while (0 === (a = b));", Some(serde_json::json!(["always"]))), + ( + "if (((((((((((foo)))))))))) === ((((((5)))))));", + "if (((((((5)))))) === ((((((((((foo)))))))))));", + Some(serde_json::json!(["always"])), + ), + ( + "function *foo() { yield(1) < a }", + "function *foo() { yield a > (1) }", + Some(serde_json::json!(["never"])), + ), + ( + "function *foo() { yield((1)) < a }", + "function *foo() { yield a > ((1)) }", + Some(serde_json::json!(["never"])), + ), + ( + "function *foo() { yield 1 < a }", + "function *foo() { yield a > 1 }", + Some(serde_json::json!(["never"])), + ), + ( + "function *foo() { yield/**/1 < a }", + "function *foo() { yield/**/a > 1 }", + Some(serde_json::json!(["never"])), + ), + ( + "function *foo() { yield(1) < ++a }", + "function *foo() { yield++a > (1) }", + Some(serde_json::json!(["never"])), + ), + ( + "function *foo() { yield(1) < (a) }", + "function *foo() { yield(a) > (1) }", + Some(serde_json::json!(["never"])), + ), + ("x=1 < a", "x=a > 1", Some(serde_json::json!(["never"]))), + ( + "function *foo() { yield++a < 1 }", + "function *foo() { yield 1 > ++a }", + Some(serde_json::json!(["always"])), + ), + ( + "function *foo() { yield(a) < 1 }", + "function *foo() { yield 1 > (a) }", + Some(serde_json::json!(["always"])), + ), + ( + "function *foo() { yield a < 1 }", + "function *foo() { yield 1 > a }", + Some(serde_json::json!(["always"])), + ), + ( + "function *foo() { yield/**/a < 1 }", + "function *foo() { yield/**/1 > a }", + Some(serde_json::json!(["always"])), + ), + ( + "function *foo() { yield++a < (1) }", + "function *foo() { yield(1) > ++a }", + Some(serde_json::json!(["always"])), + ), + ("x=a < 1", "x=1 > a", Some(serde_json::json!(["always"]))), + ("0 < f()in obj", "f() > 0 in obj", None), + ("1 > x++instanceof foo", "x++ < 1 instanceof foo", Some(serde_json::json!(["never"]))), + ("x < ('foo')in bar", "('foo') > x in bar", Some(serde_json::json!(["always"]))), + ("false <= ((x))in foo", "((x)) >= false in foo", Some(serde_json::json!(["never"]))), + ("x >= (1)instanceof foo", "(1) <= x instanceof foo", Some(serde_json::json!(["always"]))), + ("false <= ((x)) in foo", "((x)) >= false in foo", Some(serde_json::json!(["never"]))), + ("x >= 1 instanceof foo", "1 <= x instanceof foo", Some(serde_json::json!(["always"]))), + ( + "x >= 1/**/instanceof foo", + "1 <= x/**/instanceof foo", + Some(serde_json::json!(["always"])), + ), + ("(x >= 1)instanceof foo", "(1 <= x)instanceof foo", Some(serde_json::json!(["always"]))), + ( + "(x) >= (1)instanceof foo", + "(1) <= (x)instanceof foo", + Some(serde_json::json!(["always"])), + ), + ("1 > x===foo", "x < 1===foo", Some(serde_json::json!(["never"]))), + ("1 > x", "x < 1", Some(serde_json::json!(["never"]))), + ( + "if (`green` < x.y && x.y < `blue`) {}", + "if (`green` < x.y && `blue` > x.y) {}", + Some(serde_json::json!(["always", { "exceptRange": true }])), + ), + ( + "if('a' <= x && x < 'b') {}", + "if('a' <= x && 'b' > x) {}", + Some(serde_json::json!(["always"])), + ), + ( + "if ('b' <= x && x < 'a') {}", + "if (x >= 'b' && x < 'a') {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if('a' <= x && x < 1) {}", + "if(x >= 'a' && x < 1) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ( + "if (0 < a && b < max) {}", + "if (a > 0 && b < max) {}", + Some(serde_json::json!(["never", { "exceptRange": true }])), + ), + ]; + Tester::new(Yoda::NAME, Yoda::CATEGORY, pass, fail).expect_fix(fix).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_negative_index.rs b/crates/oxc_linter/src/rules/unicorn/prefer_negative_index.rs index 5ae818464fcac..b45bd8bd8c875 100644 --- a/crates/oxc_linter/src/rules/unicorn/prefer_negative_index.rs +++ b/crates/oxc_linter/src/rules/unicorn/prefer_negative_index.rs @@ -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)", diff --git a/crates/oxc_linter/src/snapshots/eslint_yoda.snap b/crates/oxc_linter/src/snapshots/eslint_yoda.snap new file mode 100644 index 0000000000000..22519439cb9cd --- /dev/null +++ b/crates/oxc_linter/src/snapshots/eslint_yoda.snap @@ -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 opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:5] + 1 │ if ("red" == value) {} + · ────────────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:5] + 1 │ if (true === value) {} + · ────────────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:5] + 1 │ if (5 != value) {} + · ────────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:5] + 1 │ if (5n != value) {} + · ─────────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:5] + 1 │ if (null !== value) {} + · ────────────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:5] + 1 │ if ("red" <= value) {} + · ────────────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:5] + 1 │ if (`red` <= value) {} + · ────────────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:5] + 1 │ if (`red` <= `${foo}`) {} + · ───────────────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:5] + 1 │ if (`red` <= `${"red"}`) {} + · ─────────────────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:5] + 1 │ if (true >= value) {} + · ───────────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ 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 opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:26] + 1 │ function foo() { return (null > value); } + · ──────────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:5] + 1 │ if (-1 < str.indexOf(substr)) {} + · ──────────────────────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:5] + 1 │ if (value == "red") {} + · ────────────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:5] + 1 │ if (value == `red`) {} + · ────────────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:5] + 1 │ if (value === true) {} + · ────────────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:5] + 1 │ if (value === 5n) {} + · ──────────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:5] + 1 │ if (`${"red"}` <= `red`) {} + · ─────────────────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ 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 opposite side of the operator. + + ⚠ 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 opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:5] + 1 │ if (1 < a && a < 0) {} + · ───── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:1] + 1 │ 0 < a && a < 1 + · ───── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ 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 opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:5] + 1 │ if (0 <= x && x < -1) {} + · ────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ 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 opposite side of the operator. + + ⚠ 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 opposite side of the operator. + + ⚠ 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 opposite side of the operator. + + ⚠ 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 opposite side of the operator. + + ⚠ 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 opposite side of the operator. + + ⚠ 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 opposite side of the operator. + + ⚠ 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 opposite side of the operator. + + ⚠ 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 opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:5] + 1 │ if (0 <= a[''] && a[' '] < 1) {} + · ────────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ 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 opposite side of the operator. + + ⚠ 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 opposite side of the operator. + + ⚠ 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 opposite side of the operator. + + ⚠ 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 opposite side of the operator. + + ⚠ 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 opposite side of the operator. + + ⚠ 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 opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:5] + 1 │ if (0 <= a.null && a[/(?0)/] <= 1) {} + · ─────────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:5] + 1 │ if (3 == a) {} + · ────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:5] + 1 │ foo(3 === a); + · ─────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:5] + 1 │ foo(a === 3); + · ─────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:5] + 1 │ foo(a === `3`); + · ───────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:5] + 1 │ if (0 <= x && x < 1) {} + · ────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ 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 opposite side of the operator. + + ⚠ 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 opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:5] + 1 │ if (foo()===1) {} + · ───────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:5] + 1 │ if (foo() === 1) {} + · ─────────────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:8] + 1 │ while (0 === (a)); + · ───────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:8] + 1 │ while (0 === (a = b)); + · ───────────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:8] + 1 │ while ((a) === 0); + · ───────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:8] + 1 │ while ((a = b) === 0); + · ───────────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:5] + 1 │ if (((((((((((foo)))))))))) === ((((((5))))))); + · ───────────────────────────────────────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:24] + 1 │ function *foo() { yield(1) < a } + · ─────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:24] + 1 │ function *foo() { yield((1)) < a } + · ───────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:25] + 1 │ function *foo() { yield 1 < a } + · ───── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:28] + 1 │ function *foo() { yield/**/1 < a } + · ───── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:24] + 1 │ function *foo() { yield(1) < ++a } + · ───────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:24] + 1 │ function *foo() { yield(1) < (a) } + · ───────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:3] + 1 │ x=1 < a + · ───── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:24] + 1 │ function *foo() { yield++a < 1 } + · ─────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:24] + 1 │ function *foo() { yield(a) < 1 } + · ─────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:25] + 1 │ function *foo() { yield a < 1 } + · ───── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:28] + 1 │ function *foo() { yield/**/a < 1 } + · ───── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:24] + 1 │ function *foo() { yield++a < (1) } + · ───────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:3] + 1 │ x=a < 1 + · ───── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:1] + 1 │ 0 < f()in obj + · ─────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:1] + 1 │ 1 > x++instanceof foo + · ─────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:1] + 1 │ x < ('foo')in bar + · ─────────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:1] + 1 │ false <= ((x))in foo + · ────────────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:1] + 1 │ x >= (1)instanceof foo + · ──────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:1] + 1 │ false <= ((x)) in foo + · ────────────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:1] + 1 │ x >= 1 instanceof foo + · ────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:1] + 1 │ x >= 1/**/instanceof foo + · ────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:2] + 1 │ (x >= 1)instanceof foo + · ────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:1] + 1 │ (x) >= (1)instanceof foo + · ────────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:1] + 1 │ 1 > x===foo + · ───── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:1] + 1 │ 1 > x + · ───── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ 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 opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:16] + 1 │ if('a' <= x && x < 'b') {} + · ─────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:5] + 1 │ if ('b' <= x && x < 'a') {} + · ──────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:4] + 1 │ if('a' <= x && x < 1) {} + · ──────── + ╰──── + help: Expected literal to be on the opposite side of the operator. + + ⚠ eslint(yoda): Require or disallow "Yoda" conditions + ╭─[yoda.tsx:1:5] + 1 │ if (0 < a && b < max) {} + · ───── + ╰──── + help: Expected literal to be on the opposite side of the operator. diff --git a/crates/oxc_linter/src/utils/unicorn.rs b/crates/oxc_linter/src/utils/unicorn.rs index 24226da97fe82..4f32b71b949ea 100644 --- a/crates/oxc_linter/src/utils/unicorn.rs +++ b/crates/oxc_linter/src/utils/unicorn.rs @@ -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,23 @@ 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(left_str), Expression::TemplateLiteral(right_str)) => { + return right_str.is_no_substitution_template() + && left_str.value == right_str.quasi().unwrap(); + } + (Expression::TemplateLiteral(left_str), Expression::StringLiteral(right_str)) => { + return left_str.is_no_substitution_template() + && left_str.quasi().unwrap() == right_str.value; + } + (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 +284,56 @@ 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; + match (&left.expression, &right.expression) { + // ex) x['/regex/'] === x[/regex/] + ( + Expression::StringLiteral(left_expression), + Expression::RegExpLiteral(right_expression), + ) => { + if left_expression.value != right_expression.raw { + return false; + } + } + // ex) x[`/regex/`] === x[/regex/] + ( + Expression::TemplateLiteral(left_expression), + Expression::RegExpLiteral(right_expression), + ) => { + if !(left_expression.is_no_substitution_template() + && left_expression.quasi().unwrap() == right_expression.raw) + { + return false; + } + } + // ex) x[/regex/] === x['/regex/'] + ( + Expression::RegExpLiteral(left_expression), + Expression::StringLiteral(right_expression), + ) => { + if left_expression.raw != right_expression.value { + return false; + } + } + // ex) x[/regex/] === x[`/regex/`] + ( + Expression::RegExpLiteral(left_expression), + Expression::TemplateLiteral(right_expression), + ) => { + if !(right_expression.is_no_substitution_template() + && left_expression.raw == right_expression.quasi().unwrap()) + { + return false; + } + } + _ => { + if !is_same_reference( + left.expression.get_inner_expression(), + right.expression.get_inner_expression(), + ctx, + ) { + return false; + } + } } } From 38a48d01934b5c3636f8ef1561bd87f301267714 Mon Sep 17 00:00:00 2001 From: tbashiyy Date: Tue, 3 Dec 2024 05:56:34 +0900 Subject: [PATCH 2/4] fix diagnostic message --- crates/oxc_linter/src/rules/eslint/yoda.rs | 13 +- .../oxc_linter/src/snapshots/eslint_yoda.snap | 170 +++++++++--------- 2 files changed, 92 insertions(+), 91 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/yoda.rs b/crates/oxc_linter/src/rules/eslint/yoda.rs index 8a1fe4146a7e7..2f5d5580d6209 100644 --- a/crates/oxc_linter/src/rules/eslint/yoda.rs +++ b/crates/oxc_linter/src/rules/eslint/yoda.rs @@ -13,9 +13,10 @@ use regex::Regex; use crate::{context::LintContext, rule::Rule, utils::is_same_reference, AstNode}; -fn yoda_diagnostic(span: Span) -> OxcDiagnostic { +fn yoda_diagnostic(span: Span, never: bool, operator: &str) -> OxcDiagnostic { + let expected_side = if never { "right" } else { "left" }; OxcDiagnostic::warn("Require or disallow \"Yoda\" conditions") - .with_help("Expected literal to be on the opposite side of the operator.") + .with_help(format!("Expected literal to be on the {expected_side} side of {operator}.")) .with_label(span) } @@ -243,12 +244,12 @@ impl Rule for Yoda { // never if self.never && is_yoda(expr) { - do_diagnostic_with_fix(expr, ctx); + do_diagnostic_with_fix(expr, ctx, self.never); } // always if !self.never && is_not_yoda(expr) { - do_diagnostic_with_fix(expr, ctx); + do_diagnostic_with_fix(expr, ctx, self.never); } } } @@ -263,8 +264,8 @@ fn is_not_yoda(expr: &BinaryExpression) -> bool { && is_literal_or_simple_template_literal(expr.right.get_inner_expression()) } -fn do_diagnostic_with_fix(expr: &BinaryExpression, ctx: &LintContext) { - ctx.diagnostic_with_fix(yoda_diagnostic(expr.span), |fix| { +fn do_diagnostic_with_fix(expr: &BinaryExpression, ctx: &LintContext, never: bool) { + ctx.diagnostic_with_fix(yoda_diagnostic(expr.span, never, expr.operator.as_str()), |fix| { let flipped_operator = flip_operator(expr.operator); let left_str = ctx.source_range(expr.left.span()); diff --git a/crates/oxc_linter/src/snapshots/eslint_yoda.snap b/crates/oxc_linter/src/snapshots/eslint_yoda.snap index 22519439cb9cd..f7968eb66ad6d 100644 --- a/crates/oxc_linter/src/snapshots/eslint_yoda.snap +++ b/crates/oxc_linter/src/snapshots/eslint_yoda.snap @@ -8,592 +8,592 @@ snapshot_kind: text 1 │ if (x <= 'foo' || 'bar' < x) {} · ────────── ╰──── - help: Expected literal to be on the opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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[/(?0)/] <= 1) {} · ─────────── ╰──── - help: Expected literal to be on the opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + 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 opposite side of the operator. + help: Expected literal to be on the right side of <. From 9b9b1d60c682815d4fd664a891042d217f6b5b23 Mon Sep 17 00:00:00 2001 From: tbashiyy Date: Tue, 3 Dec 2024 05:59:36 +0900 Subject: [PATCH 3/4] fix docs --- crates/oxc_linter/src/rules/eslint/yoda.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/oxc_linter/src/rules/eslint/yoda.rs b/crates/oxc_linter/src/rules/eslint/yoda.rs index 2f5d5580d6209..cac2bf819d052 100644 --- a/crates/oxc_linter/src/rules/eslint/yoda.rs +++ b/crates/oxc_linter/src/rules/eslint/yoda.rs @@ -31,7 +31,7 @@ declare_oxc_lint!( /// ## What it does /// /// Require or disallow "Yoda" conditions. - /// This rule aims to enforce consistent style of conditions which compare a variable to a literal value. The literal value must always come first in the comparison. + /// This rule aims to enforce consistent style of conditions which compare a variable to a literal value. /// /// ## Why is this bad? /// From ca6b24f45784f0650f8ad1e35d06df23c07e32ee Mon Sep 17 00:00:00 2001 From: Cameron Clark Date: Wed, 4 Dec 2024 15:37:37 +0000 Subject: [PATCH 4/4] minor cleanup --- crates/oxc_linter/src/utils/unicorn.rs | 52 +++++++------------------- 1 file changed, 14 insertions(+), 38 deletions(-) diff --git a/crates/oxc_linter/src/utils/unicorn.rs b/crates/oxc_linter/src/utils/unicorn.rs index 4f32b71b949ea..b2b770379b5d3 100644 --- a/crates/oxc_linter/src/utils/unicorn.rs +++ b/crates/oxc_linter/src/utils/unicorn.rs @@ -178,13 +178,10 @@ 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(left_str), Expression::TemplateLiteral(right_str)) => { - return right_str.is_no_substitution_template() - && left_str.value == right_str.quasi().unwrap(); - } - (Expression::TemplateLiteral(left_str), Expression::StringLiteral(right_str)) => { - return left_str.is_no_substitution_template() - && left_str.quasi().unwrap() == 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) @@ -284,43 +281,22 @@ pub fn is_same_member_expression( MemberExpression::ComputedMemberExpression(right), ) = (left, right) { + // 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) { - // ex) x['/regex/'] === x[/regex/] - ( - Expression::StringLiteral(left_expression), - Expression::RegExpLiteral(right_expression), - ) => { - if left_expression.value != right_expression.raw { + // 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/] - ( - Expression::TemplateLiteral(left_expression), - Expression::RegExpLiteral(right_expression), - ) => { - if !(left_expression.is_no_substitution_template() - && left_expression.quasi().unwrap() == right_expression.raw) - { - return false; - } - } - // ex) x[/regex/] === x['/regex/'] - ( - Expression::RegExpLiteral(left_expression), - Expression::StringLiteral(right_expression), - ) => { - if left_expression.raw != right_expression.value { - return false; - } - } // ex) x[/regex/] === x[`/regex/`] - ( - Expression::RegExpLiteral(left_expression), - Expression::TemplateLiteral(right_expression), - ) => { - if !(right_expression.is_no_substitution_template() - && left_expression.raw == right_expression.quasi().unwrap()) + (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; }