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.
- package/CHANGELOG.md +94 -0
- package/README.md +7 -8
- package/conf/config-schema.js +2 -0
- package/conf/default-cli-options.js +1 -1
- package/conf/eslint-recommended.js +0 -1
- package/lib/cli-engine/cascading-config-array-factory.js +38 -13
- package/lib/cli-engine/cli-engine.js +41 -14
- package/lib/cli-engine/config-array/config-array.js +13 -0
- package/lib/cli-engine/config-array/extracted-config.js +27 -0
- package/lib/cli-engine/config-array/ignore-pattern.js +231 -0
- package/lib/cli-engine/config-array/index.js +2 -0
- package/lib/cli-engine/config-array-factory.js +115 -1
- package/lib/cli-engine/file-enumerator.js +73 -40
- package/lib/cli-engine/lint-result-cache.js +2 -1
- package/lib/cli.js +2 -1
- package/lib/init/config-initializer.js +4 -3
- package/lib/linter/config-comment-parser.js +1 -1
- package/lib/linter/report-translator.js +73 -7
- package/lib/options.js +6 -0
- package/lib/rule-tester/rule-tester.js +42 -6
- package/lib/rules/array-bracket-spacing.js +8 -8
- package/lib/rules/camelcase.js +19 -6
- package/lib/rules/comma-dangle.js +5 -2
- package/lib/rules/computed-property-spacing.js +4 -4
- package/lib/rules/curly.js +9 -4
- package/lib/rules/function-call-argument-newline.js +3 -1
- package/lib/rules/grouped-accessor-pairs.js +224 -0
- package/lib/rules/indent.js +11 -0
- package/lib/rules/index.js +5 -0
- package/lib/rules/key-spacing.js +34 -15
- package/lib/rules/lines-between-class-members.js +42 -53
- package/lib/rules/multiline-comment-style.js +237 -106
- package/lib/rules/no-cond-assign.js +14 -4
- package/lib/rules/no-constructor-return.js +62 -0
- package/lib/rules/no-dupe-else-if.js +122 -0
- package/lib/rules/no-implicit-globals.js +90 -8
- package/lib/rules/no-inline-comments.js +25 -11
- package/lib/rules/no-invalid-this.js +16 -2
- package/lib/rules/no-multiple-empty-lines.js +1 -1
- package/lib/rules/no-octal-escape.js +1 -1
- package/lib/rules/no-restricted-imports.js +2 -2
- package/lib/rules/no-setter-return.js +227 -0
- package/lib/rules/no-underscore-dangle.js +23 -4
- package/lib/rules/no-unexpected-multiline.js +8 -0
- package/lib/rules/no-unsafe-negation.js +30 -5
- package/lib/rules/no-useless-computed-key.js +60 -33
- package/lib/rules/no-useless-escape.js +26 -3
- package/lib/rules/object-curly-spacing.js +8 -8
- package/lib/rules/operator-assignment.js +11 -2
- package/lib/rules/prefer-const.js +14 -7
- package/lib/rules/prefer-exponentiation-operator.js +189 -0
- package/lib/rules/prefer-numeric-literals.js +29 -28
- package/lib/rules/require-atomic-updates.js +1 -1
- package/lib/rules/require-await.js +8 -0
- package/lib/rules/semi.js +6 -3
- package/lib/rules/space-infix-ops.js +1 -1
- package/lib/rules/spaced-comment.js +5 -4
- package/lib/rules/utils/ast-utils.js +31 -4
- package/lib/shared/types.js +9 -0
- package/lib/source-code/source-code.js +87 -10
- package/package.json +4 -3
- 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 {
|
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 {
|
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 {
|
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
|
90
|
+
const operator = node.operator;
|
91
|
+
const orderingRelationRuleApplies = enforceForOrderingRelations && isOrderingRelationalOperator(operator);
|
86
92
|
|
87
93
|
if (
|
88
|
-
(isInOrInstanceOfOperator(
|
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
|
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
|
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
|
-
|
37
|
-
|
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
|
-
|
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
|
-
|
56
|
-
|
62
|
+
if (node.type === "MethodDefinition") {
|
63
|
+
allowedKey = node.static ? "prototype" : "constructor";
|
64
|
+
} else {
|
65
|
+
allowedKey = "__proto__";
|
66
|
+
}
|
57
67
|
|
58
|
-
|
59
|
-
|
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
|
-
|
85
|
+
const tokenBeforeLeftBracket = sourceCode.getTokenBefore(leftSquareBracket);
|
63
86
|
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
-
|
91
|
+
const replacementKey = (needsSpaceBeforeKey ? " " : "") + key.raw;
|
69
92
|
|
70
|
-
|
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
|
-
|
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
|
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
|
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
|
87
|
-
node.type === "
|
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
|
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 !==
|
399
|
-
|
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 !==
|
405
|
-
|
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
|
+
};
|