eslint 8.27.0 → 8.29.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.
@@ -26,6 +26,7 @@ const isPathInside = require("is-path-inside");
26
26
 
27
27
  const doFsWalk = util.promisify(fswalk.walk);
28
28
  const Minimatch = minimatch.Minimatch;
29
+ const MINIMATCH_OPTIONS = { dot: true };
29
30
 
30
31
  //-----------------------------------------------------------------------------
31
32
  // Types
@@ -76,7 +77,7 @@ class UnmatchedSearchPatternsError extends Error {
76
77
  constructor({ basePath, unmatchedPatterns, patterns, rawPatterns }) {
77
78
  super(`No files matching '${rawPatterns}' in '${basePath}' were found.`);
78
79
  this.basePath = basePath;
79
- this.patternsToCheck = unmatchedPatterns;
80
+ this.unmatchedPatterns = unmatchedPatterns;
80
81
  this.patterns = patterns;
81
82
  this.rawPatterns = rawPatterns;
82
83
  }
@@ -158,7 +159,7 @@ function globMatch({ basePath, pattern }) {
158
159
  ? normalizeToPosix(path.relative(basePath, pattern))
159
160
  : pattern;
160
161
 
161
- const matcher = new Minimatch(patternToUse);
162
+ const matcher = new Minimatch(patternToUse, MINIMATCH_OPTIONS);
162
163
 
163
164
  const fsWalkSettings = {
164
165
 
@@ -257,7 +258,7 @@ async function globSearch({
257
258
 
258
259
  relativeToPatterns.set(patternToUse, patterns[i]);
259
260
 
260
- return new minimatch.Minimatch(patternToUse);
261
+ return new Minimatch(patternToUse, MINIMATCH_OPTIONS);
261
262
  });
262
263
 
263
264
  /*
@@ -337,49 +338,43 @@ async function globSearch({
337
338
  }
338
339
 
339
340
  /**
340
- * Checks to see if there are any ignored results for a given search. This
341
- * happens either when there are unmatched patterns during a search or if
342
- * a search returns no results.
341
+ * Throws an error for unmatched patterns. The error will only contain information about the first one.
342
+ * Checks to see if there are any ignored results for a given search.
343
343
  * @param {Object} options The options for this function.
344
344
  * @param {string} options.basePath The directory to search.
345
345
  * @param {Array<string>} options.patterns An array of glob patterns
346
346
  * that were used in the original search.
347
347
  * @param {Array<string>} options.rawPatterns An array of glob patterns
348
348
  * as the user inputted them. Used for errors.
349
- * @param {Array<string>} options.patternsToCheck An array of glob patterns
350
- * to use for this check.
351
- * @returns {void}
352
- * @throws {NoFilesFoundError} If there is a pattern that doesn't match
353
- * any files and `errorOnUnmatchedPattern` is true.
354
- * @throws {AllFilesIgnoredError} If there is a pattern that matches files
355
- * when there are no ignores.
349
+ * @param {Array<string>} options.unmatchedPatterns A non-empty array of glob patterns
350
+ * that were unmatched in the original search.
351
+ * @returns {void} Always throws an error.
352
+ * @throws {NoFilesFoundError} If the first unmatched pattern
353
+ * doesn't match any files even when there are no ignores.
354
+ * @throws {AllFilesIgnoredError} If the first unmatched pattern
355
+ * matches some files when there are no ignores.
356
356
  */
357
- async function checkForIgnoredResults({
357
+ async function throwErrorForUnmatchedPatterns({
358
358
  basePath,
359
359
  patterns,
360
360
  rawPatterns,
361
- patternsToCheck = patterns
361
+ unmatchedPatterns
362
362
  }) {
363
363
 
364
- for (const pattern of patternsToCheck) {
364
+ const pattern = unmatchedPatterns[0];
365
+ const rawPattern = rawPatterns[patterns.indexOf(pattern)];
365
366
 
366
- const patternHasMatch = await globMatch({
367
- basePath,
368
- pattern
369
- });
367
+ const patternHasMatch = await globMatch({
368
+ basePath,
369
+ pattern
370
+ });
370
371
 
371
- if (patternHasMatch) {
372
- throw new AllFilesIgnoredError(
373
- rawPatterns[patterns.indexOf(pattern)]
374
- );
375
- }
372
+ if (patternHasMatch) {
373
+ throw new AllFilesIgnoredError(rawPattern);
376
374
  }
377
375
 
378
376
  // if we get here there are truly no matches
379
- throw new NoFilesFoundError(
380
- rawPatterns[patterns.indexOf(patternsToCheck[0])],
381
- true
382
- );
377
+ throw new NoFilesFoundError(rawPattern, true);
383
378
  }
384
379
 
385
380
  /**
@@ -446,9 +441,9 @@ async function globMultiSearch({ searches, configs, errorOnUnmatchedPattern }) {
446
441
 
447
442
  if (errorOnUnmatchedPattern) {
448
443
 
449
- await checkForIgnoredResults({
444
+ await throwErrorForUnmatchedPatterns({
450
445
  ...currentSearch,
451
- patternsToCheck: error.patternsToCheck
446
+ unmatchedPatterns: error.unmatchedPatterns
452
447
  });
453
448
 
454
449
  }
@@ -15,7 +15,7 @@ module.exports = {
15
15
  type: "problem",
16
16
 
17
17
  docs: {
18
- description: "Enforce \"for\" loop update clause moving the counter in the right direction.",
18
+ description: "Enforce \"for\" loop update clause moving the counter in the right direction",
19
19
  recommended: true,
20
20
  url: "https://eslint.org/docs/rules/for-direction"
21
21
  },
@@ -334,6 +334,19 @@ module.exports = {
334
334
 
335
335
  const sourceCode = context.getSourceCode();
336
336
 
337
+ /**
338
+ * Determines if the given property is key-value property.
339
+ * @param {ASTNode} property Property node to check.
340
+ * @returns {boolean} Whether the property is a key-value property.
341
+ */
342
+ function isKeyValueProperty(property) {
343
+ return !(
344
+ (property.method ||
345
+ property.shorthand ||
346
+ property.kind !== "init" || property.type !== "Property") // Could be "ExperimentalSpreadProperty" or "SpreadElement"
347
+ );
348
+ }
349
+
337
350
  /**
338
351
  * Checks whether a property is a member of the property group it follows.
339
352
  * @param {ASTNode} lastMember The last Property known to be in the group.
@@ -342,9 +355,9 @@ module.exports = {
342
355
  */
343
356
  function continuesPropertyGroup(lastMember, candidate) {
344
357
  const groupEndLine = lastMember.loc.start.line,
345
- candidateStartLine = candidate.loc.start.line;
358
+ candidateValueStartLine = (isKeyValueProperty(candidate) ? candidate.value : candidate).loc.start.line;
346
359
 
347
- if (candidateStartLine - groupEndLine <= 1) {
360
+ if (candidateValueStartLine - groupEndLine <= 1) {
348
361
  return true;
349
362
  }
350
363
 
@@ -358,7 +371,7 @@ module.exports = {
358
371
  if (
359
372
  leadingComments.length &&
360
373
  leadingComments[0].loc.start.line - groupEndLine <= 1 &&
361
- candidateStartLine - last(leadingComments).loc.end.line <= 1
374
+ candidateValueStartLine - last(leadingComments).loc.end.line <= 1
362
375
  ) {
363
376
  for (let i = 1; i < leadingComments.length; i++) {
364
377
  if (leadingComments[i].loc.start.line - leadingComments[i - 1].loc.end.line > 1) {
@@ -371,19 +384,6 @@ module.exports = {
371
384
  return false;
372
385
  }
373
386
 
374
- /**
375
- * Determines if the given property is key-value property.
376
- * @param {ASTNode} property Property node to check.
377
- * @returns {boolean} Whether the property is a key-value property.
378
- */
379
- function isKeyValueProperty(property) {
380
- return !(
381
- (property.method ||
382
- property.shorthand ||
383
- property.kind !== "init" || property.type !== "Property") // Could be "ExperimentalSpreadProperty" or "SpreadElement"
384
- );
385
- }
386
-
387
387
  /**
388
388
  * Starting from the given a node (a property.key node here) looks forward
389
389
  * until it finds the last token before a colon punctuator and returns it.
@@ -52,7 +52,8 @@ module.exports = {
52
52
  enforceForArrowConditionals: { type: "boolean" },
53
53
  enforceForSequenceExpressions: { type: "boolean" },
54
54
  enforceForNewInMemberExpressions: { type: "boolean" },
55
- enforceForFunctionPrototypeMethods: { type: "boolean" }
55
+ enforceForFunctionPrototypeMethods: { type: "boolean" },
56
+ allowParensAfterCommentPattern: { type: "string" }
56
57
  },
57
58
  additionalProperties: false
58
59
  }
@@ -86,6 +87,7 @@ module.exports = {
86
87
  context.options[1].enforceForNewInMemberExpressions === false;
87
88
  const IGNORE_FUNCTION_PROTOTYPE_METHODS = ALL_NODES && context.options[1] &&
88
89
  context.options[1].enforceForFunctionPrototypeMethods === false;
90
+ const ALLOW_PARENS_AFTER_COMMENT_PATTERN = ALL_NODES && context.options[1] && context.options[1].allowParensAfterCommentPattern;
89
91
 
90
92
  const PRECEDENCE_OF_ASSIGNMENT_EXPR = precedence({ type: "AssignmentExpression" });
91
93
  const PRECEDENCE_OF_UPDATE_EXPR = precedence({ type: "UpdateExpression" });
@@ -402,6 +404,19 @@ module.exports = {
402
404
  if (isIIFE(node) && !isParenthesised(node.callee)) {
403
405
  return;
404
406
  }
407
+
408
+ if (ALLOW_PARENS_AFTER_COMMENT_PATTERN) {
409
+ const commentsBeforeLeftParenToken = sourceCode.getCommentsBefore(leftParenToken);
410
+ const totalCommentsBeforeLeftParenTokenCount = commentsBeforeLeftParenToken.length;
411
+ const ignorePattern = new RegExp(ALLOW_PARENS_AFTER_COMMENT_PATTERN, "u");
412
+
413
+ if (
414
+ totalCommentsBeforeLeftParenTokenCount > 0 &&
415
+ ignorePattern.test(commentsBeforeLeftParenToken[totalCommentsBeforeLeftParenTokenCount - 1].value)
416
+ ) {
417
+ return;
418
+ }
419
+ }
405
420
  }
406
421
 
407
422
  /**
@@ -71,6 +71,24 @@ function isMultiplyByOne(node) {
71
71
  );
72
72
  }
73
73
 
74
+ /**
75
+ * Checks whether the given node logically represents multiplication by a fraction of `1`.
76
+ * For example, `a * 1` in `a * 1 / b` is technically multiplication by `1`, but the
77
+ * whole expression can be logically interpreted as `a * (1 / b)` rather than `(a * 1) / b`.
78
+ * @param {BinaryExpression} node A BinaryExpression node to check.
79
+ * @param {SourceCode} sourceCode The source code object.
80
+ * @returns {boolean} Whether or not the node is a multiplying by a fraction of `1`.
81
+ */
82
+ function isMultiplyByFractionOfOne(node, sourceCode) {
83
+ return node.type === "BinaryExpression" &&
84
+ node.operator === "*" &&
85
+ (node.right.type === "Literal" && node.right.value === 1) &&
86
+ node.parent.type === "BinaryExpression" &&
87
+ node.parent.operator === "/" &&
88
+ node.parent.left === node &&
89
+ !astUtils.isParenthesised(sourceCode, node);
90
+ }
91
+
74
92
  /**
75
93
  * Checks whether the result of a node is numeric or not
76
94
  * @param {ASTNode} node The node to test
@@ -290,7 +308,8 @@ module.exports = {
290
308
 
291
309
  // 1 * foo
292
310
  operatorAllowed = options.allow.includes("*");
293
- const nonNumericOperand = !operatorAllowed && options.number && isMultiplyByOne(node) && getNonNumericOperand(node);
311
+ const nonNumericOperand = !operatorAllowed && options.number && isMultiplyByOne(node) && !isMultiplyByFractionOfOne(node, sourceCode) &&
312
+ getNonNumericOperand(node);
294
313
 
295
314
  if (nonNumericOperand) {
296
315
  const recommendation = `Number(${sourceCode.getText(nonNumericOperand)})`;
@@ -59,6 +59,20 @@ module.exports = {
59
59
  }
60
60
  }
61
61
 
62
+ /**
63
+ * Reports error with the provided message.
64
+ * @param {ASTNode} node The node holding the invalid RegExp
65
+ * @param {string} message The message to report.
66
+ * @returns {void}
67
+ */
68
+ function report(node, message) {
69
+ context.report({
70
+ node,
71
+ messageId: "regexMessage",
72
+ data: { message }
73
+ });
74
+ }
75
+
62
76
  /**
63
77
  * Check if node is a string
64
78
  * @param {ASTNode} node node to evaluate
@@ -108,10 +122,13 @@ module.exports = {
108
122
 
109
123
  /**
110
124
  * Check syntax error in a given flags.
111
- * @param {string} flags The RegExp flags to validate.
125
+ * @param {string|null} flags The RegExp flags to validate.
112
126
  * @returns {string|null} The syntax error.
113
127
  */
114
128
  function validateRegExpFlags(flags) {
129
+ if (!flags) {
130
+ return null;
131
+ }
115
132
  try {
116
133
  validator.validateFlags(flags);
117
134
  return null;
@@ -122,34 +139,39 @@ module.exports = {
122
139
 
123
140
  return {
124
141
  "CallExpression, NewExpression"(node) {
125
- if (node.callee.type !== "Identifier" || node.callee.name !== "RegExp" || !isString(node.arguments[0])) {
142
+ if (node.callee.type !== "Identifier" || node.callee.name !== "RegExp") {
126
143
  return;
127
144
  }
128
- const pattern = node.arguments[0].value;
145
+
129
146
  let flags = getFlags(node);
130
147
 
131
148
  if (flags && allowedFlags) {
132
149
  flags = flags.replace(allowedFlags, "");
133
150
  }
134
151
 
135
- const message =
136
- (
137
- flags && validateRegExpFlags(flags)
138
- ) ||
139
- (
152
+ let message = validateRegExpFlags(flags);
153
+
154
+ if (message) {
155
+ report(node, message);
156
+ return;
157
+ }
158
+
159
+ if (!isString(node.arguments[0])) {
160
+ return;
161
+ }
162
+
163
+ const pattern = node.arguments[0].value;
164
+
165
+ message = (
140
166
 
141
- // If flags are unknown, report the regex only if its pattern is invalid both with and without the "u" flag
142
- flags === null
143
- ? validateRegExpPattern(pattern, true) && validateRegExpPattern(pattern, false)
144
- : validateRegExpPattern(pattern, flags.includes("u"))
145
- );
167
+ // If flags are unknown, report the regex only if its pattern is invalid both with and without the "u" flag
168
+ flags === null
169
+ ? validateRegExpPattern(pattern, true) && validateRegExpPattern(pattern, false)
170
+ : validateRegExpPattern(pattern, flags.includes("u"))
171
+ );
146
172
 
147
173
  if (message) {
148
- context.report({
149
- node,
150
- messageId: "regexMessage",
151
- data: { message }
152
- });
174
+ report(node, message);
153
175
  }
154
176
  }
155
177
  };
@@ -65,6 +65,10 @@ module.exports = {
65
65
  ignoreDefaultValues: {
66
66
  type: "boolean",
67
67
  default: false
68
+ },
69
+ ignoreClassFieldInitialValues: {
70
+ type: "boolean",
71
+ default: false
68
72
  }
69
73
  },
70
74
  additionalProperties: false
@@ -82,7 +86,8 @@ module.exports = {
82
86
  enforceConst = !!config.enforceConst,
83
87
  ignore = new Set((config.ignore || []).map(normalizeIgnoreValue)),
84
88
  ignoreArrayIndexes = !!config.ignoreArrayIndexes,
85
- ignoreDefaultValues = !!config.ignoreDefaultValues;
89
+ ignoreDefaultValues = !!config.ignoreDefaultValues,
90
+ ignoreClassFieldInitialValues = !!config.ignoreClassFieldInitialValues;
86
91
 
87
92
  const okTypes = detectObjects ? [] : ["ObjectExpression", "Property", "AssignmentExpression"];
88
93
 
@@ -106,6 +111,17 @@ module.exports = {
106
111
  return parent.type === "AssignmentPattern" && parent.right === fullNumberNode;
107
112
  }
108
113
 
114
+ /**
115
+ * Returns whether the number is the initial value of a class field.
116
+ * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node
117
+ * @returns {boolean} true if the number is the initial value of a class field.
118
+ */
119
+ function isClassFieldInitialValue(fullNumberNode) {
120
+ const parent = fullNumberNode.parent;
121
+
122
+ return parent.type === "PropertyDefinition" && parent.value === fullNumberNode;
123
+ }
124
+
109
125
  /**
110
126
  * Returns whether the given node is used as a radix within parseInt() or Number.parseInt()
111
127
  * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node
@@ -194,6 +210,7 @@ module.exports = {
194
210
  if (
195
211
  isIgnoredValue(value) ||
196
212
  (ignoreDefaultValues && isDefaultValue(fullNumberNode)) ||
213
+ (ignoreClassFieldInitialValues && isClassFieldInitialValue(fullNumberNode)) ||
197
214
  isParseIntRadix(fullNumberNode) ||
198
215
  isJSXNumber(fullNumberNode) ||
199
216
  (ignoreArrayIndexes && isArrayIndex(fullNumberNode, value))
@@ -16,7 +16,7 @@ const getPropertyName = require("./utils/ast-utils").getStaticPropertyName;
16
16
  // Helpers
17
17
  //------------------------------------------------------------------------------
18
18
 
19
- const nonCallableGlobals = ["Atomics", "JSON", "Math", "Reflect"];
19
+ const nonCallableGlobals = ["Atomics", "JSON", "Math", "Reflect", "Intl"];
20
20
 
21
21
  /**
22
22
  * Returns the name of the node to report
@@ -23,6 +23,61 @@ const regexpp = require("regexpp");
23
23
 
24
24
  const parser = new regexpp.RegExpParser();
25
25
 
26
+ /**
27
+ * Creates fixer suggestions for the regex, if statically determinable.
28
+ * @param {number} groupStart Starting index of the regex group.
29
+ * @param {string} pattern The regular expression pattern to be checked.
30
+ * @param {string} rawText Source text of the regexNode.
31
+ * @param {ASTNode} regexNode AST node which contains the regular expression.
32
+ * @returns {Array<SuggestionResult>} Fixer suggestions for the regex, if statically determinable.
33
+ */
34
+ function suggestIfPossible(groupStart, pattern, rawText, regexNode) {
35
+ switch (regexNode.type) {
36
+ case "Literal":
37
+ if (typeof regexNode.value === "string" && rawText.includes("\\")) {
38
+ return null;
39
+ }
40
+ break;
41
+ case "TemplateLiteral":
42
+ if (regexNode.expressions.length || rawText.slice(1, -1) !== pattern) {
43
+ return null;
44
+ }
45
+ break;
46
+ default:
47
+ return null;
48
+ }
49
+
50
+ const start = regexNode.range[0] + groupStart + 2;
51
+
52
+ return [
53
+ {
54
+ fix(fixer) {
55
+ const existingTemps = pattern.match(/temp\d+/gu) || [];
56
+ const highestTempCount = existingTemps.reduce(
57
+ (previous, next) =>
58
+ Math.max(previous, Number(next.slice("temp".length))),
59
+ 0
60
+ );
61
+
62
+ return fixer.insertTextBeforeRange(
63
+ [start, start],
64
+ `?<temp${highestTempCount + 1}>`
65
+ );
66
+ },
67
+ messageId: "addGroupName"
68
+ },
69
+ {
70
+ fix(fixer) {
71
+ return fixer.insertTextBeforeRange(
72
+ [start, start],
73
+ "?:"
74
+ );
75
+ },
76
+ messageId: "addNonCapture"
77
+ }
78
+ ];
79
+ }
80
+
26
81
  //------------------------------------------------------------------------------
27
82
  // Rule Definition
28
83
  //------------------------------------------------------------------------------
@@ -38,23 +93,29 @@ module.exports = {
38
93
  url: "https://eslint.org/docs/rules/prefer-named-capture-group"
39
94
  },
40
95
 
96
+ hasSuggestions: true,
97
+
41
98
  schema: [],
42
99
 
43
100
  messages: {
101
+ addGroupName: "Add name to capture group.",
102
+ addNonCapture: "Convert group to non-capturing.",
44
103
  required: "Capture group '{{group}}' should be converted to a named or non-capturing group."
45
104
  }
46
105
  },
47
106
 
48
107
  create(context) {
108
+ const sourceCode = context.getSourceCode();
49
109
 
50
110
  /**
51
111
  * Function to check regular expression.
52
- * @param {string} pattern The regular expression pattern to be check.
53
- * @param {ASTNode} node AST node which contains regular expression.
112
+ * @param {string} pattern The regular expression pattern to be checked.
113
+ * @param {ASTNode} node AST node which contains the regular expression or a call/new expression.
114
+ * @param {ASTNode} regexNode AST node which contains the regular expression.
54
115
  * @param {boolean} uFlag Flag indicates whether unicode mode is enabled or not.
55
116
  * @returns {void}
56
117
  */
57
- function checkRegex(pattern, node, uFlag) {
118
+ function checkRegex(pattern, node, regexNode, uFlag) {
58
119
  let ast;
59
120
 
60
121
  try {
@@ -68,12 +129,16 @@ module.exports = {
68
129
  regexpp.visitRegExpAST(ast, {
69
130
  onCapturingGroupEnter(group) {
70
131
  if (!group.name) {
132
+ const rawText = sourceCode.getText(regexNode);
133
+ const suggest = suggestIfPossible(group.start, pattern, rawText, regexNode);
134
+
71
135
  context.report({
72
136
  node,
73
137
  messageId: "required",
74
138
  data: {
75
139
  group: group.raw
76
- }
140
+ },
141
+ suggest
77
142
  });
78
143
  }
79
144
  }
@@ -83,7 +148,7 @@ module.exports = {
83
148
  return {
84
149
  Literal(node) {
85
150
  if (node.regex) {
86
- checkRegex(node.regex.pattern, node, node.regex.flags.includes("u"));
151
+ checkRegex(node.regex.pattern, node, node, node.regex.flags.includes("u"));
87
152
  }
88
153
  },
89
154
  Program() {
@@ -101,7 +166,7 @@ module.exports = {
101
166
  const flags = getStringIfConstant(node.arguments[1]);
102
167
 
103
168
  if (regex) {
104
- checkRegex(regex, node, flags && flags.includes("u"));
169
+ checkRegex(regex, node, node.arguments[0], flags && flags.includes("u"));
105
170
  }
106
171
  }
107
172
  }
@@ -247,7 +247,7 @@ module.exports = {
247
247
 
248
248
  docs: {
249
249
  description:
250
- "Disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead.",
250
+ "Disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead",
251
251
  recommended: false,
252
252
  url: "https://eslint.org/docs/rules/prefer-object-spread"
253
253
  },
@@ -97,7 +97,7 @@ function environment() {
97
97
  */
98
98
  function getNpmPackageVersion(pkg, { global = false } = {}) {
99
99
  const npmBinArgs = ["bin", "-g"];
100
- const npmLsArgs = ["ls", "--depth=0", "--json", "eslint"];
100
+ const npmLsArgs = ["ls", "--depth=0", "--json", pkg];
101
101
 
102
102
  if (global) {
103
103
  npmLsArgs.push("-g");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint",
3
- "version": "8.27.0",
3
+ "version": "8.29.0",
4
4
  "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
5
5
  "description": "An AST-based pattern checker for JavaScript.",
6
6
  "bin": {