eslint 3.16.1 → 3.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/README.md +1 -0
  3. package/conf/eslint-recommended.js +2 -0
  4. package/lib/ast-utils.js +3 -67
  5. package/lib/code-path-analysis/code-path-analyzer.js +2 -7
  6. package/lib/code-path-analysis/debug-helpers.js +17 -16
  7. package/lib/config/config-file.js +68 -38
  8. package/lib/config/config-rule.js +14 -10
  9. package/lib/config/plugins.js +19 -8
  10. package/lib/eslint.js +11 -10
  11. package/lib/formatters/codeframe.js +4 -9
  12. package/lib/formatters/stylish.js +5 -4
  13. package/lib/ignored-paths.js +6 -0
  14. package/lib/internal-rules/internal-no-invalid-meta.js +2 -40
  15. package/lib/rules/array-callback-return.js +15 -5
  16. package/lib/rules/arrow-body-style.js +2 -2
  17. package/lib/rules/arrow-parens.js +9 -3
  18. package/lib/rules/capitalized-comments.js +2 -1
  19. package/lib/rules/comma-dangle.js +3 -2
  20. package/lib/rules/comma-spacing.js +4 -14
  21. package/lib/rules/comma-style.js +8 -14
  22. package/lib/rules/complexity.js +14 -8
  23. package/lib/rules/consistent-return.js +17 -10
  24. package/lib/rules/curly.js +2 -2
  25. package/lib/rules/dot-notation.js +12 -6
  26. package/lib/rules/func-name-matching.js +18 -7
  27. package/lib/rules/func-names.js +20 -5
  28. package/lib/rules/keyword-spacing.js +19 -4
  29. package/lib/rules/line-comment-position.js +15 -5
  30. package/lib/rules/lines-around-comment.js +19 -0
  31. package/lib/rules/lines-around-directive.js +1 -1
  32. package/lib/rules/max-params.js +17 -4
  33. package/lib/rules/max-statements.js +11 -10
  34. package/lib/rules/new-parens.js +7 -21
  35. package/lib/rules/no-compare-neg-zero.js +53 -0
  36. package/lib/rules/no-cond-assign.js +4 -17
  37. package/lib/rules/no-else-return.js +19 -4
  38. package/lib/rules/no-empty-function.js +9 -16
  39. package/lib/rules/no-extra-parens.js +110 -121
  40. package/lib/rules/no-extra-semi.js +16 -3
  41. package/lib/rules/no-global-assign.js +1 -1
  42. package/lib/rules/no-implicit-coercion.js +21 -8
  43. package/lib/rules/no-invalid-regexp.js +2 -1
  44. package/lib/rules/no-multiple-empty-lines.js +2 -4
  45. package/lib/rules/no-native-reassign.js +1 -1
  46. package/lib/rules/no-negated-in-lhs.js +1 -1
  47. package/lib/rules/no-new-func.js +6 -8
  48. package/lib/rules/no-new.js +2 -6
  49. package/lib/rules/no-param-reassign.js +37 -7
  50. package/lib/rules/no-process-exit.js +2 -10
  51. package/lib/rules/no-restricted-properties.js +2 -0
  52. package/lib/rules/no-restricted-syntax.js +32 -21
  53. package/lib/rules/no-return-await.js +1 -1
  54. package/lib/rules/no-sequences.js +2 -2
  55. package/lib/rules/no-sync.js +8 -13
  56. package/lib/rules/no-unsafe-negation.js +1 -1
  57. package/lib/rules/no-unused-expressions.js +10 -1
  58. package/lib/rules/no-unused-vars.js +12 -12
  59. package/lib/rules/no-use-before-define.js +1 -1
  60. package/lib/rules/no-useless-computed-key.js +12 -1
  61. package/lib/rules/no-useless-escape.js +8 -2
  62. package/lib/rules/no-useless-return.js +13 -2
  63. package/lib/rules/nonblock-statement-body-position.js +114 -0
  64. package/lib/rules/object-curly-spacing.js +2 -2
  65. package/lib/rules/object-shorthand.js +10 -3
  66. package/lib/rules/operator-assignment.js +20 -3
  67. package/lib/rules/padded-blocks.js +37 -31
  68. package/lib/rules/prefer-const.js +1 -1
  69. package/lib/rules/prefer-destructuring.js +1 -1
  70. package/lib/rules/quotes.js +1 -0
  71. package/lib/rules/semi-spacing.js +2 -15
  72. package/lib/rules/semi.js +17 -13
  73. package/lib/rules/sort-vars.js +3 -5
  74. package/lib/rules/space-before-function-paren.js +53 -77
  75. package/lib/rules/space-in-parens.js +4 -8
  76. package/lib/rules/space-unary-ops.js +19 -1
  77. package/lib/rules/strict.js +8 -2
  78. package/lib/rules/yoda.js +2 -2
  79. package/lib/testers/rule-tester.js +44 -13
  80. package/lib/util/fix-tracker.js +121 -0
  81. package/lib/util/glob-util.js +1 -1
  82. package/lib/util/node-event-generator.js +274 -4
  83. package/lib/util/source-code-fixer.js +3 -9
  84. package/lib/util/source-code.js +99 -2
  85. package/lib/util/traverser.js +16 -25
  86. package/package.json +34 -34
