From e8503158adc1b892a2829ed76560140dbfa79a39 Mon Sep 17 00:00:00 2001 From: huangtiandi1999 Date: Tue, 18 Feb 2025 10:40:18 +0800 Subject: [PATCH 1/8] feat: add operator-assignment rule --- crates/oxc_linter/src/rules.rs | 2 + .../src/rules/eslint/operator_assignment.rs | 397 ++++++++++++++++++ 2 files changed, 399 insertions(+) create mode 100644 crates/oxc_linter/src/rules/eslint/operator_assignment.rs diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 24487181fda8c..2f4e68f6028f7 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -146,6 +146,7 @@ mod eslint { pub mod no_var; pub mod no_void; pub mod no_with; + pub mod operator_assignment; pub mod prefer_exponentiation_operator; pub mod prefer_numeric_literals; pub mod prefer_object_has_own; @@ -651,6 +652,7 @@ oxc_macros::declare_all_lint_rules! { eslint::no_var, eslint::no_void, eslint::no_with, + eslint::operator_assignment, eslint::prefer_promise_reject_errors, eslint::prefer_exponentiation_operator, eslint::prefer_numeric_literals, diff --git a/crates/oxc_linter/src/rules/eslint/operator_assignment.rs b/crates/oxc_linter/src/rules/eslint/operator_assignment.rs new file mode 100644 index 0000000000000..e0cd9f682c534 --- /dev/null +++ b/crates/oxc_linter/src/rules/eslint/operator_assignment.rs @@ -0,0 +1,397 @@ +use oxc_ast::{ast::{AssignmentExpression, AssignmentOperator, AssignmentTarget, BinaryOperator, Expression}, match_simple_assignment_target, AstKind}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; +use serde_json::Value; + +use crate::{ + context::LintContext, + fixer::{RuleFix, RuleFixer}, + rule::Rule, + AstNode, +}; + +fn operator_assignment_diagnostic(span: Span) -> OxcDiagnostic { + // See for details + OxcDiagnostic::warn("Should be an imperative statement about what is wrong") + .with_help("Should be a command-like statement that tells the user how to fix the issue") + .with_label(span) +} + +#[derive(Debug, Default, PartialEq, Clone)] +enum Mode { + #[default] + Always, + Never, +} + +impl Mode { + pub fn from(raw: &str) -> Self { + if raw == "never" { + Self::Never + } else { + Self::Always + } + } +} + +#[derive(Debug, Default, Clone)] +pub struct OperatorAssignment { + mode: Mode, +} + +declare_oxc_lint!( + /// ### What it does + /// + /// + /// ### Why is this bad? + /// + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```js + /// FIXME: Tests will fail if examples are missing or syntactically incorrect. + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```js + /// FIXME: Tests will fail if examples are missing or syntactically incorrect. + /// ``` + OperatorAssignment, + eslint, + style, + fix +); + +impl Rule for OperatorAssignment { + fn from_configuration(value: Value) -> Self { + Self { + mode: value.get(0).and_then(Value::as_str).map(Mode::from).unwrap_or_default(), + } + } + + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + let AstKind::AssignmentExpression(assign_expr) = node.kind() else { + return; + }; + if self.mode == Mode::Never { + return; + } + + } +} + +fn verfy(expr: &AssignmentExpression, ctx: &LintContext) { + if expr.operator != AssignmentOperator::Assign { + return; + } + let left= &expr.left; + if let Expression::BinaryExpression(binary_expr) = &expr.right { + let binary_operator = binary_expr.operator; + if is_commutative_operator_with_shorthand(binary_operator) || is_non_commutative_operator_with_shorthand(binary_operator) { + let replace_operator = format!("{}=", binary_operator.as_str()); + if check_is_same_reference(&left, &binary_expr.left) { + + } + } + + } +} + +fn check_is_same_reference(left: &AssignmentTarget, right: &Expression) -> bool { + match left { + match_simple_assignment_target!(AssignmentTarget) => { + let simple_assignment_target = left.to_simple_assignment_target(); + return true; + } + _ => false, + } +} + +fn is_commutative_operator_with_shorthand(operator: BinaryOperator) -> bool { + match operator { + BinaryOperator::Multiplication + | BinaryOperator::BitwiseAnd + | BinaryOperator::BitwiseXOR + | BinaryOperator::BitwiseOR => true, + _ => false, + } +} + +fn is_non_commutative_operator_with_shorthand(operator: BinaryOperator) -> bool { + match operator { + BinaryOperator::Addition + | BinaryOperator::Subtraction + | BinaryOperator::Division + | BinaryOperator::Remainder + | BinaryOperator::ShiftLeft + | BinaryOperator::ShiftRight + | BinaryOperator::ShiftRightZeroFill + | BinaryOperator::Exponential => true, + _ => false, + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![]; + let fail = vec![ + ("x = x + y", None), + ]; + + // let pass = vec![ + // ("x = y", None), + // ("x = y + x", None), + // ("x += x + y", None), + // ("x = (x + y) - z", None), + // ("x -= y", None), + // ("x = y - x", None), + // ("x *= x", None), + // ("x = y * z", None), + // ("x = (x * y) * z", None), + // ("x = y / x", None), + // ("x /= y", None), + // ("x %= y", None), + // ("x <<= y", None), + // ("x >>= x >> y", None), + // ("x >>>= y", None), + // ("x &= y", None), + // ("x **= y", None), + // ("x ^= y ^ z", None), + // ("x |= x | y", None), + // ("x = x && y", None), + // ("x = x || y", None), + // ("x = x < y", None), + // ("x = x > y", None), + // ("x = x <= y", None), + // ("x = x >= y", None), + // ("x = x instanceof y", None), + // ("x = x in y", None), + // ("x = x == y", None), + // ("x = x != y", None), + // ("x = x === y", None), + // ("x = x !== y", None), + // ("x[y] = x['y'] + z", None), + // ("x.y = x['y'] / z", None), + // ("x.y = z + x.y", None), + // ("x[fn()] = x[fn()] + y", None), + // ("x += x + y", Some(serde_json::json!(["always"]))), + // ("x = x + y", Some(serde_json::json!(["never"]))), + // ("x = x ** y", Some(serde_json::json!(["never"]))), + // ("x = y ** x", None), + // ("x = x * y + z", None), + // ("this.x = this.y + z", Some(serde_json::json!(["always"]))), + // ("this.x = foo.x + y", Some(serde_json::json!(["always"]))), + // ("this.x = foo.this.x + y", Some(serde_json::json!(["always"]))), + // ("const foo = 0; class C { foo = foo + 1; }", None), + // ("x = x && y", Some(serde_json::json!(["always"]))), + // ("x = x || y", Some(serde_json::json!(["always"]))), + // ("x = x ?? y", Some(serde_json::json!(["always"]))), + // ("x &&= y", Some(serde_json::json!(["never"]))), + // ("x ||= y", Some(serde_json::json!(["never"]))), + // ("x ??= y", Some(serde_json::json!(["never"]))), + // ]; + + // let fail = vec![ + // ("x = x + y", None), + // ("x = x - y", None), + // ("x = x * y", None), + // ("x = y * x", None), + // ("x = (y * z) * x", None), + // ("x = x / y", None), + // ("x = x % y", None), + // ("x = x << y", None), + // ("x = x >> y", None), + // ("x = x >>> y", None), + // ("x = x & y", None), + // ("x = x ^ y", None), + // ("x = x | y", None), + // ("x[0] = x[0] - y", None), + // ("x.y[z['a']][0].b = x.y[z['a']][0].b * 2", None), + // ("x = x + y", Some(serde_json::json!(["always"]))), + // ("x = (x + y)", Some(serde_json::json!(["always"]))), + // ("x = x + (y)", Some(serde_json::json!(["always"]))), + // ("x += (y)", Some(serde_json::json!(["never"]))), + // ("x += y", Some(serde_json::json!(["never"]))), + // ("foo.bar = foo.bar + baz", None), + // ("foo.bar += baz", Some(serde_json::json!(["never"]))), + // ("this.foo = this.foo + bar", None), + // ("this.foo += bar", Some(serde_json::json!(["never"]))), + // ("foo.bar.baz = foo.bar.baz + qux", None), + // ("foo.bar.baz += qux", Some(serde_json::json!(["never"]))), + // ("this.foo.bar = this.foo.bar + baz", None), + // ("this.foo.bar += baz", Some(serde_json::json!(["never"]))), + // ("foo[bar] = foo[bar] + baz", None), + // ("this[foo] = this[foo] + bar", None), + // ("foo[bar] >>>= baz", Some(serde_json::json!(["never"]))), + // ("this[foo] >>>= bar", Some(serde_json::json!(["never"]))), + // ("foo[5] = foo[5] / baz", None), + // ("this[5] = this[5] / foo", None), + // ( + // "/*1*/x/*2*/./*3*/y/*4*/= x.y +/*5*/z/*6*/./*7*/w/*8*/;", + // Some(serde_json::json!(["always"])), + // ), + // ( + // "x // 1 + // . // 2 + // y // 3 + // = x.y + //4 + // z //5 + // . //6 + // w;", + // Some(serde_json::json!(["always"])), + // ), + // ("x = /*1*/ x + y", Some(serde_json::json!(["always"]))), + // ( + // "x = //1 + // x + y", + // Some(serde_json::json!(["always"])), + // ), + // ("x.y = x/*1*/.y + z", Some(serde_json::json!(["always"]))), + // ( + // "x.y = x. //1 + // y + z", + // Some(serde_json::json!(["always"])), + // ), + // ("x = x /*1*/ + y", Some(serde_json::json!(["always"]))), + // ( + // "x = x //1 + // + y", + // Some(serde_json::json!(["always"])), + // ), + // ("/*1*/x +=/*2*/y/*3*/;", Some(serde_json::json!(["never"]))), + // ( + // "x +=//1 + // y", + // Some(serde_json::json!(["never"])), + // ), + // ("(/*1*/x += y)", Some(serde_json::json!(["never"]))), + // ("x/*1*/+= y", Some(serde_json::json!(["never"]))), + // ( + // "x //1 + // += y", + // Some(serde_json::json!(["never"])), + // ), + // ("(/*1*/x) += y", Some(serde_json::json!(["never"]))), + // ("x/*1*/.y += z", Some(serde_json::json!(["never"]))), + // ( + // "x.//1 + // y += z", + // Some(serde_json::json!(["never"])), + // ), + // ("(foo.bar) ^= ((((((((((((((((baz))))))))))))))))", Some(serde_json::json!(["never"]))), + // ("foo = foo ** bar", None), + // ("foo **= bar", Some(serde_json::json!(["never"]))), + // ("foo *= bar + 1", Some(serde_json::json!(["never"]))), + // ("foo -= bar - baz", Some(serde_json::json!(["never"]))), + // ("foo += bar + baz", Some(serde_json::json!(["never"]))), + // ("foo += bar = 1", Some(serde_json::json!(["never"]))), + // ("foo *= (bar + 1)", Some(serde_json::json!(["never"]))), + // ("foo+=-bar", Some(serde_json::json!(["never"]))), + // ("foo/=bar", Some(serde_json::json!(["never"]))), + // ("foo/=/**/bar", Some(serde_json::json!(["never"]))), + // ( + // "foo/=// + // bar", + // Some(serde_json::json!(["never"])), + // ), + // ("foo/=/^bar$/", Some(serde_json::json!(["never"]))), + // ("foo+=+bar", Some(serde_json::json!(["never"]))), + // ("foo+= +bar", Some(serde_json::json!(["never"]))), + // ("foo+=/**/+bar", Some(serde_json::json!(["never"]))), + // ("foo+=+bar===baz", Some(serde_json::json!(["never"]))), + // ("(obj?.a).b = (obj?.a).b + y", None), + // ("obj.a = obj?.a + b", None), + // ]; + + // let fix = vec![ + // ("x = x + y", "x += y", None), + // ("x = x - y", "x -= y", None), + // ("x = x * y", "x *= y", None), + // ("x = x / y", "x /= y", None), + // ("x = x % y", "x %= y", None), + // ("x = x << y", "x <<= y", None), + // ("x = x >> y", "x >>= y", None), + // ("x = x >>> y", "x >>>= y", None), + // ("x = x & y", "x &= y", None), + // ("x = x ^ y", "x ^= y", None), + // ("x = x | y", "x |= y", None), + // ("x[0] = x[0] - y", "x[0] -= y", None), + // ("x = x + y", "x += y", Some(serde_json::json!(["always"]))), + // ("x = (x + y)", "x += y", Some(serde_json::json!(["always"]))), + // ("x = x + (y)", "x += (y)", Some(serde_json::json!(["always"]))), + // ("x += (y)", "x = x + (y)", Some(serde_json::json!(["never"]))), + // ("x += y", "x = x + y", Some(serde_json::json!(["never"]))), + // ("foo.bar = foo.bar + baz", "foo.bar += baz", None), + // ("foo.bar += baz", "foo.bar = foo.bar + baz", Some(serde_json::json!(["never"]))), + // ("this.foo = this.foo + bar", "this.foo += bar", None), + // ("this.foo += bar", "this.foo = this.foo + bar", Some(serde_json::json!(["never"]))), + // ("foo[5] = foo[5] / baz", "foo[5] /= baz", None), + // ("this[5] = this[5] / foo", "this[5] /= foo", None), + // ( + // "/*1*/x/*2*/./*3*/y/*4*/= x.y +/*5*/z/*6*/./*7*/w/*8*/;", + // "/*1*/x/*2*/./*3*/y/*4*/+=/*5*/z/*6*/./*7*/w/*8*/;", + // Some(serde_json::json!(["always"])), + // ), + // ( + // "x // 1 + // . // 2 + // y // 3 + // = x.y + //4 + // z //5 + // . //6 + // w;", + // "x // 1 + // . // 2 + // y // 3 + // += //4 + // z //5 + // . //6 + // w;", + // Some(serde_json::json!(["always"])), + // ), + // ("/*1*/x +=/*2*/y/*3*/;", "/*1*/x = x +/*2*/y/*3*/;", Some(serde_json::json!(["never"]))), + // ( + // "x +=//1 + // y", + // "x = x +//1 + // y", + // Some(serde_json::json!(["never"])), + // ), + // ("(/*1*/x += y)", "(/*1*/x = x + y)", Some(serde_json::json!(["never"]))), + // ( + // "(foo.bar) ^= ((((((((((((((((baz))))))))))))))))", + // "(foo.bar) = (foo.bar) ^ ((((((((((((((((baz))))))))))))))))", + // Some(serde_json::json!(["never"])), + // ), + // ("foo = foo ** bar", "foo **= bar", None), + // ("foo **= bar", "foo = foo ** bar", Some(serde_json::json!(["never"]))), + // ("foo *= bar + 1", "foo = foo * (bar + 1)", Some(serde_json::json!(["never"]))), + // ("foo -= bar - baz", "foo = foo - (bar - baz)", Some(serde_json::json!(["never"]))), + // ("foo += bar + baz", "foo = foo + (bar + baz)", Some(serde_json::json!(["never"]))), + // ("foo += bar = 1", "foo = foo + (bar = 1)", Some(serde_json::json!(["never"]))), + // ("foo *= (bar + 1)", "foo = foo * (bar + 1)", Some(serde_json::json!(["never"]))), + // ("foo+=-bar", "foo= foo+-bar", Some(serde_json::json!(["never"]))), + // ("foo/=bar", "foo= foo/bar", Some(serde_json::json!(["never"]))), + // ("foo/=/**/bar", "foo= foo/ /**/bar", Some(serde_json::json!(["never"]))), + // ( + // "foo/=// + // bar", + // "foo= foo/ // + // bar", + // Some(serde_json::json!(["never"])), + // ), + // ("foo/=/^bar$/", "foo= foo/ /^bar$/", Some(serde_json::json!(["never"]))), + // ("foo+=+bar", "foo= foo+ +bar", Some(serde_json::json!(["never"]))), + // ("foo+= +bar", "foo= foo+ +bar", Some(serde_json::json!(["never"]))), + // ("foo+=/**/+bar", "foo= foo+/**/+bar", Some(serde_json::json!(["never"]))), + // ("foo+=+bar===baz", "foo= foo+(+bar===baz)", Some(serde_json::json!(["never"]))), + // ]; + // Tester::new(OperatorAssignment::NAME, OperatorAssignment::PLUGIN, pass, fail) + // .expect_fix(fix) + // .test_and_snapshot(); + Tester::new(OperatorAssignment::NAME, OperatorAssignment::PLUGIN, pass, fail).test_and_snapshot(); +} From 071291a1941dcc33be97420f77df7df4d43c010b Mon Sep 17 00:00:00 2001 From: huangtiandi1999 Date: Tue, 18 Feb 2025 22:31:38 +0800 Subject: [PATCH 2/8] implement primary logic --- .../src/rules/eslint/operator_assignment.rs | 544 ++++++++++-------- .../snapshots/eslint_operator_assignment.snap | 421 ++++++++++++++ 2 files changed, 738 insertions(+), 227 deletions(-) create mode 100644 crates/oxc_linter/src/snapshots/eslint_operator_assignment.snap diff --git a/crates/oxc_linter/src/rules/eslint/operator_assignment.rs b/crates/oxc_linter/src/rules/eslint/operator_assignment.rs index e0cd9f682c534..07e73e3fa6845 100644 --- a/crates/oxc_linter/src/rules/eslint/operator_assignment.rs +++ b/crates/oxc_linter/src/rules/eslint/operator_assignment.rs @@ -1,24 +1,27 @@ -use oxc_ast::{ast::{AssignmentExpression, AssignmentOperator, AssignmentTarget, BinaryOperator, Expression}, match_simple_assignment_target, AstKind}; +use oxc_ast::{ + ast::{ + AssignmentExpression, AssignmentOperator, AssignmentTarget, BinaryOperator, Expression, + MemberExpression, SimpleAssignmentTarget, + }, + match_simple_assignment_target, AstKind, +}; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::Span; use serde_json::Value; -use crate::{ - context::LintContext, - fixer::{RuleFix, RuleFixer}, - rule::Rule, - AstNode, -}; +use crate::{context::LintContext, rule::Rule, utils::is_same_member_expression, AstNode}; -fn operator_assignment_diagnostic(span: Span) -> OxcDiagnostic { - // See for details - OxcDiagnostic::warn("Should be an imperative statement about what is wrong") - .with_help("Should be a command-like statement that tells the user how to fix the issue") - .with_label(span) +fn operator_assignment_diagnostic(mode: Mode, span: Span, operator: &str) -> OxcDiagnostic { + let msg = if Mode::Never == mode { + format!("Unexpected operator assignment ({operator}) shorthand.") + } else { + format!("Assignment (=) can be replaced with operator assignment ({operator}).") + }; + OxcDiagnostic::warn(msg).with_label(span) } -#[derive(Debug, Default, PartialEq, Clone)] +#[derive(Debug, Default, PartialEq, Clone, Copy)] enum Mode { #[default] Always, @@ -42,7 +45,7 @@ pub struct OperatorAssignment { declare_oxc_lint!( /// ### What it does - /// + /// This rule requires or disallows assignment operator shorthand where possible. /// /// ### Why is this bad? /// @@ -61,14 +64,12 @@ declare_oxc_lint!( OperatorAssignment, eslint, style, - fix + pending ); impl Rule for OperatorAssignment { fn from_configuration(value: Value) -> Self { - Self { - mode: value.get(0).and_then(Value::as_str).map(Mode::from).unwrap_or_default(), - } + Self { mode: value.get(0).and_then(Value::as_str).map(Mode::from).unwrap_or_default() } } fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { @@ -76,237 +77,325 @@ impl Rule for OperatorAssignment { return; }; if self.mode == Mode::Never { - return; + prohibit(assign_expr, self.mode, ctx); + } else { + verfy(assign_expr, self.mode, ctx); } - } } -fn verfy(expr: &AssignmentExpression, ctx: &LintContext) { +fn verfy(expr: &AssignmentExpression, mode: Mode, ctx: &LintContext) { if expr.operator != AssignmentOperator::Assign { return; } - let left= &expr.left; - if let Expression::BinaryExpression(binary_expr) = &expr.right { + let left = &expr.left; + + if let Expression::BinaryExpression(binary_expr) = &expr.right.without_parentheses() { let binary_operator = binary_expr.operator; - if is_commutative_operator_with_shorthand(binary_operator) || is_non_commutative_operator_with_shorthand(binary_operator) { + let is_commutative_operator = is_commutative_operator_with_shorthand(binary_operator); + let is_non_commutative_operator = + is_non_commutative_operator_with_shorthand(binary_operator); + // for always + if is_commutative_operator || is_non_commutative_operator { let replace_operator = format!("{}=", binary_operator.as_str()); - if check_is_same_reference(&left, &binary_expr.left) { - + if check_is_same_reference(left, &binary_expr.left, ctx) { + ctx.diagnostic(operator_assignment_diagnostic(mode, expr.span, &replace_operator)); + } else if check_is_same_reference(left, &binary_expr.right, ctx) + && is_commutative_operator + { + // todo fix + ctx.diagnostic(operator_assignment_diagnostic(mode, expr.span, &replace_operator)); } } + } +} + +fn prohibit(expr: &AssignmentExpression, mode: Mode, ctx: &LintContext) { + if expr.operator != AssignmentOperator::Assign + && !check_is_logical_assign_operator(expr.operator) + { + if can_be_fixed(&expr.left) { + // todo fix + ctx.diagnostic(operator_assignment_diagnostic(mode, expr.span, expr.operator.as_str())); + } else { + ctx.diagnostic(operator_assignment_diagnostic(mode, expr.span, expr.operator.as_str())); + } + } +} + +fn can_be_fixed(target: &AssignmentTarget) -> bool { + match target { + match_simple_assignment_target!(AssignmentTarget) => { + let simple_assignment_target = target.to_simple_assignment_target(); + if matches!( + simple_assignment_target, + SimpleAssignmentTarget::AssignmentTargetIdentifier(_) + ) { + return true; + } + let Some(expr) = simple_assignment_target.as_member_expression() else { + return false; + }; + match expr { + MemberExpression::ComputedMemberExpression(computed_expr) => { + matches!( + computed_expr.object, + Expression::Identifier(_) | Expression::ThisExpression(_) + ) && computed_expr.expression.is_literal() + } + MemberExpression::StaticMemberExpression(static_expr) => { + matches!( + static_expr.object, + Expression::Identifier(_) | Expression::ThisExpression(_) + ) + } + MemberExpression::PrivateFieldExpression(_) => false, + } + } + _ => false, } } -fn check_is_same_reference(left: &AssignmentTarget, right: &Expression) -> bool { +fn check_is_same_reference(left: &AssignmentTarget, right: &Expression, ctx: &LintContext) -> bool { match left { match_simple_assignment_target!(AssignmentTarget) => { let simple_assignment_target = left.to_simple_assignment_target(); - return true; + if let SimpleAssignmentTarget::AssignmentTargetIdentifier(id1) = + simple_assignment_target + { + return matches!(right, Expression::Identifier(id2) if id2.name == id1.name); + } + + if let Some(left_member_expr) = simple_assignment_target.as_member_expression() { + if let Some(right_member_expr) = right.without_parentheses().get_member_expr() { + // x.y vs x['y'] + if (matches!(left_member_expr, MemberExpression::ComputedMemberExpression(_)) + && !matches!( + right_member_expr, + MemberExpression::ComputedMemberExpression(_) + )) + || (!matches!( + left_member_expr, + MemberExpression::ComputedMemberExpression(_) + ) && matches!( + right_member_expr, + MemberExpression::ComputedMemberExpression(_) + )) + { + return false; + } + return is_same_member_expression(left_member_expr, right_member_expr, ctx); + } + } + false } _ => false, } } fn is_commutative_operator_with_shorthand(operator: BinaryOperator) -> bool { - match operator { + matches!( + operator, BinaryOperator::Multiplication - | BinaryOperator::BitwiseAnd - | BinaryOperator::BitwiseXOR - | BinaryOperator::BitwiseOR => true, - _ => false, - } + | BinaryOperator::BitwiseAnd + | BinaryOperator::BitwiseXOR + | BinaryOperator::BitwiseOR + ) } fn is_non_commutative_operator_with_shorthand(operator: BinaryOperator) -> bool { - match operator { + matches!( + operator, BinaryOperator::Addition - | BinaryOperator::Subtraction - | BinaryOperator::Division - | BinaryOperator::Remainder - | BinaryOperator::ShiftLeft - | BinaryOperator::ShiftRight - | BinaryOperator::ShiftRightZeroFill - | BinaryOperator::Exponential => true, - _ => false, - } + | BinaryOperator::Subtraction + | BinaryOperator::Division + | BinaryOperator::Remainder + | BinaryOperator::ShiftLeft + | BinaryOperator::ShiftRight + | BinaryOperator::ShiftRightZeroFill + | BinaryOperator::Exponential + ) +} + +fn check_is_logical_assign_operator(operator: AssignmentOperator) -> bool { + matches!( + operator, + AssignmentOperator::LogicalAnd + | AssignmentOperator::LogicalOr + | AssignmentOperator::LogicalNullish + ) } #[test] fn test() { use crate::tester::Tester; - let pass = vec![]; + let pass = vec![ + ("x = y", None), + ("x = y + x", None), + ("x += x + y", None), + ("x = (x + y) - z", None), + ("x -= y", None), + ("x = y - x", None), + ("x *= x", None), + ("x = y * z", None), + ("x = (x * y) * z", None), + ("x = y / x", None), + ("x /= y", None), + ("x %= y", None), + ("x <<= y", None), + ("x >>= x >> y", None), + ("x >>>= y", None), + ("x &= y", None), + ("x **= y", None), + ("x ^= y ^ z", None), + ("x |= x | y", None), + ("x = x && y", None), + ("x = x || y", None), + ("x = x < y", None), + ("x = x > y", None), + ("x = x <= y", None), + ("x = x >= y", None), + ("x = x instanceof y", None), + ("x = x in y", None), + ("x = x == y", None), + ("x = x != y", None), + ("x = x === y", None), + ("x = x !== y", None), + ("x[y] = x['y'] + z", None), + ("x.y = x['y'] / z", None), + ("x.y = z + x.y", None), + ("x[fn()] = x[fn()] + y", None), + ("x += x + y", Some(serde_json::json!(["always"]))), + ("x = x + y", Some(serde_json::json!(["never"]))), + ("x = x ** y", Some(serde_json::json!(["never"]))), + ("x = y ** x", None), + ("x = x * y + z", None), + ("this.x = this.y + z", Some(serde_json::json!(["always"]))), + ("this.x = foo.x + y", Some(serde_json::json!(["always"]))), + ("this.x = foo.this.x + y", Some(serde_json::json!(["always"]))), + ("const foo = 0; class C { foo = foo + 1; }", None), + ("x = x && y", Some(serde_json::json!(["always"]))), + ("x = x || y", Some(serde_json::json!(["always"]))), + ("x = x ?? y", Some(serde_json::json!(["always"]))), + ("x &&= y", Some(serde_json::json!(["never"]))), + ("x ||= y", Some(serde_json::json!(["never"]))), + ("x ??= y", Some(serde_json::json!(["never"]))), + ]; + let fail = vec![ ("x = x + y", None), + ("x = x - y", None), + ("x = x * y", None), + ("x = y * x", None), + ("x = (y * z) * x", None), + ("x = x / y", None), + ("x = x % y", None), + ("x = x << y", None), + ("x = x >> y", None), + ("x = x >>> y", None), + ("x = x & y", None), + ("x = x ^ y", None), + ("x = x | y", None), + ("x[0] = x[0] - y", None), + ("x.y[z['a']][0].b = x.y[z['a']][0].b * 2", None), + ("x = x + y", Some(serde_json::json!(["always"]))), + ("x = (x + y)", Some(serde_json::json!(["always"]))), + ("x = x + (y)", Some(serde_json::json!(["always"]))), + ("x += (y)", Some(serde_json::json!(["never"]))), + ("x += y", Some(serde_json::json!(["never"]))), + ("foo.bar = foo.bar + baz", None), + ("foo.bar += baz", Some(serde_json::json!(["never"]))), + ("this.foo = this.foo + bar", None), + ("this.foo += bar", Some(serde_json::json!(["never"]))), + ("foo.bar.baz = foo.bar.baz + qux", None), + ("foo.bar.baz += qux", Some(serde_json::json!(["never"]))), + ("this.foo.bar = this.foo.bar + baz", None), + ("this.foo.bar += baz", Some(serde_json::json!(["never"]))), + ("foo[bar] = foo[bar] + baz", None), + ("this[foo] = this[foo] + bar", None), + ("foo[bar] >>>= baz", Some(serde_json::json!(["never"]))), + ("this[foo] >>>= bar", Some(serde_json::json!(["never"]))), + ("foo[5] = foo[5] / baz", None), + ("this[5] = this[5] / foo", None), + ( + "/*1*/x/*2*/./*3*/y/*4*/= x.y +/*5*/z/*6*/./*7*/w/*8*/;", + Some(serde_json::json!(["always"])), + ), + ( + "x // 1 + . // 2 + y // 3 + = x.y + //4 + z //5 + . //6 + w;", + Some(serde_json::json!(["always"])), + ), + ("x = /*1*/ x + y", Some(serde_json::json!(["always"]))), + ( + "x = //1 + x + y", + Some(serde_json::json!(["always"])), + ), + ("x.y = x/*1*/.y + z", Some(serde_json::json!(["always"]))), + ( + "x.y = x. //1 + y + z", + Some(serde_json::json!(["always"])), + ), + ("x = x /*1*/ + y", Some(serde_json::json!(["always"]))), + ( + "x = x //1 + + y", + Some(serde_json::json!(["always"])), + ), + ("/*1*/x +=/*2*/y/*3*/;", Some(serde_json::json!(["never"]))), + ( + "x +=//1 + y", + Some(serde_json::json!(["never"])), + ), + ("(/*1*/x += y)", Some(serde_json::json!(["never"]))), + ("x/*1*/+= y", Some(serde_json::json!(["never"]))), + ( + "x //1 + += y", + Some(serde_json::json!(["never"])), + ), + ("(/*1*/x) += y", Some(serde_json::json!(["never"]))), + ("x/*1*/.y += z", Some(serde_json::json!(["never"]))), + ( + "x.//1 + y += z", + Some(serde_json::json!(["never"])), + ), + ("(foo.bar) ^= ((((((((((((((((baz))))))))))))))))", Some(serde_json::json!(["never"]))), + ("foo = foo ** bar", None), + ("foo **= bar", Some(serde_json::json!(["never"]))), + ("foo *= bar + 1", Some(serde_json::json!(["never"]))), + ("foo -= bar - baz", Some(serde_json::json!(["never"]))), + ("foo += bar + baz", Some(serde_json::json!(["never"]))), + ("foo += bar = 1", Some(serde_json::json!(["never"]))), + ("foo *= (bar + 1)", Some(serde_json::json!(["never"]))), + ("foo+=-bar", Some(serde_json::json!(["never"]))), + ("foo/=bar", Some(serde_json::json!(["never"]))), + ("foo/=/**/bar", Some(serde_json::json!(["never"]))), + ( + "foo/=// + bar", + Some(serde_json::json!(["never"])), + ), + ("foo/=/^bar$/", Some(serde_json::json!(["never"]))), + ("foo+=+bar", Some(serde_json::json!(["never"]))), + ("foo+= +bar", Some(serde_json::json!(["never"]))), + ("foo+=/**/+bar", Some(serde_json::json!(["never"]))), + ("foo+=+bar===baz", Some(serde_json::json!(["never"]))), + ("(obj?.a).b = (obj?.a).b + y", None), + ("obj.a = obj?.a + b", None), ]; - // let pass = vec![ - // ("x = y", None), - // ("x = y + x", None), - // ("x += x + y", None), - // ("x = (x + y) - z", None), - // ("x -= y", None), - // ("x = y - x", None), - // ("x *= x", None), - // ("x = y * z", None), - // ("x = (x * y) * z", None), - // ("x = y / x", None), - // ("x /= y", None), - // ("x %= y", None), - // ("x <<= y", None), - // ("x >>= x >> y", None), - // ("x >>>= y", None), - // ("x &= y", None), - // ("x **= y", None), - // ("x ^= y ^ z", None), - // ("x |= x | y", None), - // ("x = x && y", None), - // ("x = x || y", None), - // ("x = x < y", None), - // ("x = x > y", None), - // ("x = x <= y", None), - // ("x = x >= y", None), - // ("x = x instanceof y", None), - // ("x = x in y", None), - // ("x = x == y", None), - // ("x = x != y", None), - // ("x = x === y", None), - // ("x = x !== y", None), - // ("x[y] = x['y'] + z", None), - // ("x.y = x['y'] / z", None), - // ("x.y = z + x.y", None), - // ("x[fn()] = x[fn()] + y", None), - // ("x += x + y", Some(serde_json::json!(["always"]))), - // ("x = x + y", Some(serde_json::json!(["never"]))), - // ("x = x ** y", Some(serde_json::json!(["never"]))), - // ("x = y ** x", None), - // ("x = x * y + z", None), - // ("this.x = this.y + z", Some(serde_json::json!(["always"]))), - // ("this.x = foo.x + y", Some(serde_json::json!(["always"]))), - // ("this.x = foo.this.x + y", Some(serde_json::json!(["always"]))), - // ("const foo = 0; class C { foo = foo + 1; }", None), - // ("x = x && y", Some(serde_json::json!(["always"]))), - // ("x = x || y", Some(serde_json::json!(["always"]))), - // ("x = x ?? y", Some(serde_json::json!(["always"]))), - // ("x &&= y", Some(serde_json::json!(["never"]))), - // ("x ||= y", Some(serde_json::json!(["never"]))), - // ("x ??= y", Some(serde_json::json!(["never"]))), - // ]; - - // let fail = vec![ - // ("x = x + y", None), - // ("x = x - y", None), - // ("x = x * y", None), - // ("x = y * x", None), - // ("x = (y * z) * x", None), - // ("x = x / y", None), - // ("x = x % y", None), - // ("x = x << y", None), - // ("x = x >> y", None), - // ("x = x >>> y", None), - // ("x = x & y", None), - // ("x = x ^ y", None), - // ("x = x | y", None), - // ("x[0] = x[0] - y", None), - // ("x.y[z['a']][0].b = x.y[z['a']][0].b * 2", None), - // ("x = x + y", Some(serde_json::json!(["always"]))), - // ("x = (x + y)", Some(serde_json::json!(["always"]))), - // ("x = x + (y)", Some(serde_json::json!(["always"]))), - // ("x += (y)", Some(serde_json::json!(["never"]))), - // ("x += y", Some(serde_json::json!(["never"]))), - // ("foo.bar = foo.bar + baz", None), - // ("foo.bar += baz", Some(serde_json::json!(["never"]))), - // ("this.foo = this.foo + bar", None), - // ("this.foo += bar", Some(serde_json::json!(["never"]))), - // ("foo.bar.baz = foo.bar.baz + qux", None), - // ("foo.bar.baz += qux", Some(serde_json::json!(["never"]))), - // ("this.foo.bar = this.foo.bar + baz", None), - // ("this.foo.bar += baz", Some(serde_json::json!(["never"]))), - // ("foo[bar] = foo[bar] + baz", None), - // ("this[foo] = this[foo] + bar", None), - // ("foo[bar] >>>= baz", Some(serde_json::json!(["never"]))), - // ("this[foo] >>>= bar", Some(serde_json::json!(["never"]))), - // ("foo[5] = foo[5] / baz", None), - // ("this[5] = this[5] / foo", None), - // ( - // "/*1*/x/*2*/./*3*/y/*4*/= x.y +/*5*/z/*6*/./*7*/w/*8*/;", - // Some(serde_json::json!(["always"])), - // ), - // ( - // "x // 1 - // . // 2 - // y // 3 - // = x.y + //4 - // z //5 - // . //6 - // w;", - // Some(serde_json::json!(["always"])), - // ), - // ("x = /*1*/ x + y", Some(serde_json::json!(["always"]))), - // ( - // "x = //1 - // x + y", - // Some(serde_json::json!(["always"])), - // ), - // ("x.y = x/*1*/.y + z", Some(serde_json::json!(["always"]))), - // ( - // "x.y = x. //1 - // y + z", - // Some(serde_json::json!(["always"])), - // ), - // ("x = x /*1*/ + y", Some(serde_json::json!(["always"]))), - // ( - // "x = x //1 - // + y", - // Some(serde_json::json!(["always"])), - // ), - // ("/*1*/x +=/*2*/y/*3*/;", Some(serde_json::json!(["never"]))), - // ( - // "x +=//1 - // y", - // Some(serde_json::json!(["never"])), - // ), - // ("(/*1*/x += y)", Some(serde_json::json!(["never"]))), - // ("x/*1*/+= y", Some(serde_json::json!(["never"]))), - // ( - // "x //1 - // += y", - // Some(serde_json::json!(["never"])), - // ), - // ("(/*1*/x) += y", Some(serde_json::json!(["never"]))), - // ("x/*1*/.y += z", Some(serde_json::json!(["never"]))), - // ( - // "x.//1 - // y += z", - // Some(serde_json::json!(["never"])), - // ), - // ("(foo.bar) ^= ((((((((((((((((baz))))))))))))))))", Some(serde_json::json!(["never"]))), - // ("foo = foo ** bar", None), - // ("foo **= bar", Some(serde_json::json!(["never"]))), - // ("foo *= bar + 1", Some(serde_json::json!(["never"]))), - // ("foo -= bar - baz", Some(serde_json::json!(["never"]))), - // ("foo += bar + baz", Some(serde_json::json!(["never"]))), - // ("foo += bar = 1", Some(serde_json::json!(["never"]))), - // ("foo *= (bar + 1)", Some(serde_json::json!(["never"]))), - // ("foo+=-bar", Some(serde_json::json!(["never"]))), - // ("foo/=bar", Some(serde_json::json!(["never"]))), - // ("foo/=/**/bar", Some(serde_json::json!(["never"]))), - // ( - // "foo/=// - // bar", - // Some(serde_json::json!(["never"])), - // ), - // ("foo/=/^bar$/", Some(serde_json::json!(["never"]))), - // ("foo+=+bar", Some(serde_json::json!(["never"]))), - // ("foo+= +bar", Some(serde_json::json!(["never"]))), - // ("foo+=/**/+bar", Some(serde_json::json!(["never"]))), - // ("foo+=+bar===baz", Some(serde_json::json!(["never"]))), - // ("(obj?.a).b = (obj?.a).b + y", None), - // ("obj.a = obj?.a + b", None), - // ]; - // let fix = vec![ // ("x = x + y", "x += y", None), // ("x = x - y", "x -= y", None), @@ -338,27 +427,27 @@ fn test() { // ), // ( // "x // 1 - // . // 2 - // y // 3 - // = x.y + //4 - // z //5 - // . //6 - // w;", + // . // 2 + // y // 3 + // = x.y + //4 + // z //5 + // . //6 + // w;", // "x // 1 - // . // 2 - // y // 3 - // += //4 - // z //5 - // . //6 - // w;", + // . // 2 + // y // 3 + // += //4 + // z //5 + // . //6 + // w;", // Some(serde_json::json!(["always"])), // ), // ("/*1*/x +=/*2*/y/*3*/;", "/*1*/x = x +/*2*/y/*3*/;", Some(serde_json::json!(["never"]))), // ( // "x +=//1 - // y", + // y", // "x = x +//1 - // y", + // y", // Some(serde_json::json!(["never"])), // ), // ("(/*1*/x += y)", "(/*1*/x = x + y)", Some(serde_json::json!(["never"]))), @@ -379,9 +468,9 @@ fn test() { // ("foo/=/**/bar", "foo= foo/ /**/bar", Some(serde_json::json!(["never"]))), // ( // "foo/=// - // bar", + // bar", // "foo= foo/ // - // bar", + // bar", // Some(serde_json::json!(["never"])), // ), // ("foo/=/^bar$/", "foo= foo/ /^bar$/", Some(serde_json::json!(["never"]))), @@ -393,5 +482,6 @@ fn test() { // Tester::new(OperatorAssignment::NAME, OperatorAssignment::PLUGIN, pass, fail) // .expect_fix(fix) // .test_and_snapshot(); - Tester::new(OperatorAssignment::NAME, OperatorAssignment::PLUGIN, pass, fail).test_and_snapshot(); + Tester::new(OperatorAssignment::NAME, OperatorAssignment::PLUGIN, pass, fail) + .test_and_snapshot(); } diff --git a/crates/oxc_linter/src/snapshots/eslint_operator_assignment.snap b/crates/oxc_linter/src/snapshots/eslint_operator_assignment.snap new file mode 100644 index 0000000000000..e21851206fb7f --- /dev/null +++ b/crates/oxc_linter/src/snapshots/eslint_operator_assignment.snap @@ -0,0 +1,421 @@ +--- +source: crates/oxc_linter/src/tester.rs +--- + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (+=). + ╭─[operator_assignment.tsx:1:1] + 1 │ x = x + y + · ───────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (-=). + ╭─[operator_assignment.tsx:1:1] + 1 │ x = x - y + · ───────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (*=). + ╭─[operator_assignment.tsx:1:1] + 1 │ x = x * y + · ───────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (*=). + ╭─[operator_assignment.tsx:1:1] + 1 │ x = y * x + · ───────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (*=). + ╭─[operator_assignment.tsx:1:1] + 1 │ x = (y * z) * x + · ─────────────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (/=). + ╭─[operator_assignment.tsx:1:1] + 1 │ x = x / y + · ───────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (%=). + ╭─[operator_assignment.tsx:1:1] + 1 │ x = x % y + · ───────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (<<=). + ╭─[operator_assignment.tsx:1:1] + 1 │ x = x << y + · ────────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (>>=). + ╭─[operator_assignment.tsx:1:1] + 1 │ x = x >> y + · ────────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (>>>=). + ╭─[operator_assignment.tsx:1:1] + 1 │ x = x >>> y + · ─────────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (&=). + ╭─[operator_assignment.tsx:1:1] + 1 │ x = x & y + · ───────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (^=). + ╭─[operator_assignment.tsx:1:1] + 1 │ x = x ^ y + · ───────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (|=). + ╭─[operator_assignment.tsx:1:1] + 1 │ x = x | y + · ───────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (-=). + ╭─[operator_assignment.tsx:1:1] + 1 │ x[0] = x[0] - y + · ─────────────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (*=). + ╭─[operator_assignment.tsx:1:1] + 1 │ x.y[z['a']][0].b = x.y[z['a']][0].b * 2 + · ─────────────────────────────────────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (+=). + ╭─[operator_assignment.tsx:1:1] + 1 │ x = x + y + · ───────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (+=). + ╭─[operator_assignment.tsx:1:1] + 1 │ x = (x + y) + · ─────────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (+=). + ╭─[operator_assignment.tsx:1:1] + 1 │ x = x + (y) + · ─────────── + ╰──── + + ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. + ╭─[operator_assignment.tsx:1:1] + 1 │ x += (y) + · ──────── + ╰──── + + ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. + ╭─[operator_assignment.tsx:1:1] + 1 │ x += y + · ────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (+=). + ╭─[operator_assignment.tsx:1:1] + 1 │ foo.bar = foo.bar + baz + · ─────────────────────── + ╰──── + + ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. + ╭─[operator_assignment.tsx:1:1] + 1 │ foo.bar += baz + · ────────────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (+=). + ╭─[operator_assignment.tsx:1:1] + 1 │ this.foo = this.foo + bar + · ───────────────────────── + ╰──── + + ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. + ╭─[operator_assignment.tsx:1:1] + 1 │ this.foo += bar + · ─────────────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (+=). + ╭─[operator_assignment.tsx:1:1] + 1 │ foo.bar.baz = foo.bar.baz + qux + · ─────────────────────────────── + ╰──── + + ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. + ╭─[operator_assignment.tsx:1:1] + 1 │ foo.bar.baz += qux + · ────────────────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (+=). + ╭─[operator_assignment.tsx:1:1] + 1 │ this.foo.bar = this.foo.bar + baz + · ───────────────────────────────── + ╰──── + + ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. + ╭─[operator_assignment.tsx:1:1] + 1 │ this.foo.bar += baz + · ─────────────────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (+=). + ╭─[operator_assignment.tsx:1:1] + 1 │ foo[bar] = foo[bar] + baz + · ───────────────────────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (+=). + ╭─[operator_assignment.tsx:1:1] + 1 │ this[foo] = this[foo] + bar + · ─────────────────────────── + ╰──── + + ⚠ eslint(operator-assignment): Unexpected operator assignment (>>>=) shorthand. + ╭─[operator_assignment.tsx:1:1] + 1 │ foo[bar] >>>= baz + · ───────────────── + ╰──── + + ⚠ eslint(operator-assignment): Unexpected operator assignment (>>>=) shorthand. + ╭─[operator_assignment.tsx:1:1] + 1 │ this[foo] >>>= bar + · ────────────────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (/=). + ╭─[operator_assignment.tsx:1:1] + 1 │ foo[5] = foo[5] / baz + · ───────────────────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (/=). + ╭─[operator_assignment.tsx:1:1] + 1 │ this[5] = this[5] / foo + · ─────────────────────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (+=). + ╭─[operator_assignment.tsx:1:6] + 1 │ /*1*/x/*2*/./*3*/y/*4*/= x.y +/*5*/z/*6*/./*7*/w/*8*/; + · ─────────────────────────────────────────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (+=). + ╭─[operator_assignment.tsx:1:1] + 1 │ ╭─▶ x // 1 + 2 │ │ . // 2 + 3 │ │ y // 3 + 4 │ │ = x.y + //4 + 5 │ │ z //5 + 6 │ │ . //6 + 7 │ ╰─▶ w; + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (+=). + ╭─[operator_assignment.tsx:1:1] + 1 │ x = /*1*/ x + y + · ─────────────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (+=). + ╭─[operator_assignment.tsx:1:1] + 1 │ ╭─▶ x = //1 + 2 │ ╰─▶ x + y + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (+=). + ╭─[operator_assignment.tsx:1:1] + 1 │ x.y = x/*1*/.y + z + · ────────────────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (+=). + ╭─[operator_assignment.tsx:1:1] + 1 │ ╭─▶ x.y = x. //1 + 2 │ ╰─▶ y + z + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (+=). + ╭─[operator_assignment.tsx:1:1] + 1 │ x = x /*1*/ + y + · ─────────────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (+=). + ╭─[operator_assignment.tsx:1:1] + 1 │ ╭─▶ x = x //1 + 2 │ ╰─▶ + y + ╰──── + + ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. + ╭─[operator_assignment.tsx:1:6] + 1 │ /*1*/x +=/*2*/y/*3*/; + · ────────── + ╰──── + + ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. + ╭─[operator_assignment.tsx:1:1] + 1 │ ╭─▶ x +=//1 + 2 │ ╰─▶ y + ╰──── + + ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. + ╭─[operator_assignment.tsx:1:7] + 1 │ (/*1*/x += y) + · ────── + ╰──── + + ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. + ╭─[operator_assignment.tsx:1:1] + 1 │ x/*1*/+= y + · ─────────── + ╰──── + + ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. + ╭─[operator_assignment.tsx:1:1] + 1 │ ╭─▶ x //1 + 2 │ ╰─▶ += y + ╰──── + + ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. + ╭─[operator_assignment.tsx:1:1] + 1 │ (/*1*/x) += y + · ────────────── + ╰──── + + ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. + ╭─[operator_assignment.tsx:1:1] + 1 │ x/*1*/.y += z + · ────────────── + ╰──── + + ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. + ╭─[operator_assignment.tsx:1:1] + 1 │ ╭─▶ x.//1 + 2 │ ╰─▶ y += z + ╰──── + + ⚠ eslint(operator-assignment): Unexpected operator assignment (^=) shorthand. + ╭─[operator_assignment.tsx:1:1] + 1 │ (foo.bar) ^= ((((((((((((((((baz)))))))))))))))) + · ──────────────────────────────────────────────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (**=). + ╭─[operator_assignment.tsx:1:1] + 1 │ foo = foo ** bar + · ──────────────── + ╰──── + + ⚠ eslint(operator-assignment): Unexpected operator assignment (**=) shorthand. + ╭─[operator_assignment.tsx:1:1] + 1 │ foo **= bar + · ─────────── + ╰──── + + ⚠ eslint(operator-assignment): Unexpected operator assignment (*=) shorthand. + ╭─[operator_assignment.tsx:1:1] + 1 │ foo *= bar + 1 + · ────────────── + ╰──── + + ⚠ eslint(operator-assignment): Unexpected operator assignment (-=) shorthand. + ╭─[operator_assignment.tsx:1:1] + 1 │ foo -= bar - baz + · ──────────────── + ╰──── + + ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. + ╭─[operator_assignment.tsx:1:1] + 1 │ foo += bar + baz + · ──────────────── + ╰──── + + ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. + ╭─[operator_assignment.tsx:1:1] + 1 │ foo += bar = 1 + · ────────────── + ╰──── + + ⚠ eslint(operator-assignment): Unexpected operator assignment (*=) shorthand. + ╭─[operator_assignment.tsx:1:1] + 1 │ foo *= (bar + 1) + · ──────────────── + ╰──── + + ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. + ╭─[operator_assignment.tsx:1:1] + 1 │ foo+=-bar + · ───────── + ╰──── + + ⚠ eslint(operator-assignment): Unexpected operator assignment (/=) shorthand. + ╭─[operator_assignment.tsx:1:1] + 1 │ foo/=bar + · ──────── + ╰──── + + ⚠ eslint(operator-assignment): Unexpected operator assignment (/=) shorthand. + ╭─[operator_assignment.tsx:1:1] + 1 │ foo/=/**/bar + · ──────────── + ╰──── + + ⚠ eslint(operator-assignment): Unexpected operator assignment (/=) shorthand. + ╭─[operator_assignment.tsx:1:1] + 1 │ ╭─▶ foo/=// + 2 │ ╰─▶ bar + ╰──── + + ⚠ eslint(operator-assignment): Unexpected operator assignment (/=) shorthand. + ╭─[operator_assignment.tsx:1:1] + 1 │ foo/=/^bar$/ + · ──────────── + ╰──── + + ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. + ╭─[operator_assignment.tsx:1:1] + 1 │ foo+=+bar + · ───────── + ╰──── + + ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. + ╭─[operator_assignment.tsx:1:1] + 1 │ foo+= +bar + · ────────── + ╰──── + + ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. + ╭─[operator_assignment.tsx:1:1] + 1 │ foo+=/**/+bar + · ───────────── + ╰──── + + ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. + ╭─[operator_assignment.tsx:1:1] + 1 │ foo+=+bar===baz + · ─────────────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (+=). + ╭─[operator_assignment.tsx:1:1] + 1 │ (obj?.a).b = (obj?.a).b + y + · ─────────────────────────── + ╰──── + + ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (+=). + ╭─[operator_assignment.tsx:1:1] + 1 │ obj.a = obj?.a + b + · ────────────────── + ╰──── From 9d01dbb9a9ce28652299389a2bf883d1fb768078 Mon Sep 17 00:00:00 2001 From: huangtiandi1999 Date: Tue, 18 Feb 2025 22:41:13 +0800 Subject: [PATCH 3/8] fix spell --- crates/oxc_linter/src/rules/eslint/operator_assignment.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/operator_assignment.rs b/crates/oxc_linter/src/rules/eslint/operator_assignment.rs index 07e73e3fa6845..5f7203fb7f31a 100644 --- a/crates/oxc_linter/src/rules/eslint/operator_assignment.rs +++ b/crates/oxc_linter/src/rules/eslint/operator_assignment.rs @@ -79,12 +79,12 @@ impl Rule for OperatorAssignment { if self.mode == Mode::Never { prohibit(assign_expr, self.mode, ctx); } else { - verfy(assign_expr, self.mode, ctx); + verify(assign_expr, self.mode, ctx); } } } -fn verfy(expr: &AssignmentExpression, mode: Mode, ctx: &LintContext) { +fn verify(expr: &AssignmentExpression, mode: Mode, ctx: &LintContext) { if expr.operator != AssignmentOperator::Assign { return; } From 02898fc70e86fa50eecf1171c4741c7fcda18db7 Mon Sep 17 00:00:00 2001 From: huangtiandi1999 Date: Fri, 21 Feb 2025 17:19:41 +0800 Subject: [PATCH 4/8] add fixer --- .../src/rules/eslint/operator_assignment.rs | 365 ++++++++++++------ .../snapshots/eslint_operator_assignment.snap | 76 ++++ 2 files changed, 320 insertions(+), 121 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/operator_assignment.rs b/crates/oxc_linter/src/rules/eslint/operator_assignment.rs index 5f7203fb7f31a..3d2c0b9d3b9c7 100644 --- a/crates/oxc_linter/src/rules/eslint/operator_assignment.rs +++ b/crates/oxc_linter/src/rules/eslint/operator_assignment.rs @@ -1,13 +1,14 @@ use oxc_ast::{ ast::{ - AssignmentExpression, AssignmentOperator, AssignmentTarget, BinaryOperator, Expression, - MemberExpression, SimpleAssignmentTarget, + AssignmentExpression, AssignmentTarget, BinaryOperator, Expression, MemberExpression, + SimpleAssignmentTarget, UnaryOperator, UpdateOperator, }, match_simple_assignment_target, AstKind, }; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; -use oxc_span::Span; +use oxc_span::{GetSpan, Span}; +use oxc_syntax::precedence::GetPrecedence; use serde_json::Value; use crate::{context::LintContext, rule::Rule, utils::is_same_member_expression, AstNode}; @@ -48,23 +49,43 @@ declare_oxc_lint!( /// This rule requires or disallows assignment operator shorthand where possible. /// /// ### Why is this bad? - /// + /// JavaScript provides shorthand operators that combine variable assignment and some simple mathematical operations. /// /// ### Examples /// - /// Examples of **incorrect** code for this rule: + /// Examples of **incorrect** code for this rule with the default `always` option: /// ```js - /// FIXME: Tests will fail if examples are missing or syntactically incorrect. + /// x = x + y; + /// x = y * x; + /// x[0] = x[0] / y; + /// x.y = x.y << z; /// ``` /// - /// Examples of **correct** code for this rule: + /// Examples of **correct** code for this rule with the default `always` option: + /// ```js + /// x = y; + /// x += y; + /// x = y * z; + /// x = (x * y) * z; + /// x[0] /= y; + /// x[foo()] = x[foo()] % 2; + /// x = y + x; // `+` is not always commutative (e.g. x = "abc") + /// + /// Examples of **incorrect** code for this rule with the `never` option: /// ```js - /// FIXME: Tests will fail if examples are missing or syntactically incorrect. + /// x *= y; + /// x ^= (y + z) / foo(); + /// ``` + /// + /// Examples of **correct** code for this rule with the `never` option: + /// ```js + /// x = x + y; + /// x.y = x.y / a.b; /// ``` OperatorAssignment, eslint, style, - pending + fix_dangerous ); impl Rule for OperatorAssignment { @@ -85,25 +106,56 @@ impl Rule for OperatorAssignment { } fn verify(expr: &AssignmentExpression, mode: Mode, ctx: &LintContext) { - if expr.operator != AssignmentOperator::Assign { + if !expr.operator.is_assign() { return; } let left = &expr.left; - if let Expression::BinaryExpression(binary_expr) = &expr.right.without_parentheses() { let binary_operator = binary_expr.operator; let is_commutative_operator = is_commutative_operator_with_shorthand(binary_operator); let is_non_commutative_operator = is_non_commutative_operator_with_shorthand(binary_operator); - // for always if is_commutative_operator || is_non_commutative_operator { let replace_operator = format!("{}=", binary_operator.as_str()); if check_is_same_reference(left, &binary_expr.left, ctx) { - ctx.diagnostic(operator_assignment_diagnostic(mode, expr.span, &replace_operator)); + ctx.diagnostic_with_fix( + operator_assignment_diagnostic(mode, expr.span, &replace_operator), + |fixer| { + if !can_be_fixed(left) { + return fixer.noop(); + } + let operator_span = get_operator_span( + Span::new(left.span().end, binary_expr.left.span().start), + "=", + ctx, + ); + let binary_operator_span = get_operator_span( + Span::new(binary_expr.left.span().end, binary_expr.right.span().start), + binary_operator.as_str(), + ctx, + ); + if ctx.has_comments_between(Span::new( + operator_span.end, + binary_operator_span.start, + )) { + return fixer.noop(); + } + // e.g. x = x + y => x += y + // binary_operator = "+" and replace_operator = "+=" + // left_text = "x " right_text = " y" + let left_text = Span::new(expr.span.start, operator_span.start) + .source_text(ctx.source_text()); + let right_text = Span::new(binary_operator_span.end, binary_expr.span.end) + .source_text(ctx.source_text()); + fixer.replace( + expr.span, + format!("{left_text}{replace_operator}{right_text}"), + ) + }, + ); } else if check_is_same_reference(left, &binary_expr.right, ctx) && is_commutative_operator { - // todo fix ctx.diagnostic(operator_assignment_diagnostic(mode, expr.span, &replace_operator)); } } @@ -111,15 +163,83 @@ fn verify(expr: &AssignmentExpression, mode: Mode, ctx: &LintContext) { } fn prohibit(expr: &AssignmentExpression, mode: Mode, ctx: &LintContext) { - if expr.operator != AssignmentOperator::Assign - && !check_is_logical_assign_operator(expr.operator) - { - if can_be_fixed(&expr.left) { - // todo fix - ctx.diagnostic(operator_assignment_diagnostic(mode, expr.span, expr.operator.as_str())); - } else { - ctx.diagnostic(operator_assignment_diagnostic(mode, expr.span, expr.operator.as_str())); - } + if !expr.operator.is_assign() && !expr.operator.is_logical() { + ctx.diagnostic_with_dangerous_fix( +operator_assignment_diagnostic(mode, expr.span, expr.operator.as_str()), + |fixer| { + if !can_be_fixed(&expr.left) { + return fixer.noop(); + } + let right_expr = &expr.right; + + let operator_span = get_operator_span( + Span::new(expr.left.span().end, right_expr.span().start), + expr.operator.as_str(), + ctx + ); + if ctx.has_comments_between(Span::new(expr.span.start, operator_span.start)) { + return fixer.noop(); + } + let Some(new_operator) = expr.operator.to_binary_operator() else { + return fixer.noop() + }; + let left_text = Span::new(expr.span.start, operator_span.start).source_text(ctx.source_text()); + let right_text = { + let right_expr_text = right_expr.span().source_text(ctx.source_text()); + let former = Span::new(operator_span.end, right_expr.span().start).source_text(ctx.source_text()); + match right_expr { + // For some special cases, we need to wrap the expression in a pair of () + // e.g. "x += y + 1" => "x = x + (y + 1)" + // "x += () => {}" => "x = x + (() => {})" + // "x += y = 1" => "x = x + (y = 1)" + // "x += yield foo()" => "x = x + (yield foo())" + // "x += y || 3" => "x = x + (y || 3)" + Expression::BinaryExpression(binary_expr) if binary_expr.operator.precedence() <= new_operator.precedence() => { + format!("{former}({right_expr_text})") + } + Expression::AssignmentExpression(_) + | Expression::ArrowFunctionExpression(_) + | Expression::YieldExpression(_) + | Expression::LogicalExpression(_) => { + format!("{former}({right_expr_text})") + } + // For the rest + _ => { + let temp_right_text = Span::new(operator_span.end, right_expr.span().end).source_text(ctx.source_text()); + let no_gap: bool = right_expr.span().start == operator_span.end; + // we match the binary operator to determine whether right_text_prefix needs to be preceded by a space + let need_fill_space = match new_operator { + BinaryOperator::Division => { + if let Some(first_comment) = ctx.comments().iter().find(|comment| { + Span::new(operator_span.end, right_expr.span().end).contains_inclusive(comment.span) + }) { + // e.g. x /=/** comments */ y + first_comment.span.start == operator_span.end + } else { + // e.g. x /=/^abc/ + matches!(right_expr, Expression::RegExpLiteral(regex_literal) if regex_literal.span.start == operator_span.end) + } + } + // x+=+y => x=x+ +y; + BinaryOperator::Addition if no_gap => { + matches!(right_expr, Expression::UnaryExpression(unary_expr) if unary_expr.operator == UnaryOperator::UnaryPlus) + || matches!(right_expr, Expression::UpdateExpression(update_expr) if update_expr.operator == UpdateOperator::Increment) + } + // x-=-y => x= x- -y + BinaryOperator::Subtraction if no_gap => { + matches!(right_expr, Expression::UnaryExpression(unary_expr) if unary_expr.operator == UnaryOperator::UnaryNegation) + || matches!(right_expr, Expression::UpdateExpression(update_expr) if update_expr.operator == UpdateOperator::Decrement) + } + _ => false, + }; + let right_text_prefix = if need_fill_space { " " } else { "" }; + format!("{right_text_prefix}{temp_right_text}") + } + } + }; + fixer.replace(expr.span, format!("{left_text}= {left_text}{}{right_text}", new_operator.as_str())) + } + ); } } @@ -157,6 +277,13 @@ fn can_be_fixed(target: &AssignmentTarget) -> bool { } } +#[expect(clippy::cast_possible_truncation)] +fn get_operator_span(init_span: Span, operator: &str, ctx: &LintContext) -> Span { + let offset = init_span.source_text(ctx.source_text()).find(operator).unwrap_or(0) as u32; + let start = init_span.start + offset; + Span::new(start, start + operator.len() as u32) +} + fn check_is_same_reference(left: &AssignmentTarget, right: &Expression, ctx: &LintContext) -> bool { match left { match_simple_assignment_target!(AssignmentTarget) => { @@ -218,19 +345,9 @@ fn is_non_commutative_operator_with_shorthand(operator: BinaryOperator) -> bool ) } -fn check_is_logical_assign_operator(operator: AssignmentOperator) -> bool { - matches!( - operator, - AssignmentOperator::LogicalAnd - | AssignmentOperator::LogicalOr - | AssignmentOperator::LogicalNullish - ) -} - #[test] fn test() { use crate::tester::Tester; - let pass = vec![ ("x = y", None), ("x = y + x", None), @@ -282,8 +399,8 @@ fn test() { ("x &&= y", Some(serde_json::json!(["never"]))), ("x ||= y", Some(serde_json::json!(["never"]))), ("x ??= y", Some(serde_json::json!(["never"]))), + ("x = () => {};", Some(serde_json::json!(["never"]))), ]; - let fail = vec![ ("x = x + y", None), ("x = x - y", None), @@ -394,94 +511,100 @@ fn test() { ("foo+=+bar===baz", Some(serde_json::json!(["never"]))), ("(obj?.a).b = (obj?.a).b + y", None), ("obj.a = obj?.a + b", None), + ("x += + (() => {})", Some(serde_json::json!(["never"]))), + ("x += + /** fooo */ (() => {})", Some(serde_json::json!(["never"]))), + ]; + let fix = vec![ + ("x = x + y", "x += y", None), + ("x = x - y", "x -= y", None), + ("x = x * y", "x *= y", None), + ("x = x / y", "x /= y", None), + ("x = x % y", "x %= y", None), + ("x = x << y", "x <<= y", None), + ("x = x >> y", "x >>= y", None), + ("x = x >>> y", "x >>>= y", None), + ("x = x & y", "x &= y", None), + ("x = x ^ y", "x ^= y", None), + ("x = x | y", "x |= y", None), + ("x[0] = x[0] - y", "x[0] -= y", None), + ("x = x + y", "x += y", Some(serde_json::json!(["always"]))), + ("x = (x + y)", "x += y", Some(serde_json::json!(["always"]))), + ("x = x + (y)", "x += (y)", Some(serde_json::json!(["always"]))), + ("x += (y)", "x = x + (y)", Some(serde_json::json!(["never"]))), + ("x += y", "x = x + y", Some(serde_json::json!(["never"]))), + ("foo.bar = foo.bar + baz", "foo.bar += baz", None), + ("foo.bar += baz", "foo.bar = foo.bar + baz", Some(serde_json::json!(["never"]))), + ("this.foo = this.foo + bar", "this.foo += bar", None), + ("this.foo += bar", "this.foo = this.foo + bar", Some(serde_json::json!(["never"]))), + ("foo[5] = foo[5] / baz", "foo[5] /= baz", None), + ("this[5] = this[5] / foo", "this[5] /= foo", None), + ( + "/*1*/x/*2*/./*3*/y/*4*/= x.y +/*5*/z/*6*/./*7*/w/*8*/;", + "/*1*/x/*2*/./*3*/y/*4*/+=/*5*/z/*6*/./*7*/w/*8*/;", + Some(serde_json::json!(["always"])), + ), + ( + "x // 1 + . // 2 + y // 3 + = x.y + //4 + z //5 + . //6 + w;", + "x // 1 + . // 2 + y // 3 + += //4 + z //5 + . //6 + w;", + Some(serde_json::json!(["always"])), + ), + ("/*1*/x +=/*2*/y/*3*/;", "/*1*/x = x +/*2*/y/*3*/;", Some(serde_json::json!(["never"]))), + ( + "x +=//1 + y", + "x = x +//1 + y", + Some(serde_json::json!(["never"])), + ), + ("(/*1*/x += y)", "(/*1*/x = x + y)", Some(serde_json::json!(["never"]))), + ( + "(foo.bar) ^= ((((((((((((((((baz))))))))))))))))", + "(foo.bar) = (foo.bar) ^ ((((((((((((((((baz))))))))))))))))", + Some(serde_json::json!(["never"])), + ), + ("foo = foo ** bar", "foo **= bar", None), + ("foo **= bar", "foo = foo ** bar", Some(serde_json::json!(["never"]))), + ("foo *= bar + 1", "foo = foo * (bar + 1)", Some(serde_json::json!(["never"]))), + ("foo -= bar - baz", "foo = foo - (bar - baz)", Some(serde_json::json!(["never"]))), + ("foo += bar + baz", "foo = foo + (bar + baz)", Some(serde_json::json!(["never"]))), + ("foo += bar = 1", "foo = foo + (bar = 1)", Some(serde_json::json!(["never"]))), + ("foo *= (bar + 1)", "foo = foo * (bar + 1)", Some(serde_json::json!(["never"]))), + ("foo+=-bar", "foo= foo+-bar", Some(serde_json::json!(["never"]))), + ("foo/=bar", "foo= foo/bar", Some(serde_json::json!(["never"]))), + ("foo/=/**/bar", "foo= foo/ /**/bar", Some(serde_json::json!(["never"]))), + ( + "foo/=// + bar", + "foo= foo/ // + bar", + Some(serde_json::json!(["never"])), + ), + ("foo/=/^bar$/", "foo= foo/ /^bar$/", Some(serde_json::json!(["never"]))), + ("foo+=+bar", "foo= foo+ +bar", Some(serde_json::json!(["never"]))), + ("foo+= +bar", "foo= foo+ +bar", Some(serde_json::json!(["never"]))), + ("foo+=/**/+bar", "foo= foo+/**/+bar", Some(serde_json::json!(["never"]))), + ("foo+=+bar===baz", "foo= foo+(+bar===baz)", Some(serde_json::json!(["never"]))), + ("x += () => {}", "x = x + (() => {})", Some(serde_json::json!(["never"]))), + ("x += + (() => {})", "x = x + + (() => {})", Some(serde_json::json!(["never"]))), + ( + "x += + /** fooo */ (() => {})", + "x = x + + /** fooo */ (() => {})", + Some(serde_json::json!(["never"])), + ), ]; - - // let fix = vec![ - // ("x = x + y", "x += y", None), - // ("x = x - y", "x -= y", None), - // ("x = x * y", "x *= y", None), - // ("x = x / y", "x /= y", None), - // ("x = x % y", "x %= y", None), - // ("x = x << y", "x <<= y", None), - // ("x = x >> y", "x >>= y", None), - // ("x = x >>> y", "x >>>= y", None), - // ("x = x & y", "x &= y", None), - // ("x = x ^ y", "x ^= y", None), - // ("x = x | y", "x |= y", None), - // ("x[0] = x[0] - y", "x[0] -= y", None), - // ("x = x + y", "x += y", Some(serde_json::json!(["always"]))), - // ("x = (x + y)", "x += y", Some(serde_json::json!(["always"]))), - // ("x = x + (y)", "x += (y)", Some(serde_json::json!(["always"]))), - // ("x += (y)", "x = x + (y)", Some(serde_json::json!(["never"]))), - // ("x += y", "x = x + y", Some(serde_json::json!(["never"]))), - // ("foo.bar = foo.bar + baz", "foo.bar += baz", None), - // ("foo.bar += baz", "foo.bar = foo.bar + baz", Some(serde_json::json!(["never"]))), - // ("this.foo = this.foo + bar", "this.foo += bar", None), - // ("this.foo += bar", "this.foo = this.foo + bar", Some(serde_json::json!(["never"]))), - // ("foo[5] = foo[5] / baz", "foo[5] /= baz", None), - // ("this[5] = this[5] / foo", "this[5] /= foo", None), - // ( - // "/*1*/x/*2*/./*3*/y/*4*/= x.y +/*5*/z/*6*/./*7*/w/*8*/;", - // "/*1*/x/*2*/./*3*/y/*4*/+=/*5*/z/*6*/./*7*/w/*8*/;", - // Some(serde_json::json!(["always"])), - // ), - // ( - // "x // 1 - // . // 2 - // y // 3 - // = x.y + //4 - // z //5 - // . //6 - // w;", - // "x // 1 - // . // 2 - // y // 3 - // += //4 - // z //5 - // . //6 - // w;", - // Some(serde_json::json!(["always"])), - // ), - // ("/*1*/x +=/*2*/y/*3*/;", "/*1*/x = x +/*2*/y/*3*/;", Some(serde_json::json!(["never"]))), - // ( - // "x +=//1 - // y", - // "x = x +//1 - // y", - // Some(serde_json::json!(["never"])), - // ), - // ("(/*1*/x += y)", "(/*1*/x = x + y)", Some(serde_json::json!(["never"]))), - // ( - // "(foo.bar) ^= ((((((((((((((((baz))))))))))))))))", - // "(foo.bar) = (foo.bar) ^ ((((((((((((((((baz))))))))))))))))", - // Some(serde_json::json!(["never"])), - // ), - // ("foo = foo ** bar", "foo **= bar", None), - // ("foo **= bar", "foo = foo ** bar", Some(serde_json::json!(["never"]))), - // ("foo *= bar + 1", "foo = foo * (bar + 1)", Some(serde_json::json!(["never"]))), - // ("foo -= bar - baz", "foo = foo - (bar - baz)", Some(serde_json::json!(["never"]))), - // ("foo += bar + baz", "foo = foo + (bar + baz)", Some(serde_json::json!(["never"]))), - // ("foo += bar = 1", "foo = foo + (bar = 1)", Some(serde_json::json!(["never"]))), - // ("foo *= (bar + 1)", "foo = foo * (bar + 1)", Some(serde_json::json!(["never"]))), - // ("foo+=-bar", "foo= foo+-bar", Some(serde_json::json!(["never"]))), - // ("foo/=bar", "foo= foo/bar", Some(serde_json::json!(["never"]))), - // ("foo/=/**/bar", "foo= foo/ /**/bar", Some(serde_json::json!(["never"]))), - // ( - // "foo/=// - // bar", - // "foo= foo/ // - // bar", - // Some(serde_json::json!(["never"])), - // ), - // ("foo/=/^bar$/", "foo= foo/ /^bar$/", Some(serde_json::json!(["never"]))), - // ("foo+=+bar", "foo= foo+ +bar", Some(serde_json::json!(["never"]))), - // ("foo+= +bar", "foo= foo+ +bar", Some(serde_json::json!(["never"]))), - // ("foo+=/**/+bar", "foo= foo+/**/+bar", Some(serde_json::json!(["never"]))), - // ("foo+=+bar===baz", "foo= foo+(+bar===baz)", Some(serde_json::json!(["never"]))), - // ]; - // Tester::new(OperatorAssignment::NAME, OperatorAssignment::PLUGIN, pass, fail) - // .expect_fix(fix) - // .test_and_snapshot(); Tester::new(OperatorAssignment::NAME, OperatorAssignment::PLUGIN, pass, fail) + .expect_fix(fix) .test_and_snapshot(); } diff --git a/crates/oxc_linter/src/snapshots/eslint_operator_assignment.snap b/crates/oxc_linter/src/snapshots/eslint_operator_assignment.snap index e21851206fb7f..de6092fdb814d 100644 --- a/crates/oxc_linter/src/snapshots/eslint_operator_assignment.snap +++ b/crates/oxc_linter/src/snapshots/eslint_operator_assignment.snap @@ -6,18 +6,21 @@ source: crates/oxc_linter/src/tester.rs 1 │ x = x + y · ───────── ╰──── + help: Replace `x = x + y` with `x += y`. ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (-=). ╭─[operator_assignment.tsx:1:1] 1 │ x = x - y · ───────── ╰──── + help: Replace `x = x - y` with `x -= y`. ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (*=). ╭─[operator_assignment.tsx:1:1] 1 │ x = x * y · ───────── ╰──── + help: Replace `x = x * y` with `x *= y`. ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (*=). ╭─[operator_assignment.tsx:1:1] @@ -36,54 +39,63 @@ source: crates/oxc_linter/src/tester.rs 1 │ x = x / y · ───────── ╰──── + help: Replace `x = x / y` with `x /= y`. ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (%=). ╭─[operator_assignment.tsx:1:1] 1 │ x = x % y · ───────── ╰──── + help: Replace `x = x % y` with `x %= y`. ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (<<=). ╭─[operator_assignment.tsx:1:1] 1 │ x = x << y · ────────── ╰──── + help: Replace `x = x << y` with `x <<= y`. ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (>>=). ╭─[operator_assignment.tsx:1:1] 1 │ x = x >> y · ────────── ╰──── + help: Replace `x = x >> y` with `x >>= y`. ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (>>>=). ╭─[operator_assignment.tsx:1:1] 1 │ x = x >>> y · ─────────── ╰──── + help: Replace `x = x >>> y` with `x >>>= y`. ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (&=). ╭─[operator_assignment.tsx:1:1] 1 │ x = x & y · ───────── ╰──── + help: Replace `x = x & y` with `x &= y`. ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (^=). ╭─[operator_assignment.tsx:1:1] 1 │ x = x ^ y · ───────── ╰──── + help: Replace `x = x ^ y` with `x ^= y`. ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (|=). ╭─[operator_assignment.tsx:1:1] 1 │ x = x | y · ───────── ╰──── + help: Replace `x = x | y` with `x |= y`. ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (-=). ╭─[operator_assignment.tsx:1:1] 1 │ x[0] = x[0] - y · ─────────────── ╰──── + help: Replace `x[0] = x[0] - y` with `x[0] -= y`. ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (*=). ╭─[operator_assignment.tsx:1:1] @@ -96,54 +108,63 @@ source: crates/oxc_linter/src/tester.rs 1 │ x = x + y · ───────── ╰──── + help: Replace `x = x + y` with `x += y`. ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (+=). ╭─[operator_assignment.tsx:1:1] 1 │ x = (x + y) · ─────────── ╰──── + help: Replace `x = (x + y)` with `x += y`. ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (+=). ╭─[operator_assignment.tsx:1:1] 1 │ x = x + (y) · ─────────── ╰──── + help: Replace `x = x + (y)` with `x += (y)`. ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. ╭─[operator_assignment.tsx:1:1] 1 │ x += (y) · ──────── ╰──── + help: Replace `x += (y)` with `x = x + (y)`. ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. ╭─[operator_assignment.tsx:1:1] 1 │ x += y · ────── ╰──── + help: Replace `x += y` with `x = x + y`. ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (+=). ╭─[operator_assignment.tsx:1:1] 1 │ foo.bar = foo.bar + baz · ─────────────────────── ╰──── + help: Replace `foo.bar = foo.bar + baz` with `foo.bar += baz`. ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. ╭─[operator_assignment.tsx:1:1] 1 │ foo.bar += baz · ────────────── ╰──── + help: Replace `foo.bar += baz` with `foo.bar = foo.bar + baz`. ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (+=). ╭─[operator_assignment.tsx:1:1] 1 │ this.foo = this.foo + bar · ───────────────────────── ╰──── + help: Replace `this.foo = this.foo + bar` with `this.foo += bar`. ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. ╭─[operator_assignment.tsx:1:1] 1 │ this.foo += bar · ─────────────── ╰──── + help: Replace `this.foo += bar` with `this.foo = this.foo + bar`. ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (+=). ╭─[operator_assignment.tsx:1:1] @@ -198,18 +219,21 @@ source: crates/oxc_linter/src/tester.rs 1 │ foo[5] = foo[5] / baz · ───────────────────── ╰──── + help: Replace `foo[5] = foo[5] / baz` with `foo[5] /= baz`. ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (/=). ╭─[operator_assignment.tsx:1:1] 1 │ this[5] = this[5] / foo · ─────────────────────── ╰──── + help: Replace `this[5] = this[5] / foo` with `this[5] /= foo`. ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (+=). ╭─[operator_assignment.tsx:1:6] 1 │ /*1*/x/*2*/./*3*/y/*4*/= x.y +/*5*/z/*6*/./*7*/w/*8*/; · ─────────────────────────────────────────── ╰──── + help: Replace `x/*2*/./*3*/y/*4*/= x.y +/*5*/z/*6*/./*7*/w` with `x/*2*/./*3*/y/*4*/+=/*5*/z/*6*/./*7*/w`. ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (+=). ╭─[operator_assignment.tsx:1:1] @@ -221,6 +245,19 @@ source: crates/oxc_linter/src/tester.rs 6 │ │ . //6 7 │ ╰─▶ w; ╰──── + help: Replace `x // 1 + . // 2 + y // 3 + = x.y + //4 + z //5 + . //6 + w` with `x // 1 + . // 2 + y // 3 + += //4 + z //5 + . //6 + w`. ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (+=). ╭─[operator_assignment.tsx:1:1] @@ -263,18 +300,23 @@ source: crates/oxc_linter/src/tester.rs 1 │ /*1*/x +=/*2*/y/*3*/; · ────────── ╰──── + help: Replace `x +=/*2*/y` with `x = x +/*2*/y`. ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. ╭─[operator_assignment.tsx:1:1] 1 │ ╭─▶ x +=//1 2 │ ╰─▶ y ╰──── + help: Replace `x +=//1 + y` with `x = x +//1 + y`. ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. ╭─[operator_assignment.tsx:1:7] 1 │ (/*1*/x += y) · ────── ╰──── + help: Replace `x += y` with `x = x + y`. ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. ╭─[operator_assignment.tsx:1:1] @@ -311,102 +353,121 @@ source: crates/oxc_linter/src/tester.rs 1 │ (foo.bar) ^= ((((((((((((((((baz)))))))))))))))) · ──────────────────────────────────────────────── ╰──── + help: Replace `(foo.bar) ^= ((((((((((((((((baz))))))))))))))))` with `(foo.bar) = (foo.bar) ^ ((((((((((((((((baz))))))))))))))))`. ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (**=). ╭─[operator_assignment.tsx:1:1] 1 │ foo = foo ** bar · ──────────────── ╰──── + help: Replace `foo = foo ** bar` with `foo **= bar`. ⚠ eslint(operator-assignment): Unexpected operator assignment (**=) shorthand. ╭─[operator_assignment.tsx:1:1] 1 │ foo **= bar · ─────────── ╰──── + help: Replace `foo **= bar` with `foo = foo ** bar`. ⚠ eslint(operator-assignment): Unexpected operator assignment (*=) shorthand. ╭─[operator_assignment.tsx:1:1] 1 │ foo *= bar + 1 · ────────────── ╰──── + help: Replace `foo *= bar + 1` with `foo = foo * (bar + 1)`. ⚠ eslint(operator-assignment): Unexpected operator assignment (-=) shorthand. ╭─[operator_assignment.tsx:1:1] 1 │ foo -= bar - baz · ──────────────── ╰──── + help: Replace `foo -= bar - baz` with `foo = foo - (bar - baz)`. ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. ╭─[operator_assignment.tsx:1:1] 1 │ foo += bar + baz · ──────────────── ╰──── + help: Replace `foo += bar + baz` with `foo = foo + (bar + baz)`. ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. ╭─[operator_assignment.tsx:1:1] 1 │ foo += bar = 1 · ────────────── ╰──── + help: Replace `foo += bar = 1` with `foo = foo + (bar = 1)`. ⚠ eslint(operator-assignment): Unexpected operator assignment (*=) shorthand. ╭─[operator_assignment.tsx:1:1] 1 │ foo *= (bar + 1) · ──────────────── ╰──── + help: Replace `foo *= (bar + 1)` with `foo = foo * (bar + 1)`. ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. ╭─[operator_assignment.tsx:1:1] 1 │ foo+=-bar · ───────── ╰──── + help: Replace `foo+=-bar` with `foo= foo+-bar`. ⚠ eslint(operator-assignment): Unexpected operator assignment (/=) shorthand. ╭─[operator_assignment.tsx:1:1] 1 │ foo/=bar · ──────── ╰──── + help: Replace `foo/=bar` with `foo= foo/bar`. ⚠ eslint(operator-assignment): Unexpected operator assignment (/=) shorthand. ╭─[operator_assignment.tsx:1:1] 1 │ foo/=/**/bar · ──────────── ╰──── + help: Replace `foo/=/**/bar` with `foo= foo/ /**/bar`. ⚠ eslint(operator-assignment): Unexpected operator assignment (/=) shorthand. ╭─[operator_assignment.tsx:1:1] 1 │ ╭─▶ foo/=// 2 │ ╰─▶ bar ╰──── + help: Replace `foo/=// + bar` with `foo= foo/ // + bar`. ⚠ eslint(operator-assignment): Unexpected operator assignment (/=) shorthand. ╭─[operator_assignment.tsx:1:1] 1 │ foo/=/^bar$/ · ──────────── ╰──── + help: Replace `foo/=/^bar$/` with `foo= foo/ /^bar$/`. ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. ╭─[operator_assignment.tsx:1:1] 1 │ foo+=+bar · ───────── ╰──── + help: Replace `foo+=+bar` with `foo= foo+ +bar`. ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. ╭─[operator_assignment.tsx:1:1] 1 │ foo+= +bar · ────────── ╰──── + help: Replace `foo+= +bar` with `foo= foo+ +bar`. ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. ╭─[operator_assignment.tsx:1:1] 1 │ foo+=/**/+bar · ───────────── ╰──── + help: Replace `foo+=/**/+bar` with `foo= foo+/**/+bar`. ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. ╭─[operator_assignment.tsx:1:1] 1 │ foo+=+bar===baz · ─────────────── ╰──── + help: Replace `foo+=+bar===baz` with `foo= foo+(+bar===baz)`. ⚠ eslint(operator-assignment): Assignment (=) can be replaced with operator assignment (+=). ╭─[operator_assignment.tsx:1:1] @@ -419,3 +480,18 @@ source: crates/oxc_linter/src/tester.rs 1 │ obj.a = obj?.a + b · ────────────────── ╰──── + help: Replace `obj.a = obj?.a + b` with `obj.a += b`. + + ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. + ╭─[operator_assignment.tsx:1:1] + 1 │ x += + (() => {}) + · ───────────────── + ╰──── + help: Replace `x += + (() => {})` with `x = x + + (() => {})`. + + ⚠ eslint(operator-assignment): Unexpected operator assignment (+=) shorthand. + ╭─[operator_assignment.tsx:1:1] + 1 │ x += + /** fooo */ (() => {}) + · ───────────────────────────── + ╰──── + help: Replace `x += + /** fooo */ (() => {})` with `x = x + + /** fooo */ (() => {})`. From 44f17b3ca00c721b3d95ed29632bc559ee0bf068 Mon Sep 17 00:00:00 2001 From: huangtiandi1999 Date: Sun, 23 Feb 2025 12:35:31 +0800 Subject: [PATCH 5/8] improve linter rules doc --- .../src/rules/eslint/operator_assignment.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/crates/oxc_linter/src/rules/eslint/operator_assignment.rs b/crates/oxc_linter/src/rules/eslint/operator_assignment.rs index 3d2c0b9d3b9c7..d24ae022c0568 100644 --- a/crates/oxc_linter/src/rules/eslint/operator_assignment.rs +++ b/crates/oxc_linter/src/rules/eslint/operator_assignment.rs @@ -82,6 +82,22 @@ declare_oxc_lint!( /// x = x + y; /// x.y = x.y / a.b; /// ``` + /// + /// ### Options + /// This rule has a single string option: + /// + /// `{ type: String, default: 'always' }` + /// + /// * `always` requires assignment operator shorthand where possible + /// * `never` disallows assignment operator shorthand + /// + /// Example: + /// + /// ```json + /// "eslint/max-nested-callbacks": ["error", "always"] + /// + /// "eslint/max-nested-callbacks": ["error", "never"] + /// ``` OperatorAssignment, eslint, style, From a0c3c811ec1cc6a50f36f6891e229873c69cb1c3 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 23 Feb 2025 04:37:04 +0000 Subject: [PATCH 6/8] [autofix.ci] apply automated fixes --- .../src/rules/eslint/operator_assignment.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/operator_assignment.rs b/crates/oxc_linter/src/rules/eslint/operator_assignment.rs index d24ae022c0568..8ebb20282cdd9 100644 --- a/crates/oxc_linter/src/rules/eslint/operator_assignment.rs +++ b/crates/oxc_linter/src/rules/eslint/operator_assignment.rs @@ -1,9 +1,10 @@ use oxc_ast::{ + AstKind, ast::{ AssignmentExpression, AssignmentTarget, BinaryOperator, Expression, MemberExpression, SimpleAssignmentTarget, UnaryOperator, UpdateOperator, }, - match_simple_assignment_target, AstKind, + match_simple_assignment_target, }; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; @@ -11,7 +12,7 @@ use oxc_span::{GetSpan, Span}; use oxc_syntax::precedence::GetPrecedence; use serde_json::Value; -use crate::{context::LintContext, rule::Rule, utils::is_same_member_expression, AstNode}; +use crate::{AstNode, context::LintContext, rule::Rule, utils::is_same_member_expression}; fn operator_assignment_diagnostic(mode: Mode, span: Span, operator: &str) -> OxcDiagnostic { let msg = if Mode::Never == mode { @@ -31,11 +32,7 @@ enum Mode { impl Mode { pub fn from(raw: &str) -> Self { - if raw == "never" { - Self::Never - } else { - Self::Always - } + if raw == "never" { Self::Never } else { Self::Always } } } From 16a9b9181e589d57b4eb5f57aca8ef2aedd141f2 Mon Sep 17 00:00:00 2001 From: shulaoda Date: Mon, 24 Feb 2025 02:53:42 +0800 Subject: [PATCH 7/8] refactor: improve the documentation and use as_simple_assignment_target --- .../src/rules/eslint/operator_assignment.rs | 120 +++++++++--------- 1 file changed, 58 insertions(+), 62 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/operator_assignment.rs b/crates/oxc_linter/src/rules/eslint/operator_assignment.rs index 8ebb20282cdd9..aaf9d977aecad 100644 --- a/crates/oxc_linter/src/rules/eslint/operator_assignment.rs +++ b/crates/oxc_linter/src/rules/eslint/operator_assignment.rs @@ -1,10 +1,9 @@ use oxc_ast::{ - AstKind, ast::{ AssignmentExpression, AssignmentTarget, BinaryOperator, Expression, MemberExpression, SimpleAssignmentTarget, UnaryOperator, UpdateOperator, }, - match_simple_assignment_target, + AstKind, }; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; @@ -12,7 +11,7 @@ use oxc_span::{GetSpan, Span}; use oxc_syntax::precedence::GetPrecedence; use serde_json::Value; -use crate::{AstNode, context::LintContext, rule::Rule, utils::is_same_member_expression}; +use crate::{context::LintContext, rule::Rule, utils::is_same_member_expression, AstNode}; fn operator_assignment_diagnostic(mode: Mode, span: Span, operator: &str) -> OxcDiagnostic { let msg = if Mode::Never == mode { @@ -32,7 +31,11 @@ enum Mode { impl Mode { pub fn from(raw: &str) -> Self { - if raw == "never" { Self::Never } else { Self::Always } + if raw == "never" { + Self::Never + } else { + Self::Always + } } } @@ -43,10 +46,16 @@ pub struct OperatorAssignment { declare_oxc_lint!( /// ### What it does + /// /// This rule requires or disallows assignment operator shorthand where possible. + /// It encourages the use of shorthand assignment operators like `+=`, `-=`, `*=`, `/=`, etc. + /// to make the code more concise and readable. /// /// ### Why is this bad? - /// JavaScript provides shorthand operators that combine variable assignment and some simple mathematical operations. + /// + /// JavaScript provides shorthand operators that combine variable assignment and simple + /// mathematical operations. Failing to use these shorthand operators can lead to unnecessarily + /// verbose code and can be seen as a missed opportunity for clarity and simplicity. /// /// ### Examples /// @@ -67,6 +76,7 @@ declare_oxc_lint!( /// x[0] /= y; /// x[foo()] = x[foo()] % 2; /// x = y + x; // `+` is not always commutative (e.g. x = "abc") + /// ``` /// /// Examples of **incorrect** code for this rule with the `never` option: /// ```js @@ -81,15 +91,15 @@ declare_oxc_lint!( /// ``` /// /// ### Options + /// /// This rule has a single string option: /// - /// `{ type: String, default: 'always' }` + /// `{ type: string, default: "always" }` /// /// * `always` requires assignment operator shorthand where possible /// * `never` disallows assignment operator shorthand /// /// Example: - /// /// ```json /// "eslint/max-nested-callbacks": ["error", "always"] /// @@ -219,7 +229,7 @@ operator_assignment_diagnostic(mode, expr.span, expr.operator.as_str()), // For the rest _ => { let temp_right_text = Span::new(operator_span.end, right_expr.span().end).source_text(ctx.source_text()); - let no_gap: bool = right_expr.span().start == operator_span.end; + let no_gap = right_expr.span().start == operator_span.end; // we match the binary operator to determine whether right_text_prefix needs to be preceded by a space let need_fill_space = match new_operator { BinaryOperator::Division => { @@ -257,37 +267,31 @@ operator_assignment_diagnostic(mode, expr.span, expr.operator.as_str()), } fn can_be_fixed(target: &AssignmentTarget) -> bool { - match target { - match_simple_assignment_target!(AssignmentTarget) => { - let simple_assignment_target = target.to_simple_assignment_target(); - - if matches!( - simple_assignment_target, - SimpleAssignmentTarget::AssignmentTargetIdentifier(_) - ) { - return true; + if let Some(simple_assignment_target) = target.as_simple_assignment_target() { + if matches!(simple_assignment_target, SimpleAssignmentTarget::AssignmentTargetIdentifier(_)) + { + return true; + } + let Some(expr) = simple_assignment_target.as_member_expression() else { + return false; + }; + return match expr { + MemberExpression::ComputedMemberExpression(computed_expr) => { + matches!( + computed_expr.object, + Expression::Identifier(_) | Expression::ThisExpression(_) + ) && computed_expr.expression.is_literal() } - let Some(expr) = simple_assignment_target.as_member_expression() else { - return false; - }; - match expr { - MemberExpression::ComputedMemberExpression(computed_expr) => { - matches!( - computed_expr.object, - Expression::Identifier(_) | Expression::ThisExpression(_) - ) && computed_expr.expression.is_literal() - } - MemberExpression::StaticMemberExpression(static_expr) => { - matches!( - static_expr.object, - Expression::Identifier(_) | Expression::ThisExpression(_) - ) - } - MemberExpression::PrivateFieldExpression(_) => false, + MemberExpression::StaticMemberExpression(static_expr) => { + matches!( + static_expr.object, + Expression::Identifier(_) | Expression::ThisExpression(_) + ) } - } - _ => false, + MemberExpression::PrivateFieldExpression(_) => false, + }; } + false } #[expect(clippy::cast_possible_truncation)] @@ -298,40 +302,29 @@ fn get_operator_span(init_span: Span, operator: &str, ctx: &LintContext) -> Span } fn check_is_same_reference(left: &AssignmentTarget, right: &Expression, ctx: &LintContext) -> bool { - match left { - match_simple_assignment_target!(AssignmentTarget) => { - let simple_assignment_target = left.to_simple_assignment_target(); - if let SimpleAssignmentTarget::AssignmentTargetIdentifier(id1) = - simple_assignment_target - { - return matches!(right, Expression::Identifier(id2) if id2.name == id1.name); - } + if let Some(simple_assignment_target) = left.as_simple_assignment_target() { + if let SimpleAssignmentTarget::AssignmentTargetIdentifier(id1) = simple_assignment_target { + return matches!(right, Expression::Identifier(id2) if id2.name == id1.name); + } - if let Some(left_member_expr) = simple_assignment_target.as_member_expression() { - if let Some(right_member_expr) = right.without_parentheses().get_member_expr() { - // x.y vs x['y'] - if (matches!(left_member_expr, MemberExpression::ComputedMemberExpression(_)) - && !matches!( + if let Some(left_member_expr) = simple_assignment_target.as_member_expression() { + if let Some(right_member_expr) = right.without_parentheses().get_member_expr() { + // x.y vs x['y'] + if (matches!(left_member_expr, MemberExpression::ComputedMemberExpression(_)) + && !matches!(right_member_expr, MemberExpression::ComputedMemberExpression(_))) + || (!matches!(left_member_expr, MemberExpression::ComputedMemberExpression(_)) + && matches!( right_member_expr, MemberExpression::ComputedMemberExpression(_) )) - || (!matches!( - left_member_expr, - MemberExpression::ComputedMemberExpression(_) - ) && matches!( - right_member_expr, - MemberExpression::ComputedMemberExpression(_) - )) - { - return false; - } - return is_same_member_expression(left_member_expr, right_member_expr, ctx); + { + return false; } + return is_same_member_expression(left_member_expr, right_member_expr, ctx); } - false } - _ => false, } + false } fn is_commutative_operator_with_shorthand(operator: BinaryOperator) -> bool { @@ -361,6 +354,7 @@ fn is_non_commutative_operator_with_shorthand(operator: BinaryOperator) -> bool #[test] fn test() { use crate::tester::Tester; + let pass = vec![ ("x = y", None), ("x = y + x", None), @@ -414,6 +408,7 @@ fn test() { ("x ??= y", Some(serde_json::json!(["never"]))), ("x = () => {};", Some(serde_json::json!(["never"]))), ]; + let fail = vec![ ("x = x + y", None), ("x = x - y", None), @@ -527,6 +522,7 @@ fn test() { ("x += + (() => {})", Some(serde_json::json!(["never"]))), ("x += + /** fooo */ (() => {})", Some(serde_json::json!(["never"]))), ]; + let fix = vec![ ("x = x + y", "x += y", None), ("x = x - y", "x -= y", None), From a4111ab88be113433589427952160757956885fb Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 23 Feb 2025 18:54:30 +0000 Subject: [PATCH 8/8] [autofix.ci] apply automated fixes --- .../oxc_linter/src/rules/eslint/operator_assignment.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/operator_assignment.rs b/crates/oxc_linter/src/rules/eslint/operator_assignment.rs index aaf9d977aecad..facfac39f2748 100644 --- a/crates/oxc_linter/src/rules/eslint/operator_assignment.rs +++ b/crates/oxc_linter/src/rules/eslint/operator_assignment.rs @@ -1,9 +1,9 @@ use oxc_ast::{ + AstKind, ast::{ AssignmentExpression, AssignmentTarget, BinaryOperator, Expression, MemberExpression, SimpleAssignmentTarget, UnaryOperator, UpdateOperator, }, - AstKind, }; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; @@ -11,7 +11,7 @@ use oxc_span::{GetSpan, Span}; use oxc_syntax::precedence::GetPrecedence; use serde_json::Value; -use crate::{context::LintContext, rule::Rule, utils::is_same_member_expression, AstNode}; +use crate::{AstNode, context::LintContext, rule::Rule, utils::is_same_member_expression}; fn operator_assignment_diagnostic(mode: Mode, span: Span, operator: &str) -> OxcDiagnostic { let msg = if Mode::Never == mode { @@ -31,11 +31,7 @@ enum Mode { impl Mode { pub fn from(raw: &str) -> Self { - if raw == "never" { - Self::Never - } else { - Self::Always - } + if raw == "never" { Self::Never } else { Self::Always } } }