eslint 9.0.0-beta.1 → 9.0.0-rc.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 +19 -7
- package/bin/eslint.js +2 -1
- package/lib/cli.js +28 -11
- package/lib/config/flat-config-schema.js +1 -2
- package/lib/config/rule-validator.js +15 -2
- package/lib/eslint/eslint-helpers.js +0 -1
- package/lib/eslint/eslint.js +8 -1
- package/lib/linter/code-path-analysis/code-path-analyzer.js +0 -1
- package/lib/linter/index.js +1 -3
- package/lib/linter/linter.js +46 -31
- package/lib/rule-tester/index.js +3 -1
- package/lib/rules/complexity.js +13 -0
- package/lib/rules/constructor-super.js +53 -23
- package/lib/rules/no-fallthrough.js +41 -16
- package/lib/rules/no-misleading-character-class.js +110 -63
- package/lib/rules/no-restricted-imports.js +183 -47
- package/lib/rules/no-this-before-super.js +28 -9
- package/lib/rules/no-unused-vars.js +14 -1
- package/lib/rules/no-useless-return.js +7 -2
- package/lib/rules/utils/char-source.js +240 -0
- package/lib/rules/utils/lazy-loading-rule-map.js +1 -1
- package/lib/rules/utils/unicode/index.js +9 -4
- package/lib/shared/runtime-info.js +1 -0
- package/lib/source-code/index.js +3 -1
- package/lib/source-code/source-code.js +165 -1
- package/package.json +9 -6
- package/lib/cli-engine/xml-escape.js +0 -34
- package/lib/shared/deprecation-warnings.js +0 -58
@@ -3,11 +3,18 @@
|
|
3
3
|
*/
|
4
4
|
"use strict";
|
5
5
|
|
6
|
-
const {
|
6
|
+
const {
|
7
|
+
CALL,
|
8
|
+
CONSTRUCT,
|
9
|
+
ReferenceTracker,
|
10
|
+
getStaticValue,
|
11
|
+
getStringIfConstant
|
12
|
+
} = require("@eslint-community/eslint-utils");
|
7
13
|
const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp");
|
8
14
|
const { isCombiningCharacter, isEmojiModifier, isRegionalIndicatorSymbol, isSurrogatePair } = require("./utils/unicode");
|
9
15
|
const astUtils = require("./utils/ast-utils.js");
|
10
16
|
const { isValidWithUnicodeFlag } = require("./utils/regular-expressions");
|
17
|
+
const { parseStringLiteral, parseTemplateToken } = require("./utils/char-source");
|
11
18
|
|
12
19
|
//------------------------------------------------------------------------------
|
13
20
|
// Helpers
|
@@ -193,6 +200,33 @@ const findCharacterSequences = {
|
|
193
200
|
|
194
201
|
const kinds = Object.keys(findCharacterSequences);
|
195
202
|
|
203
|
+
/**
|
204
|
+
* Gets the value of the given node if it's a static value other than a regular expression object,
|
205
|
+
* or the node's `regex` property.
|
206
|
+
* The purpose of this method is to provide a replacement for `getStaticValue` in environments where certain regular expressions cannot be evaluated.
|
207
|
+
* A known example is Node.js 18 which does not support the `v` flag.
|
208
|
+
* Calling `getStaticValue` on a regular expression node with the `v` flag on Node.js 18 always returns `null`.
|
209
|
+
* A limitation of this method is that it can only detect a regular expression if the specified node is itself a regular expression literal node.
|
210
|
+
* @param {ASTNode | undefined} node The node to be inspected.
|
211
|
+
* @param {Scope} initialScope Scope to start finding variables. This function tries to resolve identifier references which are in the given scope.
|
212
|
+
* @returns {{ value: any } | { regex: { pattern: string, flags: string } } | null} The static value of the node, or `null`.
|
213
|
+
*/
|
214
|
+
function getStaticValueOrRegex(node, initialScope) {
|
215
|
+
if (!node) {
|
216
|
+
return null;
|
217
|
+
}
|
218
|
+
if (node.type === "Literal" && node.regex) {
|
219
|
+
return { regex: node.regex };
|
220
|
+
}
|
221
|
+
|
222
|
+
const staticValue = getStaticValue(node, initialScope);
|
223
|
+
|
224
|
+
if (staticValue?.value instanceof RegExp) {
|
225
|
+
return null;
|
226
|
+
}
|
227
|
+
return staticValue;
|
228
|
+
}
|
229
|
+
|
196
230
|
//------------------------------------------------------------------------------
|
197
231
|
// Rule Definition
|
198
232
|
//------------------------------------------------------------------------------
|
@@ -225,62 +259,7 @@ module.exports = {
|
|
225
259
|
create(context) {
|
226
260
|
const sourceCode = context.sourceCode;
|
227
261
|
const parser = new RegExpParser();
|
228
|
-
|
229
|
-
/**
|
230
|
-
* Generates a granular loc for context.report, if directly calculable.
|
231
|
-
* @param {Character[]} chars Individual characters being reported on.
|
232
|
-
* @param {Node} node Parent string node to report within.
|
233
|
-
* @returns {Object | null} Granular loc for context.report, if directly calculable.
|
234
|
-
* @see https://github.com/eslint/eslint/pull/17515
|
235
|
-
*/
|
236
|
-
function generateReportLocation(chars, node) {
|
237
|
-
|
238
|
-
// Limit to to literals and expression-less templates with raw values === their value.
|
239
|
-
switch (node.type) {
|
240
|
-
case "TemplateLiteral":
|
241
|
-
if (node.expressions.length || sourceCode.getText(node).slice(1, -1) !== node.quasis[0].value.cooked) {
|
242
|
-
return null;
|
243
|
-
}
|
244
|
-
break;
|
245
|
-
|
246
|
-
case "Literal":
|
247
|
-
if (typeof node.value === "string" && node.value !== node.raw.slice(1, -1)) {
|
248
|
-
return null;
|
249
|
-
}
|
250
|
-
break;
|
251
|
-
|
252
|
-
default:
|
253
|
-
return null;
|
254
|
-
}
|
255
|
-
|
256
|
-
return {
|
257
|
-
start: sourceCode.getLocFromIndex(node.range[0] + 1 + chars[0].start),
|
258
|
-
end: sourceCode.getLocFromIndex(node.range[0] + 1 + chars.at(-1).end)
|
259
|
-
};
|
260
|
-
}
|
261
|
-
|
262
|
-
/**
|
263
|
-
* Finds the report loc(s) for a range of matches.
|
264
|
-
* @param {Character[][]} matches Characters that should trigger a report.
|
265
|
-
* @param {Node} node The node to report.
|
266
|
-
* @returns {Object | null} Node loc(s) for context.report.
|
267
|
-
*/
|
268
|
-
function getNodeReportLocations(matches, node) {
|
269
|
-
const locs = [];
|
270
|
-
|
271
|
-
for (const chars of matches) {
|
272
|
-
const loc = generateReportLocation(chars, node);
|
273
|
-
|
274
|
-
// If a report can't match to a range, don't report any others
|
275
|
-
if (!loc) {
|
276
|
-
return [node.loc];
|
277
|
-
}
|
278
|
-
|
279
|
-
locs.push(loc);
|
280
|
-
}
|
281
|
-
|
282
|
-
return locs;
|
283
|
-
}
|
262
|
+
const checkedPatternNodes = new Set();
|
284
263
|
|
285
264
|
/**
|
286
265
|
* Verify a given regular expression.
|
@@ -320,12 +299,58 @@ module.exports = {
|
|
320
299
|
} else {
|
321
300
|
foundKindMatches.set(kind, [...findCharacterSequences[kind](chars)]);
|
322
301
|
}
|
323
|
-
|
324
302
|
}
|
325
303
|
}
|
326
304
|
}
|
327
305
|
});
|
328
306
|
|
307
|
+
let codeUnits = null;
|
308
|
+
|
309
|
+
/**
|
310
|
+
* Finds the report loc(s) for a range of matches.
|
311
|
+
* Only literals and expression-less templates generate granular errors.
|
312
|
+
* @param {Character[][]} matches Lists of individual characters being reported on.
|
313
|
+
* @returns {Location[]} locs for context.report.
|
314
|
+
* @see https://github.com/eslint/eslint/pull/17515
|
315
|
+
*/
|
316
|
+
function getNodeReportLocations(matches) {
|
317
|
+
if (!astUtils.isStaticTemplateLiteral(node) && node.type !== "Literal") {
|
318
|
+
return matches.length ? [node.loc] : [];
|
319
|
+
}
|
320
|
+
return matches.map(chars => {
|
321
|
+
const firstIndex = chars[0].start;
|
322
|
+
const lastIndex = chars.at(-1).end - 1;
|
323
|
+
let start;
|
324
|
+
let end;
|
325
|
+
|
326
|
+
if (node.type === "TemplateLiteral") {
|
327
|
+
const source = sourceCode.getText(node);
|
328
|
+
const offset = node.range[0];
|
329
|
+
|
330
|
+
codeUnits ??= parseTemplateToken(source);
|
331
|
+
start = offset + codeUnits[firstIndex].start;
|
332
|
+
end = offset + codeUnits[lastIndex].end;
|
333
|
+
} else if (typeof node.value === "string") { // String Literal
|
334
|
+
const source = node.raw;
|
335
|
+
const offset = node.range[0];
|
336
|
+
|
337
|
+
codeUnits ??= parseStringLiteral(source);
|
338
|
+
start = offset + codeUnits[firstIndex].start;
|
339
|
+
end = offset + codeUnits[lastIndex].end;
|
340
|
+
} else { // RegExp Literal
|
341
|
+
const offset = node.range[0] + 1; // Add 1 to skip the leading slash.
|
342
|
+
|
343
|
+
start = offset + firstIndex;
|
344
|
+
end = offset + lastIndex + 1;
|
345
|
+
}
|
346
|
+
|
347
|
+
return {
|
348
|
+
start: sourceCode.getLocFromIndex(start),
|
349
|
+
end: sourceCode.getLocFromIndex(end)
|
350
|
+
};
|
351
|
+
});
|
352
|
+
}
|
353
|
+
|
329
354
|
for (const [kind, matches] of foundKindMatches) {
|
330
355
|
let suggest;
|
331
356
|
|
@@ -336,7 +361,7 @@ module.exports = {
|
|
336
361
|
}];
|
337
362
|
}
|
338
363
|
|
339
|
-
const locs = getNodeReportLocations(matches
|
364
|
+
const locs = getNodeReportLocations(matches);
|
340
365
|
|
341
366
|
for (const loc of locs) {
|
342
367
|
context.report({
|
@@ -351,6 +376,9 @@ module.exports = {
|
|
351
376
|
|
352
377
|
return {
|
353
378
|
"Literal[regex]"(node) {
|
379
|
+
if (checkedPatternNodes.has(node)) {
|
380
|
+
return;
|
381
|
+
}
|
354
382
|
verify(node, node.regex.pattern, node.regex.flags, fixer => {
|
355
383
|
if (!isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, node.regex.pattern)) {
|
356
384
|
return null;
|
@@ -371,12 +399,31 @@ module.exports = {
|
|
371
399
|
for (const { node: refNode } of tracker.iterateGlobalReferences({
|
372
400
|
RegExp: { [CALL]: true, [CONSTRUCT]: true }
|
373
401
|
})) {
|
402
|
+
let pattern, flags;
|
374
403
|
const [patternNode, flagsNode] = refNode.arguments;
|
375
|
-
const
|
376
|
-
|
404
|
+
const evaluatedPattern = getStaticValueOrRegex(patternNode, scope);
|
405
|
+
|
406
|
+
if (!evaluatedPattern) {
|
407
|
+
continue;
|
408
|
+
}
|
409
|
+
if (flagsNode) {
|
410
|
+
if (evaluatedPattern.regex) {
|
411
|
+
pattern = evaluatedPattern.regex.pattern;
|
412
|
+
checkedPatternNodes.add(patternNode);
|
413
|
+
} else {
|
414
|
+
pattern = String(evaluatedPattern.value);
|
415
|
+
}
|
416
|
+
flags = getStringIfConstant(flagsNode, scope);
|
417
|
+
} else {
|
418
|
+
if (evaluatedPattern.regex) {
|
419
|
+
continue;
|
420
|
+
}
|
421
|
+
pattern = String(evaluatedPattern.value);
|
422
|
+
flags = "";
|
423
|
+
}
|
377
424
|
|
378
|
-
if (typeof
|
379
|
-
verify(patternNode, pattern, flags
|
425
|
+
if (typeof flags === "string") {
|
426
|
+
verify(patternNode, pattern, flags, fixer => {
|
380
427
|
|
381
428
|
if (!isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, pattern)) {
|
382
429
|
return null;
|
@@ -34,10 +34,17 @@ const arrayOfStringsOrObjects = {
|
|
34
34
|
items: {
|
35
35
|
type: "string"
|
36
36
|
}
|
37
|
+
},
|
38
|
+
allowImportNames: {
|
39
|
+
type: "array",
|
40
|
+
items: {
|
41
|
+
type: "string"
|
42
|
+
}
|
37
43
|
}
|
38
44
|
},
|
39
45
|
additionalProperties: false,
|
40
|
-
required: ["name"]
|
46
|
+
required: ["name"],
|
47
|
+
not: { required: ["importNames", "allowImportNames"] }
|
41
48
|
}
|
42
49
|
]
|
43
50
|
},
|
@@ -66,6 +73,14 @@ const arrayOfStringsOrObjectPatterns = {
|
|
66
73
|
minItems: 1,
|
67
74
|
uniqueItems: true
|
68
75
|
},
|
76
|
+
allowImportNames: {
|
77
|
+
type: "array",
|
78
|
+
items: {
|
79
|
+
type: "string"
|
80
|
+
},
|
81
|
+
minItems: 1,
|
82
|
+
uniqueItems: true
|
83
|
+
},
|
69
84
|
group: {
|
70
85
|
type: "array",
|
71
86
|
items: {
|
@@ -77,6 +92,9 @@ const arrayOfStringsOrObjectPatterns = {
|
|
77
92
|
importNamePattern: {
|
78
93
|
type: "string"
|
79
94
|
},
|
95
|
+
allowImportNamePattern: {
|
96
|
+
type: "string"
|
97
|
+
},
|
80
98
|
message: {
|
81
99
|
type: "string",
|
82
100
|
minLength: 1
|
@@ -86,7 +104,16 @@ const arrayOfStringsOrObjectPatterns = {
|
|
86
104
|
}
|
87
105
|
},
|
88
106
|
additionalProperties: false,
|
89
|
-
required: ["group"]
|
107
|
+
required: ["group"],
|
108
|
+
not: {
|
109
|
+
anyOf: [
|
110
|
+
{ required: ["importNames", "allowImportNames"] },
|
111
|
+
{ required: ["importNamePattern", "allowImportNamePattern"] },
|
112
|
+
{ required: ["importNames", "allowImportNamePattern"] },
|
113
|
+
{ required: ["importNamePattern", "allowImportNames"] },
|
114
|
+
{ required: ["allowImportNames", "allowImportNamePattern"] }
|
115
|
+
]
|
116
|
+
}
|
90
117
|
},
|
91
118
|
uniqueItems: true
|
92
119
|
}
|
@@ -131,7 +158,23 @@ module.exports = {
|
|
131
158
|
|
132
159
|
importName: "'{{importName}}' import from '{{importSource}}' is restricted.",
|
133
160
|
// eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period
|
134
|
-
importNameWithCustomMessage: "'{{importName}}' import from '{{importSource}}' is restricted. {{customMessage}}"
|
161
|
+
importNameWithCustomMessage: "'{{importName}}' import from '{{importSource}}' is restricted. {{customMessage}}",
|
162
|
+
|
163
|
+
allowedImportName: "'{{importName}}' import from '{{importSource}}' is restricted because only '{{allowedImportNames}}' import(s) is/are allowed.",
|
164
|
+
// eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period
|
165
|
+
allowedImportNameWithCustomMessage: "'{{importName}}' import from '{{importSource}}' is restricted because only '{{allowedImportNames}}' import(s) is/are allowed. {{customMessage}}",
|
166
|
+
|
167
|
+
everythingWithAllowImportNames: "* import is invalid because only '{{allowedImportNames}}' from '{{importSource}}' is/are allowed.",
|
168
|
+
// eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period
|
169
|
+
everythingWithAllowImportNamesAndCustomMessage: "* import is invalid because only '{{allowedImportNames}}' from '{{importSource}}' is/are allowed. {{customMessage}}",
|
170
|
+
|
171
|
+
allowedImportNamePattern: "'{{importName}}' import from '{{importSource}}' is restricted because only imports that match the pattern '{{allowedImportNamePattern}}' are allowed from '{{importSource}}'.",
|
172
|
+
// eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period
|
173
|
+
allowedImportNamePatternWithCustomMessage: "'{{importName}}' import from '{{importSource}}' is restricted because only imports that match the pattern '{{allowedImportNamePattern}}' are allowed from '{{importSource}}'. {{customMessage}}",
|
174
|
+
|
175
|
+
everythingWithAllowedImportNamePattern: "* import is invalid because only imports that match the pattern '{{allowedImportNamePattern}}' from '{{importSource}}' are allowed.",
|
176
|
+
// eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period
|
177
|
+
everythingWithAllowedImportNamePatternWithCustomMessage: "* import is invalid because only imports that match the pattern '{{allowedImportNamePattern}}' from '{{importSource}}' are allowed. {{customMessage}}"
|
135
178
|
},
|
136
179
|
|
137
180
|
schema: {
|
@@ -175,7 +218,8 @@ module.exports = {
|
|
175
218
|
} else {
|
176
219
|
memo[path].push({
|
177
220
|
message: importSource.message,
|
178
|
-
importNames: importSource.importNames
|
221
|
+
importNames: importSource.importNames,
|
222
|
+
allowImportNames: importSource.allowImportNames
|
179
223
|
});
|
180
224
|
}
|
181
225
|
return memo;
|
@@ -190,12 +234,18 @@ module.exports = {
|
|
190
234
|
}
|
191
235
|
|
192
236
|
// relative paths are supported for this rule
|
193
|
-
const restrictedPatternGroups = restrictedPatterns.map(
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
237
|
+
const restrictedPatternGroups = restrictedPatterns.map(
|
238
|
+
({ group, message, caseSensitive, importNames, importNamePattern, allowImportNames, allowImportNamePattern }) => (
|
239
|
+
{
|
240
|
+
matcher: ignore({ allowRelativePaths: true, ignorecase: !caseSensitive }).add(group),
|
241
|
+
customMessage: message,
|
242
|
+
importNames,
|
243
|
+
importNamePattern,
|
244
|
+
allowImportNames,
|
245
|
+
allowImportNamePattern
|
246
|
+
}
|
247
|
+
)
|
248
|
+
);
|
199
249
|
|
200
250
|
// if no imports are restricted we don't need to check
|
201
251
|
if (Object.keys(restrictedPaths).length === 0 && restrictedPatternGroups.length === 0) {
|
@@ -218,42 +268,9 @@ module.exports = {
|
|
218
268
|
groupedRestrictedPaths[importSource].forEach(restrictedPathEntry => {
|
219
269
|
const customMessage = restrictedPathEntry.message;
|
220
270
|
const restrictedImportNames = restrictedPathEntry.importNames;
|
271
|
+
const allowedImportNames = restrictedPathEntry.allowImportNames;
|
221
272
|
|
222
|
-
if (restrictedImportNames) {
|
223
|
-
if (importNames.has("*")) {
|
224
|
-
const specifierData = importNames.get("*")[0];
|
225
|
-
|
226
|
-
context.report({
|
227
|
-
node,
|
228
|
-
messageId: customMessage ? "everythingWithCustomMessage" : "everything",
|
229
|
-
loc: specifierData.loc,
|
230
|
-
data: {
|
231
|
-
importSource,
|
232
|
-
importNames: restrictedImportNames,
|
233
|
-
customMessage
|
234
|
-
}
|
235
|
-
});
|
236
|
-
}
|
237
|
-
|
238
|
-
restrictedImportNames.forEach(importName => {
|
239
|
-
if (importNames.has(importName)) {
|
240
|
-
const specifiers = importNames.get(importName);
|
241
|
-
|
242
|
-
specifiers.forEach(specifier => {
|
243
|
-
context.report({
|
244
|
-
node,
|
245
|
-
messageId: customMessage ? "importNameWithCustomMessage" : "importName",
|
246
|
-
loc: specifier.loc,
|
247
|
-
data: {
|
248
|
-
importSource,
|
249
|
-
customMessage,
|
250
|
-
importName
|
251
|
-
}
|
252
|
-
});
|
253
|
-
});
|
254
|
-
}
|
255
|
-
});
|
256
|
-
} else {
|
273
|
+
if (!restrictedImportNames && !allowedImportNames) {
|
257
274
|
context.report({
|
258
275
|
node,
|
259
276
|
messageId: customMessage ? "pathWithCustomMessage" : "path",
|
@@ -262,7 +279,72 @@ module.exports = {
|
|
262
279
|
customMessage
|
263
280
|
}
|
264
281
|
});
|
282
|
+
|
283
|
+
return;
|
265
284
|
}
|
285
|
+
|
286
|
+
importNames.forEach((specifiers, importName) => {
|
287
|
+
if (importName === "*") {
|
288
|
+
const [specifier] = specifiers;
|
289
|
+
|
290
|
+
if (restrictedImportNames) {
|
291
|
+
context.report({
|
292
|
+
node,
|
293
|
+
messageId: customMessage ? "everythingWithCustomMessage" : "everything",
|
294
|
+
loc: specifier.loc,
|
295
|
+
data: {
|
296
|
+
importSource,
|
297
|
+
importNames: restrictedImportNames,
|
298
|
+
customMessage
|
299
|
+
}
|
300
|
+
});
|
301
|
+
} else if (allowedImportNames) {
|
302
|
+
context.report({
|
303
|
+
node,
|
304
|
+
messageId: customMessage ? "everythingWithAllowImportNamesAndCustomMessage" : "everythingWithAllowImportNames",
|
305
|
+
loc: specifier.loc,
|
306
|
+
data: {
|
307
|
+
importSource,
|
308
|
+
allowedImportNames,
|
309
|
+
customMessage
|
310
|
+
}
|
311
|
+
});
|
312
|
+
}
|
313
|
+
|
314
|
+
return;
|
315
|
+
}
|
316
|
+
|
317
|
+
if (restrictedImportNames && restrictedImportNames.includes(importName)) {
|
318
|
+
specifiers.forEach(specifier => {
|
319
|
+
context.report({
|
320
|
+
node,
|
321
|
+
messageId: customMessage ? "importNameWithCustomMessage" : "importName",
|
322
|
+
loc: specifier.loc,
|
323
|
+
data: {
|
324
|
+
importSource,
|
325
|
+
customMessage,
|
326
|
+
importName
|
327
|
+
}
|
328
|
+
});
|
329
|
+
});
|
330
|
+
}
|
331
|
+
|
332
|
+
if (allowedImportNames && !allowedImportNames.includes(importName)) {
|
333
|
+
specifiers.forEach(specifier => {
|
334
|
+
context.report({
|
335
|
+
node,
|
336
|
+
loc: specifier.loc,
|
337
|
+
messageId: customMessage ? "allowedImportNameWithCustomMessage" : "allowedImportName",
|
338
|
+
data: {
|
339
|
+
importSource,
|
340
|
+
customMessage,
|
341
|
+
importName,
|
342
|
+
allowedImportNames
|
343
|
+
}
|
344
|
+
});
|
345
|
+
});
|
346
|
+
}
|
347
|
+
});
|
266
348
|
});
|
267
349
|
}
|
268
350
|
|
@@ -281,12 +363,14 @@ module.exports = {
|
|
281
363
|
const customMessage = group.customMessage;
|
282
364
|
const restrictedImportNames = group.importNames;
|
283
365
|
const restrictedImportNamePattern = group.importNamePattern ? new RegExp(group.importNamePattern, "u") : null;
|
366
|
+
const allowedImportNames = group.allowImportNames;
|
367
|
+
const allowedImportNamePattern = group.allowImportNamePattern ? new RegExp(group.allowImportNamePattern, "u") : null;
|
284
368
|
|
285
|
-
|
369
|
+
/**
|
286
370
|
* If we are not restricting to any specific import names and just the pattern itself,
|
287
371
|
* report the error and move on
|
288
372
|
*/
|
289
|
-
if (!restrictedImportNames && !restrictedImportNamePattern) {
|
373
|
+
if (!restrictedImportNames && !allowedImportNames && !restrictedImportNamePattern && !allowedImportNamePattern) {
|
290
374
|
context.report({
|
291
375
|
node,
|
292
376
|
messageId: customMessage ? "patternWithCustomMessage" : "patterns",
|
@@ -313,6 +397,28 @@ module.exports = {
|
|
313
397
|
customMessage
|
314
398
|
}
|
315
399
|
});
|
400
|
+
} else if (allowedImportNames) {
|
401
|
+
context.report({
|
402
|
+
node,
|
403
|
+
messageId: customMessage ? "everythingWithAllowImportNamesAndCustomMessage" : "everythingWithAllowImportNames",
|
404
|
+
loc: specifier.loc,
|
405
|
+
data: {
|
406
|
+
importSource,
|
407
|
+
allowedImportNames,
|
408
|
+
customMessage
|
409
|
+
}
|
410
|
+
});
|
411
|
+
} else if (allowedImportNamePattern) {
|
412
|
+
context.report({
|
413
|
+
node,
|
414
|
+
messageId: customMessage ? "everythingWithAllowedImportNamePatternWithCustomMessage" : "everythingWithAllowedImportNamePattern",
|
415
|
+
loc: specifier.loc,
|
416
|
+
data: {
|
417
|
+
importSource,
|
418
|
+
allowedImportNamePattern,
|
419
|
+
customMessage
|
420
|
+
}
|
421
|
+
});
|
316
422
|
} else {
|
317
423
|
context.report({
|
318
424
|
node,
|
@@ -346,6 +452,36 @@ module.exports = {
|
|
346
452
|
});
|
347
453
|
});
|
348
454
|
}
|
455
|
+
|
456
|
+
if (allowedImportNames && !allowedImportNames.includes(importName)) {
|
457
|
+
specifiers.forEach(specifier => {
|
458
|
+
context.report({
|
459
|
+
node,
|
460
|
+
messageId: customMessage ? "allowedImportNameWithCustomMessage" : "allowedImportName",
|
461
|
+
loc: specifier.loc,
|
462
|
+
data: {
|
463
|
+
importSource,
|
464
|
+
customMessage,
|
465
|
+
importName,
|
466
|
+
allowedImportNames
|
467
|
+
}
|
468
|
+
});
|
469
|
+
});
|
470
|
+
} else if (allowedImportNamePattern && !allowedImportNamePattern.test(importName)) {
|
471
|
+
specifiers.forEach(specifier => {
|
472
|
+
context.report({
|
473
|
+
node,
|
474
|
+
messageId: customMessage ? "allowedImportNamePatternWithCustomMessage" : "allowedImportNamePattern",
|
475
|
+
loc: specifier.loc,
|
476
|
+
data: {
|
477
|
+
importSource,
|
478
|
+
customMessage,
|
479
|
+
importName,
|
480
|
+
allowedImportNamePattern
|
481
|
+
}
|
482
|
+
});
|
483
|
+
});
|
484
|
+
}
|
349
485
|
});
|
350
486
|
}
|
351
487
|
|
@@ -30,6 +30,29 @@ function isConstructorFunction(node) {
|
|
30
30
|
);
|
31
31
|
}
|
32
32
|
|
33
|
+
/*
|
34
|
+
* Information for each code path segment.
|
35
|
+
* - superCalled: The flag which shows `super()` called in all code paths.
|
36
|
+
* - invalidNodes: The array of invalid ThisExpression and Super nodes.
|
37
|
+
*/
|
38
|
+
/**
|
39
|
+
*
|
40
|
+
*/
|
41
|
+
class SegmentInfo {
|
42
|
+
|
43
|
+
/**
|
44
|
+
* Indicates whether `super()` is called in all code paths.
|
45
|
+
* @type {boolean}
|
46
|
+
*/
|
47
|
+
superCalled = false;
|
48
|
+
|
49
|
+
/**
|
50
|
+
* The array of invalid ThisExpression and Super nodes.
|
51
|
+
* @type {ASTNode[]}
|
52
|
+
*/
|
53
|
+
invalidNodes = [];
|
54
|
+
}
|
55
|
+
|
33
56
|
//------------------------------------------------------------------------------
|
34
57
|
// Rule Definition
|
35
58
|
//------------------------------------------------------------------------------
|
@@ -64,13 +87,7 @@ module.exports = {
|
|
64
87
|
*/
|
65
88
|
let funcInfo = null;
|
66
89
|
|
67
|
-
|
68
|
-
* Information for each code path segment.
|
69
|
-
* Each key is the id of a code path segment.
|
70
|
-
* Each value is an object:
|
71
|
-
* - superCalled: The flag which shows `super()` called in all code paths.
|
72
|
-
* - invalidNodes: The array of invalid ThisExpression and Super nodes.
|
73
|
-
*/
|
90
|
+
/** @type {Record<string, SegmentInfo>} */
|
74
91
|
let segInfoMap = Object.create(null);
|
75
92
|
|
76
93
|
/**
|
@@ -79,7 +96,7 @@ module.exports = {
|
|
79
96
|
* @returns {boolean} `true` if `super()` is called.
|
80
97
|
*/
|
81
98
|
function isCalled(segment) {
|
82
|
-
return !segment.reachable || segInfoMap[segment.id]
|
99
|
+
return !segment.reachable || segInfoMap[segment.id]?.superCalled;
|
83
100
|
}
|
84
101
|
|
85
102
|
/**
|
@@ -285,7 +302,7 @@ module.exports = {
|
|
285
302
|
funcInfo.codePath.traverseSegments(
|
286
303
|
{ first: toSegment, last: fromSegment },
|
287
304
|
(segment, controller) => {
|
288
|
-
const info = segInfoMap[segment.id];
|
305
|
+
const info = segInfoMap[segment.id] ?? new SegmentInfo();
|
289
306
|
|
290
307
|
if (info.superCalled) {
|
291
308
|
controller.skip();
|
@@ -295,6 +312,8 @@ module.exports = {
|
|
295
312
|
) {
|
296
313
|
info.superCalled = true;
|
297
314
|
}
|
315
|
+
|
316
|
+
segInfoMap[segment.id] = info;
|
298
317
|
}
|
299
318
|
);
|
300
319
|
},
|
@@ -70,6 +70,9 @@ module.exports = {
|
|
70
70
|
},
|
71
71
|
destructuredArrayIgnorePattern: {
|
72
72
|
type: "string"
|
73
|
+
},
|
74
|
+
ignoreClassWithStaticInitBlock: {
|
75
|
+
type: "boolean"
|
73
76
|
}
|
74
77
|
},
|
75
78
|
additionalProperties: false
|
@@ -92,7 +95,8 @@ module.exports = {
|
|
92
95
|
vars: "all",
|
93
96
|
args: "after-used",
|
94
97
|
ignoreRestSiblings: false,
|
95
|
-
caughtErrors: "all"
|
98
|
+
caughtErrors: "all",
|
99
|
+
ignoreClassWithStaticInitBlock: false
|
96
100
|
};
|
97
101
|
|
98
102
|
const firstOption = context.options[0];
|
@@ -105,6 +109,7 @@ module.exports = {
|
|
105
109
|
config.args = firstOption.args || config.args;
|
106
110
|
config.ignoreRestSiblings = firstOption.ignoreRestSiblings || config.ignoreRestSiblings;
|
107
111
|
config.caughtErrors = firstOption.caughtErrors || config.caughtErrors;
|
112
|
+
config.ignoreClassWithStaticInitBlock = firstOption.ignoreClassWithStaticInitBlock || config.ignoreClassWithStaticInitBlock;
|
108
113
|
|
109
114
|
if (firstOption.varsIgnorePattern) {
|
110
115
|
config.varsIgnorePattern = new RegExp(firstOption.varsIgnorePattern, "u");
|
@@ -613,6 +618,14 @@ module.exports = {
|
|
613
618
|
continue;
|
614
619
|
}
|
615
620
|
|
621
|
+
if (type === "ClassName") {
|
622
|
+
const hasStaticBlock = def.node.body.body.some(node => node.type === "StaticBlock");
|
623
|
+
|
624
|
+
if (config.ignoreClassWithStaticInitBlock && hasStaticBlock) {
|
625
|
+
continue;
|
626
|
+
}
|
627
|
+
}
|
628
|
+
|
616
629
|
// skip catch variables
|
617
630
|
if (type === "CatchClause") {
|
618
631
|
if (config.caughtErrors === "none") {
|