eslint 9.0.0-beta.2 → 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 CHANGED
@@ -303,8 +303,8 @@ The following companies, organizations, and individuals support ESLint's ongoing
303
303
  <!-- NOTE: This section is autogenerated. Do not manually edit.-->
304
304
  <!--sponsorsstart-->
305
305
  <h3>Platinum Sponsors</h3>
306
- <p><a href="#"><img src="https://images.opencollective.com/2021-frameworks-fund/logo.png" alt="Chrome Frameworks Fund" height="undefined"></a> <a href="https://automattic.com"><img src="https://images.opencollective.com/automattic/d0ef3e1/logo.png" alt="Automattic" height="undefined"></a></p><h3>Gold Sponsors</h3>
307
- <p><a href="https://engineering.salesforce.com"><img src="https://images.opencollective.com/salesforce/ca8f997/logo.png" alt="Salesforce" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="96"></a></p><h3>Silver Sponsors</h3>
306
+ <p><a href="https://automattic.com"><img src="https://images.opencollective.com/automattic/d0ef3e1/logo.png" alt="Automattic" height="undefined"></a></p><h3>Gold Sponsors</h3>
307
+ <p><a href="https://bitwarden.com"><img src="https://avatars.githubusercontent.com/u/15990069?v=4" alt="Bitwarden" height="96"></a> <a href="https://engineering.salesforce.com"><img src="https://images.opencollective.com/salesforce/ca8f997/logo.png" alt="Salesforce" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="96"></a></p><h3>Silver Sponsors</h3>
308
308
  <p><a href="https://www.jetbrains.com/"><img src="https://images.opencollective.com/jetbrains/eb04ddc/logo.png" alt="JetBrains" height="64"></a> <a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/5c4fa84/logo.png" alt="Liftoff" height="64"></a> <a href="https://americanexpress.io"><img src="https://avatars.githubusercontent.com/u/3853301?v=4" alt="American Express" height="64"></a> <a href="https://www.workleap.com"><img src="https://avatars.githubusercontent.com/u/53535748?u=d1e55d7661d724bf2281c1bfd33cb8f99fe2465f&v=4" alt="Workleap" height="64"></a></p><h3>Bronze Sponsors</h3>
309
309
  <p><a href="https://www.notion.so"><img src="https://images.opencollective.com/notion/bf3b117/logo.png" alt="notion" height="32"></a> <a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://icons8.com/"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://transloadit.com/"><img src="https://avatars.githubusercontent.com/u/125754?v=4" alt="Transloadit" height="32"></a> <a href="https://www.ignitionapp.com"><img src="https://avatars.githubusercontent.com/u/5753491?v=4" alt="Ignition" height="32"></a> <a href="https://nx.dev"><img src="https://avatars.githubusercontent.com/u/23692104?v=4" alt="Nx" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774?v=4" alt="HeroCoders" height="32"></a> <a href="https://usenextbase.com"><img src="https://avatars.githubusercontent.com/u/145838380?v=4" alt="Nextbase Starter Kit" height="32"></a></p>
310
310
  <!--sponsorsend-->
