eslint 8.8.0 → 8.11.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.
@@ -146,7 +146,7 @@ module.exports = {
146
146
 
147
147
  /**
148
148
  * Checks if a given binding identifier uses the original name as-is.
149
- * - If it's in object destructuring, the original name is its property name.
149
+ * - If it's in object destructuring or object expression, the original name is its property name.
150
150
  * - If it's in import declaration, the original name is its exported name.
151
151
  * @param {ASTNode} node The `Identifier` node to check.
152
152
  * @returns {boolean} `true` if the identifier uses the original name as-is.
@@ -161,7 +161,7 @@ module.exports = {
161
161
  switch (parent.type) {
162
162
  case "Property":
163
163
  return (
164
- parent.parent.type === "ObjectPattern" &&
164
+ (parent.parent.type === "ObjectPattern" || parent.parent.type === "ObjectExpression") &&
165
165
  parent.value === valueNode &&
166
166
  !parent.computed &&
167
167
  parent.key.type === "Identifier" &&
@@ -243,8 +243,18 @@ module.exports = {
243
243
  node: lastItem,
244
244
  loc: trailingToken.loc,
245
245
  messageId: "unexpected",
246
- fix(fixer) {
247
- return fixer.remove(trailingToken);
246
+ *fix(fixer) {
247
+ yield fixer.remove(trailingToken);
248
+
249
+ /*
250
+ * Extend the range of the fix to include surrounding tokens to ensure
251
+ * that the element after which the comma is removed stays _last_.
252
+ * This intentionally makes conflicts in fix ranges with rules that may be
253
+ * adding or removing elements in the same autofix pass.
254
+ * https://github.com/eslint/eslint/issues/15660
255
+ */
256
+ yield fixer.insertTextBefore(sourceCode.getTokenBefore(trailingToken), "");
257
+ yield fixer.insertTextAfter(sourceCode.getTokenAfter(trailingToken), "");
248
258
  }
249
259
  });
250
260
  }
@@ -282,8 +292,18 @@ module.exports = {
282
292
  end: astUtils.getNextLocation(sourceCode, trailingToken.loc.end)
283
293
  },
284
294
  messageId: "missing",
285
- fix(fixer) {
286
- return fixer.insertTextAfter(trailingToken, ",");
295
+ *fix(fixer) {
296
+ yield fixer.insertTextAfter(trailingToken, ",");
297
+
298
+ /*
299
+ * Extend the range of the fix to include surrounding tokens to ensure
300
+ * that the element after which the comma is inserted stays _last_.
301
+ * This intentionally makes conflicts in fix ranges with rules that may be
302
+ * adding or removing elements in the same autofix pass.
303
+ * https://github.com/eslint/eslint/issues/15660
304
+ */
305
+ yield fixer.insertTextBefore(trailingToken, "");
306
+ yield fixer.insertTextAfter(sourceCode.getTokenAfter(trailingToken), "");
287
307
  }
288
308
  });
289
309
  }
@@ -227,9 +227,13 @@ module.exports = {
227
227
  return null;
228
228
  }
229
229
 
230
+ const rightParen = node.params.length
231
+ ? sourceCode.getTokenAfter(node.params[node.params.length - 1], astUtils.isClosingParenToken)
232
+ : sourceCode.getTokenAfter(firstToken);
233
+
230
234
  return {
231
235
  leftParen: firstToken,
232
- rightParen: sourceCode.getTokenBefore(node.body, astUtils.isClosingParenToken)
236
+ rightParen
233
237
  };
234
238
  }
235
239
 
