eslint 6.6.0 → 6.8.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 (62) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +7 -8
  3. package/conf/config-schema.js +2 -0
  4. package/conf/default-cli-options.js +1 -1
  5. package/conf/eslint-recommended.js +0 -1
  6. package/lib/cli-engine/cascading-config-array-factory.js +38 -13
  7. package/lib/cli-engine/cli-engine.js +41 -14
  8. package/lib/cli-engine/config-array/config-array.js +13 -0
  9. package/lib/cli-engine/config-array/extracted-config.js +27 -0
  10. package/lib/cli-engine/config-array/ignore-pattern.js +231 -0
  11. package/lib/cli-engine/config-array/index.js +2 -0
  12. package/lib/cli-engine/config-array-factory.js +115 -1
  13. package/lib/cli-engine/file-enumerator.js +73 -40
  14. package/lib/cli-engine/lint-result-cache.js +2 -1
  15. package/lib/cli.js +2 -1
  16. package/lib/init/config-initializer.js +4 -3
  17. package/lib/linter/config-comment-parser.js +1 -1
  18. package/lib/linter/report-translator.js +73 -7
  19. package/lib/options.js +6 -0
  20. package/lib/rule-tester/rule-tester.js +42 -6
  21. package/lib/rules/array-bracket-spacing.js +8 -8
  22. package/lib/rules/camelcase.js +19 -6
  23. package/lib/rules/comma-dangle.js +5 -2
  24. package/lib/rules/computed-property-spacing.js +4 -4
  25. package/lib/rules/curly.js +9 -4
  26. package/lib/rules/function-call-argument-newline.js +3 -1
  27. package/lib/rules/grouped-accessor-pairs.js +224 -0
  28. package/lib/rules/indent.js +11 -0
  29. package/lib/rules/index.js +5 -0
  30. package/lib/rules/key-spacing.js +34 -15
  31. package/lib/rules/lines-between-class-members.js +42 -53
  32. package/lib/rules/multiline-comment-style.js +237 -106
  33. package/lib/rules/no-cond-assign.js +14 -4
  34. package/lib/rules/no-constructor-return.js +62 -0
  35. package/lib/rules/no-dupe-else-if.js +122 -0
  36. package/lib/rules/no-implicit-globals.js +90 -8
  37. package/lib/rules/no-inline-comments.js +25 -11
  38. package/lib/rules/no-invalid-this.js +16 -2
  39. package/lib/rules/no-multiple-empty-lines.js +1 -1
  40. package/lib/rules/no-octal-escape.js +1 -1
  41. package/lib/rules/no-restricted-imports.js +2 -2
  42. package/lib/rules/no-setter-return.js +227 -0
  43. package/lib/rules/no-underscore-dangle.js +23 -4
  44. package/lib/rules/no-unexpected-multiline.js +8 -0
  45. package/lib/rules/no-unsafe-negation.js +30 -5
  46. package/lib/rules/no-useless-computed-key.js +60 -33
  47. package/lib/rules/no-useless-escape.js +26 -3
  48. package/lib/rules/object-curly-spacing.js +8 -8
  49. package/lib/rules/operator-assignment.js +11 -2
  50. package/lib/rules/prefer-const.js +14 -7
  51. package/lib/rules/prefer-exponentiation-operator.js +189 -0
  52. package/lib/rules/prefer-numeric-literals.js +29 -28
  53. package/lib/rules/require-atomic-updates.js +1 -1
  54. package/lib/rules/require-await.js +8 -0
  55. package/lib/rules/semi.js +6 -3
  56. package/lib/rules/space-infix-ops.js +1 -1
  57. package/lib/rules/spaced-comment.js +5 -4
  58. package/lib/rules/utils/ast-utils.js +31 -4
  59. package/lib/shared/types.js +9 -0
  60. package/lib/source-code/source-code.js +87 -10
  61. package/package.json +4 -3
  62. package/lib/cli-engine/ignored-paths.js +0 -363
@@ -38,6 +38,10 @@ module.exports = {
38
38
  type: "boolean",
39
39
  default: false
40
40
  },
