Skip to content

Commit 3342e9f

Browse files
Fix: don't modify operator precedence in operator-assignment autofixer (#8358)
* Fix: don't modify operator precedence in operator-assignment autofixer Previously, the operator-assignment autofixer could sometimes modify semantics or produce a syntax error due to different operator precedence. This commit updates the fixer to surround the right side of an assignment with parentheses if it has lower precedence than its new neighbor. * Add additional test
1 parent f88375f commit 3342e9f

File tree

2 files changed

+46
-3
lines changed

2 files changed

+46
-3
lines changed

lib/rules/operator-assignment.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44
*/
55
"use strict";
66

7+
//------------------------------------------------------------------------------
8+
// Requirements
9+
//------------------------------------------------------------------------------
10+
11+
const astUtils = require("../ast-utils");
12+
713
//------------------------------------------------------------------------------
814
// Helpers
915
//------------------------------------------------------------------------------
@@ -171,9 +177,20 @@ module.exports = {
171177
if (canBeFixed(node.left)) {
172178
const operatorToken = getOperatorToken(node);
173179
const leftText = sourceCode.getText().slice(node.range[0], operatorToken.range[0]);
174-
const rightText = sourceCode.getText().slice(operatorToken.range[1], node.range[1]);
180+
const newOperator = node.operator.slice(0, -1);
181+
let rightText;
182+
183+
// If this change would modify precedence (e.g. `foo *= bar + 1` => `foo = foo * (bar + 1)`), parenthesize the right side.
184+
if (
185+
astUtils.getPrecedence(node.right) <= astUtils.getPrecedence({ type: "BinaryExpression", operator: newOperator }) &&
186+
!astUtils.isParenthesised(sourceCode, node.right)
187+
) {
188+
rightText = `${sourceCode.text.slice(operatorToken.range[1], node.right.range[0])}(${sourceCode.getText(node.right)})`;
189+
} else {
190+
rightText = sourceCode.text.slice(operatorToken.range[1], node.range[1]);
191+
}
175192

176-
return fixer.replaceText(node, `${leftText}= ${leftText}${node.operator.slice(0, -1)}${rightText}`);
193+
return fixer.replaceText(node, `${leftText}= ${leftText}${newOperator}${rightText}`);
177194
}
178195
return null;
179196
}

tests/lib/rules/operator-assignment.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ ruleTester.run("operator-assignment", rule, {
8181
"x = x === y",
8282
"x = x !== y",
8383
"x = x && y",
84-
"x = x || y"
84+
"x = x || y",
85+
"x = x * y + z"
8586
],
8687

8788
invalid: [{
@@ -214,6 +215,31 @@ ruleTester.run("operator-assignment", rule, {
214215
output: "foo = foo ** bar",
215216
options: ["never"],
216217
errors: UNEXPECTED_OPERATOR_ASSIGNMENT
218+
}, {
219+
code: "foo *= bar + 1",
220+
output: "foo = foo * (bar + 1)",
221+
options: ["never"],
222+
errors: UNEXPECTED_OPERATOR_ASSIGNMENT
223+
}, {
224+
code: "foo -= bar - baz",
225+
output: "foo = foo - (bar - baz)",
226+
options: ["never"],
227+
errors: UNEXPECTED_OPERATOR_ASSIGNMENT
228+
}, {
229+
code: "foo += bar + baz",
230+
output: "foo = foo + (bar + baz)", // addition is not associative in JS, e.g. (1 + 2) + '3' !== 1 + (2 + '3')
231+
options: ["never"],
232+
errors: UNEXPECTED_OPERATOR_ASSIGNMENT
233+
}, {
234+
code: "foo += bar = 1",
235+
output: "foo = foo + (bar = 1)",
236+
options: ["never"],
237+
errors: UNEXPECTED_OPERATOR_ASSIGNMENT
238+
}, {
239+
code: "foo *= (bar + 1)",
240+
output: "foo = foo * (bar + 1)",
241+
options: ["never"],
242+
errors: UNEXPECTED_OPERATOR_ASSIGNMENT
217243
}]
218244

219245
});

0 commit comments

Comments
 (0)