package/bin/eslint.js CHANGED
@@ -149,7 +149,8 @@ ${getErrorMessage(error)}`;
149
149
  }
150
150
 
151
151
  // Otherwise, call the CLI.
152
- const exitCode = await require("../lib/cli").execute(
152
+ const cli = require("../lib/cli");
153
+ const exitCode = await cli.execute(
153
154
  process.argv,
154
155
  process.argv.includes("--stdin") ? await readStdin() : null,
155
156
  true
package/lib/cli.js CHANGED
@@ -37,6 +37,7 @@ const debug = require("debug")("eslint:cli");
37
37
  /** @typedef {import("./eslint/eslint").LintMessage} LintMessage */
38
38
  /** @typedef {import("./eslint/eslint").LintResult} LintResult */
39
39
  /** @typedef {import("./options").ParsedCLIOptions} ParsedCLIOptions */
40
+ /** @typedef {import("./shared/types").Plugin} Plugin */
40
41
  /** @typedef {import("./shared/types").ResultsMeta} ResultsMeta */
41
42
 
42
43
  //------------------------------------------------------------------------------
@@ -47,6 +48,32 @@ const mkdir = promisify(fs.mkdir);
47
48
  const stat = promisify(fs.stat);
48
49
  const writeFile = promisify(fs.writeFile);
49
50
 
51
+ /**
52
+ * Loads plugins with the specified names.
53
+ * @param {{ "import": (name: string) => Promise<any> }} importer An object with an `import` method called once for each plugin.
54
+ * @param {string[]} pluginNames The names of the plugins to be loaded, with or without the "eslint-plugin-" prefix.
55
+ * @returns {Promise<Record<string, Plugin>>} A mapping of plugin short names to implementations.
56
+ */
57
+ async function loadPlugins(importer, pluginNames) {
58
+ const plugins = {};
59
+
60
+ await Promise.all(pluginNames.map(async pluginName => {
61
+
62
+ const longName = naming.normalizePackageName(pluginName, "eslint-plugin");
63
+ const module = await importer.import(longName);
64
+
65
+ if (!("default" in module)) {
66
+ throw new Error(`"${longName}" cannot be used with the \`--plugin\` option because its default module does not provide a \`default\` export`);
67
+ }
68
+
69
+ const shortName = naming.getShorthandName(pluginName, "eslint-plugin");
70
+
71
+ plugins[shortName] = module.default;
72
+ }));
73
+
74
+ return plugins;
75
+ }
76
+
50
77
  /**
51
78
  * Predicate function for whether or not to apply fixes in quiet mode.
52
79
  * If a message is a warning, do not apply a fix.
@@ -152,17 +179,7 @@ async function translateOptions({
152
179
  }
153
180
 
154
181
  if (plugin) {
155
- const plugins = {};
156
-
157
- for (const pluginName of plugin) {
158
-
159
- const shortName = naming.getShorthandName(pluginName, "eslint-plugin");
160
- const longName = naming.normalizePackageName(pluginName, "eslint-plugin");
161
-
162
- plugins[shortName] = await importer.import(longName);
163
- }
164
-
165
- overrideConfig[0].plugins = plugins;
182
+ overrideConfig[0].plugins = await loadPlugins(importer, plugin);
166
183
  }
167
184
 
168
185
  } else {
@@ -588,6 +588,5 @@ const flatConfigSchema = {
588
588
 
589
589
  module.exports = {
590
590
  flatConfigSchema,
591
- assertIsRuleSeverity,
592
- assertIsRuleOptions
591
+ assertIsRuleSeverity
593
592
  };
@@ -907,7 +907,6 @@ function getCacheFile(cacheFile, cwd) {
907
907
  //-----------------------------------------------------------------------------
908
908
 
909
909
  module.exports = {
910
- isGlobPattern,
911
910
  findFiles,
912
911
 
913
912
  isNonEmptyString,
@@ -222,7 +222,6 @@ function forwardCurrentToHead(analyzer, node) {
222
222
  : "onUnreachableCodePathSegmentStart";
223
223
 
224
224
  debug.dump(`${eventName} ${headSegment.id}`);
225
-
226
225
  CodePathSegment.markUsed(headSegment);
227
226
  analyzer.emitter.emit(
228
227
  eventName,
@@ -1,13 +1,11 @@
1
1
  "use strict";
2
2
 
3
3
  const { Linter } = require("./linter");
4
- const { interpolate } = require("./interpolate");
5
4
  const SourceCodeFixer = require("./source-code-fixer");
6
5
 
7
6
  module.exports = {
8
7
  Linter,
9
8
 
10
9
  // For testers.
11
- SourceCodeFixer,
12
- interpolate
10
+ SourceCodeFixer
13
11
  };
@@ -30,7 +30,6 @@ const
30
30
  } = require("@eslint/eslintrc/universal"),
31
31
  Traverser = require("../shared/traverser"),
32
32
  { SourceCode } = require("../source-code"),
33
- CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"),
34
33
  applyDisableDirectives = require("./apply-disable-directives"),
35
34
  ConfigCommentParser = require("./config-comment-parser"),
36
35
  NodeEventGenerator = require("./node-event-generator"),
@@ -53,13 +52,13 @@ const commentParser = new ConfigCommentParser();
53
52
  const DEFAULT_ERROR_LOC = { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } };
54
53
  const parserSymbol = Symbol.for("eslint.RuleTester.parser");
55
54
  const { LATEST_ECMA_VERSION } = require("../../conf/ecma-version");
55
+ const STEP_KIND_VISIT = 1;
56
+ const STEP_KIND_CALL = 2;
56
57
 
57
58
  //------------------------------------------------------------------------------
58
59
  // Typedefs
59
60
  //------------------------------------------------------------------------------
60
61
 
61
- /** @typedef {InstanceType<import("../cli-engine/config-array").ConfigArray>} ConfigArray */
62
- /** @typedef {InstanceType<import("../cli-engine/config-array").ExtractedConfig>} ExtractedConfig */
63
62
  /** @typedef {import("../shared/types").ConfigData} ConfigData */
64
63
  /** @typedef {import("../shared/types").Environment} Environment */
65
64
  /** @typedef {import("../shared/types").GlobalConf} GlobalConf */
