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.
- package/lib/eslint/eslint-helpers.js +26 -31
- package/lib/rules/for-direction.js +1 -1
- package/lib/rules/key-spacing.js +16 -16
- package/lib/rules/no-extra-parens.js +16 -1
- package/lib/rules/no-implicit-coercion.js +20 -1
- package/lib/rules/no-invalid-regexp.js +40 -18
- package/lib/rules/no-magic-numbers.js +18 -1
- package/lib/rules/no-obj-calls.js +1 -1
- package/lib/rules/prefer-named-capture-group.js +71 -6
- package/lib/rules/prefer-object-spread.js +1 -1
- package/lib/shared/runtime-info.js +1 -1
- package/package.json +1 -1
@@ -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.
|
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
|
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
|
-
*
|
341
|
-
*
|
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.
|
350
|
-
*
|
351
|
-
* @returns {void}
|
352
|
-
* @throws {NoFilesFoundError} If
|
353
|
-
* any files
|
354
|
-
* @throws {AllFilesIgnoredError} If
|
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
|
357
|
+
async function throwErrorForUnmatchedPatterns({
|
358
358
|
basePath,
|
359
359
|
patterns,
|
360
360
|
rawPatterns,
|
361
|
-
|
361
|
+
unmatchedPatterns
|
362
362
|
}) {
|
363
363
|
|
364
|
-
|
364
|
+
const pattern = unmatchedPatterns[0];
|
365
|
+
const rawPattern = rawPatterns[patterns.indexOf(pattern)];
|
365
366
|
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
367
|
+
const patternHasMatch = await globMatch({
|
368
|
+
basePath,
|
369
|
+
pattern
|
370
|
+
});
|
370
371
|
|
371
|
-
|
372
|
-
|
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
|
444
|
+
await throwErrorForUnmatchedPatterns({
|
450
445
|
...currentSearch,
|
451
|
-
|
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
|
},
|
package/lib/rules/key-spacing.js
CHANGED
@@ -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
|
-
|
358
|
+
candidateValueStartLine = (isKeyValueProperty(candidate) ? candidate.value : candidate).loc.start.line;
|
346
359
|
|
347
|
-
if (
|
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
|
-
|
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) &&
|
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"
|
142
|
+
if (node.callee.type !== "Identifier" || node.callee.name !== "RegExp") {
|
126
143
|
return;
|
127
144
|
}
|
128
|
-
|
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
|
-
|
136
|
-
|
137
|
-
|
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
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
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
|
-
|
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
|
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",
|
100
|
+
const npmLsArgs = ["ls", "--depth=0", "--json", pkg];
|
101
101
|
|
102
102
|
if (global) {
|
103
103
|
npmLsArgs.push("-g");
|