@@ -41,7 +41,8 @@ module.exports = {
41
41
  schema: [{
42
42
  type: "object",
43
43
  properties: {
44
- allowParens: { type: "boolean", default: true }
44
+ allowParens: { type: "boolean", default: true },
45
+ onlyOneSimpleParam: { type: "boolean", default: false }
45
46
  },
46
47
  additionalProperties: false
47
48
  }],
@@ -54,6 +55,7 @@ module.exports = {
54
55
  create(context) {
55
56
  const config = context.options[0] || {};
56
57
  const allowParens = config.allowParens || (config.allowParens === void 0);
58
+ const onlyOneSimpleParam = config.onlyOneSimpleParam;
57
59
  const sourceCode = context.getSourceCode();
58
60
 
59
61
 
@@ -65,7 +67,9 @@ module.exports = {
65
67
  function checkArrowFunc(node) {
66
68
  const body = node.body;
67
69
 
68
- if (isConditional(body) && !(allowParens && astUtils.isParenthesised(sourceCode, body))) {
70
+ if (isConditional(body) &&
71
+ !(allowParens && astUtils.isParenthesised(sourceCode, body)) &&
72
+ !(onlyOneSimpleParam && !(node.params.length === 1 && node.params[0].type === "Identifier"))) {
69
73
  context.report({
70
74
  node,
71
75
  messageId: "confusing",
@@ -120,12 +120,30 @@ module.exports = {
120
120
  return false;
121
121
  }
122
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
+
123
140
  /**
124
141
  * Checks if a node has a constant truthiness value.
125
142
  * @param {ASTNode} node The AST node to check.
126
- * @param {boolean} inBooleanPosition `false` if checking branch of a condition.
127
- * `true` in all other cases. When `false`, checks if -- for both string and
128
- * number -- if coerced to that type, the value will be constant.
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.
129
147
  * @returns {Bool} true when node's truthiness is constant
130
148
  * @private
131
149
  */
@@ -215,6 +233,15 @@ module.exports = {
215
233
  return isConstant(node.expressions[node.expressions.length - 1], inBooleanPosition);
216
234
  case "SpreadElement":
217
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);
218
245
 
219
246
  // no default
220
247
  }
@@ -11,6 +11,15 @@
11
11
 
12
12
  const astUtils = require("./utils/ast-utils");
13
13
 
14
+ //------------------------------------------------------------------------------
15
+ // Helpers
16
+ //------------------------------------------------------------------------------
17
+
18
+ const FUNC_EXPR_NODE_TYPES = ["ArrowFunctionExpression", "FunctionExpression"];
19
+ const CALL_EXPR_NODE_TYPE = ["CallExpression"];
20
+ const FOR_IN_OF_TYPE = /^For(?:In|Of)Statement$/u;
21
+ const SENTINEL_TYPE = /^(?:(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|CatchClause|ImportDeclaration|ExportNamedDeclaration)$/u;
22
+
14
23
  //------------------------------------------------------------------------------
15
24
  // Rule Definition
16
25
  //------------------------------------------------------------------------------
@@ -37,7 +46,8 @@ module.exports = {
37
46
  items: {
38
47
  type: "string"
39
48
  }
40
- }
49
+ },
50
+ ignoreOnInitialization: { type: "boolean", default: false }
41
51
  },
42
52
  additionalProperties: false
43
53
  }
@@ -54,9 +64,109 @@ module.exports = {
54
64
  const options = {
55
65
  builtinGlobals: context.options[0] && context.options[0].builtinGlobals,
56
66
  hoist: (context.options[0] && context.options[0].hoist) || "functions",
57
- allow: (context.options[0] && context.options[0].allow) || []
67
+ allow: (context.options[0] && context.options[0].allow) || [],
68
+ ignoreOnInitialization: context.options[0] && context.options[0].ignoreOnInitialization
58
69
  };
59
70
 
71
+ /**
72
+ * Checks whether or not a given location is inside of the range of a given node.
73
+ * @param {ASTNode} node An node to check.
74
+ * @param {number} location A location to check.
75
+ * @returns {boolean} `true` if the location is inside of the range of the node.
76
+ */
77
+ function isInRange(node, location) {
78
+ return node && node.range[0] <= location && location <= node.range[1];
79
+ }
80
+
81
+ /**
82
+ * Searches from the current node through its ancestry to find a matching node.
83
+ * @param {ASTNode} node a node to get.
84
+ * @param {(node: ASTNode) => boolean} match a callback that checks whether or not the node verifies its condition or not.
85
+ * @returns {ASTNode|null} the matching node.
86
+ */
87
+ function findSelfOrAncestor(node, match) {
88
+ let currentNode = node;
89
+
90
+ while (currentNode && !match(currentNode)) {
91
+ currentNode = currentNode.parent;
92
+ }
93
+ return currentNode;
94
+ }
95
+
96
+ /**
97
+ * Finds function's outer scope.
98
+ * @param {Scope} scope Function's own scope.
99
+ * @returns {Scope} Function's outer scope.
100
+ */
101
+ function getOuterScope(scope) {
102
+ const upper = scope.upper;
103
+
104
+ if (upper.type === "function-expression-name") {
105
+ return upper.upper;
106
+ }
107
+ return upper;
108
+ }
109
+
110
+ /**
111
+ * Checks if a variable and a shadowedVariable have the same init pattern ancestor.
112
+ * @param {Object} variable a variable to check.
113
+ * @param {Object} shadowedVariable a shadowedVariable to check.
114
+ * @returns {boolean} Whether or not the variable and the shadowedVariable have the same init pattern ancestor.
115
+ */
116
+ function isInitPatternNode(variable, shadowedVariable) {
117
+ const outerDef = shadowedVariable.defs[0];
118
+
119
+ if (!outerDef) {
120
+ return false;
121
+ }
122
+
123
+ const { variableScope } = variable.scope;
124
+
125
+
126
+ if (!(FUNC_EXPR_NODE_TYPES.includes(variableScope.block.type) && getOuterScope(variableScope) === shadowedVariable.scope)) {
127
+ return false;
128
+ }
129
+
130
+ const fun = variableScope.block;
131
+ const { parent } = fun;
132
+
133
+ const callExpression = findSelfOrAncestor(
134
+ parent,
135
+ node => CALL_EXPR_NODE_TYPE.includes(node.type)
136
+ );
137
+
138
+ if (!callExpression) {
139
+ return false;
140
+ }
141
+
142
+ let node = outerDef.name;
143
+ const location = callExpression.range[1];
144
+
145
+ while (node) {
146
+ if (node.type === "VariableDeclarator") {
147
+ if (isInRange(node.init, location)) {
148
+ return true;
149
+ }
150
+ if (FOR_IN_OF_TYPE.test(node.parent.parent.type) &&
151
+ isInRange(node.parent.parent.right, location)
152
+ ) {
153
+ return true;
154
+ }
155
+ break;
156
+ } else if (node.type === "AssignmentPattern") {
157
+ if (isInRange(node.right, location)) {
158
+ return true;
159
+ }
160
+ } else if (SENTINEL_TYPE.test(node.type)) {
161
+ break;
162
+ }
163
+
164
+ node = node.parent;
165
+ }
166
+
167
+ return false;
168
+ }
169
+
60
170
  /**
61
171
  * Check if variable name is allowed.
62
172
  * @param {ASTNode} variable The variable to check.
@@ -99,11 +209,11 @@ module.exports = {
99
209
 
100
210
  return (
101
211
  outer &&
102
- inner &&
103
- outer[0] < inner[0] &&
104
- inner[1] < outer[1] &&
105
- ((innerDef.type === "FunctionName" && innerDef.node.type === "FunctionExpression") || innerDef.node.type === "ClassExpression") &&
106
- outerScope === innerScope.upper
212
+ inner &&
213
+ outer[0] < inner[0] &&
214
+ inner[1] < outer[1] &&
215
+ ((innerDef.type === "FunctionName" && innerDef.node.type === "FunctionExpression") || innerDef.node.type === "ClassExpression") &&
216
+ outerScope === innerScope.upper
107
217
  );
108
218
  }
109
219
 
@@ -154,11 +264,11 @@ module.exports = {
154
264
 
155
265
  return (
156
266
  inner &&
157
- outer &&
158
- inner[1] < outer[0] &&
267
+ outer &&
268
+ inner[1] < outer[0] &&
159
269
 
160
- // Excepts FunctionDeclaration if is {"hoist":"function"}.
161
- (options.hoist !== "functions" || !outerDef || outerDef.node.type !== "FunctionDeclaration")
270
+ // Excepts FunctionDeclaration if is {"hoist":"function"}.
271
+ (options.hoist !== "functions" || !outerDef || outerDef.node.type !== "FunctionDeclaration")
162
272
  );
163
273
  }
164
274
 
@@ -175,8 +285,8 @@ module.exports = {
175
285
 
176
286
  // Skips "arguments" or variables of a class name in the class scope of ClassDeclaration.
177
287
  if (variable.identifiers.length === 0 ||
178
- isDuplicatedClassNameVariable(variable) ||
179
- isAllowed(variable)
288
+ isDuplicatedClassNameVariable(variable) ||
289
+ isAllowed(variable)
180
290
  ) {
181
291
  continue;
182
292
  }
@@ -185,9 +295,10 @@ module.exports = {
185
295
  const shadowed = astUtils.getVariableByName(scope.upper, variable.name);
186
296
 
187
297
  if (shadowed &&
188
- (shadowed.identifiers.length > 0 || (options.builtinGlobals && "writeable" in shadowed)) &&
189
- !isOnInitializer(variable, shadowed) &&
190
- !(options.hoist !== "all" && isInTdz(variable, shadowed))
298
+ (shadowed.identifiers.length > 0 || (options.builtinGlobals && "writeable" in shadowed)) &&
299
+ !isOnInitializer(variable, shadowed) &&
300
+ !(options.ignoreOnInitialization && isInitPatternNode(variable, shadowed)) &&
301
+ !(options.hoist !== "all" && isInTdz(variable, shadowed))
191
302
  ) {
192
303
  const location = getDeclaredLocation(shadowed);
193
304
  const messageId = location.global ? "noShadowGlobal" : "noShadow";
@@ -67,6 +67,9 @@ module.exports = {
67
67
  },
68
68
  caughtErrorsIgnorePattern: {
69
69
  type: "string"
70
+ },
71
+ destructuredArrayIgnorePattern: {
72
+ type: "string"
70
73
  }
71
74
  },
72
75
  additionalProperties: false
@@ -114,6 +117,10 @@ module.exports = {
114
117
  if (firstOption.caughtErrorsIgnorePattern) {
115
118
  config.caughtErrorsIgnorePattern = new RegExp(firstOption.caughtErrorsIgnorePattern, "u");
116
119
  }
120
+
121
+ if (firstOption.destructuredArrayIgnorePattern) {
122
+ config.destructuredArrayIgnorePattern = new RegExp(firstOption.destructuredArrayIgnorePattern, "u");
123
+ }
117
124
  }
118
125
  }
119
126
 
@@ -155,7 +162,14 @@ module.exports = {
155
162
  * @returns {UnusedVarMessageData} The message data to be used with this unused variable.
156
163
  */
157
164
  function getAssignedMessageData(unusedVar) {
158
- const additional = config.varsIgnorePattern ? `. Allowed unused vars must match ${config.varsIgnorePattern.toString()}` : "";
165
+ const def = unusedVar.defs[0];
166
+ let additional = "";
167
+
168
+ if (config.destructuredArrayIgnorePattern && def && def.name.parent.type === "ArrayPattern") {
169
+ additional = `. Allowed unused elements of array destructuring patterns must match ${config.destructuredArrayIgnorePattern.toString()}`;
170
+ } else if (config.varsIgnorePattern) {
171
+ additional = `. Allowed unused vars must match ${config.varsIgnorePattern.toString()}`;
172
+ }
159
173
 
160
174
  return {
161
175
  varName: unusedVar.name,
@@ -584,6 +598,19 @@ module.exports = {
584
598
 
585
599
  if (def) {
586
600
  const type = def.type;
601
+ const refUsedInArrayPatterns = variable.references.some(ref => ref.identifier.parent.type === "ArrayPattern");
602
+
603
+ // skip elements of array destructuring patterns
604
+ if (
605
+ (
606
+ def.name.parent.type === "ArrayPattern" ||
607
+ refUsedInArrayPatterns
608
+ ) &&
609
+ config.destructuredArrayIgnorePattern &&
610
+ config.destructuredArrayIgnorePattern.test(def.name.name)
611
+ ) {
612
+ continue;
613
+ }
587
614
 
588
615
  // skip catch variables
589
616
  if (type === "CatchClause") {
@@ -19,6 +19,8 @@ module.exports = {
19
19
  url: "https://eslint.org/docs/rules/valid-typeof"
20
20
  },
21
21
 
22
+ hasSuggestions: true,
23
+
22
24
  schema: [
23
25
  {
24
26
  type: "object",
@@ -33,7 +35,8 @@ module.exports = {
33
35
  ],
34
36
  messages: {
35
37
  invalidValue: "Invalid typeof comparison value.",
36
- notString: "Typeof comparisons should be to string literals."
38
+ notString: "Typeof comparisons should be to string literals.",
39
+ suggestString: 'Use `"{{type}}"` instead of `{{type}}`.'
37
40
  }
38
41
  },
39
42
 
@@ -44,6 +47,21 @@ module.exports = {
44
47
 
45
48
  const requireStringLiterals = context.options[0] && context.options[0].requireStringLiterals;
46
49
 
50
+ let globalScope;
51
+
52
+ /**
53
+ * Checks whether the given node represents a reference to a global variable that is not declared in the source code.
54
+ * These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables.
55
+ * @param {ASTNode} node `Identifier` node to check.
56
+ * @returns {boolean} `true` if the node is a reference to a global variable.
57
+ */
58
+ function isReferenceToGlobalVariable(node) {
59
+ const variable = globalScope.set.get(node.name);
60
+
61
+ return variable && variable.defs.length === 0 &&
62
+ variable.references.some(ref => ref.identifier === node);
63
+ }
64
+
47
65
  /**
48
66
  * Determines whether a node is a typeof expression.
49
67
  * @param {ASTNode} node The node
@@ -59,6 +77,10 @@ module.exports = {
59
77
 
60
78
  return {
61
79
 
80
+ Program() {
81
+ globalScope = context.getScope();
82
+ },
83
+
62
84
  UnaryExpression(node) {
63
85
  if (isTypeofExpression(node)) {
64
86
  const parent = context.getAncestors().pop();
@@ -72,6 +94,20 @@ module.exports = {
72
94
  if (VALID_TYPES.indexOf(value) === -1) {
73
95
  context.report({ node: sibling, messageId: "invalidValue" });
74
96
  }
97
+ } else if (sibling.type === "Identifier" && sibling.name === "undefined" && isReferenceToGlobalVariable(sibling)) {
98
+ context.report({
99
+ node: sibling,
100
+ messageId: requireStringLiterals ? "notString" : "invalidValue",
101
+ suggest: [
102
+ {
103
+ messageId: "suggestString",
104
+ data: { type: "undefined" },
105
+ fix(fixer) {
106
+ return fixer.replaceText(sibling, '"undefined"');
107
+ }
108
+ }
109
+ ]
110
+ });
75
111
  } else if (requireStringLiterals && !isTypeofExpression(sibling)) {
76
112
  context.report({ node: sibling, messageId: "notString" });
77
113
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint",
3
- "version": "8.8.0",
3
+ "version": "8.11.0",
4
4
  "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
5
5
  "description": "An AST-based pattern checker for JavaScript.",
6
6
  "bin": {
@@ -47,7 +47,7 @@
47
47
  "homepage": "https://eslint.org",
48
48
  "bugs": "https://github.com/eslint/eslint/issues/",
49
49
  "dependencies": {
50
- "@eslint/eslintrc": "^1.0.5",
50
+ "@eslint/eslintrc": "^1.2.1",
51
51
  "@humanwhocodes/config-array": "^0.9.2",
52
52
  "ajv": "^6.10.0",
53
53
  "chalk": "^4.0.0",
@@ -55,10 +55,10 @@
55
55
  "debug": "^4.3.2",
56
56
  "doctrine": "^3.0.0",
57
57
  "escape-string-regexp": "^4.0.0",
58
- "eslint-scope": "^7.1.0",
58
+ "eslint-scope": "^7.1.1",
59
59
  "eslint-utils": "^3.0.0",
60
- "eslint-visitor-keys": "^3.2.0",
61
- "espree": "^9.3.0",
60
+ "eslint-visitor-keys": "^3.3.0",
61
+ "espree": "^9.3.1",
62
62
  "esquery": "^1.4.0",
63
63
  "esutils": "^2.0.2",
64
64
  "fast-deep-equal": "^3.1.3",
@@ -122,6 +122,7 @@
122
122
  "node-polyfill-webpack-plugin": "^1.0.3",
123
123
  "npm-license": "^0.3.3",
124
124
  "nyc": "^15.0.1",
125
+ "pirates": "^4.0.5",
125
126
  "progress": "^2.0.3",
126
127
  "proxyquire": "^2.0.1",
127
128
  "puppeteer": "^9.1.1",