eslint 8.11.0 → 8.14.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/lib/cli-engine/cli-engine.js +2 -1
- package/lib/cli-engine/formatters/html.js +3 -3
- package/lib/eslint/eslint.js +4 -19
- package/lib/linter/apply-disable-directives.js +1 -1
- package/lib/rules/index.js +1 -0
- package/lib/rules/no-constant-binary-expression.js +500 -0
- package/lib/rules/no-constant-condition.js +4 -197
- package/lib/rules/no-eval.js +23 -18
- package/lib/rules/no-invalid-this.js +3 -3
- package/lib/rules/operator-assignment.js +7 -5
- package/lib/rules/padding-line-between-statements.js +2 -4
- package/lib/rules/utils/ast-utils.js +203 -7
- package/lib/shared/types.js +24 -0
- package/messages/all-files-ignored.js +1 -1
- package/messages/extend-config-missing.js +1 -1
- package/messages/failed-to-read-json.js +1 -1
- package/messages/file-not-found.js +1 -1
- package/messages/no-config-found.js +1 -1
- package/messages/plugin-invalid.js +1 -1
- package/messages/plugin-missing.js +1 -1
- package/messages/print-config-with-directory-path.js +1 -1
- package/messages/whitespace-found.js +1 -1
- package/package.json +3 -3
@@ -5,6 +5,8 @@
|
|
5
5
|
|
6
6
|
"use strict";
|
7
7
|
|
8
|
+
const { isConstant } = require("./utils/ast-utils");
|
9
|
+
|
8
10
|
//------------------------------------------------------------------------------
|
9
11
|
// Helpers
|
10
12
|
//------------------------------------------------------------------------------
|
@@ -53,201 +55,6 @@ module.exports = {
|
|
53
55
|
// Helpers
|
54
56
|
//--------------------------------------------------------------------------
|
55
57
|
|
56
|
-
/**
|
57
|
-
* Returns literal's value converted to the Boolean type
|
58
|
-
* @param {ASTNode} node any `Literal` node
|
59
|
-
* @returns {boolean | null} `true` when node is truthy, `false` when node is falsy,
|
60
|
-
* `null` when it cannot be determined.
|
61
|
-
*/
|
62
|
-
function getBooleanValue(node) {
|
63
|
-
if (node.value === null) {
|
64
|
-
|
65
|
-
/*
|
66
|
-
* it might be a null literal or bigint/regex literal in unsupported environments .
|
67
|
-
* https://github.com/estree/estree/blob/14df8a024956ea289bd55b9c2226a1d5b8a473ee/es5.md#regexpliteral
|
68
|
-
* https://github.com/estree/estree/blob/14df8a024956ea289bd55b9c2226a1d5b8a473ee/es2020.md#bigintliteral
|
69
|
-
*/
|
70
|
-
|
71
|
-
if (node.raw === "null") {
|
72
|
-
return false;
|
73
|
-
}
|
74
|
-
|
75
|
-
// regex is always truthy
|
76
|
-
if (typeof node.regex === "object") {
|
77
|
-
return true;
|
78
|
-
}
|
79
|
-
|
80
|
-
return null;
|
81
|
-
}
|
82
|
-
|
83
|
-
return !!node.value;
|
84
|
-
}
|
85
|
-
|
86
|
-
/**
|
87
|
-
* Checks if a branch node of LogicalExpression short circuits the whole condition
|
88
|
-
* @param {ASTNode} node The branch of main condition which needs to be checked
|
89
|
-
* @param {string} operator The operator of the main LogicalExpression.
|
90
|
-
* @returns {boolean} true when condition short circuits whole condition
|
91
|
-
*/
|
92
|
-
function isLogicalIdentity(node, operator) {
|
93
|
-
switch (node.type) {
|
94
|
-
case "Literal":
|
95
|
-
return (operator === "||" && getBooleanValue(node) === true) ||
|
96
|
-
(operator === "&&" && getBooleanValue(node) === false);
|
97
|
-
|
98
|
-
case "UnaryExpression":
|
99
|
-
return (operator === "&&" && node.operator === "void");
|
100
|
-
|
101
|
-
case "LogicalExpression":
|
102
|
-
|
103
|
-
/*
|
104
|
-
* handles `a && false || b`
|
105
|
-
* `false` is an identity element of `&&` but not `||`
|
106
|
-
*/
|
107
|
-
return operator === node.operator &&
|
108
|
-
(
|
109
|
-
isLogicalIdentity(node.left, operator) ||
|
110
|
-
isLogicalIdentity(node.right, operator)
|
111
|
-
);
|
112
|
-
|
113
|
-
case "AssignmentExpression":
|
114
|
-
return ["||=", "&&="].includes(node.operator) &&
|
115
|
-
operator === node.operator.slice(0, -1) &&
|
116
|
-
isLogicalIdentity(node.right, operator);
|
117
|
-
|
118
|
-
// no default
|
119
|
-
}
|
120
|
-
return false;
|
121
|
-
}
|
122
|
-
|
123
|
-
/**
|
124
|
-
* Checks if an identifier is a reference to a global variable.
|
125
|
-
* @param {ASTNode} node An identifier node to check.
|
126
|
-
* @returns {boolean} `true` if the identifier is a reference to a global variable.
|
127
|
-
*/
|
128
|
-
function isReferenceToGlobalVariable(node) {
|
129
|
-
const scope = context.getScope();
|
130
|
-
const reference = scope.references.find(ref => ref.identifier === node);
|
131
|
-
|
132
|
-
return Boolean(
|
133
|
-
reference &&
|
134
|
-
reference.resolved &&
|
135
|
-
reference.resolved.scope.type === "global" &&
|
136
|
-
reference.resolved.defs.length === 0
|
137
|
-
);
|
138
|
-
}
|
139
|
-
|
140
|
-
/**
|
141
|
-
* Checks if a node has a constant truthiness value.
|
142
|
-
* @param {ASTNode} node The AST node to check.
|
143
|
-
* @param {boolean} inBooleanPosition `true` if checking the test of a
|
144
|
-
* condition. `false` in all other cases. When `false`, checks if -- for
|
145
|
-
* both string and number -- if coerced to that type, the value will
|
146
|
-
* be constant.
|
147
|
-
* @returns {Bool} true when node's truthiness is constant
|
148
|
-
* @private
|
149
|
-
*/
|
150
|
-
function isConstant(node, inBooleanPosition) {
|
151
|
-
|
152
|
-
// node.elements can return null values in the case of sparse arrays ex. [,]
|
153
|
-
if (!node) {
|
154
|
-
return true;
|
155
|
-
}
|
156
|
-
switch (node.type) {
|
157
|
-
case "Literal":
|
158
|
-
case "ArrowFunctionExpression":
|
159
|
-
case "FunctionExpression":
|
160
|
-
return true;
|
161
|
-
case "ClassExpression":
|
162
|
-
case "ObjectExpression":
|
163
|
-
|
164
|
-
/**
|
165
|
-
* In theory objects like:
|
166
|
-
*
|
167
|
-
* `{toString: () => a}`
|
168
|
-
* `{valueOf: () => a}`
|
169
|
-
*
|
170
|
-
* Or a classes like:
|
171
|
-
*
|
172
|
-
* `class { static toString() { return a } }`
|
173
|
-
* `class { static valueOf() { return a } }`
|
174
|
-
*
|
175
|
-
* Are not constant verifiably when `inBooleanPosition` is
|
176
|
-
* false, but it's an edge case we've opted not to handle.
|
177
|
-
*/
|
178
|
-
return true;
|
179
|
-
case "TemplateLiteral":
|
180
|
-
return (inBooleanPosition && node.quasis.some(quasi => quasi.value.cooked.length)) ||
|
181
|
-
node.expressions.every(exp => isConstant(exp, false));
|
182
|
-
|
183
|
-
case "ArrayExpression": {
|
184
|
-
if (!inBooleanPosition) {
|
185
|
-
return node.elements.every(element => isConstant(element, false));
|
186
|
-
}
|
187
|
-
return true;
|
188
|
-
}
|
189
|
-
|
190
|
-
case "UnaryExpression":
|
191
|
-
if (
|
192
|
-
node.operator === "void" ||
|
193
|
-
node.operator === "typeof" && inBooleanPosition
|
194
|
-
) {
|
195
|
-
return true;
|
196
|
-
}
|
197
|
-
|
198
|
-
if (node.operator === "!") {
|
199
|
-
return isConstant(node.argument, true);
|
200
|
-
}
|
201
|
-
|
202
|
-
return isConstant(node.argument, false);
|
203
|
-
|
204
|
-
case "BinaryExpression":
|
205
|
-
return isConstant(node.left, false) &&
|
206
|
-
isConstant(node.right, false) &&
|
207
|
-
node.operator !== "in";
|
208
|
-
|
209
|
-
case "LogicalExpression": {
|
210
|
-
const isLeftConstant = isConstant(node.left, inBooleanPosition);
|
211
|
-
const isRightConstant = isConstant(node.right, inBooleanPosition);
|
212
|
-
const isLeftShortCircuit = (isLeftConstant && isLogicalIdentity(node.left, node.operator));
|
213
|
-
const isRightShortCircuit = (inBooleanPosition && isRightConstant && isLogicalIdentity(node.right, node.operator));
|
214
|
-
|
215
|
-
return (isLeftConstant && isRightConstant) ||
|
216
|
-
isLeftShortCircuit ||
|
217
|
-
isRightShortCircuit;
|
218
|
-
}
|
219
|
-
case "NewExpression":
|
220
|
-
return inBooleanPosition;
|
221
|
-
case "AssignmentExpression":
|
222
|
-
if (node.operator === "=") {
|
223
|
-
return isConstant(node.right, inBooleanPosition);
|
224
|
-
}
|
225
|
-
|
226
|
-
if (["||=", "&&="].includes(node.operator) && inBooleanPosition) {
|
227
|
-
return isLogicalIdentity(node.right, node.operator.slice(0, -1));
|
228
|
-
}
|
229
|
-
|
230
|
-
return false;
|
231
|
-
|
232
|
-
case "SequenceExpression":
|
233
|
-
return isConstant(node.expressions[node.expressions.length - 1], inBooleanPosition);
|
234
|
-
case "SpreadElement":
|
235
|
-
return isConstant(node.argument, inBooleanPosition);
|
236
|
-
case "CallExpression":
|
237
|
-
if (node.callee.type === "Identifier" && node.callee.name === "Boolean") {
|
238
|
-
if (node.arguments.length === 0 || isConstant(node.arguments[0], true)) {
|
239
|
-
return isReferenceToGlobalVariable(node.callee);
|
240
|
-
}
|
241
|
-
}
|
242
|
-
return false;
|
243
|
-
case "Identifier":
|
244
|
-
return node.name === "undefined" && isReferenceToGlobalVariable(node);
|
245
|
-
|
246
|
-
// no default
|
247
|
-
}
|
248
|
-
return false;
|
249
|
-
}
|
250
|
-
|
251
58
|
/**
|
252
59
|
* Tracks when the given node contains a constant condition.
|
253
60
|
* @param {ASTNode} node The AST node to check.
|
@@ -255,7 +62,7 @@ module.exports = {
|
|
255
62
|
* @private
|
256
63
|
*/
|
257
64
|
function trackConstantConditionLoop(node) {
|
258
|
-
if (node.test && isConstant(node.test, true)) {
|
65
|
+
if (node.test && isConstant(context.getScope(), node.test, true)) {
|
259
66
|
loopsInCurrentScope.add(node);
|
260
67
|
}
|
261
68
|
}
|
@@ -280,7 +87,7 @@ module.exports = {
|
|
280
87
|
* @private
|
281
88
|
*/
|
282
89
|
function reportIfConstant(node) {
|
283
|
-
if (node.test && isConstant(node.test, true)) {
|
90
|
+
if (node.test && isConstant(context.getScope(), node.test, true)) {
|
284
91
|
context.report({ node: node.test, messageId: "unexpected" });
|
285
92
|
}
|
286
93
|
}
|
package/lib/rules/no-eval.js
CHANGED
@@ -72,21 +72,25 @@ module.exports = {
|
|
72
72
|
let funcInfo = null;
|
73
73
|
|
74
74
|
/**
|
75
|
-
* Pushs a
|
75
|
+
* Pushs a `this` scope (non-arrow function, class static block, or class field initializer) information to the stack.
|
76
|
+
* Top-level scopes are handled separately.
|
76
77
|
*
|
77
78
|
* This is used in order to check whether or not `this` binding is a
|
78
79
|
* reference to the global object.
|
79
|
-
* @param {ASTNode} node A node of the scope.
|
80
|
-
* FunctionDeclaration, FunctionExpression
|
80
|
+
* @param {ASTNode} node A node of the scope.
|
81
|
+
* For functions, this is one of FunctionDeclaration, FunctionExpression.
|
82
|
+
* For class static blocks, this is StaticBlock.
|
83
|
+
* For class field initializers, this can be any node that is PropertyDefinition#value.
|
81
84
|
* @returns {void}
|
82
85
|
*/
|
83
|
-
function
|
86
|
+
function enterThisScope(node) {
|
84
87
|
const strict = context.getScope().isStrict;
|
85
88
|
|
86
89
|
funcInfo = {
|
87
90
|
upper: funcInfo,
|
88
91
|
node,
|
89
92
|
strict,
|
93
|
+
isTopLevelOfScript: false,
|
90
94
|
defaultThis: false,
|
91
95
|
initialized: strict
|
92
96
|
};
|
@@ -96,7 +100,7 @@ module.exports = {
|
|
96
100
|
* Pops a variable scope from the stack.
|
97
101
|
* @returns {void}
|
98
102
|
*/
|
99
|
-
function
|
103
|
+
function exitThisScope() {
|
100
104
|
funcInfo = funcInfo.upper;
|
101
105
|
}
|
102
106
|
|
@@ -222,12 +226,14 @@ module.exports = {
|
|
222
226
|
strict =
|
223
227
|
scope.isStrict ||
|
224
228
|
node.sourceType === "module" ||
|
225
|
-
(features.globalReturn && scope.childScopes[0].isStrict)
|
229
|
+
(features.globalReturn && scope.childScopes[0].isStrict),
|
230
|
+
isTopLevelOfScript = node.sourceType !== "module" && !features.globalReturn;
|
226
231
|
|
227
232
|
funcInfo = {
|
228
233
|
upper: null,
|
229
234
|
node,
|
230
235
|
strict,
|
236
|
+
isTopLevelOfScript,
|
231
237
|
defaultThis: true,
|
232
238
|
initialized: true
|
233
239
|
};
|
@@ -236,21 +242,19 @@ module.exports = {
|
|
236
242
|
"Program:exit"() {
|
237
243
|
const globalScope = context.getScope();
|
238
244
|
|
239
|
-
|
245
|
+
exitThisScope();
|
240
246
|
reportAccessingEval(globalScope);
|
241
247
|
reportAccessingEvalViaGlobalObject(globalScope);
|
242
248
|
},
|
243
249
|
|
244
|
-
FunctionDeclaration:
|
245
|
-
"FunctionDeclaration:exit":
|
246
|
-
FunctionExpression:
|
247
|
-
"FunctionExpression:exit":
|
248
|
-
|
249
|
-
"
|
250
|
-
|
251
|
-
"
|
252
|
-
StaticBlock: enterVarScope,
|
253
|
-
"StaticBlock:exit": exitVarScope,
|
250
|
+
FunctionDeclaration: enterThisScope,
|
251
|
+
"FunctionDeclaration:exit": exitThisScope,
|
252
|
+
FunctionExpression: enterThisScope,
|
253
|
+
"FunctionExpression:exit": exitThisScope,
|
254
|
+
"PropertyDefinition > *.value": enterThisScope,
|
255
|
+
"PropertyDefinition > *.value:exit": exitThisScope,
|
256
|
+
StaticBlock: enterThisScope,
|
257
|
+
"StaticBlock:exit": exitThisScope,
|
254
258
|
|
255
259
|
ThisExpression(node) {
|
256
260
|
if (!isMember(node.parent, "eval")) {
|
@@ -269,7 +273,8 @@ module.exports = {
|
|
269
273
|
);
|
270
274
|
}
|
271
275
|
|
272
|
-
|
276
|
+
// `this` at the top level of scripts always refers to the global object
|
277
|
+
if (funcInfo.isTopLevelOfScript || (!funcInfo.strict && funcInfo.defaultThis)) {
|
273
278
|
|
274
279
|
// `this.eval` is possible built-in `eval`.
|
275
280
|
report(node.parent);
|
@@ -1,5 +1,5 @@
|
|
1
1
|
/**
|
2
|
-
* @fileoverview A rule to disallow `this` keywords
|
2
|
+
* @fileoverview A rule to disallow `this` keywords in contexts where the value of `this` is `undefined`.
|
3
3
|
* @author Toru Nagashima
|
4
4
|
*/
|
5
5
|
|
@@ -36,7 +36,7 @@ module.exports = {
|
|
36
36
|
type: "suggestion",
|
37
37
|
|
38
38
|
docs: {
|
39
|
-
description: "disallow `this`
|
39
|
+
description: "disallow use of `this` in contexts where the value of `this` is `undefined`",
|
40
40
|
recommended: false,
|
41
41
|
url: "https://eslint.org/docs/rules/no-invalid-this"
|
42
42
|
},
|
@@ -98,11 +98,11 @@ module.exports = {
|
|
98
98
|
const scope = context.getScope();
|
99
99
|
const features = context.parserOptions.ecmaFeatures || {};
|
100
100
|
|
101
|
+
// `this` at the top level of scripts always refers to the global object
|
101
102
|
stack.push({
|
102
103
|
init: true,
|
103
104
|
node,
|
104
105
|
valid: !(
|
105
|
-
scope.isStrict ||
|
106
106
|
node.sourceType === "module" ||
|
107
107
|
(features.globalReturn && scope.childScopes[0].isStrict)
|
108
108
|
)
|
@@ -76,8 +76,8 @@ module.exports = {
|
|
76
76
|
|
77
77
|
fixable: "code",
|
78
78
|
messages: {
|
79
|
-
replaced: "Assignment (=) can be replaced with operator assignment ({{operator}}
|
80
|
-
unexpected: "Unexpected operator assignment ({{operator}}
|
79
|
+
replaced: "Assignment (=) can be replaced with operator assignment ({{operator}}).",
|
80
|
+
unexpected: "Unexpected operator assignment ({{operator}}) shorthand."
|
81
81
|
}
|
82
82
|
},
|
83
83
|
|
@@ -109,11 +109,13 @@ module.exports = {
|
|
109
109
|
const operator = expr.operator;
|
110
110
|
|
111
111
|
if (isCommutativeOperatorWithShorthand(operator) || isNonCommutativeOperatorWithShorthand(operator)) {
|
112
|
+
const replacementOperator = `${operator}=`;
|
113
|
+
|
112
114
|
if (astUtils.isSameReference(left, expr.left, true)) {
|
113
115
|
context.report({
|
114
116
|
node,
|
115
117
|
messageId: "replaced",
|
116
|
-
data: { operator },
|
118
|
+
data: { operator: replacementOperator },
|
117
119
|
fix(fixer) {
|
118
120
|
if (canBeFixed(left) && canBeFixed(expr.left)) {
|
119
121
|
const equalsToken = getOperatorToken(node);
|
@@ -126,7 +128,7 @@ module.exports = {
|
|
126
128
|
return null;
|
127
129
|
}
|
128
130
|
|
129
|
-
return fixer.replaceText(node, `${leftText}${
|
131
|
+
return fixer.replaceText(node, `${leftText}${replacementOperator}${rightText}`);
|
130
132
|
}
|
131
133
|
return null;
|
132
134
|
}
|
@@ -141,7 +143,7 @@ module.exports = {
|
|
141
143
|
context.report({
|
142
144
|
node,
|
143
145
|
messageId: "replaced",
|
144
|
-
data: { operator }
|
146
|
+
data: { operator: replacementOperator }
|
145
147
|
});
|
146
148
|
}
|
147
149
|
}
|
@@ -450,8 +450,7 @@ module.exports = {
|
|
450
450
|
type: "array",
|
451
451
|
items: { enum: Object.keys(StatementTypes) },
|
452
452
|
minItems: 1,
|
453
|
-
uniqueItems: true
|
454
|
-
additionalItems: false
|
453
|
+
uniqueItems: true
|
455
454
|
}
|
456
455
|
]
|
457
456
|
}
|
@@ -466,8 +465,7 @@ module.exports = {
|
|
466
465
|
},
|
467
466
|
additionalProperties: false,
|
468
467
|
required: ["blankLine", "prev", "next"]
|
469
|
-
}
|
470
|
-
additionalItems: false
|
468
|
+
}
|
471
469
|
},
|
472
470
|
|
473
471
|
messages: {
|
@@ -32,6 +32,7 @@ const thisTagPattern = /^[\s*]*@this/mu;
|
|
32
32
|
|
33
33
|
|
34
34
|
const COMMENTS_IGNORE_PATTERN = /^\s*(?:eslint|jshint\s+|jslint\s+|istanbul\s+|globals?\s+|exported\s+|jscs)/u;
|
35
|
+
const ESLINT_DIRECTIVE_PATTERN = /^(?:eslint[- ]|(?:globals?|exported) )/u;
|
35
36
|
const LINEBREAKS = new Set(["\r\n", "\r", "\n", "\u2028", "\u2029"]);
|
36
37
|
|
37
38
|
// A set of node types that can contain a list of statements
|
@@ -788,6 +789,203 @@ function getModuleExportName(node) {
|
|
788
789
|
return node.value;
|
789
790
|
}
|
790
791
|
|
792
|
+
/**
|
793
|
+
* Returns literal's value converted to the Boolean type
|
794
|
+
* @param {ASTNode} node any `Literal` node
|
795
|
+
* @returns {boolean | null} `true` when node is truthy, `false` when node is falsy,
|
796
|
+
* `null` when it cannot be determined.
|
797
|
+
*/
|
798
|
+
function getBooleanValue(node) {
|
799
|
+
if (node.value === null) {
|
800
|
+
|
801
|
+
/*
|
802
|
+
* it might be a null literal or bigint/regex literal in unsupported environments .
|
803
|
+
* https://github.com/estree/estree/blob/14df8a024956ea289bd55b9c2226a1d5b8a473ee/es5.md#regexpliteral
|
804
|
+
* https://github.com/estree/estree/blob/14df8a024956ea289bd55b9c2226a1d5b8a473ee/es2020.md#bigintliteral
|
805
|
+
*/
|
806
|
+
|
807
|
+
if (node.raw === "null") {
|
808
|
+
return false;
|
809
|
+
}
|
810
|
+
|
811
|
+
// regex is always truthy
|
812
|
+
if (typeof node.regex === "object") {
|
813
|
+
return true;
|
814
|
+
}
|
815
|
+
|
816
|
+
return null;
|
817
|
+
}
|
818
|
+
|
819
|
+
return !!node.value;
|
820
|
+
}
|
821
|
+
|
822
|
+
/**
|
823
|
+
* Checks if a branch node of LogicalExpression short circuits the whole condition
|
824
|
+
* @param {ASTNode} node The branch of main condition which needs to be checked
|
825
|
+
* @param {string} operator The operator of the main LogicalExpression.
|
826
|
+
* @returns {boolean} true when condition short circuits whole condition
|
827
|
+
*/
|
828
|
+
function isLogicalIdentity(node, operator) {
|
829
|
+
switch (node.type) {
|
830
|
+
case "Literal":
|
831
|
+
return (operator === "||" && getBooleanValue(node) === true) ||
|
832
|
+
(operator === "&&" && getBooleanValue(node) === false);
|
833
|
+
|
834
|
+
case "UnaryExpression":
|
835
|
+
return (operator === "&&" && node.operator === "void");
|
836
|
+
|
837
|
+
case "LogicalExpression":
|
838
|
+
|
839
|
+
/*
|
840
|
+
* handles `a && false || b`
|
841
|
+
* `false` is an identity element of `&&` but not `||`
|
842
|
+
*/
|
843
|
+
return operator === node.operator &&
|
844
|
+
(
|
845
|
+
isLogicalIdentity(node.left, operator) ||
|
846
|
+
isLogicalIdentity(node.right, operator)
|
847
|
+
);
|
848
|
+
|
849
|
+
case "AssignmentExpression":
|
850
|
+
return ["||=", "&&="].includes(node.operator) &&
|
851
|
+
operator === node.operator.slice(0, -1) &&
|
852
|
+
isLogicalIdentity(node.right, operator);
|
853
|
+
|
854
|
+
// no default
|
855
|
+
}
|
856
|
+
return false;
|
857
|
+
}
|
858
|
+
|
859
|
+
/**
|
860
|
+
* Checks if an identifier is a reference to a global variable.
|
861
|
+
* @param {Scope} scope The scope in which the identifier is referenced.
|
862
|
+
* @param {ASTNode} node An identifier node to check.
|
863
|
+
* @returns {boolean} `true` if the identifier is a reference to a global variable.
|
864
|
+
*/
|
865
|
+
function isReferenceToGlobalVariable(scope, node) {
|
866
|
+
const reference = scope.references.find(ref => ref.identifier === node);
|
867
|
+
|
868
|
+
return Boolean(
|
869
|
+
reference &&
|
870
|
+
reference.resolved &&
|
871
|
+
reference.resolved.scope.type === "global" &&
|
872
|
+
reference.resolved.defs.length === 0
|
873
|
+
);
|
874
|
+
}
|
875
|
+
|
876
|
+
|
877
|
+
/**
|
878
|
+
* Checks if a node has a constant truthiness value.
|
879
|
+
* @param {Scope} scope Scope in which the node appears.
|
880
|
+
* @param {ASTNode} node The AST node to check.
|
881
|
+
* @param {boolean} inBooleanPosition `true` if checking the test of a
|
882
|
+
* condition. `false` in all other cases. When `false`, checks if -- for
|
883
|
+
* both string and number -- if coerced to that type, the value will
|
884
|
+
* be constant.
|
885
|
+
* @returns {boolean} true when node's truthiness is constant
|
886
|
+
* @private
|
887
|
+
*/
|
888
|
+
function isConstant(scope, node, inBooleanPosition) {
|
889
|
+
|
890
|
+
// node.elements can return null values in the case of sparse arrays ex. [,]
|
891
|
+
if (!node) {
|
892
|
+
return true;
|
893
|
+
}
|
894
|
+
switch (node.type) {
|
895
|
+
case "Literal":
|
896
|
+
case "ArrowFunctionExpression":
|
897
|
+
case "FunctionExpression":
|
898
|
+
return true;
|
899
|
+
case "ClassExpression":
|
900
|
+
case "ObjectExpression":
|
901
|
+
|
902
|
+
/**
|
903
|
+
* In theory objects like:
|
904
|
+
*
|
905
|
+
* `{toString: () => a}`
|
906
|
+
* `{valueOf: () => a}`
|
907
|
+
*
|
908
|
+
* Or a classes like:
|
909
|
+
*
|
910
|
+
* `class { static toString() { return a } }`
|
911
|
+
* `class { static valueOf() { return a } }`
|
912
|
+
*
|
913
|
+
* Are not constant verifiably when `inBooleanPosition` is
|
914
|
+
* false, but it's an edge case we've opted not to handle.
|
915
|
+
*/
|
916
|
+
return true;
|
917
|
+
case "TemplateLiteral":
|
918
|
+
return (inBooleanPosition && node.quasis.some(quasi => quasi.value.cooked.length)) ||
|
919
|
+
node.expressions.every(exp => isConstant(scope, exp, false));
|
920
|
+
|
921
|
+
case "ArrayExpression": {
|
922
|
+
if (!inBooleanPosition) {
|
923
|
+
return node.elements.every(element => isConstant(scope, element, false));
|
924
|
+
}
|
925
|
+
return true;
|
926
|
+
}
|
927
|
+
|
928
|
+
case "UnaryExpression":
|
929
|
+
if (
|
930
|
+
node.operator === "void" ||
|
931
|
+
node.operator === "typeof" && inBooleanPosition
|
932
|
+
) {
|
933
|
+
return true;
|
934
|
+
}
|
935
|
+
|
936
|
+
if (node.operator === "!") {
|
937
|
+
return isConstant(scope, node.argument, true);
|
938
|
+
}
|
939
|
+
|
940
|
+
return isConstant(scope, node.argument, false);
|
941
|
+
|
942
|
+
case "BinaryExpression":
|
943
|
+
return isConstant(scope, node.left, false) &&
|
944
|
+
isConstant(scope, node.right, false) &&
|
945
|
+
node.operator !== "in";
|
946
|
+
|
947
|
+
case "LogicalExpression": {
|
948
|
+
const isLeftConstant = isConstant(scope, node.left, inBooleanPosition);
|
949
|
+
const isRightConstant = isConstant(scope, node.right, inBooleanPosition);
|
950
|
+
const isLeftShortCircuit = (isLeftConstant && isLogicalIdentity(node.left, node.operator));
|
951
|
+
const isRightShortCircuit = (inBooleanPosition && isRightConstant && isLogicalIdentity(node.right, node.operator));
|
952
|
+
|
953
|
+
return (isLeftConstant && isRightConstant) ||
|
954
|
+
isLeftShortCircuit ||
|
955
|
+
isRightShortCircuit;
|
956
|
+
}
|
957
|
+
case "NewExpression":
|
958
|
+
return inBooleanPosition;
|
959
|
+
case "AssignmentExpression":
|
960
|
+
if (node.operator === "=") {
|
961
|
+
return isConstant(scope, node.right, inBooleanPosition);
|
962
|
+
}
|
963
|
+
|
964
|
+
if (["||=", "&&="].includes(node.operator) && inBooleanPosition) {
|
965
|
+
return isLogicalIdentity(node.right, node.operator.slice(0, -1));
|
966
|
+
}
|
967
|
+
|
968
|
+
return false;
|
969
|
+
|
970
|
+
case "SequenceExpression":
|
971
|
+
return isConstant(scope, node.expressions[node.expressions.length - 1], inBooleanPosition);
|
972
|
+
case "SpreadElement":
|
973
|
+
return isConstant(scope, node.argument, inBooleanPosition);
|
974
|
+
case "CallExpression":
|
975
|
+
if (node.callee.type === "Identifier" && node.callee.name === "Boolean") {
|
976
|
+
if (node.arguments.length === 0 || isConstant(scope, node.arguments[0], true)) {
|
977
|
+
return isReferenceToGlobalVariable(scope, node.callee);
|
978
|
+
}
|
979
|
+
}
|
980
|
+
return false;
|
981
|
+
case "Identifier":
|
982
|
+
return node.name === "undefined" && isReferenceToGlobalVariable(scope, node);
|
983
|
+
|
984
|
+
// no default
|
985
|
+
}
|
986
|
+
return false;
|
987
|
+
}
|
988
|
+
|
791
989
|
//------------------------------------------------------------------------------
|
792
990
|
// Public Interface
|
793
991
|
//------------------------------------------------------------------------------
|
@@ -908,12 +1106,8 @@ module.exports = {
|
|
908
1106
|
const comment = node.value.trim();
|
909
1107
|
|
910
1108
|
return (
|
911
|
-
node.type === "Line" && comment.
|
912
|
-
node.type === "Block" && (
|
913
|
-
comment.indexOf("global ") === 0 ||
|
914
|
-
comment.indexOf("eslint ") === 0 ||
|
915
|
-
comment.indexOf("eslint-") === 0
|
916
|
-
)
|
1109
|
+
node.type === "Line" && comment.startsWith("eslint-") ||
|
1110
|
+
node.type === "Block" && ESLINT_DIRECTIVE_PATTERN.test(comment)
|
917
1111
|
);
|
918
1112
|
},
|
919
1113
|
|
@@ -1905,6 +2099,7 @@ module.exports = {
|
|
1905
2099
|
return OCTAL_OR_NON_OCTAL_DECIMAL_ESCAPE_PATTERN.test(rawString);
|
1906
2100
|
},
|
1907
2101
|
|
2102
|
+
isReferenceToGlobalVariable,
|
1908
2103
|
isLogicalExpression,
|
1909
2104
|
isCoalesceExpression,
|
1910
2105
|
isMixedLogicalAndCoalesceExpressions,
|
@@ -1918,5 +2113,6 @@ module.exports = {
|
|
1918
2113
|
isSameReference,
|
1919
2114
|
isLogicalAssignmentOperator,
|
1920
2115
|
getSwitchCaseColonToken,
|
1921
|
-
getModuleExportName
|
2116
|
+
getModuleExportName,
|
2117
|
+
isConstant
|
1922
2118
|
};
|
package/lib/shared/types.js
CHANGED
@@ -173,3 +173,27 @@ module.exports = {};
|
|
173
173
|
* @property {string} ruleId The rule ID.
|
174
174
|
* @property {string[]} replacedBy The rule IDs that replace this deprecated rule.
|
175
175
|
*/
|
176
|
+
|
177
|
+
/**
|
178
|
+
* A linting result.
|
179
|
+
* @typedef {Object} LintResult
|
180
|
+
* @property {string} filePath The path to the file that was linted.
|
181
|
+
* @property {LintMessage[]} messages All of the messages for the result.
|
182
|
+
* @property {SuppressedLintMessage[]} suppressedMessages All of the suppressed messages for the result.
|
183
|
+
* @property {number} errorCount Number of errors for the result.
|
184
|
+
* @property {number} fatalErrorCount Number of fatal errors for the result.
|
185
|
+
* @property {number} warningCount Number of warnings for the result.
|
186
|
+
* @property {number} fixableErrorCount Number of fixable errors for the result.
|
187
|
+
* @property {number} fixableWarningCount Number of fixable warnings for the result.
|
188
|
+
* @property {string} [source] The source code of the file that was linted.
|
189
|
+
* @property {string} [output] The source code of the file that was linted, with as many fixes applied as possible.
|
190
|
+
* @property {DeprecatedRuleInfo[]} usedDeprecatedRules The list of used deprecated rules.
|
191
|
+
*/
|
192
|
+
|
193
|
+
/**
|
194
|
+
* A formatter function.
|
195
|
+
* @callback FormatterFunction
|
196
|
+
* @param {LintResult[]} results The list of linting results.
|
197
|
+
* @param {{cwd: string, rulesMeta: Record<string, RuleMeta>}} [context] A context object.
|
198
|
+
* @returns {string | Promise<string>} Formatted text.
|
199
|
+
*/
|