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.
@@ -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("../../../linter/config-comment-parser"),
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 { directivePart } = commentParser.extractDirectiveComment(comment.value);
895
+ const directive = commentParser.parseDirective(comment.value);
1006
896
 
1007
- const directiveMatch = directivesPattern.exec(directivePart);
897
+ if (!directive) {
898
+ return false;
899
+ }
1008
900
 
1009
- if (!directiveMatch) {
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(directiveMatch[1]);
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
- const directiveText = match[1];
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(directiveText);
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 (directiveText === "eslint-disable-line" && comment.loc.start.line !== comment.loc.end.line) {
1061
- const message = `${directiveText} comment should not span multiple lines.`;
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
- const directiveValue = directivePart.slice(match.index + directiveText.length);
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 = directiveText.slice("eslint-".length);
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: directiveValue,
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 { directiveText, directiveValue } = commentParser.parseDirective(comment);
1026
+ const { label, value } = commentParser.parseDirective(comment.value);
1140
1027
 
1141
- switch (directiveText) {
1028
+ switch (label) {
1142
1029
  case "exported":
1143
- Object.assign(exportedVariables, commentParser.parseListConfig(directiveValue, comment));
1030
+ Object.assign(exportedVariables, commentParser.parseListConfig(value));
1144
1031
  break;
1145
1032
 
1146
1033
  case "globals":
1147
1034
  case "global":
1148
- for (const [id, { value }] of Object.entries(commentParser.parseStringConfig(directiveValue, comment))) {
1035
+ for (const [id, idSetting] of Object.entries(commentParser.parseStringConfig(value))) {
1149
1036
  let normalizedValue;
1150
1037
 
1151
1038
  try {
1152
- normalizedValue = normalizeConfigGlobal(value);
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.parseJsonConfig(directiveValue);
1062
+ const parseResult = commentParser.parseJSONLikeConfig(value);
1176
1063
 
1177
- if (parseResult.success) {
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 TraversalStep({
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 TraversalStep({
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 TraversalStep({
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} node The backing Comment 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, node, sourceCode) {
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 = node.value
88
- .slice(listStartOffset) // remove directive name and all whitespace before the list
89
- .split(/\s-{2,}\s/u)[0] // remove `-- comment`, if it exists
90
- .trimEnd(); // remove all whitespace after the list
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 matchStartOffset = listStartOffset + match.index;
105
- const matchEndOffset = matchStartOffset + matchedText.length;
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 removalStartOffset, removalEndOffset;
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
- removalStartOffset = matchStartOffset + firstIndexOfComma;
127
- removalEndOffset = matchStartOffset + lastIndexOfComma;
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
- removalStartOffset = matchStartOffset;
150
- removalEndOffset = matchEndOffset;
138
+ removalStart = matchStart;
139
+ removalEnd = matchEnd;
151
140
  }
152
141
 
153
142
  return {
154
143
  description: `'${ruleId}'`,
155
144
  fix: {
156
145
  range: [
157
- commentValueStart + removalStartOffset,
158
- commentValueStart + removalEndOffset
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.node, sourceCode)
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;