@@ -988,22 +987,13 @@ function createRuleListeners(rule, ruleContext) {
988
987
  * @param {string} physicalFilename The full path of the file on disk without any code block information
989
988
  * @param {Function} ruleFilter A predicate function to filter which rules should be executed.
990
989
  * @returns {LintMessage[]} An array of reported problems
990
+ * @throws {Error} If traversal into a node fails.
991
991
  */
992
992
  function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageOptions, settings, filename, disableFixes, cwd, physicalFilename, ruleFilter) {
993
993
  const emitter = createEmitter();
994
- const nodeQueue = [];
995
- let currentNode = sourceCode.ast;
996
-
997
- Traverser.traverse(sourceCode.ast, {
998
- enter(node, parent) {
999
- node.parent = parent;
1000
- nodeQueue.push({ isEntering: true, node });
1001
- },
1002
- leave(node) {
1003
- nodeQueue.push({ isEntering: false, node });
1004
- },
1005
- visitorKeys: sourceCode.visitorKeys
1006
- });
994
+
995
+ // must happen first to assign all node.parent properties
996
+ const eventQueue = sourceCode.traverse();
1007
997
 
1008
998
  /*
1009
999
  * Create a frozen object with the ruleContext properties and methods that are shared by all rules.
@@ -1133,25 +1123,34 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO
1133
1123
  });
1134
1124
  });
1135
1125
 
1136
- // only run code path analyzer if the top level node is "Program", skip otherwise
1137
- const eventGenerator = nodeQueue[0].node.type === "Program"
1138
- ? new CodePathAnalyzer(new NodeEventGenerator(emitter, { visitorKeys: sourceCode.visitorKeys, fallback: Traverser.getKeys }))
1139
- : new NodeEventGenerator(emitter, { visitorKeys: sourceCode.visitorKeys, fallback: Traverser.getKeys });
1126
+ const eventGenerator = new NodeEventGenerator(emitter, { visitorKeys: sourceCode.visitorKeys, fallback: Traverser.getKeys });
1140
1127
 
1141
- nodeQueue.forEach(traversalInfo => {
1142
- currentNode = traversalInfo.node;
1128
+ for (const step of eventQueue) {
1129
+ switch (step.kind) {
1130
+ case STEP_KIND_VISIT: {
1131
+ try {
1132
+ if (step.phase === 1) {
1133
+ eventGenerator.enterNode(step.target);
1134
+ } else {
1135
+ eventGenerator.leaveNode(step.target);
1136
+ }
1137
+ } catch (err) {
1138
+ err.currentNode = step.target;
1139
+ throw err;
1140
+ }
1141
+ break;
1142
+ }
1143
1143
 
1144
- try {
1145
- if (traversalInfo.isEntering) {
1146
- eventGenerator.enterNode(currentNode);
1147
- } else {
1148
- eventGenerator.leaveNode(currentNode);
1144
+ case STEP_KIND_CALL: {
1145
+ emitter.emit(step.target, ...step.args);
1146
+ break;
1149
1147
  }
1150
- } catch (err) {
1151
- err.currentNode = currentNode;
1152
- throw err;
1148
+
1149
+ default:
1150
+ throw new Error(`Invalid traversal step found: "${step.type}".`);
1153
1151
  }
1154
- });
1152
+
1153
+ }
1155
1154
 
1156
1155
  return lintingProblems;
1157
1156
  }
@@ -1,5 +1,7 @@
1
1
  "use strict";
2
2
 
3
+ const RuleTester = require("./rule-tester");
4
+
3
5
  module.exports = {
4
- RuleTester: require("./rule-tester")
6
+ RuleTester
5
7
  };
@@ -119,6 +119,30 @@ function isPossibleConstructor(node) {
119
119
  }
120
120
  }
121
121
 
122
+ /**
123
+ * A class to store information about a code path segment.
124
+ */
125
+ class SegmentInfo {
126
+
127
+ /**
128
+ * Indicates if super() is called in all code paths.
129
+ * @type {boolean}
130
+ */
131
+ calledInEveryPaths = false;
132
+
133
+ /**
134
+ * Indicates if super() is called in any code paths.
135
+ * @type {boolean}
136
+ */
137
+ calledInSomePaths = false;
138
+
139
+ /**
140
+ * The nodes which have been validated and don't need to be reconsidered.
141
+ * @type {ASTNode[]}
142
+ */
143
+ validNodes = [];
144
+ }
145
+
122
146
  //------------------------------------------------------------------------------
123
147
  // Rule Definition
124
148
  //------------------------------------------------------------------------------
@@ -159,12 +183,8 @@ module.exports = {
159
183
  */
160
184
  let funcInfo = null;
161
185
 
162
- /*
163
- * {Map<string, {calledInSomePaths: boolean, calledInEveryPaths: boolean}>}
164
- * Information for each code path segment.
165
- * - calledInSomePaths: A flag of be called `super()` in some code paths.
166
- * - calledInEveryPaths: A flag of be called `super()` in all code paths.
167
- * - validNodes:
186
+ /**
187
+ * @type {Record<string, SegmentInfo>}
168
188
  */
169
189
  let segInfoMap = Object.create(null);
170
190
 
@@ -174,7 +194,16 @@ module.exports = {
174
194
  * @returns {boolean} The flag which shows `super()` is called in some paths
175
195
  */
176
196
  function isCalledInSomePath(segment) {
177
- return segment.reachable && segInfoMap[segment.id].calledInSomePaths;
197
+ return segment.reachable && segInfoMap[segment.id]?.calledInSomePaths;
198
+ }
199
+
200
+ /**
201
+ * Determines if a segment has been seen in the traversal.
202
+ * @param {CodePathSegment} segment A code path segment to check.
203
+ * @returns {boolean} `true` if the segment has been seen.
204
+ */
205
+ function hasSegmentBeenSeen(segment) {
206
+ return !!segInfoMap[segment.id];
178
207
  }
179
208
 
180
209
  /**
@@ -190,10 +219,10 @@ module.exports = {
190
219
  * If not skipped, this never becomes true after a loop.
191
220
  */
192
221
  if (segment.nextSegments.length === 1 &&
193
- segment.nextSegments[0].isLoopedPrevSegment(segment)
194
- ) {
222
+ segment.nextSegments[0]?.isLoopedPrevSegment(segment)) {
195
223
  return true;
196
224
  }
225
+
197
226
  return segment.reachable && segInfoMap[segment.id].calledInEveryPaths;
198
227
  }
199
228
 
@@ -250,9 +279,9 @@ module.exports = {
250
279
  }
251
280
 
252
281
  // Reports if `super()` lacked.
253
- const segments = codePath.returnedSegments;
254
- const calledInEveryPaths = segments.every(isCalledInEveryPath);
255
- const calledInSomePaths = segments.some(isCalledInSomePath);
282
+ const seenSegments = codePath.returnedSegments.filter(hasSegmentBeenSeen);
283
+ const calledInEveryPaths = seenSegments.every(isCalledInEveryPath);
284
+ const calledInSomePaths = seenSegments.some(isCalledInSomePath);
256
285
 
257
286
  if (!calledInEveryPaths) {
258
287
  context.report({
@@ -278,18 +307,16 @@ module.exports = {
278
307
  }
279
308
 
280
309
  // Initialize info.
281
- const info = segInfoMap[segment.id] = {
282
- calledInSomePaths: false,
283
- calledInEveryPaths: false,
284
- validNodes: []
285
- };
310
+ const info = segInfoMap[segment.id] = new SegmentInfo();
286
311
 
287
312
  // When there are previous segments, aggregates these.
288
313
  const prevSegments = segment.prevSegments;
289
314
 
290
315
  if (prevSegments.length > 0) {
291
- info.calledInSomePaths = prevSegments.some(isCalledInSomePath);
292
- info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath);
316
+ const seenPrevSegments = prevSegments.filter(hasSegmentBeenSeen);
317
+
318
+ info.calledInSomePaths = seenPrevSegments.some(isCalledInSomePath);
319
+ info.calledInEveryPaths = seenPrevSegments.every(isCalledInEveryPath);
293
320
  }
294
321
  },
295
322
 
@@ -326,12 +353,12 @@ module.exports = {
326
353
  funcInfo.codePath.traverseSegments(
327
354
  { first: toSegment, last: fromSegment },
328
355
  segment => {
329
- const info = segInfoMap[segment.id];
330
- const prevSegments = segment.prevSegments;
356
+ const info = segInfoMap[segment.id] ?? new SegmentInfo();
357
+ const seenPrevSegments = segment.prevSegments.filter(hasSegmentBeenSeen);
331
358
 
332
359
  // Updates flags.
333
- info.calledInSomePaths = prevSegments.some(isCalledInSomePath);
334
- info.calledInEveryPaths = prevSegments.every(isCalledInEveryPath);
360
+ info.calledInSomePaths = seenPrevSegments.some(isCalledInSomePath);
361
+ info.calledInEveryPaths = seenPrevSegments.every(isCalledInEveryPath);
335
362
 
336
363
  // If flags become true anew, reports the valid nodes.
337
364
  if (info.calledInSomePaths || isRealLoop) {
@@ -348,6 +375,9 @@ module.exports = {
348
375
  });
349
376
  }
350
377
  }
378
+
379
+ // save just in case we created a new SegmentInfo object
380
+ segInfoMap[segment.id] = info;
351
381
  }
352
382
  );
353
383
  },
@@ -48,9 +48,9 @@ function isFallThroughComment(comment, fallthroughCommentPattern) {
48
48
  * @param {ASTNode} subsequentCase The case after caseWhichFallsThrough.
49
49
  * @param {RuleContext} context A rule context which stores comments.
50
50
  * @param {RegExp} fallthroughCommentPattern A pattern to match comment to.
51
- * @returns {boolean} `true` if the case has a valid fallthrough comment.
51
+ * @returns {null | object} the comment if the case has a valid fallthrough comment, otherwise null
52
52
  */
53
- function hasFallthroughComment(caseWhichFallsThrough, subsequentCase, context, fallthroughCommentPattern) {
53
+ function getFallthroughComment(caseWhichFallsThrough, subsequentCase, context, fallthroughCommentPattern) {
54
54
  const sourceCode = context.sourceCode;
55
55
 
56
56
  if (caseWhichFallsThrough.consequent.length === 1 && caseWhichFallsThrough.consequent[0].type === "BlockStatement") {
@@ -58,13 +58,17 @@ function hasFallthroughComment(caseWhichFallsThrough, subsequentCase, context, f
58
58
  const commentInBlock = sourceCode.getCommentsBefore(trailingCloseBrace).pop();
59
59
 
60
60
  if (commentInBlock && isFallThroughComment(commentInBlock.value, fallthroughCommentPattern)) {
61
- return true;
61
+ return commentInBlock;
62
62
  }
63
63
  }
64
64
 
65
65
  const comment = sourceCode.getCommentsBefore(subsequentCase).pop();
66
66
 
67
- return Boolean(comment && isFallThroughComment(comment.value, fallthroughCommentPattern));
67
+ if (comment && isFallThroughComment(comment.value, fallthroughCommentPattern)) {
68
+ return comment;
69
+ }
70
+
71
+ return null;
68
72
  }
69
73
 
70
74
  /**
@@ -103,12 +107,17 @@ module.exports = {
103
107
  allowEmptyCase: {
104
108
  type: "boolean",
105
109
  default: false
110
+ },
111
+ reportUnusedFallthroughComment: {
112
+ type: "boolean",
113
+ default: false
106
114
  }
107
115
  },
108
116
  additionalProperties: false
109
117
  }
110
118
  ],
111
119
  messages: {
120
+ unusedFallthroughComment: "Found a comment that would permit fallthrough, but case cannot fall through.",
112
121
  case: "Expected a 'break' statement before 'case'.",
113
122
  default: "Expected a 'break' statement before 'default'."
114
123
  }
@@ -120,12 +129,13 @@ module.exports = {
120
129
  let currentCodePathSegments = new Set();
121
130
  const sourceCode = context.sourceCode;
122
131
  const allowEmptyCase = options.allowEmptyCase || false;
132
+ const reportUnusedFallthroughComment = options.reportUnusedFallthroughComment || false;
123
133
 
124
134
  /*
125
135
  * We need to use leading comments of the next SwitchCase node because
126
136
  * trailing comments is wrong if semicolons are omitted.
127
137
  */
128
- let fallthroughCase = null;
138
+ let previousCase = null;
129
139
  let fallthroughCommentPattern = null;
130
140
 
131
141
  if (options.commentPattern) {
@@ -168,13 +178,23 @@ module.exports = {
168
178
  * And reports the previous fallthrough node if that does not exist.
169
179
  */
170
180
 
171
- if (fallthroughCase && (!hasFallthroughComment(fallthroughCase, node, context, fallthroughCommentPattern))) {
172
- context.report({
173
- messageId: node.test ? "case" : "default",
174
- node
175
- });
181
+ if (previousCase && previousCase.node.parent === node.parent) {
182
+ const previousCaseFallthroughComment = getFallthroughComment(previousCase.node, node, context, fallthroughCommentPattern);
183
+
184
+ if (previousCase.isFallthrough && !(previousCaseFallthroughComment)) {
185
+ context.report({
186
+ messageId: node.test ? "case" : "default",
187
+ node
188
+ });
189
+ } else if (reportUnusedFallthroughComment && !previousCase.isSwitchExitReachable && previousCaseFallthroughComment) {
190
+ context.report({
191
+ messageId: "unusedFallthroughComment",
192
+ node: previousCaseFallthroughComment
193
+ });
194
+ }
195
+
176
196
  }
177
- fallthroughCase = null;
197
+ previousCase = null;
178
198
  },
179
199
 
180
200
  "SwitchCase:exit"(node) {
@@ -185,11 +205,16 @@ module.exports = {
185
205
  * `break`, `return`, or `throw` are unreachable.
186
206
  * And allows empty cases and the last case.
187
207
  */
188
- if (isAnySegmentReachable(currentCodePathSegments) &&
189
- (node.consequent.length > 0 || (!allowEmptyCase && hasBlankLinesBetween(node, nextToken))) &&
190
- node.parent.cases.at(-1) !== node) {
191
- fallthroughCase = node;
192
- }
208
+ const isSwitchExitReachable = isAnySegmentReachable(currentCodePathSegments);
209
+ const isFallthrough = isSwitchExitReachable && (node.consequent.length > 0 || (!allowEmptyCase && hasBlankLinesBetween(node, nextToken))) &&
210
+ node.parent.cases.at(-1) !== node;
211
+
212
+ previousCase = {
213
+ node,
214
+ isSwitchExitReachable,
215
+ isFallthrough
216
+ };
217
+
193
218
  }
194
219
  };
195
220
  }
@@ -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].superCalled;
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
  },
@@ -146,7 +146,9 @@ module.exports = {
146
146
  continue;
147
147
  }
148
148
 
149
- uselessReturns.push(...segmentInfoMap.get(segment).uselessReturns);
149
+ if (segmentInfoMap.has(segment)) {
150
+ uselessReturns.push(...segmentInfoMap.get(segment).uselessReturns);
151
+ }
150
152
  }
151
153
 
152
154
  return uselessReturns;
@@ -182,6 +184,10 @@ module.exports = {
182
184
 
183
185
  const info = segmentInfoMap.get(segment);
184
186
 
187
+ if (!info) {
188
+ return;
189
+ }
190
+
185
191
  info.uselessReturns = info.uselessReturns.filter(node => {
186
192
  if (scopeInfo.traversedTryBlockStatements && scopeInfo.traversedTryBlockStatements.length > 0) {
187
193
  const returnInitialRange = node.range[0];
@@ -275,7 +281,6 @@ module.exports = {
275
281
  * NOTE: This event is notified for only reachable segments.
276
282
  */
277
283
  onCodePathSegmentStart(segment) {
278
-
279
284
  scopeInfo.currentSegments.add(segment);
280
285
 
281
286
  const info = {
@@ -6,7 +6,7 @@
6
6
 
7
7
  const debug = require("debug")("eslint:rules");
8
8
 
9
- /** @typedef {import("./types").Rule} Rule */
9
+ /** @typedef {import("../../shared/types").Rule} Rule */
10
10
 
11
11
  /**
12
12
  * The `Map` object that loads each rule when it's accessed.
@@ -3,9 +3,14 @@
3
3
  */
4
4
  "use strict";
5
5
 
6
+ const isCombiningCharacter = require("./is-combining-character");
7
+ const isEmojiModifier = require("./is-emoji-modifier");
8
+ const isRegionalIndicatorSymbol = require("./is-regional-indicator-symbol");
9
+ const isSurrogatePair = require("./is-surrogate-pair");
10
+
6
11
  module.exports = {
7
- isCombiningCharacter: require("./is-combining-character"),
8
- isEmojiModifier: require("./is-emoji-modifier"),
9
- isRegionalIndicatorSymbol: require("./is-regional-indicator-symbol"),
10
- isSurrogatePair: require("./is-surrogate-pair")
12
+ isCombiningCharacter,
13
+ isEmojiModifier,
14
+ isRegionalIndicatorSymbol,
15
+ isSurrogatePair
11
16
  };
@@ -162,6 +162,7 @@ function version() {
162
162
  //------------------------------------------------------------------------------
163
163
 
164
164
  module.exports = {
165
+ __esModule: true, // Indicate intent for imports, remove ambiguity for Knip (see: https://github.com/eslint/eslint/pull/18005#discussion_r1484422616)
165
166
  environment,
166
167
  version
167
168
  };
@@ -1,5 +1,7 @@
1
1
  "use strict";
2
2
 
3
+ const SourceCode = require("./source-code");
4
+
3
5
  module.exports = {
4
- SourceCode: require("./source-code")
6
+ SourceCode
5
7
  };
@@ -18,8 +18,12 @@ const
18
18
  directivesPattern
19
19
  } = require("../shared/directives"),
20
20
 
21
- /* eslint-disable-next-line n/no-restricted-require -- Too messy to figure out right now. */
21
+ /* eslint-disable n/no-restricted-require -- Should eventually be moved into SourceCode. */
22
+ CodePathAnalyzer = require("../linter/code-path-analysis/code-path-analyzer"),
23
+ createEmitter = require("../linter/safe-emitter"),
22
24
  ConfigCommentParser = require("../linter/config-comment-parser"),
25
+ /* eslint-enable n/no-restricted-require -- Should eventually be moved into SourceCode. */
26
+
23
27
  eslintScope = require("eslint-scope");
24
28
 
25
29
  //------------------------------------------------------------------------------
@@ -34,6 +38,16 @@ const
34
38
 
35
39
  const commentParser = new ConfigCommentParser();
36
40
 
41
+ const CODE_PATH_EVENTS = [
42
+ "onCodePathStart",
43
+ "onCodePathEnd",
44
+ "onCodePathSegmentStart",
45
+ "onCodePathSegmentEnd",
46
+ "onCodePathSegmentLoop",
47
+ "onUnreachableCodePathSegmentStart",
48
+ "onUnreachableCodePathSegmentEnd"
49
+ ];
50
+
37
51
  /**
38
52
  * Validates that the given AST has the required information.
39
53
  * @param {ASTNode} ast The Program node of the AST to check.
@@ -300,6 +314,65 @@ function markExportedVariables(globalScope, variables) {
300
314
 
301
315
  }
302
316
 
317
+ const STEP_KIND = {
318
+ visit: 1,
319
+ call: 2
320
+ };
321
+
322
+ /**
323
+ * A class to represent a step in the traversal process.
324
+ */
325
+ class TraversalStep {
326
+
327
+ /**
328
+ * The type of the step.
329
+ * @type {string}
330
+ */
331
+ type;
332
+
333
+ /**
334
+ * The kind of the step. Represents the same data as the `type` property
335
+ * but it's a number for performance.
336
+ * @type {number}
337
+ */
338
+ kind;
339
+
340
+ /**
341
+ * The target of the step.
342
+ * @type {ASTNode|string}
343
+ */
344
+ target;
345
+
346
+ /**
347
+ * The phase of the step.
348
+ * @type {number|undefined}
349
+ */
350
+ phase;
351
+
352
+ /**
353
+ * The arguments of the step.
354
+ * @type {Array<any>}
355
+ */
356
+ args;
357
+
358
+ /**
359
+ * Creates a new instance.
360
+ * @param {Object} options The options for the step.
361
+ * @param {string} options.type The type of the step.
362
+ * @param {ASTNode|string} options.target The target of the step.
363
+ * @param {number|undefined} [options.phase] The phase of the step.
364
+ * @param {Array<any>} options.args The arguments of the step.
365
+ * @returns {void}
366
+ */
367
+ constructor({ type, target, phase, args }) {
368
+ this.type = type;
369
+ this.kind = STEP_KIND[type];
370
+ this.target = target;
371
+ this.phase = phase;
372
+ this.args = args;
373
+ }
374
+ }
375
+
303
376
  //------------------------------------------------------------------------------
304
377
  // Public Interface
305
378
  //------------------------------------------------------------------------------
@@ -311,6 +384,12 @@ const caches = Symbol("caches");
311
384
  */
312
385
  class SourceCode extends TokenStore {
313
386
 
387
+ /**
388
+ * The cache of steps that were taken while traversing the source code.
389
+ * @type {Array<TraversalStep>}
390
+ */
391
+ #steps;
392
+
314
393
  /**
315
394
  * @param {string|Object} textOrConfig The source code text or config object.
316
395
  * @param {string} textOrConfig.text The source code text.
@@ -972,6 +1051,91 @@ class SourceCode extends TokenStore {
972
1051
 
973
1052
  }
974
1053
 
1054
+ /**
1055
+ * Traverse the source code and return the steps that were taken.
1056
+ * @returns {Array<TraversalStep>} The steps that were taken while traversing the source code.
1057
+ */
1058
+ traverse() {
1059
+
1060
+ // Because the AST doesn't mutate, we can cache the steps
1061
+ if (this.#steps) {
1062
+ return this.#steps;
1063
+ }
1064
+
1065
+ const steps = this.#steps = [];
1066
+
1067
+ /*
1068
+ * This logic works for any AST, not just ESTree. Because ESLint has allowed
1069
+ * custom parsers to return any AST, we need to ensure that the traversal
1070
+ * logic works for any AST.
1071
+ */
1072
+ const emitter = createEmitter();
1073
+ let analyzer = {
1074
+ enterNode(node) {
1075
+ steps.push(new TraversalStep({
1076
+ type: "visit",
1077
+ target: node,
1078
+ phase: 1,
1079
+ args: [node, node.parent]
1080
+ }));
1081
+ },
1082
+ leaveNode(node) {
1083
+ steps.push(new TraversalStep({
1084
+ type: "visit",
1085
+ target: node,
1086
+ phase: 2,
1087
+ args: [node, node.parent]
1088
+ }));
1089
+ },
1090
+ emitter
1091
+ };
1092
+
1093
+ /*
1094
+ * We do code path analysis for ESTree only. Code path analysis is not
1095
+ * necessary for other ASTs, and it's also not possible to do for other
1096
+ * ASTs because the necessary information is not available.
1097
+ *
1098
+ * Generally speaking, we can tell that the AST is an ESTree if it has a
1099
+ * Program node at the top level. This is not a perfect heuristic, but it
1100
+ * is good enough for now.
1101
+ */
1102
+ const isESTree = this.ast.type === "Program";
1103
+
1104
+ if (isESTree) {
1105
+ analyzer = new CodePathAnalyzer(analyzer);
1106
+
1107
+ CODE_PATH_EVENTS.forEach(eventName => {
1108
+ emitter.on(eventName, (...args) => {
1109
+ steps.push(new TraversalStep({
1110
+ type: "call",
1111
+ target: eventName,
1112
+ args
1113
+ }));
1114
+ });
1115
+ });
1116
+ }
1117
+
1118
+ /*
1119
+ * The actual AST traversal is done by the `Traverser` class. This class
1120
+ * is responsible for walking the AST and calling the appropriate methods
1121
+ * on the `analyzer` object, which is appropriate for the given AST.
1122
+ */
1123
+ Traverser.traverse(this.ast, {
1124
+ enter(node, parent) {
1125
+
1126
+ // save the parent node on a property for backwards compatibility
1127
+ node.parent = parent;
1128
+
1129
+ analyzer.enterNode(node);
1130
+ },
1131
+ leave(node) {
1132
+ analyzer.leaveNode(node);
1133
+ },
1134
+ visitorKeys: this.visitorKeys
1135
+ });
1136
+
1137
+ return steps;
1138
+ }
975
1139
  }
976
1140
 
977
1141
  module.exports = SourceCode;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint",
3
- "version": "9.0.0-beta.2",
3
+ "version": "9.0.0-rc.0",
4
4
  "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
5
5
  "description": "An AST-based pattern checker for JavaScript.",
6
6
  "bin": {
@@ -23,6 +23,7 @@
23
23
  "lint:docs:rule-examples": "node Makefile.js checkRuleExamples",
24
24
  "lint:fix": "node Makefile.js lint -- fix",
25
25
  "lint:fix:docs:js": "node Makefile.js lintDocsJS -- fix",
26
+ "lint:unused": "knip",
26
27
  "release:generate:alpha": "node Makefile.js generatePrerelease -- alpha",
27
28
  "release:generate:beta": "node Makefile.js generatePrerelease -- beta",
28
29
  "release:generate:latest": "node Makefile.js generateRelease",
@@ -48,7 +49,7 @@
48
49
  "node tools/fetch-docs-links.js",
49
50
  "git add docs/src/_data/further_reading_links.json"
50
51
  ],
51
- "docs/**/*.svg": "npx svgo -r --multipass"
52
+ "docs/**/*.svg": "npx -y svgo -r --multipass"
52
53
  },
53
54
  "files": [
54
55
  "LICENSE",
@@ -66,7 +67,7 @@
66
67
  "@eslint-community/eslint-utils": "^4.2.0",
67
68
  "@eslint-community/regexpp": "^4.6.1",
68
69
  "@eslint/eslintrc": "^3.0.2",
69
- "@eslint/js": "9.0.0-beta.2",
70
+ "@eslint/js": "9.0.0-rc.0",
70
71
  "@humanwhocodes/config-array": "^0.11.14",
71
72
  "@humanwhocodes/module-importer": "^1.0.1",
72
73
  "@nodelib/fs.walk": "^1.2.8",
@@ -75,7 +76,7 @@
75
76
  "cross-spawn": "^7.0.2",
76
77
  "debug": "^4.3.2",
77
78
  "escape-string-regexp": "^4.0.0",
78
- "eslint-scope": "^8.0.0",
79
+ "eslint-scope": "^8.0.1",
79
80
  "eslint-visitor-keys": "^4.0.0",
80
81
  "espree": "^10.0.1",
81
82
  "esquery": "^1.4.2",
@@ -101,6 +102,8 @@
101
102
  "devDependencies": {
102
103
  "@babel/core": "^7.4.3",
103
104
  "@babel/preset-env": "^7.4.3",
105
+ "@types/estree": "^1.0.5",
106
+ "@types/node": "^20.11.5",
104
107
  "@wdio/browser-runner": "^8.14.6",
105
108
  "@wdio/cli": "^8.14.6",
106
109
  "@wdio/concise-reporter": "^8.14.0",
@@ -131,6 +134,7 @@
131
134
  "got": "^11.8.3",
132
135
  "gray-matter": "^4.0.3",
133
136
  "js-yaml": "^4.1.0",
137
+ "knip": "^5.0.1",
134
138
  "lint-staged": "^11.0.0",
135
139
  "load-perf": "^0.2.0",
136
140
  "markdown-it": "^12.2.0",
@@ -138,7 +142,6 @@
138
142
  "markdownlint": "^0.33.0",
139
143
  "markdownlint-cli": "^0.39.0",
140
144
  "marked": "^4.0.8",
141
- "memfs": "^3.0.1",
142
145
  "metascraper": "^5.25.7",
143
146
  "metascraper-description": "^5.25.7",
144
147
  "metascraper-image": "^5.29.3",
@@ -157,8 +160,8 @@
157
160
  "semver": "^7.5.3",
158
161
  "shelljs": "^0.8.5",
159
162
  "sinon": "^11.0.0",
163
+ "typescript": "^5.3.3",
160
164
  "vite-plugin-commonjs": "^0.10.0",
161
- "webdriverio": "^8.14.6",
162
165
  "webpack": "^5.23.0",
163
166
  "webpack-cli": "^4.5.0",
164
167
  "yorkie": "^2.0.0"
@@ -1,34 +0,0 @@
1
- /**
2
- * @fileoverview XML character escaper
3
- * @author George Chung
4
- */
5
- "use strict";
6
-
7
- //------------------------------------------------------------------------------
8
- // Public Interface
9
- //------------------------------------------------------------------------------
10
-
11
- /**
12
- * Returns the escaped value for a character
13
- * @param {string} s string to examine
14
- * @returns {string} severity level
15
- * @private
16
- */
17
- module.exports = function(s) {
18
- return (`${s}`).replace(/[<>&"'\x00-\x1F\x7F\u0080-\uFFFF]/gu, c => { // eslint-disable-line no-control-regex -- Converting controls to entities
19
- switch (c) {
20
- case "<":
21
- return "&lt;";
22
- case ">":
23
- return "&gt;";
24
- case "&":
25
- return "&amp;";
26
- case "\"":
27
- return "&quot;";
28
- case "'":
29
- return "&apos;";
30
- default:
31
- return `&#${c.charCodeAt(0)};`;
32
- }
33
- });
34
- };
@@ -1,58 +0,0 @@
1
- /**
2
- * @fileoverview Provide the function that emits deprecation warnings.
3
- * @author Toru Nagashima <http://github.com/mysticatea>
4
- */
5
- "use strict";
6
-
7
- //------------------------------------------------------------------------------
8
- // Requirements
9
- //------------------------------------------------------------------------------
10
-
11
- const path = require("path");
12
-
13
- //------------------------------------------------------------------------------
14
- // Private
15
- //------------------------------------------------------------------------------
16
-
17
- // Definitions for deprecation warnings.
18
- const deprecationWarningMessages = {
19
- ESLINT_LEGACY_ECMAFEATURES:
20
- "The 'ecmaFeatures' config file property is deprecated and has no effect."
21
- };
22
-
23
- const sourceFileErrorCache = new Set();
24
-
25
- /**
26
- * Emits a deprecation warning containing a given filepath. A new deprecation warning is emitted
27
- * for each unique file path, but repeated invocations with the same file path have no effect.
28
- * No warnings are emitted if the `--no-deprecation` or `--no-warnings` Node runtime flags are active.
29
- * @param {string} source The name of the configuration source to report the warning for.
30
- * @param {string} errorCode The warning message to show.
31
- * @returns {void}
32
- */
33
- function emitDeprecationWarning(source, errorCode) {
34
- const cacheKey = JSON.stringify({ source, errorCode });
35
-
36
- if (sourceFileErrorCache.has(cacheKey)) {
37
- return;
38
- }
39
-
40
- sourceFileErrorCache.add(cacheKey);
41
-
42
- const rel = path.relative(process.cwd(), source);
43
- const message = deprecationWarningMessages[errorCode];
44
-
45
- process.emitWarning(
46
- `${message} (found in "${rel}")`,
47
- "DeprecationWarning",
48
- errorCode
49
- );
50
- }
51
-
52
- //------------------------------------------------------------------------------
53
- // Public Interface
54
- //------------------------------------------------------------------------------
55
-
56
- module.exports = {
57
- emitDeprecationWarning
58
- };