41
+ allowAfterThisConstructor: {
42
+ type: "boolean",
43
+ default: false
44
+ },
41
45
  enforceInMethodNames: {
42
46
  type: "boolean",
43
47
  default: false
@@ -54,6 +58,7 @@ module.exports = {
54
58
  const ALLOWED_VARIABLES = options.allow ? options.allow : [];
55
59
  const allowAfterThis = typeof options.allowAfterThis !== "undefined" ? options.allowAfterThis : false;
56
60
  const allowAfterSuper = typeof options.allowAfterSuper !== "undefined" ? options.allowAfterSuper : false;
61
+ const allowAfterThisConstructor = typeof options.allowAfterThisConstructor !== "undefined" ? options.allowAfterThisConstructor : false;
57
62
  const enforceInMethodNames = typeof options.enforceInMethodNames !== "undefined" ? options.enforceInMethodNames : false;
58
63
 
59
64
  //-------------------------------------------------------------------------
@@ -72,7 +77,7 @@ module.exports = {
72
77
 
73
78
  /**
74
79
  * Check if identifier has a underscore at the end
75
- * @param {ASTNode} identifier node to evaluate
80
+ * @param {string} identifier name of the node
76
81
  * @returns {boolean} true if its is present
77
82
  * @private
78
83
  */
@@ -84,7 +89,7 @@ module.exports = {
84
89
 
85
90
  /**
86
91
  * Check if identifier is a special case member expression
87
- * @param {ASTNode} identifier node to evaluate
92
+ * @param {string} identifier name of the node
88
93
  * @returns {boolean} true if its is a special case
89
94
  * @private
90
95
  */
@@ -94,7 +99,7 @@ module.exports = {
94
99
 
95
100
  /**
96
101
  * Check if identifier is a special case variable expression
97
- * @param {ASTNode} identifier node to evaluate
102
+ * @param {string} identifier name of the node
98
103
  * @returns {boolean} true if its is a special case
99
104
  * @private
100
105
  */
@@ -104,6 +109,18 @@ module.exports = {
104
109
  return identifier === "_";
105
110
  }
106
111
 
112
+ /**
113
+ * Check if a node is a member reference of this.constructor
114
+ * @param {ASTNode} node node to evaluate
115
+ * @returns {boolean} true if it is a reference on this.constructor
116
+ * @private
117
+ */
118
+ function isThisConstructorReference(node) {
119
+ return node.object.type === "MemberExpression" &&
120
+ node.object.property.name === "constructor" &&
121
+ node.object.object.type === "ThisExpression";
122
+ }
123
+
107
124
  /**
108
125
  * Check if function has a underscore at the end
109
126
  * @param {ASTNode} node node to evaluate
@@ -156,11 +173,13 @@ module.exports = {
156
173
  function checkForTrailingUnderscoreInMemberExpression(node) {
157
174
  const identifier = node.property.name,
158
175
  isMemberOfThis = node.object.type === "ThisExpression",
159
- isMemberOfSuper = node.object.type === "Super";
176
+ isMemberOfSuper = node.object.type === "Super",
177
+ isMemberOfThisConstructor = isThisConstructorReference(node);
160
178
 
161
179
  if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier) &&
162
180
  !(isMemberOfThis && allowAfterThis) &&
163
181
  !(isMemberOfSuper && allowAfterSuper) &&
182
+ !(isMemberOfThisConstructor && allowAfterThisConstructor) &&
164
183
  !isSpecialCaseIdentifierForMemberExpression(identifier) && !isAllowed(identifier)) {
165
184
  context.report({
166
185
  node,
@@ -74,6 +74,14 @@ module.exports = {
74
74
  if (node.tag.loc.end.line === node.quasi.loc.start.line) {
75
75
  return;
76
76
  }
77
+
78
+ // handle generics type parameters on template tags
79
+ const tokenBefore = sourceCode.getTokenBefore(node.quasi);
80
+
81
+ if (tokenBefore.loc.end.line === node.quasi.loc.start.line) {
82
+ return;
83
+ }
84
+
77
85
  context.report({ node, loc: node.loc.start, messageId: "taggedTemplate" });
78
86
  },
79
87
 
@@ -54,7 +54,8 @@ module.exports = {
54
54
  description: "disallow negating the left operand of relational operators",
55
55
  category: "Possible Errors",
56
56
  recommended: true,
57
- url: "https://eslint.org/docs/rules/no-unsafe-negation"
57
+ url: "https://eslint.org/docs/rules/no-unsafe-negation",
58
+ suggestion: true
58
59
  },
59
60
 
60
61
  schema: [
@@ -69,9 +70,13 @@ module.exports = {
69
70
  additionalProperties: false
70
71
  }
71
72
  ],
73
+
72
74
  fixable: null,
75
+
73
76
  messages: {
74
- unexpected: "Unexpected negating the left operand of '{{operator}}' operator."
77
+ unexpected: "Unexpected negating the left operand of '{{operator}}' operator.",
78
+ suggestNegatedExpression: "Negate '{{operator}}' expression instead of its left operand. This changes the current behavior.",
79
+ suggestParenthesisedNegation: "Wrap negation in '()' to make the intention explicit. This preserves the current behavior."
75
80
  }
76
81
  },
77
82
 
@@ -82,10 +87,11 @@ module.exports = {
82
87
 
83
88
  return {
84
89
  BinaryExpression(node) {
85
- const orderingRelationRuleApplies = enforceForOrderingRelations && isOrderingRelationalOperator(node.operator);
90
+ const operator = node.operator;
91
+ const orderingRelationRuleApplies = enforceForOrderingRelations && isOrderingRelationalOperator(operator);
86
92
 
87
93
  if (
88
- (isInOrInstanceOfOperator(node.operator) || orderingRelationRuleApplies) &&
94
+ (isInOrInstanceOfOperator(operator) || orderingRelationRuleApplies) &&
89
95
  isNegation(node.left) &&
90
96
  !astUtils.isParenthesised(sourceCode, node.left)
91
97
  ) {
@@ -93,7 +99,26 @@ module.exports = {
93
99
  node,
94
100
  loc: node.left.loc,
95
101
  messageId: "unexpected",
96
- data: { operator: node.operator }
102
+ data: { operator },
103
+ suggest: [
104
+ {
105
+ messageId: "suggestNegatedExpression",
106
+ data: { operator },
107
+ fix(fixer) {
108
+ const negationToken = sourceCode.getFirstToken(node.left);
109
+ const fixRange = [negationToken.range[1], node.range[1]];
110
+ const text = sourceCode.text.slice(fixRange[0], fixRange[1]);
111
+
112
+ return fixer.replaceTextRange(fixRange, `(${text})`);
113
+ }
114
+ },
115
+ {
116
+ messageId: "suggestParenthesisedNegation",
117
+ fix(fixer) {
118
+ return fixer.replaceText(node.left, `(${sourceCode.getText(node.left)})`);
119
+ }
120
+ }
121
+ ]
97
122
  });
98
123
  }
99
124
  }
@@ -8,6 +8,7 @@
8
8
  // Requirements
9
9
  //------------------------------------------------------------------------------
10
10
 
11
+ const lodash = require("lodash");
11
12
  const astUtils = require("./utils/ast-utils");
12
13
 
13
14
  //------------------------------------------------------------------------------
@@ -21,57 +22,83 @@ module.exports = {
21
22
  type: "suggestion",
22
23
 
23
24
  docs: {
24
- description: "disallow unnecessary computed property keys in object literals",
25
+ description: "disallow unnecessary computed property keys in objects and classes",
25
26
  category: "ECMAScript 6",
26
27
  recommended: false,
27
28
  url: "https://eslint.org/docs/rules/no-useless-computed-key"
28
29
  },
29
30
 
30
- schema: [],
31
+ schema: [{
32
+ type: "object",
33
+ properties: {
34
+ enforceForClassMembers: {
35
+ type: "boolean",
36
+ default: false
37
+ }
38
+ },
39
+ additionalProperties: false
40
+ }],
31
41
  fixable: "code"
32
42
  },
33
43
  create(context) {
34
44
  const sourceCode = context.getSourceCode();
45
+ const enforceForClassMembers = context.options[0] && context.options[0].enforceForClassMembers;
46
+
47
+ /**
48
+ * Reports a given node if it violated this rule.
49
+ * @param {ASTNode} node The node to check.
50
+ * @returns {void}
51
+ */
52
+ function check(node) {
53
+ if (!node.computed) {
54
+ return;
55
+ }
35
56
 
36
- return {
37
- Property(node) {
38
- if (!node.computed) {
39
- return;
40
- }
41
-
42
- const key = node.key,
43
- nodeType = typeof key.value;
57
+ const key = node.key,
58
+ nodeType = typeof key.value;
44
59
 
45
- if (key.type === "Literal" && (nodeType === "string" || nodeType === "number") && key.value !== "__proto__") {
46
- context.report({
47
- node,
48
- message: MESSAGE_UNNECESSARY_COMPUTED,
49
- data: { property: sourceCode.getText(key) },
50
- fix(fixer) {
51
- const leftSquareBracket = sourceCode.getFirstToken(node, astUtils.isOpeningBracketToken);
52
- const rightSquareBracket = sourceCode.getFirstTokenBetween(node.key, node.value, astUtils.isClosingBracketToken);
53
- const tokensBetween = sourceCode.getTokensBetween(leftSquareBracket, rightSquareBracket, 1);
60
+ let allowedKey;
54
61
 
55
- if (tokensBetween.slice(0, -1).some((token, index) =>
56
- sourceCode.getText().slice(token.range[1], tokensBetween[index + 1].range[0]).trim())) {
62
+ if (node.type === "MethodDefinition") {
63
+ allowedKey = node.static ? "prototype" : "constructor";
64
+ } else {
65
+ allowedKey = "__proto__";
66
+ }
57
67
 
58
- // If there are comments between the brackets and the property name, don't do a fix.
59
- return null;
60
- }
68
+ if (key.type === "Literal" && (nodeType === "string" || nodeType === "number") && key.value !== allowedKey) {
69
+ context.report({
70
+ node,
71
+ message: MESSAGE_UNNECESSARY_COMPUTED,
72
+ data: { property: sourceCode.getText(key) },
73
+ fix(fixer) {
74
+ const leftSquareBracket = sourceCode.getFirstToken(node, astUtils.isOpeningBracketToken);
75
+ const rightSquareBracket = sourceCode.getFirstTokenBetween(node.key, node.value, astUtils.isClosingBracketToken);
76
+ const tokensBetween = sourceCode.getTokensBetween(leftSquareBracket, rightSquareBracket, 1);
77
+
78
+ if (tokensBetween.slice(0, -1).some((token, index) =>
79
+ sourceCode.getText().slice(token.range[1], tokensBetween[index + 1].range[0]).trim())) {
80
+
81
+ // If there are comments between the brackets and the property name, don't do a fix.
82
+ return null;
83
+ }
61
84
 
62
- const tokenBeforeLeftBracket = sourceCode.getTokenBefore(leftSquareBracket);
85
+ const tokenBeforeLeftBracket = sourceCode.getTokenBefore(leftSquareBracket);
63
86
 
64
- // Insert a space before the key to avoid changing identifiers, e.g. ({ get[2]() {} }) to ({ get2() {} })
65
- const needsSpaceBeforeKey = tokenBeforeLeftBracket.range[1] === leftSquareBracket.range[0] &&
66
- !astUtils.canTokensBeAdjacent(tokenBeforeLeftBracket, sourceCode.getFirstToken(key));
87
+ // Insert a space before the key to avoid changing identifiers, e.g. ({ get[2]() {} }) to ({ get2() {} })
88
+ const needsSpaceBeforeKey = tokenBeforeLeftBracket.range[1] === leftSquareBracket.range[0] &&
89
+ !astUtils.canTokensBeAdjacent(tokenBeforeLeftBracket, sourceCode.getFirstToken(key));
67
90
 
68
- const replacementKey = (needsSpaceBeforeKey ? " " : "") + key.raw;
91
+ const replacementKey = (needsSpaceBeforeKey ? " " : "") + key.raw;
69
92
 
70
- return fixer.replaceTextRange([leftSquareBracket.range[0], rightSquareBracket.range[1]], replacementKey);
71
- }
72
- });
73
- }
93
+ return fixer.replaceTextRange([leftSquareBracket.range[0], rightSquareBracket.range[1]], replacementKey);
94
+ }
95
+ });
74
96
  }
97
+ }
98
+
99
+ return {
100
+ Property: check,
101
+ MethodDefinition: enforceForClassMembers ? check : lodash.noop
75
102
  };
76
103
  }
77
104
  };
@@ -85,7 +85,14 @@ module.exports = {
85
85
  description: "disallow unnecessary escape characters",
86
86
  category: "Best Practices",
87
87
  recommended: true,
88
- url: "https://eslint.org/docs/rules/no-useless-escape"
88
+ url: "https://eslint.org/docs/rules/no-useless-escape",
89
+ suggestion: true
90
+ },
91
+
92
+ messages: {
93
+ unnecessaryEscape: "Unnecessary escape character: \\{{character}}.",
94
+ removeEscape: "Remove the `\\`. This maintains the current functionality.",
95
+ escapeBackslash: "Replace the `\\` with `\\\\` to include the actual backslash character."
89
96
  },
90
97
 
91
98
  schema: []
@@ -103,6 +110,8 @@ module.exports = {
103
110
  */
104
111
  function report(node, startOffset, character) {
105
112
  const start = sourceCode.getLocFromIndex(sourceCode.getIndexFromLoc(node.loc.start) + startOffset);
113
+ const rangeStart = sourceCode.getIndexFromLoc(node.loc.start) + startOffset;
114
+ const range = [rangeStart, rangeStart + 1];
106
115
 
107
116
  context.report({
108
117
  node,
@@ -110,8 +119,22 @@ module.exports = {
110
119
  start,
111
120
  end: { line: start.line, column: start.column + 1 }
112
121
  },
113
- message: "Unnecessary escape character: \\{{character}}.",
114
- data: { character }
122
+ messageId: "unnecessaryEscape",
123
+ data: { character },
124
+ suggest: [
125
+ {
126
+ messageId: "removeEscape",
127
+ fix(fixer) {
128
+ return fixer.removeRange(range);
129
+ }
130
+ },
131
+ {
132
+ messageId: "escapeBackslash",
133
+ fix(fixer) {
134
+ return fixer.insertTextBeforeRange(range, "\\");
135
+ }
136
+ }
137
+ ]
115
138
  });
116
139
  }
117
140
 
@@ -74,16 +74,16 @@ module.exports = {
74
74
  * @returns {void}
75
75
  */
76
76
  function reportNoBeginningSpace(node, token) {
77
+ const nextToken = context.getSourceCode().getTokenAfter(token, { includeComments: true });
78
+
77
79
  context.report({
78
80
  node,
79
- loc: token.loc.start,
81
+ loc: { start: token.loc.end, end: nextToken.loc.start },
80
82
  message: "There should be no space after '{{token}}'.",
81
83
  data: {
82
84
  token: token.value
83
85
  },
84
86
  fix(fixer) {
85
- const nextToken = context.getSourceCode().getTokenAfter(token, { includeComments: true });
86
-
87
87
  return fixer.removeRange([token.range[1], nextToken.range[0]]);
88
88
  }
89
89
  });
@@ -96,16 +96,16 @@ module.exports = {
96
96
  * @returns {void}
97
97
  */
98
98
  function reportNoEndingSpace(node, token) {
99
+ const previousToken = context.getSourceCode().getTokenBefore(token, { includeComments: true });
100
+
99
101
  context.report({
100
102
  node,
101
- loc: token.loc.start,
103
+ loc: { start: previousToken.loc.end, end: token.loc.start },
102
104
  message: "There should be no space before '{{token}}'.",
103
105
  data: {
104
106
  token: token.value
105
107
  },
106
108
  fix(fixer) {
107
- const previousToken = context.getSourceCode().getTokenBefore(token, { includeComments: true });
108
-
109
109
  return fixer.removeRange([previousToken.range[1], token.range[0]]);
110
110
  }
111
111
  });
@@ -120,7 +120,7 @@ module.exports = {
120
120
  function reportRequiredBeginningSpace(node, token) {
121
121
  context.report({
122
122
  node,
123
- loc: token.loc.start,
123
+ loc: token.loc,
124
124
  message: "A space is required after '{{token}}'.",
125
125
  data: {
126
126
  token: token.value
@@ -140,7 +140,7 @@ module.exports = {
140
140
  function reportRequiredEndingSpace(node, token) {
141
141
  context.report({
142
142
  node,
143
- loc: token.loc.start,
143
+ loc: token.loc,
144
144
  message: "A space is required before '{{token}}'.",
145
145
  data: {
146
146
  token: token.value
@@ -71,6 +71,9 @@ function same(a, b) {
71
71
  */
72
72
  return same(a.object, b.object) && same(a.property, b.property);
73
73
 
74
+ case "ThisExpression":
75
+ return true;
76
+
74
77
  default:
75
78
  return false;
76
79
  }
@@ -83,8 +86,14 @@ function same(a, b) {
83
86
  * @returns {boolean} `true` if the node can be fixed
84
87
  */
85
88
  function canBeFixed(node) {
86
- return node.type === "Identifier" ||
87
- node.type === "MemberExpression" && node.object.type === "Identifier" && (!node.computed || node.property.type === "Literal");
89
+ return (
90
+ node.type === "Identifier" ||
91
+ (
92
+ node.type === "MemberExpression" &&
93
+ (node.object.type === "Identifier" || node.object.type === "ThisExpression") &&
94
+ (!node.computed || node.property.type === "Literal")
95
+ )
96
+ );
88
97
  }
89
98
 
90
99
  module.exports = {
@@ -356,7 +356,9 @@ module.exports = {
356
356
  const ignoreReadBeforeAssign = options.ignoreReadBeforeAssign === true;
357
357
  const variables = [];
358
358
  let reportCount = 0;
359
- let name = "";
359
+ let checkedId = null;
360
+ let checkedName = "";
361
+
360
362
 
361
363
  /**
362
364
  * Reports given identifier nodes if all of the nodes should be declared
@@ -387,25 +389,30 @@ module.exports = {
387
389
  /*
388
390
  * First we check the declaration type and then depending on
389
391
  * if the type is a "VariableDeclarator" or its an "ObjectPattern"
390
- * we compare the name from the first identifier, if the names are different
391
- * we assign the new name and reset the count of reportCount and nodeCount in
392
+ * we compare the name and id from the first identifier, if the names are different
393
+ * we assign the new name, id and reset the count of reportCount and nodeCount in
392
394
  * order to check each block for the number of reported errors and base our fix
393
395
  * based on comparing nodes.length and nodesToReport.length.
394
396
  */
395
397
 
396
398
  if (firstDecParent.type === "VariableDeclarator") {
397
399
 
398
- if (firstDecParent.id.name !== name) {
399
- name = firstDecParent.id.name;
400
+ if (firstDecParent.id.name !== checkedName) {
401
+ checkedName = firstDecParent.id.name;
400
402
  reportCount = 0;
401
403
  }
402
404
 
403
405
  if (firstDecParent.id.type === "ObjectPattern") {
404
- if (firstDecParent.init.name !== name) {
405
- name = firstDecParent.init.name;
406
+ if (firstDecParent.init.name !== checkedName) {
407
+ checkedName = firstDecParent.init.name;
406
408
  reportCount = 0;
407
409
  }
408
410
  }
411
+
412
+ if (firstDecParent.id !== checkedId) {
413
+ checkedId = firstDecParent.id;
414
+ reportCount = 0;
415
+ }
409
416
  }
410
417
  }
411
418
  }
@@ -0,0 +1,189 @@
1
+ /**
2
+ * @fileoverview Rule to disallow Math.pow in favor of the ** operator
3
+ * @author Milos Djermanovic
4
+ */
5
+
6
+ "use strict";
7
+
8
+ //------------------------------------------------------------------------------
9
+ // Requirements
10
+ //------------------------------------------------------------------------------
11
+
12
+ const astUtils = require("./utils/ast-utils");
13
+ const { CALL, ReferenceTracker } = require("eslint-utils");
14
+
15
+ //------------------------------------------------------------------------------
16
+ // Helpers
17
+ //------------------------------------------------------------------------------
18
+
19
+ const PRECEDENCE_OF_EXPONENTIATION_EXPR = astUtils.getPrecedence({ type: "BinaryExpression", operator: "**" });
20
+
21
+ /**
22
+ * Determines whether the given node needs parens if used as the base in an exponentiation binary expression.
23
+ * @param {ASTNode} base The node to check.
24
+ * @returns {boolean} `true` if the node needs to be parenthesised.
25
+ */
26
+ function doesBaseNeedParens(base) {
27
+ return (
28
+
29
+ // '**' is right-associative, parens are needed when Math.pow(a ** b, c) is converted to (a ** b) ** c
30
+ astUtils.getPrecedence(base) <= PRECEDENCE_OF_EXPONENTIATION_EXPR ||
31
+
32
+ // An unary operator cannot be used immediately before an exponentiation expression
33
+ base.type === "UnaryExpression"
34
+ );
35
+ }
36
+
37
+ /**
38
+ * Determines whether the given node needs parens if used as the exponent in an exponentiation binary expression.
39
+ * @param {ASTNode} exponent The node to check.
40
+ * @returns {boolean} `true` if the node needs to be parenthesised.
41
+ */
42
+ function doesExponentNeedParens(exponent) {
43
+
44
+ // '**' is right-associative, there is no need for parens when Math.pow(a, b ** c) is converted to a ** b ** c
45
+ return astUtils.getPrecedence(exponent) < PRECEDENCE_OF_EXPONENTIATION_EXPR;
46
+ }
47
+
48
+ /**
49
+ * Determines whether an exponentiation binary expression at the place of the given node would need parens.
50
+ * @param {ASTNode} node A node that would be replaced by an exponentiation binary expression.
51
+ * @param {SourceCode} sourceCode A SourceCode object.
52
+ * @returns {boolean} `true` if the expression needs to be parenthesised.
53
+ */
54
+ function doesExponentiationExpressionNeedParens(node, sourceCode) {
55
+ const parent = node.parent;
56
+
57
+ const needsParens = (
58
+ parent.type === "ClassDeclaration" ||
59
+ (
60
+ parent.type.endsWith("Expression") &&
61
+ astUtils.getPrecedence(parent) >= PRECEDENCE_OF_EXPONENTIATION_EXPR &&
62
+ !(parent.type === "BinaryExpression" && parent.operator === "**" && parent.right === node) &&
63
+ !((parent.type === "CallExpression" || parent.type === "NewExpression") && parent.arguments.includes(node)) &&
64
+ !(parent.type === "MemberExpression" && parent.computed && parent.property === node) &&
65
+ !(parent.type === "ArrayExpression")
66
+ )
67
+ );
68
+
69
+ return needsParens && !astUtils.isParenthesised(sourceCode, node);
70
+ }
71
+
72
+ /**
73
+ * Optionally parenthesizes given text.
74
+ * @param {string} text The text to parenthesize.
75
+ * @param {boolean} shouldParenthesize If `true`, the text will be parenthesised.
76
+ * @returns {string} parenthesised or unchanged text.
77
+ */
78
+ function parenthesizeIfShould(text, shouldParenthesize) {
79
+ return shouldParenthesize ? `(${text})` : text;
80
+ }
81
+
82
+ //------------------------------------------------------------------------------
83
+ // Rule Definition
84
+ //------------------------------------------------------------------------------
85
+
86
+ module.exports = {
87
+ meta: {
88
+ type: "suggestion",
89
+
90
+ docs: {
91
+ description: "disallow the use of `Math.pow` in favor of the `**` operator",
92
+ category: "Stylistic Issues",
93
+ recommended: false,
94
+ url: "https://eslint.org/docs/rules/prefer-exponentiation-operator"
95
+ },
96
+
97
+ schema: [],
98
+ fixable: "code",
99
+
100
+ messages: {
101
+ useExponentiation: "Use the '**' operator instead of 'Math.pow'."
102
+ }
103
+ },
104
+
105
+ create(context) {
106
+ const sourceCode = context.getSourceCode();
107
+
108
+ /**
109
+ * Reports the given node.
110
+ * @param {ASTNode} node 'Math.pow()' node to report.
111
+ * @returns {void}
112
+ */
113
+ function report(node) {
114
+ context.report({
115
+ node,
116
+ messageId: "useExponentiation",
117
+ fix(fixer) {
118
+ if (
119
+ node.arguments.length !== 2 ||
120
+ node.arguments.some(arg => arg.type === "SpreadElement") ||
121
+ sourceCode.getCommentsInside(node).length > 0
122
+ ) {
123
+ return null;
124
+ }
125
+
126
+ const base = node.arguments[0],
127
+ exponent = node.arguments[1],
128
+ baseText = sourceCode.getText(base),
129
+ exponentText = sourceCode.getText(exponent),
130
+ shouldParenthesizeBase = doesBaseNeedParens(base),
131
+ shouldParenthesizeExponent = doesExponentNeedParens(exponent),
132
+ shouldParenthesizeAll = doesExponentiationExpressionNeedParens(node, sourceCode);
133
+
134
+ let prefix = "",
135
+ suffix = "";
136
+
137
+ if (!shouldParenthesizeAll) {
138
+ if (!shouldParenthesizeBase) {
139
+ const firstReplacementToken = sourceCode.getFirstToken(base),
140
+ tokenBefore = sourceCode.getTokenBefore(node);
141
+
142
+ if (
143
+ tokenBefore &&
144
+ tokenBefore.range[1] === node.range[0] &&
145
+ !astUtils.canTokensBeAdjacent(tokenBefore, firstReplacementToken)
146
+ ) {
147
+ prefix = " "; // a+Math.pow(++b, c) -> a+ ++b**c
148
+ }
149
+ }
150
+ if (!shouldParenthesizeExponent) {
151
+ const lastReplacementToken = sourceCode.getLastToken(exponent),
152
+ tokenAfter = sourceCode.getTokenAfter(node);
153
+
154
+ if (
155
+ tokenAfter &&
156
+ node.range[1] === tokenAfter.range[0] &&
157
+ !astUtils.canTokensBeAdjacent(lastReplacementToken, tokenAfter)
158
+ ) {
159
+ suffix = " "; // Math.pow(a, b)in c -> a**b in c
160
+ }
161
+ }
162
+ }
163
+
164
+ const baseReplacement = parenthesizeIfShould(baseText, shouldParenthesizeBase),
165
+ exponentReplacement = parenthesizeIfShould(exponentText, shouldParenthesizeExponent),
166
+ replacement = parenthesizeIfShould(`${baseReplacement}**${exponentReplacement}`, shouldParenthesizeAll);
167
+
168
+ return fixer.replaceText(node, `${prefix}${replacement}${suffix}`);
169
+ }
170
+ });
171
+ }
172
+
173
+ return {
174
+ Program() {
175
+ const scope = context.getScope();
176
+ const tracker = new ReferenceTracker(scope);
177
+ const trackMap = {
178
+ Math: {
179
+ pow: { [CALL]: true }
180
+ }
181
+ };
182
+
183
+ for (const { node } of tracker.iterateGlobalReferences(trackMap)) {
184
+ report(node);
185
+ }
186
+ }
187
+ };
188
+ }
189
+ };