eslint 9.38.0 → 9.39.1

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
@@ -342,8 +342,8 @@ to get your logo on our READMEs and [website](https://eslint.org/sponsors).
342
342
  <h3>Platinum Sponsors</h3>
343
343
  <p><a href="https://automattic.com"><img src="https://images.opencollective.com/automattic/d0ef3e1/logo.png" alt="Automattic" height="128"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="128"></a></p><h3>Gold Sponsors</h3>
344
344
  <p><a href="https://qlty.sh/"><img src="https://images.opencollective.com/qltysh/33d157d/logo.png" alt="Qlty Software" height="96"></a> <a href="https://trunk.io/"><img src="https://images.opencollective.com/trunkio/fb92d60/avatar.png" alt="trunk.io" height="96"></a> <a href="https://shopify.engineering/"><img src="https://avatars.githubusercontent.com/u/8085" alt="Shopify" height="96"></a></p><h3>Silver Sponsors</h3>
345
- <p><a href="https://vite.dev/"><img src="https://images.opencollective.com/vite/e6d15e1/logo.png" alt="Vite" 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" alt="American Express" height="64"></a> <a href="https://stackblitz.com"><img src="https://avatars.githubusercontent.com/u/28635252" alt="StackBlitz" height="64"></a></p><h3>Bronze Sponsors</h3>
346
- <p><a href="https://syntax.fm"><img src="https://github.com/syntaxfm.png" alt="Syntax" height="32"></a> <a href="https://cybozu.co.jp/"><img src="https://images.opencollective.com/cybozu/933e46d/logo.png" alt="Cybozu" height="32"></a> <a href="https://sentry.io"><img src="https://github.com/getsentry.png" alt="Sentry" 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://www.gitbook.com"><img src="https://avatars.githubusercontent.com/u/7111340" alt="GitBook" height="32"></a> <a href="https://nx.dev"><img src="https://avatars.githubusercontent.com/u/23692104" alt="Nx" height="32"></a> <a href="https://opensource.mercedes-benz.com/"><img src="https://avatars.githubusercontent.com/u/34240465" alt="Mercedes-Benz Group" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774" alt="HeroCoders" height="32"></a> <a href="https://www.lambdatest.com"><img src="https://avatars.githubusercontent.com/u/171592363" alt="LambdaTest" height="32"></a></p>
345
+ <p><a href="https://vite.dev/"><img src="https://images.opencollective.com/vite/e6d15e1/logo.png" alt="Vite" height="64"></a> <a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/2d6c3b6/logo.png" alt="Liftoff" height="64"></a> <a href="https://americanexpress.io"><img src="https://avatars.githubusercontent.com/u/3853301" alt="American Express" height="64"></a> <a href="https://stackblitz.com"><img src="https://avatars.githubusercontent.com/u/28635252" alt="StackBlitz" height="64"></a></p><h3>Bronze Sponsors</h3>
346
+ <p><a href="https://cybozu.co.jp/"><img src="https://images.opencollective.com/cybozu/933e46d/logo.png" alt="Cybozu" 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://www.gitbook.com"><img src="https://avatars.githubusercontent.com/u/7111340" alt="GitBook" height="32"></a> <a href="https://nx.dev"><img src="https://avatars.githubusercontent.com/u/23692104" alt="Nx" height="32"></a> <a href="https://opensource.mercedes-benz.com/"><img src="https://avatars.githubusercontent.com/u/34240465" alt="Mercedes-Benz Group" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774" alt="HeroCoders" height="32"></a> <a href="https://www.lambdatest.com"><img src="https://avatars.githubusercontent.com/u/171592363" alt="LambdaTest" height="32"></a></p>
347
347
  <h3>Technology Sponsors</h3>
348
348
  Technology sponsors allow us to use their products and services for free as part of a contribution to the open source ecosystem and our work.
349
349
  <p><a href="https://netlify.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/netlify-icon.svg" alt="Netlify" height="32"></a> <a href="https://algolia.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/algolia-icon.svg" alt="Algolia" height="32"></a> <a href="https://1password.com"><img src="https://raw.githubusercontent.com/eslint/eslint.org/main/src/assets/images/techsponsors/1password-icon.svg" alt="1Password" height="32"></a></p>
@@ -17,6 +17,7 @@ const { pathToFileURL } = require("node:url");
17
17
  const { SHARE_ENV, Worker } = require("node:worker_threads");
18
18
  const { version } = require("../../package.json");
19
19
  const { defaultConfig } = require("../config/default-config");
20
+ const timing = require("../linter/timing");
20
21
 
21
22
  const {
22
23
  createDebug,
@@ -503,6 +504,11 @@ async function runWorkers(
503
504
  worstNetLintingRatio,
504
505
  netLintingRatio,
505
506
  );
507
+
508
+ if (timing.enabled && indexedResults.timings) {
509
+ timing.mergeData(indexedResults.timings);
510
+ }
511
+
506
512
  for (const result of indexedResults) {
507
513
  const { index } = result;
508
514
  delete result.index;
@@ -522,7 +528,17 @@ async function runWorkers(
522
528
  for (let index = 0; index < workerCount; ++index) {
523
529
  promises[index] = new Promise(workerExecutor);
524
530
  }
525
- await Promise.all(promises);
531
+
532
+ try {
533
+ await Promise.all(promises);
534
+ } catch (error) {
535
+ /*
536
+ * If any worker fails, suppress timing display in the main thread
537
+ * to avoid printing partial or misleading timing output.
538
+ */
539
+ timing.disableDisplay();
540
+ throw error;
541
+ }
526
542
 
527
543
  if (worstNetLintingRatio < LOW_NET_LINTING_RATIO) {
528
544
  warnOnLowNetLintingRatio();
@@ -29,6 +29,7 @@ const {
29
29
  processOptions,
30
30
  } = require("./eslint-helpers");
31
31
  const { WarningService } = require("../services/warning-service");
32
+ const timing = require("../linter/timing");
32
33
 
33
34
  const depsLoadedTime = hrtimeBigint();
34
35
 
@@ -39,7 +40,7 @@ const depsLoadedTime = hrtimeBigint();
39
40
  /** @typedef {import("../types").ESLint.LintResult} LintResult */
40
41
  /** @typedef {import("../types").ESLint.Options} ESLintOptions */
41
42
  /** @typedef {LintResult & { index?: number; }} IndexedLintResult */
42
- /** @typedef {IndexedLintResult[] & { netLintingDuration: bigint; }} WorkerLintResults */
43
+ /** @typedef {IndexedLintResult[] & { netLintingDuration: bigint; timings?: Record<string, number>; }} WorkerLintResults */
43
44
  /**
44
45
  * @typedef {Object} WorkerData - Data passed to the worker thread.
45
46
  * @property {ESLintOptions | string} eslintOptionsOrURL - The unprocessed ESLint options or the URL of the options module.
@@ -57,6 +58,12 @@ const debug = createDebug(`eslint:worker:thread-${threadId}`);
57
58
  // Main
58
59
  //------------------------------------------------------------------------------
59
60
 
61
+ /*
62
+ * Prevent timing module from printing profiling output from worker threads.
63
+ * The main thread is responsible for displaying any aggregated timings.
64
+ */
65
+ timing.disableDisplay();
66
+
60
67
  debug("Dependencies loaded in %t", depsLoadedTime - startTime);
61
68
 
62
69
  (async () => {
@@ -158,5 +165,9 @@ debug("Dependencies loaded in %t", depsLoadedTime - startTime);
158
165
  indexedResults.netLintingDuration =
159
166
  lintingDuration - loadConfigTotalDuration - readFileCounter.duration;
160
167
 
168
+ if (timing.enabled) {
169
+ indexedResults.timings = timing.getData();
170
+ }
171
+
161
172
  parentPort.postMessage(indexedResults);
162
173
  })();
@@ -1303,7 +1303,7 @@ class SourceCode extends TokenStore {
1303
1303
  new VisitNodeStep({
1304
1304
  target: node,
1305
1305
  phase: 1,
1306
- args: [node, node.parent],
1306
+ args: [node],
1307
1307
  }),
1308
1308
  );
1309
1309
  },
@@ -1312,7 +1312,7 @@ class SourceCode extends TokenStore {
1312
1312
  new VisitNodeStep({
1313
1313
  target: node,
1314
1314
  phase: 2,
1315
- args: [node, node.parent],
1315
+ args: [node],
1316
1316
  }),
1317
1317
  );
1318
1318
  },
@@ -426,7 +426,7 @@ function validateSuggestions(suggest, messages) {
426
426
 
427
427
  if (typeof suggestion.fix !== "function") {
428
428
  throw new TypeError(
429
- `context.report() called with a suggest option without a fix function. See: ${suggestion}`,
429
+ `context.report() called with a suggest option without a fix function. See: ${JSON.stringify(suggestion, null, 2)}`,
430
430
  );
431
431
  }
432
432
  });
@@ -17,8 +17,9 @@ const vk = require("eslint-visitor-keys");
17
17
  //-----------------------------------------------------------------------------
18
18
 
19
19
  /**
20
- * @import { ESQueryParsedSelector } from "./esquery.js";
21
20
  * @import { Language, SourceCode } from "@eslint/core";
21
+ * @import { ESQueryOptions } from "esquery";
22
+ * @import { ESQueryParsedSelector } from "./esquery.js";
22
23
  * @import { SourceCodeVisitor } from "./source-code-visitor.js";
23
24
  */
24
25
 
@@ -47,11 +48,10 @@ class ESQueryHelper {
47
48
  * Creates a new instance.
48
49
  * @param {SourceCodeVisitor} visitor The visitor containing the functions to call.
49
50
  * @param {ESQueryOptions} esqueryOptions `esquery` options for traversing custom nodes.
50
- * @returns {NodeEventGenerator} new instance
51
51
  */
52
52
  constructor(visitor, esqueryOptions) {
53
53
  /**
54
- * The emitter to use during traversal.
54
+ * The visitor to use during traversal.
55
55
  * @type {SourceCodeVisitor}
56
56
  */
57
57
  this.visitor = visitor;
@@ -288,7 +288,10 @@ class SourceCodeTraverser {
288
288
  false,
289
289
  )
290
290
  .forEach(selector => {
291
- visitor.callSync(selector, step.target);
291
+ visitor.callSync(
292
+ selector,
293
+ ...(step.args ?? [step.target]),
294
+ );
292
295
  });
293
296
  currentAncestry.unshift(step.target);
294
297
  } else {
@@ -300,7 +303,10 @@ class SourceCodeTraverser {
300
303
  true,
301
304
  )
302
305
  .forEach(selector => {
303
- visitor.callSync(selector, step.target);
306
+ visitor.callSync(
307
+ selector,
308
+ ...(step.args ?? [step.target]),
309
+ );
304
310
  });
305
311
  }
306
312
  } catch (err) {
@@ -131,6 +131,7 @@ function display(data) {
131
131
  /* c8 ignore next */
132
132
  module.exports = (function () {
133
133
  const data = Object.create(null);
134
+ let displayEnabled = true;
134
135
 
135
136
  /**
136
137
  * Time the run
@@ -158,9 +159,42 @@ module.exports = (function () {
158
159
  };
159
160
  }
160
161
 
162
+ /**
163
+ * Returns a shallow copy of the collected timings data.
164
+ * @returns {Record<string, number>} mapping of ruleId to total time in ms
165
+ */
166
+ function getData() {
167
+ return { ...data };
168
+ }
169
+
170
+ /**
171
+ * Merges rule timing totals collected elsewhere into this process' totals.
172
+ * @param {Record<string, number>} dataToMerge mapping of ruleId to total time in ms
173
+ * @returns {void}
174
+ */
175
+ function mergeData(dataToMerge) {
176
+ for (const [key, value] of Object.entries(dataToMerge)) {
177
+ if (typeof data[key] === "undefined") {
178
+ data[key] = 0;
179
+ }
180
+ data[key] += value;
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Disables printing of timing data on process exit.
186
+ * Intended for worker threads or non-main contexts.
187
+ * @returns {void}
188
+ */
189
+ function disableDisplay() {
190
+ displayEnabled = false;
191
+ }
192
+
161
193
  if (enabled) {
162
194
  process.on("exit", () => {
163
- display(data);
195
+ if (displayEnabled && Object.keys(data).length > 0) {
196
+ display(data);
197
+ }
164
198
  });
165
199
  }
166
200
 
@@ -168,5 +202,8 @@ module.exports = (function () {
168
202
  time,
169
203
  enabled,
170
204
  getListSize,
205
+ getData,
206
+ mergeData,
207
+ disableDisplay,
171
208
  };
172
209
  })();
@@ -68,6 +68,7 @@ module.exports = {
68
68
  },
69
69
 
70
70
  create(context) {
71
+ const sourceCode = context.sourceCode;
71
72
  const option = context.options[0];
72
73
  let threshold = THRESHOLD_DEFAULT;
73
74
  let VARIANT = "classic";
@@ -177,12 +178,10 @@ module.exports = {
177
178
  name = "class field initializer";
178
179
  } else if (codePath.origin === "class-static-block") {
179
180
  name = "class static block";
181
+ loc = sourceCode.getFirstToken(node).loc;
180
182
  } else {
181
183
  name = astUtils.getFunctionNameWithKind(node);
182
- loc = astUtils.getFunctionHeadLoc(
183
- node,
184
- context.sourceCode,
185
- );
184
+ loc = astUtils.getFunctionHeadLoc(node, sourceCode);
186
185
  }
187
186
 
188
187
  context.report({
@@ -46,7 +46,10 @@ module.exports = {
46
46
  */
47
47
  function report(node) {
48
48
  context.report({
49
- node,
49
+ loc: {
50
+ start: node.loc.start,
51
+ end: sourceCode.getTokenBefore(node.body).loc.end,
52
+ },
50
53
  messageId: "incorrectDirection",
51
54
  });
52
55
  }
@@ -5,6 +5,12 @@
5
5
 
6
6
  "use strict";
7
7
 
8
+ //------------------------------------------------------------------------------
9
+ // Requirements
10
+ //------------------------------------------------------------------------------
11
+
12
+ const astUtils = require("./utils/ast-utils");
13
+
8
14
  //------------------------------------------------------------------------------
9
15
  // Rule Definition
10
16
  //------------------------------------------------------------------------------
@@ -58,10 +64,15 @@ module.exports = {
58
64
 
59
65
  // Checks and reports duplications.
60
66
  const defs = variable.defs.filter(isParameter);
67
+ const loc = {
68
+ start: astUtils.getOpeningParenOfParams(node, sourceCode)
69
+ .loc.start,
70
+ end: sourceCode.getTokenBefore(node.body).loc.end,
71
+ };
61
72
 
62
73
  if (defs.length >= 2) {
63
74
  context.report({
64
- node,
75
+ loc,
65
76
  messageId: "unexpected",
66
77
  data: { name: variable.name },
67
78
  });
@@ -106,7 +106,7 @@ module.exports = {
106
106
 
107
107
  if (isDuplicate) {
108
108
  context.report({
109
- node,
109
+ loc: node.key.loc,
110
110
  messageId: "unexpected",
111
111
  data: { name },
112
112
  });
@@ -19,6 +19,86 @@ const OPTIONS = {
19
19
  //------------------------------------------------------------------------------
20
20
  const astUtils = require("./utils/ast-utils");
21
21
 
22
+ //--------------------------------------------------------------------------
23
+ // Helpers
24
+ //--------------------------------------------------------------------------
25
+ const CTOR_PREFIX_REGEX = /[^_$0-9]/u;
26
+ const JSDOC_COMMENT_REGEX = /^\s*\*/u;
27
+
28
+ /**
29
+ * Determines if the first character of the name is a capital letter.
30
+ * @param {string} name The name of the node to evaluate.
31
+ * @returns {boolean} True if the first character of the property name is a capital letter, false if not.
32
+ * @private
33
+ */
34
+ function isConstructor(name) {
35
+ const match = CTOR_PREFIX_REGEX.exec(name);
36
+
37
+ // Not a constructor if name has no characters apart from '_', '$' and digits e.g. '_', '$$', '_8'
38
+ if (!match) {
39
+ return false;
40
+ }
41
+
42
+ const firstChar = name.charAt(match.index);
43
+
44
+ return firstChar === firstChar.toUpperCase();
45
+ }
46
+
47
+ /**
48
+ * Determines if the property can have a shorthand form.
49
+ * @param {ASTNode} property Property AST node
50
+ * @returns {boolean} True if the property can have a shorthand form
51
+ * @private
52
+ */
53
+ function canHaveShorthand(property) {
54
+ return (
55
+ property.kind !== "set" &&
56
+ property.kind !== "get" &&
57
+ property.type !== "SpreadElement" &&
58
+ property.type !== "SpreadProperty" &&
59
+ property.type !== "ExperimentalSpreadProperty"
60
+ );
61
+ }
62
+
63
+ /**
64
+ * Checks whether a node is a string literal.
65
+ * @param {ASTNode} node Any AST node.
66
+ * @returns {boolean} `true` if it is a string literal.
67
+ */
68
+ function isStringLiteral(node) {
69
+ return node.type === "Literal" && typeof node.value === "string";
70
+ }
71
+
72
+ /**
73
+ * Determines if the property is a shorthand or not.
74
+ * @param {ASTNode} property Property AST node
75
+ * @returns {boolean} True if the property is considered shorthand, false if not.
76
+ * @private
77
+ */
78
+ function isShorthand(property) {
79
+ // property.method is true when `{a(){}}`.
80
+ return property.shorthand || property.method;
81
+ }
82
+
83
+ /**
84
+ * Determines if the property's key and method or value are named equally.
85
+ * @param {ASTNode} property Property AST node
86
+ * @returns {boolean} True if the key and value are named equally, false if not.
87
+ * @private
88
+ */
89
+ function isRedundant(property) {
90
+ const value = property.value;
91
+
92
+ if (value.type === "FunctionExpression") {
93
+ return !value.id; // Only anonymous should be shorthand method.
94
+ }
95
+ if (value.type === "Identifier") {
96
+ return astUtils.getStaticPropertyName(property) === value.name;
97
+ }
98
+
99
+ return false;
100
+ }
101
+
22
102
  //------------------------------------------------------------------------------
23
103
  // Rule Definition
24
104
  //------------------------------------------------------------------------------
@@ -139,86 +219,6 @@ module.exports = {
139
219
  const AVOID_EXPLICIT_RETURN_ARROWS = !!PARAMS.avoidExplicitReturnArrows;
140
220
  const sourceCode = context.sourceCode;
141
221
 
142
- //--------------------------------------------------------------------------
143
- // Helpers
144
- //--------------------------------------------------------------------------
145
-
146
- const CTOR_PREFIX_REGEX = /[^_$0-9]/u;
147
-
148
- /**
149
- * Determines if the first character of the name is a capital letter.
150
- * @param {string} name The name of the node to evaluate.
151
- * @returns {boolean} True if the first character of the property name is a capital letter, false if not.
152
- * @private
153
- */
154
- function isConstructor(name) {
155
- const match = CTOR_PREFIX_REGEX.exec(name);
156
-
157
- // Not a constructor if name has no characters apart from '_', '$' and digits e.g. '_', '$$', '_8'
158
- if (!match) {
159
- return false;
160
- }
161
-
162
- const firstChar = name.charAt(match.index);
163
-
164
- return firstChar === firstChar.toUpperCase();
165
- }
166
-
167
- /**
168
- * Determines if the property can have a shorthand form.
169
- * @param {ASTNode} property Property AST node
170
- * @returns {boolean} True if the property can have a shorthand form
171
- * @private
172
- */
173
- function canHaveShorthand(property) {
174
- return (
175
- property.kind !== "set" &&
176
- property.kind !== "get" &&
177
- property.type !== "SpreadElement" &&
178
- property.type !== "SpreadProperty" &&
179
- property.type !== "ExperimentalSpreadProperty"
180
- );
181
- }
182
-
183
- /**
184
- * Checks whether a node is a string literal.
185
- * @param {ASTNode} node Any AST node.
186
- * @returns {boolean} `true` if it is a string literal.
187
- */
188
- function isStringLiteral(node) {
189
- return node.type === "Literal" && typeof node.value === "string";
190
- }
191
-
192
- /**
193
- * Determines if the property is a shorthand or not.
194
- * @param {ASTNode} property Property AST node
195
- * @returns {boolean} True if the property is considered shorthand, false if not.
196
- * @private
197
- */
198
- function isShorthand(property) {
199
- // property.method is true when `{a(){}}`.
200
- return property.shorthand || property.method;
201
- }
202
-
203
- /**
204
- * Determines if the property's key and method or value are named equally.
205
- * @param {ASTNode} property Property AST node
206
- * @returns {boolean} True if the key and value are named equally, false if not.
207
- * @private
208
- */
209
- function isRedundant(property) {
210
- const value = property.value;
211
-
212
- if (value.type === "FunctionExpression") {
213
- return !value.id; // Only anonymous should be shorthand method.
214
- }
215
- if (value.type === "Identifier") {
216
- return astUtils.getStaticPropertyName(property) === value.name;
217
- }
218
-
219
- return false;
220
- }
221
-
222
222
  /**
223
223
  * Ensures that an object's properties are consistently shorthand, or not shorthand at all.
224
224
  * @param {ASTNode} node Property AST node
@@ -582,6 +582,19 @@ module.exports = {
582
582
  node.key.name === node.value.name &&
583
583
  APPLY_TO_PROPS
584
584
  ) {
585
+ // Skip if there are JSDoc comments inside the property (e.g., JSDoc type annotations)
586
+ const comments = sourceCode.getCommentsInside(node);
587
+ if (
588
+ comments.some(
589
+ comment =>
590
+ comment.type === "Block" &&
591
+ JSDOC_COMMENT_REGEX.test(comment.value) &&
592
+ comment.value.includes("@type"),
593
+ )
594
+ ) {
595
+ return;
596
+ }
597
+
585
598
  // {x: x} should be written as {x}
586
599
  context.report({
587
600
  node,
@@ -606,6 +619,18 @@ module.exports = {
606
619
  return;
607
620
  }
608
621
 
622
+ const comments = sourceCode.getCommentsInside(node);
623
+ if (
624
+ comments.some(
625
+ comment =>
626
+ comment.type === "Block" &&
627
+ comment.value.startsWith("*") &&
628
+ comment.value.includes("@type"),
629
+ )
630
+ ) {
631
+ return;
632
+ }
633
+
609
634
  // {"x": x} should be written as {x}
610
635
  context.report({
611
636
  node,
@@ -2729,4 +2729,5 @@ module.exports = {
2729
2729
  isStartOfExpressionStatement,
2730
2730
  needsPrecedingSemicolon,
2731
2731
  isImportAttributeKey,
2732
+ getOpeningParenOfParams,
2732
2733
  };