@@ -26,17 +26,9 @@ module.exports = {
26
26
  //--------------------------------------------------------------------------
27
27
 
28
28
  return {
29
-
30
- CallExpression(node) {
31
- const callee = node.callee;
32
-
33
- if (callee.type === "MemberExpression" && callee.object.name === "process" &&
34
- callee.property.name === "exit"
35
- ) {
36
- context.report({ node, message: "Don't use process.exit(); throw an error instead." });
37
- }
29
+ "CallExpression > MemberExpression.callee[object.name = 'process'][property.name = 'exit']"(node) {
30
+ context.report({ node: node.parent, message: "Don't use process.exit(); throw an error instead." });
38
31
  }
39
-
40
32
  };
41
33
 
42
34
  }
@@ -109,6 +109,7 @@ module.exports = {
109
109
  if (matchedObjectProperty) {
110
110
  const message = matchedObjectProperty.message ? ` ${matchedObjectProperty.message}` : "";
111
111
 
112
+ // eslint-disable-next-line eslint-plugin/report-message-format
112
113
  context.report({ node, message: "'{{objectName}}.{{propertyName}}' is restricted from being used.{{message}}", data: {
113
114
  objectName,
114
115
  propertyName,
@@ -117,6 +118,7 @@ module.exports = {
117
118
  } else if (globalMatchedProperty) {
118
119
  const message = globalMatchedProperty.message ? ` ${globalMatchedProperty.message}` : "";
119
120
 
121
+ // eslint-disable-next-line eslint-plugin/report-message-format
120
122
  context.report({ node, message: "'{{propertyName}}' is restricted from being used.{{message}}", data: {
121
123
  propertyName,
122
124
  message
@@ -8,8 +8,6 @@
8
8
  // Rule Definition
9
9
  //------------------------------------------------------------------------------
10
10
 
11
- const nodeTypes = require("espree").Syntax;
12
-
13
11
  module.exports = {
14
12
  meta: {
15
13
  docs: {
@@ -20,31 +18,44 @@ module.exports = {
20
18
 
21
19
  schema: {
22
20
  type: "array",
23
- items: [
24
- {
25
- enum: Object.keys(nodeTypes).map(k => nodeTypes[k])
26
- }
27
- ],
21
+ items: [{
22
+ oneOf: [
23
+ {
24
+ type: "string"
25
+ },
26
+ {
27
+ type: "object",
28
+ properties: {
29
+ selector: { type: "string" },
30
+ message: { type: "string" }
31
+ },
32
+ required: ["selector"],
33
+ additionalProperties: false
34
+ }
35
+ ]
36
+ }],
28
37
  uniqueItems: true,
29
38
  minItems: 0
30
39
  }
31
40
  },
32
41
 
33
42
  create(context) {
34
-
35
- /**
36
- * Generates a warning from the provided node, saying that node type is not allowed.
37
- * @param {ASTNode} node The node to warn on
38
- * @returns {void}
39
- */
40
- function warn(node) {
41
- context.report({ node, message: "Using '{{type}}' is not allowed.", data: node });
42
- }
43
-
44
- return context.options.reduce((result, nodeType) => {
45
- result[nodeType] = warn;
46
-
47
- return result;
43
+ return context.options.reduce((result, selectorOrObject) => {
44
+ const isStringFormat = (typeof selectorOrObject === "string");
45
+ const hasCustomMessage = !isStringFormat && Boolean(selectorOrObject.message);
46
+
47
+ const selector = isStringFormat ? selectorOrObject : selectorOrObject.selector;
48
+ const message = hasCustomMessage ? selectorOrObject.message : "Using '{{selector}}' is not allowed.";
49
+
50
+ return Object.assign(result, {
51
+ [selector](node) {
52
+ context.report({
53
+ node,
54
+ message,
55
+ data: hasCustomMessage ? {} : { selector }
56
+ });
57
+ }
58
+ });
48
59
  }, {});
49
60
 
50
61
  }
@@ -19,7 +19,7 @@ module.exports = {
19
19
  category: "Best Practices",
20
20
  recommended: false // TODO: set to true
21
21
  },
22
- fixable: false,
22
+ fixable: null,
23
23
  schema: [
24
24
  ]
25
25
  },
@@ -76,8 +76,8 @@ module.exports = {
76
76
  nextToken = sourceCode.getTokenAfter(node, 1);
77
77
 
78
78
  return isParenthesised(node) && previousToken && nextToken &&
79
- previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
80
- nextToken.value === ")" && nextToken.range[0] >= node.range[1];
79
+ astUtils.isOpeningParenToken(previousToken) && previousToken.range[1] <= node.range[0] &&
80
+ astUtils.isClosingParenToken(nextToken) && nextToken.range[0] >= node.range[1];
81
81
  }
82
82
 
83
83
  return {
@@ -26,19 +26,14 @@ module.exports = {
26
26
 
27
27
  return {
28
28
 
29
- MemberExpression(node) {
30
- const propertyName = node.property.name,
31
- syncRegex = /.*Sync$/;
32
-
33
- if (syncRegex.exec(propertyName) !== null) {
34
- context.report({
35
- node,
36
- message: "Unexpected sync method: '{{propertyName}}'.",
37
- data: {
38
- propertyName
39
- }
40
- });
41
- }
29
+ "MemberExpression[property.name=/.*Sync$/]"(node) {
30
+ context.report({
31
+ node,
32
+ message: "Unexpected sync method: '{{propertyName}}'.",
33
+ data: {
34
+ propertyName: node.property.name
35
+ }
36
+ });
42
37
  }
43
38
  };
44
39
 
@@ -44,7 +44,7 @@ module.exports = {
44
44
  docs: {
45
45
  description: "disallow negating the left operand of relational operators",
46
46
  category: "Possible Errors",
47
- recommended: false
47
+ recommended: true
48
48
  },
49
49
  schema: [],
50
50
  fixable: "code"
@@ -25,6 +25,9 @@ module.exports = {
25
25
  },
26
26
  allowTernary: {
27
27
  type: "boolean"
28
+ },
29
+ allowTaggedTemplates: {
30
+ type: "boolean"
28
31
  }
29
32
  },
30
33
  additionalProperties: false
@@ -35,7 +38,8 @@ module.exports = {
35
38
  create(context) {
36
39
  const config = context.options[0] || {},
37
40
  allowShortCircuit = config.allowShortCircuit || false,
38
- allowTernary = config.allowTernary || false;
41
+ allowTernary = config.allowTernary || false,
42
+ allowTaggedTemplates = config.allowTaggedTemplates || false;
39
43
 
40
44
  /**
41
45
  * @param {ASTNode} node - any node
@@ -95,12 +99,17 @@ module.exports = {
95
99
  return isValidExpression(node.consequent) && isValidExpression(node.alternate);
96
100
  }
97
101
  }
102
+
98
103
  if (allowShortCircuit) {
99
104
  if (node.type === "LogicalExpression") {
100
105
  return isValidExpression(node.right);
101
106
  }
102
107
  }
103
108
 
109
+ if (allowTaggedTemplates && node.type === "TaggedTemplateExpression") {
110
+ return true;
111
+ }
112
+
104
113
  return /^(?:Assignment|Call|New|Update|Yield|Await)Expression$/.test(node.type) ||
105
114
  (node.type === "UnaryExpression" && ["delete", "void"].indexOf(node.operator) >= 0);
106
115
  }
@@ -66,6 +66,7 @@ module.exports = {
66
66
 
67
67
  const DEFINED_MESSAGE = "'{{name}}' is defined but never used.";
68
68
  const ASSIGNED_MESSAGE = "'{{name}}' is assigned a value but never used.";
69
+ const REST_PROPERTY_TYPE = /^(?:Experimental)?RestProperty$/;
69
70
 
70
71
  const config = {
71
72
  vars: "all",
@@ -139,17 +140,16 @@ module.exports = {
139
140
  */
140
141
  function hasRestSpreadSibling(variable) {
141
142
  if (config.ignoreRestSiblings) {
142
- const restProperties = new Set(["ExperimentalRestProperty", "RestProperty"]);
143
-
144
- return variable.defs
145
- .filter(def => def.name.type === "Identifier")
146
- .some(def => (
147
- def.node.id &&
148
- def.node.id.type === "ObjectPattern" &&
149
- def.node.id.properties.length &&
150
- restProperties.has(def.node.id.properties[def.node.id.properties.length - 1].type) && // last property is a rest property
151
- !restProperties.has(def.name.parent.type) // variable is sibling of the rest property
152
- ));
143
+ return variable.defs.some(def => {
144
+ const propertyNode = def.name.parent;
145
+ const patternNode = propertyNode.parent;
146
+
147
+ return (
148
+ propertyNode.type === "Property" &&
149
+ patternNode.type === "ObjectPattern" &&
150
+ REST_PROPERTY_TYPE.test(patternNode.properties[patternNode.properties.length - 1].type)
151
+ );
152
+ });
153
153
  }
154
154
 
155
155
  return false;
@@ -568,7 +568,7 @@ module.exports = {
568
568
  function getLocation(variable) {
569
569
  const comment = variable.eslintExplicitGlobalComment;
570
570
 
571
- return astUtils.getLocationFromRangeIndex(sourceCode, comment.range[0] + 2 + getColumnInComment(variable, comment));
571
+ return sourceCode.getLocFromIndex(comment.range[0] + 2 + getColumnInComment(variable, comment));
572
572
  }
573
573
 
574
574
  //--------------------------------------------------------------------------
@@ -166,7 +166,7 @@ module.exports = {
166
166
  const options = parseOptions(context.options[0]);
167
167
 
168
168
  /**
169
- * Determines whether a given use-before-define case should be reportedaccording to the options.
169
+ * Determines whether a given use-before-define case should be reported according to the options.
170
170
  * @param {escope.Variable} variable The variable that gets used before being defined
171
171
  * @param {escope.Reference} reference The reference to the variable
172
172
  * @returns {boolean} `true` if the usage should be reported
@@ -9,6 +9,7 @@
9
9
  //------------------------------------------------------------------------------
10
10
 
11
11
  const astUtils = require("../ast-utils");
12
+ const esUtils = require("esutils");
12
13
 
13
14
  //------------------------------------------------------------------------------
14
15
  // Rule Definition
@@ -55,7 +56,17 @@ module.exports = {
55
56
  // If there are comments between the brackets and the property name, don't do a fix.
56
57
  return null;
57
58
  }
58
- return fixer.replaceTextRange([leftSquareBracket.range[0], rightSquareBracket.range[1]], key.raw);
59
+
60
+ const tokenBeforeLeftBracket = sourceCode.getTokenBefore(leftSquareBracket);
61
+
62
+ // Insert a space before the key to avoid changing identifiers, e.g. ({ get[2]() {} }) to ({ get2() {} })
63
+ const needsSpaceBeforeKey = tokenBeforeLeftBracket.range[1] === leftSquareBracket.range[0] &&
64
+ esUtils.code.isIdentifierPartES6(tokenBeforeLeftBracket.value.slice(-1).charCodeAt(0)) &&
65
+ esUtils.code.isIdentifierPartES6(key.raw.charCodeAt(0));
66
+
67
+ const replacementKey = (needsSpaceBeforeKey ? " " : "") + key.raw;
68
+
69
+ return fixer.replaceTextRange([leftSquareBracket.range[0], rightSquareBracket.range[1]], replacementKey);
59
70
  }
60
71
  });
61
72
  }
@@ -94,7 +94,7 @@ module.exports = {
94
94
  function report(node, startOffset, character) {
95
95
  context.report({
96
96
  node,
97
- loc: astUtils.getLocationFromRangeIndex(sourceCode, astUtils.getRangeIndexFromLocation(sourceCode, node.loc.start) + startOffset),
97
+ loc: sourceCode.getLocFromIndex(sourceCode.getIndexFromLoc(node.loc.start) + startOffset),
98
98
  message: "Unnecessary escape character: \\{{character}}.",
99
99
  data: { character }
100
100
  });
@@ -147,7 +147,13 @@ module.exports = {
147
147
  function check(node) {
148
148
  const isTemplateElement = node.type === "TemplateElement";
149
149
 
150
- if (isTemplateElement && node.parent && node.parent.parent && node.parent.parent.type === "TaggedTemplateExpression") {
150
+ if (
151
+ isTemplateElement &&
152
+ node.parent &&
153
+ node.parent.parent &&
154
+ node.parent.parent.type === "TaggedTemplateExpression" &&
155
+ node.parent === node.parent.parent.quasi
156
+ ) {
151
157
 
152
158
  // Don't report tagged template literals, because the backslash character is accessible to the tag function.
153
159
  return;
@@ -8,7 +8,8 @@
8
8
  // Requirements
9
9
  //------------------------------------------------------------------------------
10
10
 
11
- const astUtils = require("../ast-utils");
11
+ const astUtils = require("../ast-utils"),
12
+ FixTracker = require("../util/fix-tracker");
12
13
 
13
14
  //------------------------------------------------------------------------------
14
15
  // Helpers
@@ -219,7 +220,17 @@ module.exports = {
219
220
  loc: node.loc,
220
221
  message: "Unnecessary return statement.",
221
222
  fix(fixer) {
222
- return isRemovable(node) ? fixer.remove(node) : null;
223
+ if (isRemovable(node)) {
224
+
225
+ // Extend the replacement range to include the
226
+ // entire function to avoid conflicting with
227
+ // no-else-return.
228
+ // https://github.com/eslint/eslint/issues/8026
229
+ return new FixTracker(fixer, context.getSourceCode())
230
+ .retainEnclosingFunction(node)
231
+ .remove(node);
232
+ }
233
+ return null;
223
234
  }
224
235
  });
225
236
  }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * @fileoverview enforce the location of single-line statements
3
+ * @author Teddy Katz
4
+ */
5
+ "use strict";
6
+
7
+ //------------------------------------------------------------------------------
8
+ // Rule Definition
9
+ //------------------------------------------------------------------------------
10
+
11
+ const POSITION_SCHEMA = { enum: ["beside", "below", "any"] };
12
+
13
+ module.exports = {
14
+ meta: {
15
+ docs: {
16
+ description: "enforce the location of single-line statements",
17
+ category: "Stylistic Issues",
18
+ recommended: false
19
+ },
20
+ fixable: "whitespace",
21
+ schema: [
22
+ POSITION_SCHEMA,
23
+ {
24
+ properties: {
25
+ overrides: {
26
+ properties: {
27
+ if: POSITION_SCHEMA,
28
+ else: POSITION_SCHEMA,
29
+ while: POSITION_SCHEMA,
30
+ do: POSITION_SCHEMA,
31
+ for: POSITION_SCHEMA
32
+ },
33
+ additionalProperties: false
34
+ }
35
+ },
36
+ additionalProperties: false
37
+ }
38
+ ]
39
+ },
40
+
41
+ create(context) {
42
+ const sourceCode = context.getSourceCode();
43
+
44
+ //----------------------------------------------------------------------
45
+ // Helpers
46
+ //----------------------------------------------------------------------
47
+
48
+ /**
49
+ * Gets the applicable preference for a particular keyword
50
+ * @param {string} keywordName The name of a keyword, e.g. 'if'
51
+ * @returns {string} The applicable option for the keyword, e.g. 'beside'
52
+ */
53
+ function getOption(keywordName) {
54
+ return context.options[1] && context.options[1].overrides && context.options[1].overrides[keywordName] ||
55
+ context.options[0] ||
56
+ "beside";
57
+ }
58
+
59
+ /**
60
+ * Validates the location of a single-line statement
61
+ * @param {ASTNode} node The single-line statement
62
+ * @param {string} keywordName The applicable keyword name for the single-line statement
63
+ * @returns {void}
64
+ */
65
+ function validateStatement(node, keywordName) {
66
+ const option = getOption(keywordName);
67
+
68
+ if (node.type === "BlockStatement" || option === "any") {
69
+ return;
70
+ }
71
+
72
+ const tokenBefore = sourceCode.getTokenBefore(node);
73
+
74
+ if (tokenBefore.loc.end.line === node.loc.start.line && option === "below") {
75
+ context.report({
76
+ node,
77
+ message: "Expected a linebreak before this statement.",
78
+ fix: fixer => fixer.insertTextBefore(node, "\n")
79
+ });
80
+ } else if (tokenBefore.loc.end.line !== node.loc.start.line && option === "beside") {
81
+ context.report({
82
+ node,
83
+ message: "Expected no linebreak before this statement.",
84
+ fix(fixer) {
85
+ if (sourceCode.getText().slice(tokenBefore.range[1], node.range[0]).trim()) {
86
+ return null;
87
+ }
88
+ return fixer.replaceTextRange([tokenBefore.range[1], node.range[0]], " ");
89
+ }
90
+ });
91
+ }
92
+ }
93
+
94
+ //----------------------------------------------------------------------
95
+ // Public
96
+ //----------------------------------------------------------------------
97
+
98
+ return {
99
+ IfStatement(node) {
100
+ validateStatement(node.consequent, "if");
101
+
102
+ // Check the `else` node, but don't check 'else if' statements.
103
+ if (node.alternate && node.alternate.type !== "IfStatement") {
104
+ validateStatement(node.alternate, "else");
105
+ }
106
+ },
107
+ WhileStatement: node => validateStatement(node.body, "while"),
108
+ DoWhileStatement: node => validateStatement(node.body, "do"),
109
+ ForStatement: node => validateStatement(node.body, "for"),
110
+ ForInStatement: node => validateStatement(node.body, "for"),
111
+ ForOfStatement: node => validateStatement(node.body, "for")
112
+ };
113
+ }
114
+ };
@@ -171,8 +171,8 @@ module.exports = {
171
171
 
172
172
  if (astUtils.isTokenOnSameLine(penultimate, last)) {
173
173
  const shouldCheckPenultimate = (
174
- options.arraysInObjectsException && penultimate.value === "]" ||
175
- options.objectsInObjectsException && penultimate.value === "}"
174
+ options.arraysInObjectsException && astUtils.isClosingBracketToken(penultimate) ||
175
+ options.objectsInObjectsException && astUtils.isClosingBraceToken(penultimate)
176
176
  );
177
177
  const penultimateType = shouldCheckPenultimate && sourceCode.getNodeByRangeIndex(penultimate.start).type;
178
178
 
@@ -230,7 +230,10 @@ module.exports = {
230
230
  const functionToken = sourceCode.getTokens(node.value).find(token => token.type === "Keyword" && token.value === "function");
231
231
  const tokenBeforeParams = node.value.generator ? sourceCode.getTokenAfter(functionToken) : functionToken;
232
232
 
233
- return fixer.replaceTextRange([firstKeyToken.range[0], tokenBeforeParams.range[1]], keyPrefix + keyText);
233
+ return fixer.replaceTextRange(
234
+ [firstKeyToken.range[0], node.range[1]],
235
+ keyPrefix + keyText + sourceCode.text.slice(tokenBeforeParams.range[1], node.value.range[1])
236
+ );
234
237
  }
235
238
  const arrowToken = sourceCode.getTokens(node.value).find(token => token.value === "=>");
236
239
  const tokenBeforeArrow = sourceCode.getTokenBefore(arrowToken);
@@ -238,7 +241,10 @@ module.exports = {
238
241
  const oldParamText = sourceCode.text.slice(sourceCode.getFirstToken(node.value, node.value.async ? 1 : 0).range[0], tokenBeforeArrow.range[1]);
239
242
  const newParamText = hasParensAroundParameters ? oldParamText : `(${oldParamText})`;
240
243
 
241
- return fixer.replaceTextRange([firstKeyToken.range[0], arrowToken.range[1]], keyPrefix + keyText + newParamText);
244
+ return fixer.replaceTextRange(
245
+ [firstKeyToken.range[0], node.range[1]],
246
+ keyPrefix + keyText + newParamText + sourceCode.text.slice(arrowToken.range[1], node.value.range[1])
247
+ );
242
248
 
243
249
  }
244
250
 
@@ -368,11 +374,12 @@ module.exports = {
368
374
  // Checks for property/method shorthand.
369
375
  if (isConciseProperty) {
370
376
  if (node.method && (APPLY_NEVER || AVOID_QUOTES && isStringLiteral(node.key))) {
377
+ const message = APPLY_NEVER ? "Expected longform method syntax." : "Expected longform method syntax for string literal keys.";
371
378
 
372
379
  // { x() {} } should be written as { x: function() {} }
373
380
  context.report({
374
381
  node,
375
- message: `Expected longform method syntax${APPLY_NEVER ? "" : " for string literal keys"}.`,
382
+ message,
376
383
  fix: fixer => makeFunctionLongform(fixer, node)
377
384
  });
378
385
  } else if (APPLY_NEVER) {
@@ -4,6 +4,12 @@
4
4
  */
5
5
  "use strict";
6
6
 
7
+ //------------------------------------------------------------------------------
8
+ // Requirements
9
+ //------------------------------------------------------------------------------
10
+
11
+ const astUtils = require("../ast-utils");
12
+
7
13
  //------------------------------------------------------------------------------
8
14
  // Helpers
9
15
  //------------------------------------------------------------------------------
@@ -135,7 +141,7 @@ module.exports = {
135
141
  const equalsToken = getOperatorToken(node);
136
142
  const operatorToken = getOperatorToken(expr);
137
143
  const leftText = sourceCode.getText().slice(node.range[0], equalsToken.range[0]);
138
- const rightText = sourceCode.getText().slice(operatorToken.range[1], node.range[1]);
144
+ const rightText = sourceCode.getText().slice(operatorToken.range[1], node.right.range[1]);
139
145
 
140
146
  return fixer.replaceText(node, `${leftText}${expr.operator}=${rightText}`);
141
147
  }
@@ -171,9 +177,20 @@ module.exports = {
171
177
  if (canBeFixed(node.left)) {
172
178
  const operatorToken = getOperatorToken(node);
173
179
  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
+ }
175
192
 
176
- return fixer.replaceText(node, `${leftText}= ${leftText}${node.operator.slice(0, -1)}${rightText}`);
193
+ return fixer.replaceText(node, `${leftText}= ${leftText}${newOperator}${rightText}`);
177
194
  }
178
195
  return null;
179
196
  }