eslint 9.9.0 → 9.10.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.
@@ -646,6 +646,23 @@ function createExtraneousResultsError() {
646
646
  return new TypeError("Results object was not created from this ESLint instance.");
647
647
  }
648
648
 
649
+ /**
650
+ * Creates a fixer function based on the provided fix, fixTypesSet, and config.
651
+ * @param {Function|boolean} fix The original fix option.
652
+ * @param {Set<string>} fixTypesSet A set of fix types to filter messages for fixing.
653
+ * @param {FlatConfig} config The config for the file that generated the message.
654
+ * @returns {Function|boolean} The fixer function or the original fix value.
655
+ */
656
+ function getFixerForFixTypes(fix, fixTypesSet, config) {
657
+ if (!fix || !fixTypesSet) {
658
+ return fix;
659
+ }
660
+
661
+ const originalFix = (typeof fix === "function") ? fix : () => true;
662
+
663
+ return message => shouldMessageBeFixed(message, config, fixTypesSet) && originalFix(message);
664
+ }
665
+
649
666
  //-----------------------------------------------------------------------------
650
667
  // Main API
651
668
  //-----------------------------------------------------------------------------
@@ -994,16 +1011,7 @@ class ESLint {
994
1011
 
995
1012
 
996
1013
  // set up fixer for fixTypes if necessary
997
- let fixer = fix;
998
-
999
- if (fix && fixTypesSet) {
1000
-
1001
- // save original value of options.fix in case it's a function
1002
- const originalFix = (typeof fix === "function")
1003
- ? fix : () => true;
1004
-
1005
- fixer = message => shouldMessageBeFixed(message, config, fixTypesSet) && originalFix(message);
1006
- }
1014
+ const fixer = getFixerForFixTypes(fix, fixTypesSet, config);
1007
1015
 
1008
1016
  return retrier.retry(() => fs.readFile(filePath, { encoding: "utf8", signal: controller.signal })
1009
1017
  .then(text => {
@@ -1108,13 +1116,18 @@ class ESLint {
1108
1116
  allowInlineConfig,
1109
1117
  cwd,
1110
1118
  fix,
1119
+ fixTypes,
1111
1120
  warnIgnored: constructorWarnIgnored,
1112
1121
  ruleFilter,
1113
1122
  stats
1114
1123
  } = eslintOptions;
1115
1124
  const results = [];
1116
1125
  const startTime = Date.now();
1126
+ const fixTypesSet = fixTypes ? new Set(fixTypes) : null;
1117
1127
  const resolvedFilename = path.resolve(cwd, filePath || "__placeholder__.js");
1128
+ const config = configs.getConfig(resolvedFilename);
1129
+
1130
+ const fixer = getFixerForFixTypes(fix, fixTypesSet, config);
1118
1131
 
1119
1132
  // Clear the last used config arrays.
1120
1133
  if (resolvedFilename && await this.isPathIgnored(resolvedFilename)) {
@@ -1133,7 +1146,7 @@ class ESLint {
1133
1146
  filePath: resolvedFilename.endsWith("__placeholder__.js") ? "<text>" : resolvedFilename,
1134
1147
  configs,
1135
1148
  cwd,
1136
- fix,
1149
+ fix: fixer,
1137
1150
  allowInlineConfig,
1138
1151
  ruleFilter,
1139
1152
  stats,
@@ -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 } = require("@eslint/plugin-kit"),
24
24
 
25
25
  eslintScope = require("eslint-scope");
26
26
 
@@ -316,65 +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
319
  /**
379
320
  * A class to represent a directive comment.
380
321
  * @implements {IDirective}
@@ -1002,16 +943,18 @@ class SourceCode extends TokenStore {
1002
943
  return false;
1003
944
  }
1004
945
 
1005
- const { directivePart } = commentParser.extractDirectiveComment(comment.value);
946
+ const directive = commentParser.parseDirective(comment.value);
1006
947
 
1007
- const directiveMatch = directivesPattern.exec(directivePart);
948
+ if (!directive) {
949
+ return false;
950
+ }
1008
951
 
1009
- if (!directiveMatch) {
952
+ if (!directivesPattern.test(directive.label)) {
1010
953
  return false;
1011
954
  }
1012
955
 
1013
956
  // only certain comment types are supported as line comments
1014
- return comment.type !== "Line" || !!/^eslint-disable-(next-)?line$/u.test(directiveMatch[1]);
957
+ return comment.type !== "Line" || !!/^eslint-disable-(next-)?line$/u.test(directive.label);
1015
958
  });
1016
959
 
1017
960
  this[caches].set("configNodes", configNodes);
@@ -1038,27 +981,24 @@ class SourceCode extends TokenStore {
1038
981
  const directives = [];
1039
982
 
1040
983
  this.getInlineConfigNodes().forEach(comment => {
1041
- const { directivePart, justificationPart } = commentParser.extractDirectiveComment(comment.value);
1042
984
 
1043
- // Step 1: Extract the directive text
1044
- const match = directivesPattern.exec(directivePart);
1045
-
1046
- if (!match) {
1047
- return;
1048
- }
1049
-
1050
- const directiveText = match[1];
985
+ // Step 1: Parse the directive
986
+ const {
987
+ label,
988
+ value,
989
+ justification: justificationPart
990
+ } = commentParser.parseDirective(comment.value);
1051
991
 
1052
992
  // Step 2: Extract the directive value
1053
- const lineCommentSupported = /^eslint-disable-(next-)?line$/u.test(directiveText);
993
+ const lineCommentSupported = /^eslint-disable-(next-)?line$/u.test(label);
1054
994
 
1055
995
  if (comment.type === "Line" && !lineCommentSupported) {
1056
996
  return;
1057
997
  }
1058
998
 
1059
999
  // 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.`;
1000
+ if (label === "eslint-disable-line" && comment.loc.start.line !== comment.loc.end.line) {
1001
+ const message = `${label} comment should not span multiple lines.`;
1062
1002
 
1063
1003
  problems.push({
1064
1004
  ruleId: null,
@@ -1069,19 +1009,17 @@ class SourceCode extends TokenStore {
1069
1009
  }
1070
1010
 
1071
1011
  // Step 4: Extract the directive value and create the Directive object
1072
- const directiveValue = directivePart.slice(match.index + directiveText.length);
1073
-
1074
- switch (directiveText) {
1012
+ switch (label) {
1075
1013
  case "eslint-disable":
1076
1014
  case "eslint-enable":
1077
1015
  case "eslint-disable-next-line":
1078
1016
  case "eslint-disable-line": {
1079
- const directiveType = directiveText.slice("eslint-".length);
1017
+ const directiveType = label.slice("eslint-".length);
1080
1018
 
1081
1019
  directives.push(new Directive({
1082
1020
  type: directiveType,
1083
1021
  node: comment,
1084
- value: directiveValue,
1022
+ value,
1085
1023
  justification: justificationPart
1086
1024
  }));
1087
1025
  }
@@ -1136,20 +1074,20 @@ class SourceCode extends TokenStore {
1136
1074
 
1137
1075
  this.getInlineConfigNodes().forEach(comment => {
1138
1076
 
1139
- const { directiveText, directiveValue } = commentParser.parseDirective(comment);
1077
+ const { label, value } = commentParser.parseDirective(comment.value);
1140
1078
 
1141
- switch (directiveText) {
1079
+ switch (label) {
1142
1080
  case "exported":
1143
- Object.assign(exportedVariables, commentParser.parseListConfig(directiveValue, comment));
1081
+ Object.assign(exportedVariables, commentParser.parseListConfig(value));
1144
1082
  break;
1145
1083
 
1146
1084
  case "globals":
1147
1085
  case "global":
1148
- for (const [id, { value }] of Object.entries(commentParser.parseStringConfig(directiveValue, comment))) {
1086
+ for (const [id, idSetting] of Object.entries(commentParser.parseStringConfig(value))) {
1149
1087
  let normalizedValue;
1150
1088
 
1151
1089
  try {
1152
- normalizedValue = normalizeConfigGlobal(value);
1090
+ normalizedValue = normalizeConfigGlobal(idSetting);
1153
1091
  } catch (err) {
1154
1092
  problems.push({
1155
1093
  ruleId: null,
@@ -1172,9 +1110,9 @@ class SourceCode extends TokenStore {
1172
1110
  break;
1173
1111
 
1174
1112
  case "eslint": {
1175
- const parseResult = commentParser.parseJsonConfig(directiveValue);
1113
+ const parseResult = commentParser.parseJSONLikeConfig(value);
1176
1114
 
1177
- if (parseResult.success) {
1115
+ if (parseResult.ok) {
1178
1116
  configs.push({
1179
1117
  config: {
1180
1118
  rules: parseResult.config
@@ -1251,16 +1189,14 @@ class SourceCode extends TokenStore {
1251
1189
  const emitter = createEmitter();
1252
1190
  let analyzer = {
1253
1191
  enterNode(node) {
1254
- steps.push(new TraversalStep({
1255
- type: "visit",
1192
+ steps.push(new VisitNodeStep({
1256
1193
  target: node,
1257
1194
  phase: 1,
1258
1195
  args: [node, node.parent]
1259
1196
  }));
1260
1197
  },
1261
1198
  leaveNode(node) {
1262
- steps.push(new TraversalStep({
1263
- type: "visit",
1199
+ steps.push(new VisitNodeStep({
1264
1200
  target: node,
1265
1201
  phase: 2,
1266
1202
  args: [node, node.parent]
@@ -1283,8 +1219,7 @@ class SourceCode extends TokenStore {
1283
1219
 
1284
1220
  CODE_PATH_EVENTS.forEach(eventName => {
1285
1221
  emitter.on(eventName, (...args) => {
1286
- steps.push(new TraversalStep({
1287
- type: "call",
1222
+ steps.push(new CallMethodStep({
1288
1223
  target: eventName,
1289
1224
  args
1290
1225
  }));
@@ -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;