eslint 9.9.1 → 9.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/lib/config/config.js +278 -0
- package/lib/config/flat-config-array.js +3 -204
- package/lib/config/flat-config-helpers.js +1 -5
- package/lib/languages/js/source-code/source-code.js +29 -145
- package/lib/linter/apply-disable-directives.js +17 -28
- package/lib/linter/file-context.js +134 -0
- package/lib/linter/linter.js +182 -150
- package/lib/linter/vfile.js +8 -1
- package/lib/rules/id-length.js +8 -0
- package/lib/rules/no-invalid-regexp.js +34 -18
- package/lib/rules/no-useless-constructor.js +18 -2
- package/lib/rules/require-unicode-regexp.js +95 -14
- package/lib/rules/utils/regular-expressions.js +11 -3
- package/lib/services/processor-service.js +109 -0
- package/lib/shared/types.js +1 -1
- package/lib/types/index.d.ts +1668 -0
- package/lib/types/rules/best-practices.d.ts +1083 -0
- package/lib/types/rules/deprecated.d.ts +294 -0
- package/lib/types/rules/ecmascript-6.d.ts +607 -0
- package/lib/types/rules/index.d.ts +50 -0
- package/lib/types/rules/node-commonjs.d.ts +160 -0
- package/lib/types/rules/possible-errors.d.ts +631 -0
- package/lib/types/rules/strict-mode.d.ts +38 -0
- package/lib/types/rules/stylistic-issues.d.ts +1942 -0
- package/lib/types/rules/variables.d.ts +221 -0
- package/lib/types/universal.d.ts +6 -0
- package/lib/types/use-at-your-own-risk.d.ts +85 -0
- package/lib/universal.js +10 -0
- package/package.json +29 -10
- package/lib/linter/config-comment-parser.js +0 -169
@@ -20,7 +20,7 @@ const
|
|
20
20
|
|
21
21
|
CodePathAnalyzer = require("../../../linter/code-path-analysis/code-path-analyzer"),
|
22
22
|
createEmitter = require("../../../linter/safe-emitter"),
|
23
|
-
ConfigCommentParser = require("
|
23
|
+
{ ConfigCommentParser, VisitNodeStep, CallMethodStep, Directive } = require("@eslint/plugin-kit"),
|
24
24
|
|
25
25
|
eslintScope = require("eslint-scope");
|
26
26
|
|
@@ -316,116 +316,6 @@ function markExportedVariables(globalScope, variables) {
|
|
316
316
|
|
317
317
|
}
|
318
318
|
|
319
|
-
const STEP_KIND = {
|
320
|
-
visit: 1,
|
321
|
-
call: 2
|
322
|
-
};
|
323
|
-
|
324
|
-
/**
|
325
|
-
* A class to represent a step in the traversal process.
|
326
|
-
*/
|
327
|
-
class TraversalStep {
|
328
|
-
|
329
|
-
/**
|
330
|
-
* The type of the step.
|
331
|
-
* @type {string}
|
332
|
-
*/
|
333
|
-
type;
|
334
|
-
|
335
|
-
/**
|
336
|
-
* The kind of the step. Represents the same data as the `type` property
|
337
|
-
* but it's a number for performance.
|
338
|
-
* @type {number}
|
339
|
-
*/
|
340
|
-
kind;
|
341
|
-
|
342
|
-
/**
|
343
|
-
* The target of the step.
|
344
|
-
* @type {ASTNode|string}
|
345
|
-
*/
|
346
|
-
target;
|
347
|
-
|
348
|
-
/**
|
349
|
-
* The phase of the step.
|
350
|
-
* @type {number|undefined}
|
351
|
-
*/
|
352
|
-
phase;
|
353
|
-
|
354
|
-
/**
|
355
|
-
* The arguments of the step.
|
356
|
-
* @type {Array<any>}
|
357
|
-
*/
|
358
|
-
args;
|
359
|
-
|
360
|
-
/**
|
361
|
-
* Creates a new instance.
|
362
|
-
* @param {Object} options The options for the step.
|
363
|
-
* @param {string} options.type The type of the step.
|
364
|
-
* @param {ASTNode|string} options.target The target of the step.
|
365
|
-
* @param {number|undefined} [options.phase] The phase of the step.
|
366
|
-
* @param {Array<any>} options.args The arguments of the step.
|
367
|
-
* @returns {void}
|
368
|
-
*/
|
369
|
-
constructor({ type, target, phase, args }) {
|
370
|
-
this.type = type;
|
371
|
-
this.kind = STEP_KIND[type];
|
372
|
-
this.target = target;
|
373
|
-
this.phase = phase;
|
374
|
-
this.args = args;
|
375
|
-
}
|
376
|
-
}
|
377
|
-
|
378
|
-
/**
|
379
|
-
* A class to represent a directive comment.
|
380
|
-
* @implements {IDirective}
|
381
|
-
*/
|
382
|
-
class Directive {
|
383
|
-
|
384
|
-
/**
|
385
|
-
* The type of directive.
|
386
|
-
* @type {"disable"|"enable"|"disable-next-line"|"disable-line"}
|
387
|
-
* @readonly
|
388
|
-
*/
|
389
|
-
type;
|
390
|
-
|
391
|
-
/**
|
392
|
-
* The node representing the directive.
|
393
|
-
* @type {ASTNode|Comment}
|
394
|
-
* @readonly
|
395
|
-
*/
|
396
|
-
node;
|
397
|
-
|
398
|
-
/**
|
399
|
-
* Everything after the "eslint-disable" portion of the directive,
|
400
|
-
* but before the "--" that indicates the justification.
|
401
|
-
* @type {string}
|
402
|
-
* @readonly
|
403
|
-
*/
|
404
|
-
value;
|
405
|
-
|
406
|
-
/**
|
407
|
-
* The justification for the directive.
|
408
|
-
* @type {string}
|
409
|
-
* @readonly
|
410
|
-
*/
|
411
|
-
justification;
|
412
|
-
|
413
|
-
/**
|
414
|
-
* Creates a new instance.
|
415
|
-
* @param {Object} options The options for the directive.
|
416
|
-
* @param {"disable"|"enable"|"disable-next-line"|"disable-line"} options.type The type of directive.
|
417
|
-
* @param {ASTNode|Comment} options.node The node representing the directive.
|
418
|
-
* @param {string} options.value The value of the directive.
|
419
|
-
* @param {string} options.justification The justification for the directive.
|
420
|
-
*/
|
421
|
-
constructor({ type, node, value, justification }) {
|
422
|
-
this.type = type;
|
423
|
-
this.node = node;
|
424
|
-
this.value = value;
|
425
|
-
this.justification = justification;
|
426
|
-
}
|
427
|
-
}
|
428
|
-
|
429
319
|
//------------------------------------------------------------------------------
|
430
320
|
// Public Interface
|
431
321
|
//------------------------------------------------------------------------------
|
@@ -1002,16 +892,18 @@ class SourceCode extends TokenStore {
|
|
1002
892
|
return false;
|
1003
893
|
}
|
1004
894
|
|
1005
|
-
const
|
895
|
+
const directive = commentParser.parseDirective(comment.value);
|
1006
896
|
|
1007
|
-
|
897
|
+
if (!directive) {
|
898
|
+
return false;
|
899
|
+
}
|
1008
900
|
|
1009
|
-
if (!
|
901
|
+
if (!directivesPattern.test(directive.label)) {
|
1010
902
|
return false;
|
1011
903
|
}
|
1012
904
|
|
1013
905
|
// only certain comment types are supported as line comments
|
1014
|
-
return comment.type !== "Line" || !!/^eslint-disable-(next-)?line$/u.test(
|
906
|
+
return comment.type !== "Line" || !!/^eslint-disable-(next-)?line$/u.test(directive.label);
|
1015
907
|
});
|
1016
908
|
|
1017
909
|
this[caches].set("configNodes", configNodes);
|
@@ -1038,27 +930,24 @@ class SourceCode extends TokenStore {
|
|
1038
930
|
const directives = [];
|
1039
931
|
|
1040
932
|
this.getInlineConfigNodes().forEach(comment => {
|
1041
|
-
const { directivePart, justificationPart } = commentParser.extractDirectiveComment(comment.value);
|
1042
|
-
|
1043
|
-
// Step 1: Extract the directive text
|
1044
|
-
const match = directivesPattern.exec(directivePart);
|
1045
|
-
|
1046
|
-
if (!match) {
|
1047
|
-
return;
|
1048
|
-
}
|
1049
933
|
|
1050
|
-
|
934
|
+
// Step 1: Parse the directive
|
935
|
+
const {
|
936
|
+
label,
|
937
|
+
value,
|
938
|
+
justification: justificationPart
|
939
|
+
} = commentParser.parseDirective(comment.value);
|
1051
940
|
|
1052
941
|
// Step 2: Extract the directive value
|
1053
|
-
const lineCommentSupported = /^eslint-disable-(next-)?line$/u.test(
|
942
|
+
const lineCommentSupported = /^eslint-disable-(next-)?line$/u.test(label);
|
1054
943
|
|
1055
944
|
if (comment.type === "Line" && !lineCommentSupported) {
|
1056
945
|
return;
|
1057
946
|
}
|
1058
947
|
|
1059
948
|
// Step 3: Validate the directive does not span multiple lines
|
1060
|
-
if (
|
1061
|
-
const message = `${
|
949
|
+
if (label === "eslint-disable-line" && comment.loc.start.line !== comment.loc.end.line) {
|
950
|
+
const message = `${label} comment should not span multiple lines.`;
|
1062
951
|
|
1063
952
|
problems.push({
|
1064
953
|
ruleId: null,
|
@@ -1069,19 +958,17 @@ class SourceCode extends TokenStore {
|
|
1069
958
|
}
|
1070
959
|
|
1071
960
|
// Step 4: Extract the directive value and create the Directive object
|
1072
|
-
|
1073
|
-
|
1074
|
-
switch (directiveText) {
|
961
|
+
switch (label) {
|
1075
962
|
case "eslint-disable":
|
1076
963
|
case "eslint-enable":
|
1077
964
|
case "eslint-disable-next-line":
|
1078
965
|
case "eslint-disable-line": {
|
1079
|
-
const directiveType =
|
966
|
+
const directiveType = label.slice("eslint-".length);
|
1080
967
|
|
1081
968
|
directives.push(new Directive({
|
1082
969
|
type: directiveType,
|
1083
970
|
node: comment,
|
1084
|
-
value
|
971
|
+
value,
|
1085
972
|
justification: justificationPart
|
1086
973
|
}));
|
1087
974
|
}
|
@@ -1136,20 +1023,20 @@ class SourceCode extends TokenStore {
|
|
1136
1023
|
|
1137
1024
|
this.getInlineConfigNodes().forEach(comment => {
|
1138
1025
|
|
1139
|
-
const {
|
1026
|
+
const { label, value } = commentParser.parseDirective(comment.value);
|
1140
1027
|
|
1141
|
-
switch (
|
1028
|
+
switch (label) {
|
1142
1029
|
case "exported":
|
1143
|
-
Object.assign(exportedVariables, commentParser.parseListConfig(
|
1030
|
+
Object.assign(exportedVariables, commentParser.parseListConfig(value));
|
1144
1031
|
break;
|
1145
1032
|
|
1146
1033
|
case "globals":
|
1147
1034
|
case "global":
|
1148
|
-
for (const [id,
|
1035
|
+
for (const [id, idSetting] of Object.entries(commentParser.parseStringConfig(value))) {
|
1149
1036
|
let normalizedValue;
|
1150
1037
|
|
1151
1038
|
try {
|
1152
|
-
normalizedValue = normalizeConfigGlobal(
|
1039
|
+
normalizedValue = normalizeConfigGlobal(idSetting);
|
1153
1040
|
} catch (err) {
|
1154
1041
|
problems.push({
|
1155
1042
|
ruleId: null,
|
@@ -1172,9 +1059,9 @@ class SourceCode extends TokenStore {
|
|
1172
1059
|
break;
|
1173
1060
|
|
1174
1061
|
case "eslint": {
|
1175
|
-
const parseResult = commentParser.
|
1062
|
+
const parseResult = commentParser.parseJSONLikeConfig(value);
|
1176
1063
|
|
1177
|
-
if (parseResult.
|
1064
|
+
if (parseResult.ok) {
|
1178
1065
|
configs.push({
|
1179
1066
|
config: {
|
1180
1067
|
rules: parseResult.config
|
@@ -1251,16 +1138,14 @@ class SourceCode extends TokenStore {
|
|
1251
1138
|
const emitter = createEmitter();
|
1252
1139
|
let analyzer = {
|
1253
1140
|
enterNode(node) {
|
1254
|
-
steps.push(new
|
1255
|
-
type: "visit",
|
1141
|
+
steps.push(new VisitNodeStep({
|
1256
1142
|
target: node,
|
1257
1143
|
phase: 1,
|
1258
1144
|
args: [node, node.parent]
|
1259
1145
|
}));
|
1260
1146
|
},
|
1261
1147
|
leaveNode(node) {
|
1262
|
-
steps.push(new
|
1263
|
-
type: "visit",
|
1148
|
+
steps.push(new VisitNodeStep({
|
1264
1149
|
target: node,
|
1265
1150
|
phase: 2,
|
1266
1151
|
args: [node, node.parent]
|
@@ -1283,8 +1168,7 @@ class SourceCode extends TokenStore {
|
|
1283
1168
|
|
1284
1169
|
CODE_PATH_EVENTS.forEach(eventName => {
|
1285
1170
|
emitter.on(eventName, (...args) => {
|
1286
|
-
steps.push(new
|
1287
|
-
type: "call",
|
1171
|
+
steps.push(new CallMethodStep({
|
1288
1172
|
target: eventName,
|
1289
1173
|
args
|
1290
1174
|
}));
|
@@ -60,34 +60,23 @@ function groupByParentDirective(directives) {
|
|
60
60
|
/**
|
61
61
|
* Creates removal details for a set of directives within the same comment.
|
62
62
|
* @param {Directive[]} directives Unused directives to be removed.
|
63
|
-
* @param {Token}
|
63
|
+
* @param {{node: Token, value: string}} parentDirective Data about the backing directive.
|
64
64
|
* @param {SourceCode} sourceCode The source code object for the file being linted.
|
65
65
|
* @returns {{ description, fix, unprocessedDirective }[]} Details for later creation of output Problems.
|
66
66
|
*/
|
67
|
-
function createIndividualDirectivesRemoval(directives,
|
68
|
-
|
69
|
-
const range = sourceCode.getRange(node);
|
70
|
-
|
71
|
-
/*
|
72
|
-
* `node.value` starts right after `//` or `/*`.
|
73
|
-
* All calculated offsets will be relative to this index.
|
74
|
-
*/
|
75
|
-
const commentValueStart = range[0] + "//".length;
|
76
|
-
|
77
|
-
// Find where the list of rules starts. `\S+` matches with the directive name (e.g. `eslint-disable-line`)
|
78
|
-
const listStartOffset = /^\s*\S+\s+/u.exec(node.value)[0].length;
|
67
|
+
function createIndividualDirectivesRemoval(directives, parentDirective, sourceCode) {
|
79
68
|
|
80
69
|
/*
|
81
|
-
* Get the list text without any surrounding whitespace. In order to preserve the original
|
70
|
+
* Get the list of the rules text without any surrounding whitespace. In order to preserve the original
|
82
71
|
* formatting, we don't want to change that whitespace.
|
83
72
|
*
|
84
73
|
* // eslint-disable-line rule-one , rule-two , rule-three -- comment
|
85
74
|
* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
86
75
|
*/
|
87
|
-
const listText =
|
88
|
-
|
89
|
-
|
90
|
-
|
76
|
+
const listText = parentDirective.value.trim();
|
77
|
+
|
78
|
+
// Calculate where it starts in the source code text
|
79
|
+
const listStart = sourceCode.text.indexOf(listText, sourceCode.getRange(parentDirective.node)[0]);
|
91
80
|
|
92
81
|
/*
|
93
82
|
* We can assume that `listText` contains multiple elements.
|
@@ -101,13 +90,13 @@ function createIndividualDirectivesRemoval(directives, node, sourceCode) {
|
|
101
90
|
const regex = new RegExp(String.raw`(?:^|\s*,\s*)(?<quote>['"]?)${escapeRegExp(ruleId)}\k<quote>(?:\s*,\s*|$)`, "u");
|
102
91
|
const match = regex.exec(listText);
|
103
92
|
const matchedText = match[0];
|
104
|
-
const
|
105
|
-
const
|
93
|
+
const matchStart = listStart + match.index;
|
94
|
+
const matchEnd = matchStart + matchedText.length;
|
106
95
|
|
107
96
|
const firstIndexOfComma = matchedText.indexOf(",");
|
108
97
|
const lastIndexOfComma = matchedText.lastIndexOf(",");
|
109
98
|
|
110
|
-
let
|
99
|
+
let removalStart, removalEnd;
|
111
100
|
|
112
101
|
if (firstIndexOfComma !== lastIndexOfComma) {
|
113
102
|
|
@@ -123,8 +112,8 @@ function createIndividualDirectivesRemoval(directives, node, sourceCode) {
|
|
123
112
|
* // eslint-disable-line rule-one , rule-two , rule-three -- comment
|
124
113
|
* ^^^^^^^^^^^
|
125
114
|
*/
|
126
|
-
|
127
|
-
|
115
|
+
removalStart = matchStart + firstIndexOfComma;
|
116
|
+
removalEnd = matchStart + lastIndexOfComma;
|
128
117
|
|
129
118
|
} else {
|
130
119
|
|
@@ -146,16 +135,16 @@ function createIndividualDirectivesRemoval(directives, node, sourceCode) {
|
|
146
135
|
* // eslint-disable-line rule-one , rule-two , rule-three -- comment
|
147
136
|
* ^^^^^^^^^^^^^
|
148
137
|
*/
|
149
|
-
|
150
|
-
|
138
|
+
removalStart = matchStart;
|
139
|
+
removalEnd = matchEnd;
|
151
140
|
}
|
152
141
|
|
153
142
|
return {
|
154
143
|
description: `'${ruleId}'`,
|
155
144
|
fix: {
|
156
145
|
range: [
|
157
|
-
|
158
|
-
|
146
|
+
removalStart,
|
147
|
+
removalEnd
|
159
148
|
],
|
160
149
|
text: ""
|
161
150
|
},
|
@@ -206,7 +195,7 @@ function processUnusedDirectives(allDirectives, sourceCode) {
|
|
206
195
|
}
|
207
196
|
|
208
197
|
return remainingRuleIds.size
|
209
|
-
? createIndividualDirectivesRemoval(directives, parentDirective
|
198
|
+
? createIndividualDirectivesRemoval(directives, parentDirective, sourceCode)
|
210
199
|
: [createDirectiveRemoval(directives, parentDirective.node, sourceCode)];
|
211
200
|
}
|
212
201
|
);
|
@@ -0,0 +1,134 @@
|
|
1
|
+
/**
|
2
|
+
* @fileoverview The FileContext class.
|
3
|
+
* @author Nicholas C. Zakas
|
4
|
+
*/
|
5
|
+
|
6
|
+
"use strict";
|
7
|
+
|
8
|
+
/**
|
9
|
+
* Represents a file context that the linter can use to lint a file.
|
10
|
+
*/
|
11
|
+
class FileContext {
|
12
|
+
|
13
|
+
/**
|
14
|
+
* The current working directory.
|
15
|
+
* @type {string}
|
16
|
+
*/
|
17
|
+
cwd;
|
18
|
+
|
19
|
+
/**
|
20
|
+
* The filename of the file being linted.
|
21
|
+
* @type {string}
|
22
|
+
*/
|
23
|
+
filename;
|
24
|
+
|
25
|
+
/**
|
26
|
+
* The physical filename of the file being linted.
|
27
|
+
* @type {string}
|
28
|
+
*/
|
29
|
+
physicalFilename;
|
30
|
+
|
31
|
+
/**
|
32
|
+
* The source code of the file being linted.
|
33
|
+
* @type {SourceCode}
|
34
|
+
*/
|
35
|
+
sourceCode;
|
36
|
+
|
37
|
+
/**
|
38
|
+
* The parser options for the file being linted.
|
39
|
+
* @type {Record<string, unknown>}
|
40
|
+
* @deprecated Use `languageOptions` instead.
|
41
|
+
*/
|
42
|
+
parserOptions;
|
43
|
+
|
44
|
+
/**
|
45
|
+
* The path to the parser used to parse this file.
|
46
|
+
* @type {string}
|
47
|
+
* @deprecated No longer supported.
|
48
|
+
*/
|
49
|
+
parserPath;
|
50
|
+
|
51
|
+
/**
|
52
|
+
* The language options used when parsing this file.
|
53
|
+
* @type {Record<string, unknown>}
|
54
|
+
*/
|
55
|
+
languageOptions;
|
56
|
+
|
57
|
+
/**
|
58
|
+
* The settings for the file being linted.
|
59
|
+
* @type {Record<string, unknown>}
|
60
|
+
*/
|
61
|
+
settings;
|
62
|
+
|
63
|
+
/**
|
64
|
+
* Creates a new instance.
|
65
|
+
* @param {Object} config The configuration object for the file context.
|
66
|
+
* @param {string} config.cwd The current working directory.
|
67
|
+
* @param {string} config.filename The filename of the file being linted.
|
68
|
+
* @param {string} config.physicalFilename The physical filename of the file being linted.
|
69
|
+
* @param {SourceCode} config.sourceCode The source code of the file being linted.
|
70
|
+
* @param {Record<string, unknown>} config.parserOptions The parser options for the file being linted.
|
71
|
+
* @param {string} config.parserPath The path to the parser used to parse this file.
|
72
|
+
* @param {Record<string, unknown>} config.languageOptions The language options used when parsing this file.
|
73
|
+
* @param {Record<string, unknown>} config.settings The settings for the file being linted.
|
74
|
+
*/
|
75
|
+
constructor({
|
76
|
+
cwd,
|
77
|
+
filename,
|
78
|
+
physicalFilename,
|
79
|
+
sourceCode,
|
80
|
+
parserOptions,
|
81
|
+
parserPath,
|
82
|
+
languageOptions,
|
83
|
+
settings
|
84
|
+
}) {
|
85
|
+
this.cwd = cwd;
|
86
|
+
this.filename = filename;
|
87
|
+
this.physicalFilename = physicalFilename;
|
88
|
+
this.sourceCode = sourceCode;
|
89
|
+
this.parserOptions = parserOptions;
|
90
|
+
this.parserPath = parserPath;
|
91
|
+
this.languageOptions = languageOptions;
|
92
|
+
this.settings = settings;
|
93
|
+
|
94
|
+
Object.freeze(this);
|
95
|
+
}
|
96
|
+
|
97
|
+
/**
|
98
|
+
* Gets the current working directory.
|
99
|
+
* @returns {string} The current working directory.
|
100
|
+
* @deprecated Use `cwd` instead.
|
101
|
+
*/
|
102
|
+
getCwd() {
|
103
|
+
return this.cwd;
|
104
|
+
}
|
105
|
+
|
106
|
+
/**
|
107
|
+
* Gets the filename of the file being linted.
|
108
|
+
* @returns {string} The filename of the file being linted.
|
109
|
+
* @deprecated Use `filename` instead.
|
110
|
+
*/
|
111
|
+
getFilename() {
|
112
|
+
return this.filename;
|
113
|
+
}
|
114
|
+
|
115
|
+
/**
|
116
|
+
* Gets the physical filename of the file being linted.
|
117
|
+
* @returns {string} The physical filename of the file being linted.
|
118
|
+
* @deprecated Use `physicalFilename` instead.
|
119
|
+
*/
|
120
|
+
getPhysicalFilename() {
|
121
|
+
return this.physicalFilename;
|
122
|
+
}
|
123
|
+
|
124
|
+
/**
|
125
|
+
* Gets the source code of the file being linted.
|
126
|
+
* @returns {SourceCode} The source code of the file being linted.
|
127
|
+
* @deprecated Use `sourceCode` instead.
|
128
|
+
*/
|
129
|
+
getSourceCode() {
|
130
|
+
return this.sourceCode;
|
131
|
+
}
|
132
|
+
}
|
133
|
+
|
134
|
+
exports.FileContext = FileContext;
|