eslint 8.22.0 → 8.33.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/README.md +51 -45
- package/bin/eslint.js +2 -4
- package/conf/globals.js +6 -1
- package/conf/rule-type-list.json +2 -2
- package/lib/cli-engine/file-enumerator.js +4 -2
- package/lib/cli-engine/formatters/formatters-meta.json +46 -0
- package/lib/cli-engine/formatters/html.js +76 -51
- package/lib/cli.js +163 -40
- package/lib/config/default-config.js +2 -2
- package/lib/config/flat-config-array.js +1 -1
- package/lib/eslint/eslint-helpers.js +409 -87
- package/lib/eslint/eslint.js +5 -2
- package/lib/eslint/flat-eslint.js +113 -110
- package/lib/linter/code-path-analysis/code-path-segment.js +2 -2
- package/lib/linter/code-path-analysis/code-path-state.js +7 -7
- package/lib/linter/code-path-analysis/debug-helpers.js +3 -3
- package/lib/linter/code-path-analysis/id-generator.js +2 -2
- package/lib/linter/config-comment-parser.js +1 -2
- package/lib/linter/linter.js +17 -7
- package/lib/linter/timing.js +4 -4
- package/lib/options.js +293 -239
- package/lib/rule-tester/flat-rule-tester.js +13 -11
- package/lib/rule-tester/rule-tester.js +15 -11
- package/lib/rules/array-callback-return.js +2 -2
- package/lib/rules/comma-dangle.js +3 -3
- package/lib/rules/for-direction.js +1 -1
- package/lib/rules/func-name-matching.js +2 -2
- package/lib/rules/getter-return.js +14 -8
- package/lib/rules/global-require.js +2 -1
- package/lib/rules/id-length.js +43 -2
- package/lib/rules/indent-legacy.js +4 -4
- package/lib/rules/indent.js +23 -15
- package/lib/rules/index.js +3 -0
- package/lib/rules/key-spacing.js +50 -38
- package/lib/rules/lines-around-comment.js +2 -2
- package/lib/rules/logical-assignment-operators.js +474 -0
- package/lib/rules/multiline-ternary.js +2 -2
- package/lib/rules/new-cap.js +2 -2
- package/lib/rules/no-else-return.js +1 -1
- package/lib/rules/no-empty-static-block.js +47 -0
- package/lib/rules/no-empty.js +19 -2
- package/lib/rules/no-extra-boolean-cast.js +1 -1
- package/lib/rules/no-extra-parens.js +18 -3
- package/lib/rules/no-fallthrough.js +26 -5
- package/lib/rules/no-implicit-coercion.js +20 -1
- package/lib/rules/no-implicit-globals.js +5 -0
- package/lib/rules/no-invalid-regexp.js +40 -18
- package/lib/rules/no-labels.js +1 -1
- package/lib/rules/no-lone-blocks.js +1 -1
- package/lib/rules/no-loss-of-precision.js +2 -2
- package/lib/rules/no-magic-numbers.js +18 -1
- package/lib/rules/no-misleading-character-class.js +4 -4
- package/lib/rules/no-new-native-nonconstructor.js +64 -0
- package/lib/rules/no-obj-calls.js +1 -1
- package/lib/rules/no-restricted-exports.js +106 -10
- package/lib/rules/no-return-await.js +28 -1
- package/lib/rules/no-underscore-dangle.js +36 -11
- package/lib/rules/no-unneeded-ternary.js +1 -1
- package/lib/rules/no-use-before-define.js +1 -1
- package/lib/rules/no-useless-computed-key.js +1 -1
- package/lib/rules/no-var.js +2 -2
- package/lib/rules/no-warning-comments.js +24 -5
- package/lib/rules/padded-blocks.js +1 -1
- package/lib/rules/prefer-arrow-callback.js +4 -3
- package/lib/rules/prefer-const.js +13 -1
- package/lib/rules/prefer-named-capture-group.js +71 -6
- package/lib/rules/prefer-object-spread.js +1 -1
- package/lib/rules/prefer-regex-literals.js +147 -32
- package/lib/rules/prefer-rest-params.js +1 -1
- package/lib/rules/require-yield.js +0 -1
- package/lib/rules/strict.js +1 -1
- package/lib/rules/utils/ast-utils.js +10 -4
- package/lib/shared/directives.js +15 -0
- package/lib/shared/logging.js +1 -1
- package/lib/shared/runtime-info.js +1 -1
- package/lib/shared/traverser.js +1 -1
- package/lib/shared/types.js +15 -2
- package/lib/source-code/token-store/cursor.js +1 -1
- package/messages/print-config-with-directory-path.js +1 -1
- package/package.json +27 -27
@@ -53,6 +53,14 @@ module.exports = {
|
|
53
53
|
enforceInClassFields: {
|
54
54
|
type: "boolean",
|
55
55
|
default: false
|
56
|
+
},
|
57
|
+
allowInArrayDestructuring: {
|
58
|
+
type: "boolean",
|
59
|
+
default: true
|
60
|
+
},
|
61
|
+
allowInObjectDestructuring: {
|
62
|
+
type: "boolean",
|
63
|
+
default: true
|
56
64
|
}
|
57
65
|
},
|
58
66
|
additionalProperties: false
|
@@ -74,6 +82,8 @@ module.exports = {
|
|
74
82
|
const enforceInMethodNames = typeof options.enforceInMethodNames !== "undefined" ? options.enforceInMethodNames : false;
|
75
83
|
const enforceInClassFields = typeof options.enforceInClassFields !== "undefined" ? options.enforceInClassFields : false;
|
76
84
|
const allowFunctionParams = typeof options.allowFunctionParams !== "undefined" ? options.allowFunctionParams : true;
|
85
|
+
const allowInArrayDestructuring = typeof options.allowInArrayDestructuring !== "undefined" ? options.allowInArrayDestructuring : true;
|
86
|
+
const allowInObjectDestructuring = typeof options.allowInObjectDestructuring !== "undefined" ? options.allowInObjectDestructuring : true;
|
77
87
|
|
78
88
|
//-------------------------------------------------------------------------
|
79
89
|
// Helpers
|
@@ -195,6 +205,7 @@ module.exports = {
|
|
195
205
|
checkForDanglingUnderscoreInFunctionParameters(node);
|
196
206
|
}
|
197
207
|
|
208
|
+
|
198
209
|
/**
|
199
210
|
* Check if variable expression has a dangling underscore
|
200
211
|
* @param {ASTNode} node node to evaluate
|
@@ -202,18 +213,32 @@ module.exports = {
|
|
202
213
|
* @private
|
203
214
|
*/
|
204
215
|
function checkForDanglingUnderscoreInVariableExpression(node) {
|
205
|
-
|
216
|
+
context.getDeclaredVariables(node).forEach(variable => {
|
217
|
+
const definition = variable.defs.find(def => def.node === node);
|
218
|
+
const identifierNode = definition.name;
|
219
|
+
const identifier = identifierNode.name;
|
220
|
+
let parent = identifierNode.parent;
|
221
|
+
|
222
|
+
while (!["VariableDeclarator", "ArrayPattern", "ObjectPattern"].includes(parent.type)) {
|
223
|
+
parent = parent.parent;
|
224
|
+
}
|
206
225
|
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
226
|
+
if (
|
227
|
+
hasDanglingUnderscore(identifier) &&
|
228
|
+
!isSpecialCaseIdentifierInVariableExpression(identifier) &&
|
229
|
+
!isAllowed(identifier) &&
|
230
|
+
!(allowInArrayDestructuring && parent.type === "ArrayPattern") &&
|
231
|
+
!(allowInObjectDestructuring && parent.type === "ObjectPattern")
|
232
|
+
) {
|
233
|
+
context.report({
|
234
|
+
node,
|
235
|
+
messageId: "unexpectedUnderscore",
|
236
|
+
data: {
|
237
|
+
identifier
|
238
|
+
}
|
239
|
+
});
|
240
|
+
}
|
241
|
+
});
|
217
242
|
}
|
218
243
|
|
219
244
|
/**
|
@@ -68,7 +68,7 @@ function isInClassStaticInitializerRange(node, location) {
|
|
68
68
|
}
|
69
69
|
|
70
70
|
/**
|
71
|
-
* Checks whether a given scope is the scope of a
|
71
|
+
* Checks whether a given scope is the scope of a class static initializer.
|
72
72
|
* Static initializers are static blocks and initializers of static fields.
|
73
73
|
* @param {eslint-scope.Scope} scope A scope to check.
|
74
74
|
* @returns {boolean} `true` if the scope is a class static initializer scope.
|
package/lib/rules/no-var.js
CHANGED
@@ -90,7 +90,7 @@ function getScopeNode(node) {
|
|
90
90
|
}
|
91
91
|
}
|
92
92
|
|
93
|
-
/*
|
93
|
+
/* c8 ignore next */
|
94
94
|
return null;
|
95
95
|
}
|
96
96
|
|
@@ -159,7 +159,7 @@ function hasReferenceInTDZ(node) {
|
|
159
159
|
return !reference.init && (
|
160
160
|
start < idStart ||
|
161
161
|
(defaultValue !== null && start >= defaultStart && end <= defaultEnd) ||
|
162
|
-
(start >= initStart && end <= initEnd)
|
162
|
+
(!astUtils.isFunction(node) && start >= initStart && end <= initEnd)
|
163
163
|
);
|
164
164
|
});
|
165
165
|
};
|
@@ -37,6 +37,15 @@ module.exports = {
|
|
37
37
|
},
|
38
38
|
location: {
|
39
39
|
enum: ["start", "anywhere"]
|
40
|
+
},
|
41
|
+
decoration: {
|
42
|
+
type: "array",
|
43
|
+
items: {
|
44
|
+
type: "string",
|
45
|
+
pattern: "^\\S$"
|
46
|
+
},
|
47
|
+
minItems: 1,
|
48
|
+
uniqueItems: true
|
40
49
|
}
|
41
50
|
},
|
42
51
|
additionalProperties: false
|
@@ -53,6 +62,7 @@ module.exports = {
|
|
53
62
|
configuration = context.options[0] || {},
|
54
63
|
warningTerms = configuration.terms || ["todo", "fixme", "xxx"],
|
55
64
|
location = configuration.location || "start",
|
65
|
+
decoration = [...configuration.decoration || []].join(""),
|
56
66
|
selfConfigRegEx = /\bno-warning-comments\b/u;
|
57
67
|
|
58
68
|
/**
|
@@ -64,6 +74,7 @@ module.exports = {
|
|
64
74
|
*/
|
65
75
|
function convertToRegExp(term) {
|
66
76
|
const escaped = escapeRegExp(term);
|
77
|
+
const escapedDecoration = escapeRegExp(decoration);
|
67
78
|
|
68
79
|
/*
|
69
80
|
* When matching at the start, ignore leading whitespace, and
|
@@ -74,18 +85,23 @@ module.exports = {
|
|
74
85
|
* e.g. terms ["TODO"] matches `//TODO something`
|
75
86
|
* $ handles any terms at the end of a comment
|
76
87
|
* e.g. terms ["TODO"] matches `// something TODO`
|
77
|
-
* \s* handles optional leading spaces (for "start" location only)
|
78
|
-
* e.g. terms ["TODO"] matches `// TODO something`
|
79
88
|
* \b handles terms preceded/followed by word boundary
|
80
89
|
* e.g. terms: ["!FIX", "FIX!"] matches `// FIX!something` or `// something!FIX`
|
81
90
|
* terms: ["FIX"] matches `// FIX!` or `// !FIX`, but not `// fixed or affix`
|
91
|
+
*
|
92
|
+
* For location start:
|
93
|
+
* [\s]* handles optional leading spaces
|
94
|
+
* e.g. terms ["TODO"] matches `// TODO something`
|
95
|
+
* [\s\*]* (where "\*" is the escaped string of decoration)
|
96
|
+
* handles optional leading spaces or decoration characters (for "start" location only)
|
97
|
+
* e.g. terms ["TODO"] matches `/**** TODO something ... `
|
82
98
|
*/
|
83
99
|
const wordBoundary = "\\b";
|
84
100
|
|
85
101
|
let prefix = "";
|
86
102
|
|
87
103
|
if (location === "start") {
|
88
|
-
prefix =
|
104
|
+
prefix = `^[\\s${escapedDecoration}]*`;
|
89
105
|
} else if (/^\w/u.test(term)) {
|
90
106
|
prefix = wordBoundary;
|
91
107
|
}
|
@@ -95,12 +111,15 @@ module.exports = {
|
|
95
111
|
|
96
112
|
/*
|
97
113
|
* For location "start", the typical regex is:
|
98
|
-
*
|
114
|
+
* /^[\s]*ESCAPED_TERM\b/iu.
|
115
|
+
* Or if decoration characters are specified (e.g. "*"), then any of
|
116
|
+
* those characters may appear in any order at the start:
|
117
|
+
* /^[\s\*]*ESCAPED_TERM\b/iu.
|
99
118
|
*
|
100
119
|
* For location "anywhere" the typical regex is
|
101
120
|
* /\bESCAPED_TERM\b/iu
|
102
121
|
*
|
103
|
-
* If it starts or ends with non-word character, the prefix and suffix empty, respectively.
|
122
|
+
* If it starts or ends with non-word character, the prefix and suffix are empty, respectively.
|
104
123
|
*/
|
105
124
|
return new RegExp(`${prefix}${escaped}${suffix}`, flags);
|
106
125
|
}
|
@@ -53,7 +53,7 @@ function getVariableOfArguments(scope) {
|
|
53
53
|
}
|
54
54
|
}
|
55
55
|
|
56
|
-
/*
|
56
|
+
/* c8 ignore next */
|
57
57
|
return null;
|
58
58
|
}
|
59
59
|
|
@@ -126,7 +126,7 @@ function getCallbackInfo(node) {
|
|
126
126
|
parent = parent.parent;
|
127
127
|
}
|
128
128
|
|
129
|
-
/*
|
129
|
+
/* c8 ignore next */
|
130
130
|
throw new Error("unreachable");
|
131
131
|
}
|
132
132
|
|
@@ -335,6 +335,7 @@ module.exports = {
|
|
335
335
|
// Convert the function expression to an arrow function.
|
336
336
|
const functionToken = sourceCode.getFirstToken(node, node.async ? 1 : 0);
|
337
337
|
const leftParenToken = sourceCode.getTokenAfter(functionToken, astUtils.isOpeningParenToken);
|
338
|
+
const tokenBeforeBody = sourceCode.getTokenBefore(node.body);
|
338
339
|
|
339
340
|
if (sourceCode.commentsExistBetween(functionToken, leftParenToken)) {
|
340
341
|
|
@@ -348,7 +349,7 @@ module.exports = {
|
|
348
349
|
// Remove extra tokens and spaces.
|
349
350
|
yield fixer.removeRange([functionToken.range[0], leftParenToken.range[0]]);
|
350
351
|
}
|
351
|
-
yield fixer.
|
352
|
+
yield fixer.insertTextAfter(tokenBeforeBody, " =>");
|
352
353
|
|
353
354
|
// Get the node that will become the new arrow function.
|
354
355
|
let replacedNode = callbackInfo.isLexicalThis ? node.parent.parent : node;
|
@@ -446,7 +446,19 @@ module.exports = {
|
|
446
446
|
|
447
447
|
reportCount += nodesToReport.length;
|
448
448
|
|
449
|
-
|
449
|
+
let totalDeclarationsCount = 0;
|
450
|
+
|
451
|
+
varDeclParent.declarations.forEach(declaration => {
|
452
|
+
if (declaration.id.type === "ObjectPattern") {
|
453
|
+
totalDeclarationsCount += declaration.id.properties.length;
|
454
|
+
} else if (declaration.id.type === "ArrayPattern") {
|
455
|
+
totalDeclarationsCount += declaration.id.elements.length;
|
456
|
+
} else {
|
457
|
+
totalDeclarationsCount += 1;
|
458
|
+
}
|
459
|
+
});
|
460
|
+
|
461
|
+
shouldFix = shouldFix && (reportCount === totalDeclarationsCount);
|
450
462
|
}
|
451
463
|
}
|
452
464
|
|
@@ -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
|
},
|
@@ -146,6 +146,8 @@ module.exports = {
|
|
146
146
|
messages: {
|
147
147
|
unexpectedRegExp: "Use a regular expression literal instead of the 'RegExp' constructor.",
|
148
148
|
replaceWithLiteral: "Replace with an equivalent regular expression literal.",
|
149
|
+
replaceWithLiteralAndFlags: "Replace with an equivalent regular expression literal with flags '{{ flags }}'.",
|
150
|
+
replaceWithIntendedLiteralAndFlags: "Replace with a regular expression literal with flags '{{ flags }}'.",
|
149
151
|
unexpectedRedundantRegExp: "Regular expression literal is unnecessarily wrapped within a 'RegExp' constructor.",
|
150
152
|
unexpectedRedundantRegExpWithFlags: "Use regular expression literal with flags instead of the 'RegExp' constructor."
|
151
153
|
}
|
@@ -248,16 +250,18 @@ module.exports = {
|
|
248
250
|
|
249
251
|
/**
|
250
252
|
* Returns a ecmaVersion compatible for regexpp.
|
251
|
-
* @param {
|
253
|
+
* @param {number} ecmaVersion The ecmaVersion to convert.
|
252
254
|
* @returns {import("regexpp/ecma-versions").EcmaVersion} The resulting ecmaVersion compatible for regexpp.
|
253
255
|
*/
|
254
256
|
function getRegexppEcmaVersion(ecmaVersion) {
|
255
|
-
if (
|
257
|
+
if (ecmaVersion <= 5) {
|
256
258
|
return 5;
|
257
259
|
}
|
258
|
-
return Math.min(ecmaVersion
|
260
|
+
return Math.min(ecmaVersion, REGEXPP_LATEST_ECMA_VERSION);
|
259
261
|
}
|
260
262
|
|
263
|
+
const regexppEcmaVersion = getRegexppEcmaVersion(context.languageOptions.ecmaVersion);
|
264
|
+
|
261
265
|
/**
|
262
266
|
* Makes a character escaped or else returns null.
|
263
267
|
* @param {string} character The character to escape.
|
@@ -293,6 +297,83 @@ module.exports = {
|
|
293
297
|
}
|
294
298
|
}
|
295
299
|
|
300
|
+
/**
|
301
|
+
* Checks whether the given regex and flags are valid for the ecma version or not.
|
302
|
+
* @param {string} pattern The regex pattern to check.
|
303
|
+
* @param {string | undefined} flags The regex flags to check.
|
304
|
+
* @returns {boolean} True if the given regex pattern and flags are valid for the ecma version.
|
305
|
+
*/
|
306
|
+
function isValidRegexForEcmaVersion(pattern, flags) {
|
307
|
+
const validator = new RegExpValidator({ ecmaVersion: regexppEcmaVersion });
|
308
|
+
|
309
|
+
try {
|
310
|
+
validator.validatePattern(pattern, 0, pattern.length, flags ? flags.includes("u") : false);
|
311
|
+
if (flags) {
|
312
|
+
validator.validateFlags(flags);
|
313
|
+
}
|
314
|
+
return true;
|
315
|
+
} catch {
|
316
|
+
return false;
|
317
|
+
}
|
318
|
+
}
|
319
|
+
|
320
|
+
/**
|
321
|
+
* Checks whether two given regex flags contain the same flags or not.
|
322
|
+
* @param {string} flagsA The regex flags.
|
323
|
+
* @param {string} flagsB The regex flags.
|
324
|
+
* @returns {boolean} True if two regex flags contain same flags.
|
325
|
+
*/
|
326
|
+
function areFlagsEqual(flagsA, flagsB) {
|
327
|
+
return [...flagsA].sort().join("") === [...flagsB].sort().join("");
|
328
|
+
}
|
329
|
+
|
330
|
+
|
331
|
+
/**
|
332
|
+
* Merges two regex flags.
|
333
|
+
* @param {string} flagsA The regex flags.
|
334
|
+
* @param {string} flagsB The regex flags.
|
335
|
+
* @returns {string} The merged regex flags.
|
336
|
+
*/
|
337
|
+
function mergeRegexFlags(flagsA, flagsB) {
|
338
|
+
const flagsSet = new Set([
|
339
|
+
...flagsA,
|
340
|
+
...flagsB
|
341
|
+
]);
|
342
|
+
|
343
|
+
return [...flagsSet].join("");
|
344
|
+
}
|
345
|
+
|
346
|
+
/**
|
347
|
+
* Checks whether a give node can be fixed to the given regex pattern and flags.
|
348
|
+
* @param {ASTNode} node The node to check.
|
349
|
+
* @param {string} pattern The regex pattern to check.
|
350
|
+
* @param {string} flags The regex flags
|
351
|
+
* @returns {boolean} True if a node can be fixed to the given regex pattern and flags.
|
352
|
+
*/
|
353
|
+
function canFixTo(node, pattern, flags) {
|
354
|
+
const tokenBefore = sourceCode.getTokenBefore(node);
|
355
|
+
|
356
|
+
return sourceCode.getCommentsInside(node).length === 0 &&
|
357
|
+
(!tokenBefore || validPrecedingTokens.has(tokenBefore.value)) &&
|
358
|
+
isValidRegexForEcmaVersion(pattern, flags);
|
359
|
+
}
|
360
|
+
|
361
|
+
/**
|
362
|
+
* Returns a safe output code considering the before and after tokens.
|
363
|
+
* @param {ASTNode} node The regex node.
|
364
|
+
* @param {string} newRegExpValue The new regex expression value.
|
365
|
+
* @returns {string} The output code.
|
366
|
+
*/
|
367
|
+
function getSafeOutput(node, newRegExpValue) {
|
368
|
+
const tokenBefore = sourceCode.getTokenBefore(node);
|
369
|
+
const tokenAfter = sourceCode.getTokenAfter(node);
|
370
|
+
|
371
|
+
return (tokenBefore && !canTokensBeAdjacent(tokenBefore, newRegExpValue) && tokenBefore.range[1] === node.range[0] ? " " : "") +
|
372
|
+
newRegExpValue +
|
373
|
+
(tokenAfter && !canTokensBeAdjacent(newRegExpValue, tokenAfter) && node.range[1] === tokenAfter.range[0] ? " " : "");
|
374
|
+
|
375
|
+
}
|
376
|
+
|
296
377
|
return {
|
297
378
|
Program() {
|
298
379
|
const scope = context.getScope();
|
@@ -306,10 +387,69 @@ module.exports = {
|
|
306
387
|
|
307
388
|
for (const { node } of tracker.iterateGlobalReferences(traceMap)) {
|
308
389
|
if (disallowRedundantWrapping && isUnnecessarilyWrappedRegexLiteral(node)) {
|
390
|
+
const regexNode = node.arguments[0];
|
391
|
+
|
309
392
|
if (node.arguments.length === 2) {
|
310
|
-
|
393
|
+
const suggests = [];
|
394
|
+
|
395
|
+
const argFlags = getStringValue(node.arguments[1]) || "";
|
396
|
+
|
397
|
+
if (canFixTo(node, regexNode.regex.pattern, argFlags)) {
|
398
|
+
suggests.push({
|
399
|
+
messageId: "replaceWithLiteralAndFlags",
|
400
|
+
pattern: regexNode.regex.pattern,
|
401
|
+
flags: argFlags
|
402
|
+
});
|
403
|
+
}
|
404
|
+
|
405
|
+
const literalFlags = regexNode.regex.flags || "";
|
406
|
+
const mergedFlags = mergeRegexFlags(literalFlags, argFlags);
|
407
|
+
|
408
|
+
if (
|
409
|
+
!areFlagsEqual(mergedFlags, argFlags) &&
|
410
|
+
canFixTo(node, regexNode.regex.pattern, mergedFlags)
|
411
|
+
) {
|
412
|
+
suggests.push({
|
413
|
+
messageId: "replaceWithIntendedLiteralAndFlags",
|
414
|
+
pattern: regexNode.regex.pattern,
|
415
|
+
flags: mergedFlags
|
416
|
+
});
|
417
|
+
}
|
418
|
+
|
419
|
+
context.report({
|
420
|
+
node,
|
421
|
+
messageId: "unexpectedRedundantRegExpWithFlags",
|
422
|
+
suggest: suggests.map(({ flags, pattern, messageId }) => ({
|
423
|
+
messageId,
|
424
|
+
data: {
|
425
|
+
flags
|
426
|
+
},
|
427
|
+
fix(fixer) {
|
428
|
+
return fixer.replaceText(node, getSafeOutput(node, `/${pattern}/${flags}`));
|
429
|
+
}
|
430
|
+
}))
|
431
|
+
});
|
311
432
|
} else {
|
312
|
-
|
433
|
+
const outputs = [];
|
434
|
+
|
435
|
+
if (canFixTo(node, regexNode.regex.pattern, regexNode.regex.flags)) {
|
436
|
+
outputs.push(sourceCode.getText(regexNode));
|
437
|
+
}
|
438
|
+
|
439
|
+
|
440
|
+
context.report({
|
441
|
+
node,
|
442
|
+
messageId: "unexpectedRedundantRegExp",
|
443
|
+
suggest: outputs.map(output => ({
|
444
|
+
messageId: "replaceWithLiteral",
|
445
|
+
fix(fixer) {
|
446
|
+
return fixer.replaceText(
|
447
|
+
node,
|
448
|
+
getSafeOutput(node, output)
|
449
|
+
);
|
450
|
+
}
|
451
|
+
}))
|
452
|
+
});
|
313
453
|
}
|
314
454
|
} else if (hasOnlyStaticStringArguments(node)) {
|
315
455
|
let regexContent = getStringValue(node.arguments[0]);
|
@@ -320,21 +460,7 @@ module.exports = {
|
|
320
460
|
flags = getStringValue(node.arguments[1]);
|
321
461
|
}
|
322
462
|
|
323
|
-
|
324
|
-
const RegExpValidatorInstance = new RegExpValidator({ ecmaVersion: regexppEcmaVersion });
|
325
|
-
|
326
|
-
try {
|
327
|
-
RegExpValidatorInstance.validatePattern(regexContent, 0, regexContent.length, flags ? flags.includes("u") : false);
|
328
|
-
if (flags) {
|
329
|
-
RegExpValidatorInstance.validateFlags(flags);
|
330
|
-
}
|
331
|
-
} catch {
|
332
|
-
noFix = true;
|
333
|
-
}
|
334
|
-
|
335
|
-
const tokenBefore = sourceCode.getTokenBefore(node);
|
336
|
-
|
337
|
-
if (tokenBefore && !validPrecedingTokens.has(tokenBefore.value)) {
|
463
|
+
if (!canFixTo(node, regexContent, flags)) {
|
338
464
|
noFix = true;
|
339
465
|
}
|
340
466
|
|
@@ -342,10 +468,6 @@ module.exports = {
|
|
342
468
|
noFix = true;
|
343
469
|
}
|
344
470
|
|
345
|
-
if (sourceCode.getCommentsInside(node).length > 0) {
|
346
|
-
noFix = true;
|
347
|
-
}
|
348
|
-
|
349
471
|
if (regexContent && !noFix) {
|
350
472
|
let charIncrease = 0;
|
351
473
|
|
@@ -377,14 +499,7 @@ module.exports = {
|
|
377
499
|
suggest: noFix ? [] : [{
|
378
500
|
messageId: "replaceWithLiteral",
|
379
501
|
fix(fixer) {
|
380
|
-
|
381
|
-
|
382
|
-
return fixer.replaceText(
|
383
|
-
node,
|
384
|
-
(tokenBefore && !canTokensBeAdjacent(tokenBefore, newRegExpValue) && tokenBefore.range[1] === node.range[0] ? " " : "") +
|
385
|
-
newRegExpValue +
|
386
|
-
(tokenAfter && !canTokensBeAdjacent(newRegExpValue, tokenAfter) && node.range[1] === tokenAfter.range[0] ? " " : "")
|
387
|
-
);
|
502
|
+
return fixer.replaceText(node, getSafeOutput(node, newRegExpValue));
|
388
503
|
}
|
389
504
|
}]
|
390
505
|
});
|