eslint 7.2.0 → 7.5.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 +78 -0
- package/README.md +14 -7
- package/lib/init/config-initializer.js +89 -68
- package/lib/linter/code-path-analysis/code-path-analyzer.js +38 -0
- package/lib/linter/code-path-analysis/code-path-segment.js +0 -1
- package/lib/linter/code-path-analysis/code-path-state.js +59 -0
- package/lib/linter/code-path-analysis/debug-helpers.js +26 -19
- package/lib/rule-tester/rule-tester.js +9 -0
- package/lib/rules/accessor-pairs.js +1 -14
- package/lib/rules/array-callback-return.js +26 -17
- package/lib/rules/arrow-body-style.js +43 -8
- package/lib/rules/arrow-parens.js +91 -108
- package/lib/rules/camelcase.js +47 -0
- package/lib/rules/comma-dangle.js +2 -1
- package/lib/rules/consistent-return.js +1 -12
- package/lib/rules/constructor-super.js +1 -0
- package/lib/rules/curly.js +8 -1
- package/lib/rules/dot-location.js +20 -14
- package/lib/rules/dot-notation.js +36 -33
- package/lib/rules/func-call-spacing.js +42 -6
- package/lib/rules/func-name-matching.js +1 -4
- package/lib/rules/global-require.js +2 -1
- package/lib/rules/id-blacklist.js +14 -11
- package/lib/rules/id-denylist.js +230 -0
- package/lib/rules/id-match.js +2 -1
- package/lib/rules/indent.js +19 -0
- package/lib/rules/index.js +3 -0
- package/lib/rules/key-spacing.js +6 -2
- package/lib/rules/keyword-spacing.js +2 -2
- package/lib/rules/max-len.js +13 -2
- package/lib/rules/max-lines.js +34 -8
- package/lib/rules/new-cap.js +10 -14
- package/lib/rules/newline-per-chained-call.js +15 -5
- package/lib/rules/no-alert.js +10 -3
- package/lib/rules/no-eval.js +8 -38
- package/lib/rules/no-extend-native.js +37 -40
- package/lib/rules/no-extra-bind.js +57 -17
- package/lib/rules/no-extra-boolean-cast.js +7 -0
- package/lib/rules/no-extra-parens.js +48 -10
- package/lib/rules/no-implicit-coercion.js +11 -6
- package/lib/rules/no-implied-eval.js +7 -28
- package/lib/rules/no-import-assign.js +33 -32
- package/lib/rules/no-irregular-whitespace.js +22 -12
- package/lib/rules/no-magic-numbers.js +4 -8
- package/lib/rules/no-obj-calls.js +7 -4
- package/lib/rules/no-promise-executor-return.js +121 -0
- package/lib/rules/no-prototype-builtins.js +13 -3
- package/lib/rules/no-self-assign.js +3 -53
- package/lib/rules/no-setter-return.js +5 -8
- package/lib/rules/no-unexpected-multiline.js +2 -2
- package/lib/rules/no-unneeded-ternary.js +0 -2
- package/lib/rules/no-unreachable-loop.js +150 -0
- package/lib/rules/no-unused-expressions.js +55 -23
- package/lib/rules/no-unused-vars.js +2 -1
- package/lib/rules/no-useless-call.js +10 -7
- package/lib/rules/no-whitespace-before-property.js +16 -4
- package/lib/rules/object-curly-newline.js +4 -4
- package/lib/rules/object-property-newline.js +1 -1
- package/lib/rules/operator-assignment.js +3 -42
- package/lib/rules/operator-linebreak.js +2 -5
- package/lib/rules/padded-blocks.js +2 -1
- package/lib/rules/padding-line-between-statements.js +2 -2
- package/lib/rules/prefer-arrow-callback.js +90 -25
- package/lib/rules/prefer-exponentiation-operator.js +1 -1
- package/lib/rules/prefer-numeric-literals.js +4 -13
- package/lib/rules/prefer-promise-reject-errors.js +1 -3
- package/lib/rules/prefer-regex-literals.js +68 -13
- package/lib/rules/prefer-spread.js +2 -6
- package/lib/rules/radix.js +5 -2
- package/lib/rules/semi-spacing.js +1 -0
- package/lib/rules/sort-imports.js +28 -0
- package/lib/rules/use-isnan.js +1 -1
- package/lib/rules/utils/ast-utils.js +317 -153
- package/lib/rules/wrap-iife.js +9 -2
- package/lib/rules/yoda.js +2 -55
- package/package.json +7 -7
package/lib/rules/curly.js
CHANGED
@@ -457,11 +457,18 @@ module.exports = {
|
|
457
457
|
|
458
458
|
return {
|
459
459
|
IfStatement(node) {
|
460
|
-
|
460
|
+
const parent = node.parent;
|
461
|
+
const isElseIf = parent.type === "IfStatement" && parent.alternate === node;
|
462
|
+
|
463
|
+
if (!isElseIf) {
|
464
|
+
|
465
|
+
// This is a top `if`, check the whole `if-else-if` chain
|
461
466
|
prepareIfChecks(node).forEach(preparedCheck => {
|
462
467
|
preparedCheck.check();
|
463
468
|
});
|
464
469
|
}
|
470
|
+
|
471
|
+
// Skip `else if`, it's already checked (when the top `if` was visited)
|
465
472
|
},
|
466
473
|
|
467
474
|
WhileStatement(node) {
|
@@ -52,31 +52,37 @@ module.exports = {
|
|
52
52
|
*/
|
53
53
|
function checkDotLocation(node) {
|
54
54
|
const property = node.property;
|
55
|
-
const
|
56
|
-
|
57
|
-
// `obj` expression can be parenthesized, but those paren tokens are not a part of the `obj` node.
|
58
|
-
const tokenBeforeDot = sourceCode.getTokenBefore(dot);
|
59
|
-
|
60
|
-
const textBeforeDot = sourceCode.getText().slice(tokenBeforeDot.range[1], dot.range[0]);
|
61
|
-
const textAfterDot = sourceCode.getText().slice(dot.range[1], property.range[0]);
|
55
|
+
const dotToken = sourceCode.getTokenBefore(property);
|
62
56
|
|
63
57
|
if (onObject) {
|
64
|
-
if (!astUtils.isTokenOnSameLine(tokenBeforeDot, dot)) {
|
65
|
-
const neededTextAfterToken = astUtils.isDecimalIntegerNumericToken(tokenBeforeDot) ? " " : "";
|
66
58
|
|
59
|
+
// `obj` expression can be parenthesized, but those paren tokens are not a part of the `obj` node.
|
60
|
+
const tokenBeforeDot = sourceCode.getTokenBefore(dotToken);
|
61
|
+
|
62
|
+
if (!astUtils.isTokenOnSameLine(tokenBeforeDot, dotToken)) {
|
67
63
|
context.report({
|
68
64
|
node,
|
69
|
-
loc:
|
65
|
+
loc: dotToken.loc,
|
70
66
|
messageId: "expectedDotAfterObject",
|
71
|
-
fix
|
67
|
+
*fix(fixer) {
|
68
|
+
if (dotToken.value.startsWith(".") && astUtils.isDecimalIntegerNumericToken(tokenBeforeDot)) {
|
69
|
+
yield fixer.insertTextAfter(tokenBeforeDot, ` ${dotToken.value}`);
|
70
|
+
} else {
|
71
|
+
yield fixer.insertTextAfter(tokenBeforeDot, dotToken.value);
|
72
|
+
}
|
73
|
+
yield fixer.remove(dotToken);
|
74
|
+
}
|
72
75
|
});
|
73
76
|
}
|
74
|
-
} else if (!astUtils.isTokenOnSameLine(
|
77
|
+
} else if (!astUtils.isTokenOnSameLine(dotToken, property)) {
|
75
78
|
context.report({
|
76
79
|
node,
|
77
|
-
loc:
|
80
|
+
loc: dotToken.loc,
|
78
81
|
messageId: "expectedDotBeforeProperty",
|
79
|
-
fix
|
82
|
+
*fix(fixer) {
|
83
|
+
yield fixer.remove(dotToken);
|
84
|
+
yield fixer.insertTextBefore(property, dotToken.value);
|
85
|
+
}
|
80
86
|
});
|
81
87
|
}
|
82
88
|
}
|
@@ -87,28 +87,36 @@ module.exports = {
|
|
87
87
|
data: {
|
88
88
|
key: formattedValue
|
89
89
|
},
|
90
|
-
fix(fixer) {
|
90
|
+
*fix(fixer) {
|
91
91
|
const leftBracket = sourceCode.getTokenAfter(node.object, astUtils.isOpeningBracketToken);
|
92
92
|
const rightBracket = sourceCode.getLastToken(node);
|
93
|
+
const nextToken = sourceCode.getTokenAfter(node);
|
93
94
|
|
94
|
-
|
95
|
-
|
96
|
-
//
|
97
|
-
return null;
|
95
|
+
// Don't perform any fixes if there are comments inside the brackets.
|
96
|
+
if (sourceCode.commentsExistBetween(leftBracket, rightBracket)) {
|
97
|
+
return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
|
98
98
|
}
|
99
99
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
return fixer.replaceTextRange(
|
100
|
+
// Replace the brackets by an identifier.
|
101
|
+
if (!node.optional) {
|
102
|
+
yield fixer.insertTextBefore(
|
103
|
+
leftBracket,
|
104
|
+
astUtils.isDecimalInteger(node.object) ? " ." : "."
|
105
|
+
);
|
106
|
+
}
|
107
|
+
yield fixer.replaceTextRange(
|
109
108
|
[leftBracket.range[0], rightBracket.range[1]],
|
110
|
-
|
109
|
+
value
|
111
110
|
);
|
111
|
+
|
112
|
+
// Insert a space after the property if it will be connected to the next token.
|
113
|
+
if (
|
114
|
+
nextToken &&
|
115
|
+
rightBracket.range[1] === nextToken.range[0] &&
|
116
|
+
!astUtils.canTokensBeAdjacent(String(value), nextToken)
|
117
|
+
) {
|
118
|
+
yield fixer.insertTextAfter(node, " ");
|
119
|
+
}
|
112
120
|
}
|
113
121
|
});
|
114
122
|
}
|
@@ -141,29 +149,24 @@ module.exports = {
|
|
141
149
|
data: {
|
142
150
|
key: node.property.name
|
143
151
|
},
|
144
|
-
fix(fixer) {
|
145
|
-
const
|
146
|
-
const textAfterDot = sourceCode.text.slice(dot.range[1], node.property.range[0]);
|
147
|
-
|
148
|
-
if (textAfterDot.trim()) {
|
152
|
+
*fix(fixer) {
|
153
|
+
const dotToken = sourceCode.getTokenBefore(node.property);
|
149
154
|
|
150
|
-
|
151
|
-
|
155
|
+
// A statement that starts with `let[` is parsed as a destructuring variable declaration, not a MemberExpression.
|
156
|
+
if (node.object.type === "Identifier" && node.object.name === "let" && !node.optional) {
|
157
|
+
return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
|
152
158
|
}
|
153
159
|
|
154
|
-
if
|
155
|
-
|
156
|
-
|
157
|
-
* A statement that starts with `let[` is parsed as a destructuring variable declaration, not
|
158
|
-
* a MemberExpression.
|
159
|
-
*/
|
160
|
-
return null;
|
160
|
+
// Don't perform any fixes if there are comments between the dot and the property name.
|
161
|
+
if (sourceCode.commentsExistBetween(dotToken, node.property)) {
|
162
|
+
return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
|
161
163
|
}
|
162
164
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
165
|
+
// Replace the identifier to brackets.
|
166
|
+
if (!node.optional) {
|
167
|
+
yield fixer.remove(dotToken);
|
168
|
+
}
|
169
|
+
yield fixer.replaceText(node.property, `["${node.property.name}"]`);
|
167
170
|
}
|
168
171
|
});
|
169
172
|
}
|
@@ -126,15 +126,24 @@ module.exports = {
|
|
126
126
|
messageId: "unexpectedWhitespace",
|
127
127
|
fix(fixer) {
|
128
128
|
|
129
|
+
// Don't remove comments.
|
130
|
+
if (sourceCode.commentsExistBetween(leftToken, rightToken)) {
|
131
|
+
return null;
|
132
|
+
}
|
133
|
+
|
134
|
+
// If `?.` exsits, it doesn't hide no-undexpected-multiline errors
|
135
|
+
if (node.optional) {
|
136
|
+
return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], "?.");
|
137
|
+
}
|
138
|
+
|
129
139
|
/*
|
130
140
|
* Only autofix if there is no newline
|
131
141
|
* https://github.com/eslint/eslint/issues/7787
|
132
142
|
*/
|
133
|
-
if (
|
134
|
-
return
|
143
|
+
if (hasNewline) {
|
144
|
+
return null;
|
135
145
|
}
|
136
|
-
|
137
|
-
return null;
|
146
|
+
return fixer.removeRange([leftToken.range[1], rightToken.range[0]]);
|
138
147
|
}
|
139
148
|
});
|
140
149
|
} else if (!never && !hasWhitespace) {
|
@@ -149,6 +158,9 @@ module.exports = {
|
|
149
158
|
},
|
150
159
|
messageId: "missing",
|
151
160
|
fix(fixer) {
|
161
|
+
if (node.optional) {
|
162
|
+
return null; // Not sure if inserting a space to either before/after `?.` token.
|
163
|
+
}
|
152
164
|
return fixer.insertTextBefore(rightToken, " ");
|
153
165
|
}
|
154
166
|
});
|
@@ -161,7 +173,31 @@ module.exports = {
|
|
161
173
|
},
|
162
174
|
messageId: "unexpectedNewline",
|
163
175
|
fix(fixer) {
|
164
|
-
|
176
|
+
|
177
|
+
/*
|
178
|
+
* Only autofix if there is no newline
|
179
|
+
* https://github.com/eslint/eslint/issues/7787
|
180
|
+
* But if `?.` exsits, it doesn't hide no-undexpected-multiline errors
|
181
|
+
*/
|
182
|
+
if (!node.optional) {
|
183
|
+
return null;
|
184
|
+
}
|
185
|
+
|
186
|
+
// Don't remove comments.
|
187
|
+
if (sourceCode.commentsExistBetween(leftToken, rightToken)) {
|
188
|
+
return null;
|
189
|
+
}
|
190
|
+
|
191
|
+
const range = [leftToken.range[1], rightToken.range[0]];
|
192
|
+
const qdToken = sourceCode.getTokenAfter(leftToken);
|
193
|
+
|
194
|
+
if (qdToken.range[0] === leftToken.range[1]) {
|
195
|
+
return fixer.replaceTextRange(range, "?. ");
|
196
|
+
}
|
197
|
+
if (qdToken.range[1] === rightToken.range[0]) {
|
198
|
+
return fixer.replaceTextRange(range, " ?.");
|
199
|
+
}
|
200
|
+
return fixer.replaceTextRange(range, " ?. ");
|
165
201
|
}
|
166
202
|
});
|
167
203
|
}
|
@@ -172,7 +208,7 @@ module.exports = {
|
|
172
208
|
const lastToken = sourceCode.getLastToken(node);
|
173
209
|
const lastCalleeToken = sourceCode.getLastToken(node.callee);
|
174
210
|
const parenToken = sourceCode.getFirstTokenBetween(lastCalleeToken, lastToken, astUtils.isOpeningParenToken);
|
175
|
-
const prevToken = parenToken && sourceCode.getTokenBefore(parenToken);
|
211
|
+
const prevToken = parenToken && sourceCode.getTokenBefore(parenToken, astUtils.isNotQuestionDotToken);
|
176
212
|
|
177
213
|
// Parens in NewExpression are optional
|
178
214
|
if (!(parenToken && parenToken.range[1] < node.range[1])) {
|
@@ -117,10 +117,7 @@ module.exports = {
|
|
117
117
|
if (!node) {
|
118
118
|
return false;
|
119
119
|
}
|
120
|
-
return node.type === "CallExpression" &&
|
121
|
-
node.callee.type === "MemberExpression" &&
|
122
|
-
node.callee.object.name === objName &&
|
123
|
-
node.callee.property.name === funcName;
|
120
|
+
return node.type === "CallExpression" && astUtils.isSpecificMemberAccess(node.callee, objName, funcName);
|
124
121
|
}
|
125
122
|
|
126
123
|
/**
|
@@ -1,6 +1,6 @@
|
|
1
1
|
/**
|
2
2
|
* @fileoverview Rule that warns when identifier names that are
|
3
|
-
*
|
3
|
+
* specified in the configuration are used.
|
4
4
|
* @author Keith Cirkel (http://keithcirkel.co.uk)
|
5
5
|
*/
|
6
6
|
|
@@ -111,6 +111,9 @@ function isShorthandPropertyDefinition(node) {
|
|
111
111
|
|
112
112
|
module.exports = {
|
113
113
|
meta: {
|
114
|
+
deprecated: true,
|
115
|
+
replacedBy: ["id-denylist"],
|
116
|
+
|
114
117
|
type: "suggestion",
|
115
118
|
|
116
119
|
docs: {
|
@@ -128,25 +131,25 @@ module.exports = {
|
|
128
131
|
uniqueItems: true
|
129
132
|
},
|
130
133
|
messages: {
|
131
|
-
|
134
|
+
restricted: "Identifier '{{name}}' is restricted."
|
132
135
|
}
|
133
136
|
},
|
134
137
|
|
135
138
|
create(context) {
|
136
139
|
|
137
|
-
const
|
140
|
+
const denyList = new Set(context.options);
|
138
141
|
const reportedNodes = new Set();
|
139
142
|
|
140
143
|
let globalScope;
|
141
144
|
|
142
145
|
/**
|
143
|
-
* Checks whether the given name is
|
146
|
+
* Checks whether the given name is restricted.
|
144
147
|
* @param {string} name The name to check.
|
145
|
-
* @returns {boolean} `true` if the name is
|
148
|
+
* @returns {boolean} `true` if the name is restricted.
|
146
149
|
* @private
|
147
150
|
*/
|
148
|
-
function
|
149
|
-
return
|
151
|
+
function isRestricted(name) {
|
152
|
+
return denyList.has(name);
|
150
153
|
}
|
151
154
|
|
152
155
|
/**
|
@@ -172,8 +175,8 @@ module.exports = {
|
|
172
175
|
|
173
176
|
/*
|
174
177
|
* Member access has special rules for checking property names.
|
175
|
-
* Read access to a property with a
|
176
|
-
* Write access isn't allowed, because it potentially creates a new property with a
|
178
|
+
* Read access to a property with a restricted name is allowed, because it can be on an object that user has no control over.
|
179
|
+
* Write access isn't allowed, because it potentially creates a new property with a restricted name.
|
177
180
|
*/
|
178
181
|
if (
|
179
182
|
parent.type === "MemberExpression" &&
|
@@ -205,7 +208,7 @@ module.exports = {
|
|
205
208
|
if (!reportedNodes.has(node)) {
|
206
209
|
context.report({
|
207
210
|
node,
|
208
|
-
messageId: "
|
211
|
+
messageId: "restricted",
|
209
212
|
data: {
|
210
213
|
name: node.name
|
211
214
|
}
|
@@ -221,7 +224,7 @@ module.exports = {
|
|
221
224
|
},
|
222
225
|
|
223
226
|
Identifier(node) {
|
224
|
-
if (
|
227
|
+
if (isRestricted(node.name) && shouldCheck(node)) {
|
225
228
|
report(node);
|
226
229
|
}
|
227
230
|
}
|
@@ -0,0 +1,230 @@
|
|
1
|
+
/**
|
2
|
+
* @fileoverview Rule that warns when identifier names that are
|
3
|
+
* specified in the configuration are used.
|
4
|
+
* @author Keith Cirkel (http://keithcirkel.co.uk)
|
5
|
+
*/
|
6
|
+
|
7
|
+
"use strict";
|
8
|
+
|
9
|
+
//------------------------------------------------------------------------------
|
10
|
+
// Helpers
|
11
|
+
//------------------------------------------------------------------------------
|
12
|
+
|
13
|
+
/**
|
14
|
+
* Checks whether the given node represents assignment target in a normal assignment or destructuring.
|
15
|
+
* @param {ASTNode} node The node to check.
|
16
|
+
* @returns {boolean} `true` if the node is assignment target.
|
17
|
+
*/
|
18
|
+
function isAssignmentTarget(node) {
|
19
|
+
const parent = node.parent;
|
20
|
+
|
21
|
+
return (
|
22
|
+
|
23
|
+
// normal assignment
|
24
|
+
(
|
25
|
+
parent.type === "AssignmentExpression" &&
|
26
|
+
parent.left === node
|
27
|
+
) ||
|
28
|
+
|
29
|
+
// destructuring
|
30
|
+
parent.type === "ArrayPattern" ||
|
31
|
+
parent.type === "RestElement" ||
|
32
|
+
(
|
33
|
+
parent.type === "Property" &&
|
34
|
+
parent.value === node &&
|
35
|
+
parent.parent.type === "ObjectPattern"
|
36
|
+
) ||
|
37
|
+
(
|
38
|
+
parent.type === "AssignmentPattern" &&
|
39
|
+
parent.left === node
|
40
|
+
)
|
41
|
+
);
|
42
|
+
}
|
43
|
+
|
44
|
+
/**
|
45
|
+
* Checks whether the given node represents an imported name that is renamed in the same import/export specifier.
|
46
|
+
*
|
47
|
+
* Examples:
|
48
|
+
* import { a as b } from 'mod'; // node `a` is renamed import
|
49
|
+
* export { a as b } from 'mod'; // node `a` is renamed import
|
50
|
+
* @param {ASTNode} node `Identifier` node to check.
|
51
|
+
* @returns {boolean} `true` if the node is a renamed import.
|
52
|
+
*/
|
53
|
+
function isRenamedImport(node) {
|
54
|
+
const parent = node.parent;
|
55
|
+
|
56
|
+
return (
|
57
|
+
(
|
58
|
+
parent.type === "ImportSpecifier" &&
|
59
|
+
parent.imported !== parent.local &&
|
60
|
+
parent.imported === node
|
61
|
+
) ||
|
62
|
+
(
|
63
|
+
parent.type === "ExportSpecifier" &&
|
64
|
+
parent.parent.source && // re-export
|
65
|
+
parent.local !== parent.exported &&
|
66
|
+
parent.local === node
|
67
|
+
)
|
68
|
+
);
|
69
|
+
}
|
70
|
+
|
71
|
+
/**
|
72
|
+
* Checks whether the given node is a renamed identifier node in an ObjectPattern destructuring.
|
73
|
+
*
|
74
|
+
* Examples:
|
75
|
+
* const { a : b } = foo; // node `a` is renamed node.
|
76
|
+
* @param {ASTNode} node `Identifier` node to check.
|
77
|
+
* @returns {boolean} `true` if the node is a renamed node in an ObjectPattern destructuring.
|
78
|
+
*/
|
79
|
+
function isRenamedInDestructuring(node) {
|
80
|
+
const parent = node.parent;
|
81
|
+
|
82
|
+
return (
|
83
|
+
(
|
84
|
+
!parent.computed &&
|
85
|
+
parent.type === "Property" &&
|
86
|
+
parent.parent.type === "ObjectPattern" &&
|
87
|
+
parent.value !== node &&
|
88
|
+
parent.key === node
|
89
|
+
)
|
90
|
+
);
|
91
|
+
}
|
92
|
+
|
93
|
+
/**
|
94
|
+
* Checks whether the given node represents shorthand definition of a property in an object literal.
|
95
|
+
* @param {ASTNode} node `Identifier` node to check.
|
96
|
+
* @returns {boolean} `true` if the node is a shorthand property definition.
|
97
|
+
*/
|
98
|
+
function isShorthandPropertyDefinition(node) {
|
99
|
+
const parent = node.parent;
|
100
|
+
|
101
|
+
return (
|
102
|
+
parent.type === "Property" &&
|
103
|
+
parent.parent.type === "ObjectExpression" &&
|
104
|
+
parent.shorthand
|
105
|
+
);
|
106
|
+
}
|
107
|
+
|
108
|
+
//------------------------------------------------------------------------------
|
109
|
+
// Rule Definition
|
110
|
+
//------------------------------------------------------------------------------
|
111
|
+
|
112
|
+
module.exports = {
|
113
|
+
meta: {
|
114
|
+
type: "suggestion",
|
115
|
+
|
116
|
+
docs: {
|
117
|
+
description: "disallow specified identifiers",
|
118
|
+
category: "Stylistic Issues",
|
119
|
+
recommended: false,
|
120
|
+
url: "https://eslint.org/docs/rules/id-denylist"
|
121
|
+
},
|
122
|
+
|
123
|
+
schema: {
|
124
|
+
type: "array",
|
125
|
+
items: {
|
126
|
+
type: "string"
|
127
|
+
},
|
128
|
+
uniqueItems: true
|
129
|
+
},
|
130
|
+
messages: {
|
131
|
+
restricted: "Identifier '{{name}}' is restricted."
|
132
|
+
}
|
133
|
+
},
|
134
|
+
|
135
|
+
create(context) {
|
136
|
+
|
137
|
+
const denyList = new Set(context.options);
|
138
|
+
const reportedNodes = new Set();
|
139
|
+
|
140
|
+
let globalScope;
|
141
|
+
|
142
|
+
/**
|
143
|
+
* Checks whether the given name is restricted.
|
144
|
+
* @param {string} name The name to check.
|
145
|
+
* @returns {boolean} `true` if the name is restricted.
|
146
|
+
* @private
|
147
|
+
*/
|
148
|
+
function isRestricted(name) {
|
149
|
+
return denyList.has(name);
|
150
|
+
}
|
151
|
+
|
152
|
+
/**
|
153
|
+
* Checks whether the given node represents a reference to a global variable that is not declared in the source code.
|
154
|
+
* These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables.
|
155
|
+
* @param {ASTNode} node `Identifier` node to check.
|
156
|
+
* @returns {boolean} `true` if the node is a reference to a global variable.
|
157
|
+
*/
|
158
|
+
function isReferenceToGlobalVariable(node) {
|
159
|
+
const variable = globalScope.set.get(node.name);
|
160
|
+
|
161
|
+
return variable && variable.defs.length === 0 &&
|
162
|
+
variable.references.some(ref => ref.identifier === node);
|
163
|
+
}
|
164
|
+
|
165
|
+
/**
|
166
|
+
* Determines whether the given node should be checked.
|
167
|
+
* @param {ASTNode} node `Identifier` node.
|
168
|
+
* @returns {boolean} `true` if the node should be checked.
|
169
|
+
*/
|
170
|
+
function shouldCheck(node) {
|
171
|
+
const parent = node.parent;
|
172
|
+
|
173
|
+
/*
|
174
|
+
* Member access has special rules for checking property names.
|
175
|
+
* Read access to a property with a restricted name is allowed, because it can be on an object that user has no control over.
|
176
|
+
* Write access isn't allowed, because it potentially creates a new property with a restricted name.
|
177
|
+
*/
|
178
|
+
if (
|
179
|
+
parent.type === "MemberExpression" &&
|
180
|
+
parent.property === node &&
|
181
|
+
!parent.computed
|
182
|
+
) {
|
183
|
+
return isAssignmentTarget(parent);
|
184
|
+
}
|
185
|
+
|
186
|
+
return (
|
187
|
+
parent.type !== "CallExpression" &&
|
188
|
+
parent.type !== "NewExpression" &&
|
189
|
+
!isRenamedImport(node) &&
|
190
|
+
!isRenamedInDestructuring(node) &&
|
191
|
+
!(
|
192
|
+
isReferenceToGlobalVariable(node) &&
|
193
|
+
!isShorthandPropertyDefinition(node)
|
194
|
+
)
|
195
|
+
);
|
196
|
+
}
|
197
|
+
|
198
|
+
/**
|
199
|
+
* Reports an AST node as a rule violation.
|
200
|
+
* @param {ASTNode} node The node to report.
|
201
|
+
* @returns {void}
|
202
|
+
* @private
|
203
|
+
*/
|
204
|
+
function report(node) {
|
205
|
+
if (!reportedNodes.has(node)) {
|
206
|
+
context.report({
|
207
|
+
node,
|
208
|
+
messageId: "restricted",
|
209
|
+
data: {
|
210
|
+
name: node.name
|
211
|
+
}
|
212
|
+
});
|
213
|
+
reportedNodes.add(node);
|
214
|
+
}
|
215
|
+
}
|
216
|
+
|
217
|
+
return {
|
218
|
+
|
219
|
+
Program() {
|
220
|
+
globalScope = context.getScope();
|
221
|
+
},
|
222
|
+
|
223
|
+
Identifier(node) {
|
224
|
+
if (isRestricted(node.name) && shouldCheck(node)) {
|
225
|
+
report(node);
|
226
|
+
}
|
227
|
+
}
|
228
|
+
};
|
229
|
+
}
|
230
|
+
};
|
package/lib/rules/id-match.js
CHANGED
package/lib/rules/indent.js
CHANGED
@@ -32,6 +32,7 @@ const KNOWN_NODES = new Set([
|
|
32
32
|
"BreakStatement",
|
33
33
|
"CallExpression",
|
34
34
|
"CatchClause",
|
35
|
+
"ChainExpression",
|
35
36
|
"ClassBody",
|
36
37
|
"ClassDeclaration",
|
37
38
|
"ClassExpression",
|
@@ -934,6 +935,24 @@ module.exports = {
|
|
934
935
|
parameterParens.add(openingParen);
|
935
936
|
parameterParens.add(closingParen);
|
936
937
|
|
938
|
+
/*
|
939
|
+
* If `?.` token exists, set desired offset for that.
|
940
|
+
* This logic is copied from `MemberExpression`'s.
|
941
|
+
*/
|
942
|
+
if (node.optional) {
|
943
|
+
const dotToken = sourceCode.getTokenAfter(node.callee, astUtils.isQuestionDotToken);
|
944
|
+
const calleeParenCount = sourceCode.getTokensBetween(node.callee, dotToken, { filter: astUtils.isClosingParenToken }).length;
|
945
|
+
const firstTokenOfCallee = calleeParenCount
|
946
|
+
? sourceCode.getTokenBefore(node.callee, { skip: calleeParenCount - 1 })
|
947
|
+
: sourceCode.getFirstToken(node.callee);
|
948
|
+
const lastTokenOfCallee = sourceCode.getTokenBefore(dotToken);
|
949
|
+
const offsetBase = lastTokenOfCallee.loc.end.line === openingParen.loc.start.line
|
950
|
+
? lastTokenOfCallee
|
951
|
+
: firstTokenOfCallee;
|
952
|
+
|
953
|
+
offsets.setDesiredOffset(dotToken, offsetBase, 1);
|
954
|
+
}
|
955
|
+
|
937
956
|
const offsetAfterToken = node.callee.type === "TaggedTemplateExpression" ? sourceCode.getFirstToken(node.callee.quasi) : openingParen;
|
938
957
|
const offsetToken = sourceCode.getTokenBefore(offsetAfterToken);
|
939
958
|
|
package/lib/rules/index.js
CHANGED
@@ -57,6 +57,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
|
|
57
57
|
"guard-for-in": () => require("./guard-for-in"),
|
58
58
|
"handle-callback-err": () => require("./handle-callback-err"),
|
59
59
|
"id-blacklist": () => require("./id-blacklist"),
|
60
|
+
"id-denylist": () => require("./id-denylist"),
|
60
61
|
"id-length": () => require("./id-length"),
|
61
62
|
"id-match": () => require("./id-match"),
|
62
63
|
"implicit-arrow-linebreak": () => require("./implicit-arrow-linebreak"),
|
@@ -176,6 +177,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
|
|
176
177
|
"no-plusplus": () => require("./no-plusplus"),
|
177
178
|
"no-process-env": () => require("./no-process-env"),
|
178
179
|
"no-process-exit": () => require("./no-process-exit"),
|
180
|
+
"no-promise-executor-return": () => require("./no-promise-executor-return"),
|
179
181
|
"no-proto": () => require("./no-proto"),
|
180
182
|
"no-prototype-builtins": () => require("./no-prototype-builtins"),
|
181
183
|
"no-redeclare": () => require("./no-redeclare"),
|
@@ -212,6 +214,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
|
|
212
214
|
"no-unmodified-loop-condition": () => require("./no-unmodified-loop-condition"),
|
213
215
|
"no-unneeded-ternary": () => require("./no-unneeded-ternary"),
|
214
216
|
"no-unreachable": () => require("./no-unreachable"),
|
217
|
+
"no-unreachable-loop": () => require("./no-unreachable-loop"),
|
215
218
|
"no-unsafe-finally": () => require("./no-unsafe-finally"),
|
216
219
|
"no-unsafe-negation": () => require("./no-unsafe-negation"),
|
217
220
|
"no-unused-expressions": () => require("./no-unused-expressions"),
|