eslint 10.1.0 → 10.2.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
@@ -218,6 +218,8 @@ Packages that are controlled by the ESLint team and have no external dependencie
218
218
 
219
219
  For external packages, we don't use `require(esm)` because a package could add a top-level `await` and thus break ESLint. We can use an external ESM-only package only in case it is needed only in asynchronous code, in which case it can be loaded using dynamic `import()`.
220
220
 
221
+ These policies don't apply to packages intended for our own usage only, such as `eslint-config-eslint`.
222
+
221
223
  ## License
222
224
 
223
225
  MIT License
@@ -358,7 +360,7 @@ to get your logo on our READMEs and [website](https://eslint.org/sponsors).
358
360
  <p><a href="https://automattic.com"><img src="https://images.opencollective.com/automattic/d0ef3e1/logo.png" alt="Automattic" height="128"></a></p><h3>Gold Sponsors</h3>
359
361
  <p><a href="https://qlty.sh/"><img src="https://images.opencollective.com/qltysh/33d157d/logo.png" alt="Qlty Software" height="96"></a></p><h3>Silver Sponsors</h3>
360
362
  <p><a href="https://vite.dev/"><img src="https://images.opencollective.com/vite/d472863/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://stackblitz.com"><img src="https://avatars.githubusercontent.com/u/28635252" alt="StackBlitz" height="64"></a></p><h3>Bronze Sponsors</h3>
361
- <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://opensource.sap.com"><img src="https://avatars.githubusercontent.com/u/2531208" alt="SAP" height="32"></a> <a href="https://www.crawljobs.com/"><img src="https://images.opencollective.com/crawljobs-poland/fa43a17/logo.png" alt="CrawlJobs" height="32"></a> <a href="https://depot.dev"><img src="https://images.opencollective.com/depot/39125a1/logo.png" alt="Depot" height="32"></a> <a href="https://www.n-ix.com/"><img src="https://images.opencollective.com/n-ix-ltd/575a7a5/logo.png" alt="N-iX Ltd" 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://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="TestMu AI Open Source Office (Formerly LambdaTest)" height="32"></a></p>
363
+ <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://opensource.sap.com"><img src="https://avatars.githubusercontent.com/u/2531208" alt="SAP" height="32"></a> <a href="https://www.crawljobs.com/"><img src="https://images.opencollective.com/crawljobs-poland/fa43a17/logo.png" alt="CrawlJobs" height="32"></a> <a href="#"><img src="https://images.opencollective.com/aeriusventilations-org/avatar.png" alt="aeriusventilation's Org" height="32"></a> <a href="https://depot.dev"><img src="https://images.opencollective.com/depot/39125a1/logo.png" alt="Depot" 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://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="TestMu AI Open Source Office (Formerly LambdaTest)" height="32"></a></p>
362
364
  <h3>Technology Sponsors</h3>
363
365
  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.
364
366
  <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>
package/conf/globals.js CHANGED
@@ -144,6 +144,7 @@ const es2026 = {
144
144
  AsyncDisposableStack: false,
145
145
  DisposableStack: false,
146
146
  SuppressedError: false,
147
+ Temporal: false,
147
148
  };
148
149
 
149
150
  //-----------------------------------------------------------------------------
@@ -228,6 +228,76 @@ function splitPluginIdentifier(identifier) {
228
228
  };
229
229
  }
230
230
 
231
+ /**
232
+ * Normalizes a language name by replacing the built-in `@/` plugin prefix with `js/`.
233
+ * @param {string} languageName The language name to normalize.
234
+ * @returns {string} The normalized language name.
235
+ */
236
+ function normalizeLanguageName(languageName) {
237
+ return languageName.startsWith("@/")
238
+ ? `js/${languageName.slice(2)}`
239
+ : languageName;
240
+ }
241
+
242
+ /**
243
+ * Checks if a rule's `meta.languages` supports the given language.
244
+ * @param {Array<string>|undefined} ruleLangs The rule's `meta.languages` array.
245
+ * @param {string} configLanguageName The normalized language name from the config (e.g., "js/js").
246
+ * @param {Array<string>} validPluginNames The valid plugin name aliases for the config's plugin
247
+ * (normalized plugin name plus its `meta.namespace` if defined).
248
+ * @returns {boolean} True if the rule supports the language, false otherwise.
249
+ */
250
+ function doesRuleSupportLanguage(
251
+ ruleLangs,
252
+ configLanguageName,
253
+ validPluginNames,
254
+ ) {
255
+ // If no languages specified, works with all languages (backward compatible)
256
+ if (!ruleLangs) {
257
+ return true;
258
+ }
259
+
260
+ const { objectName: configLangPart } =
261
+ splitPluginIdentifier(configLanguageName);
262
+
263
+ for (const langEntry of ruleLangs) {
264
+ // Skip non-string entries
265
+ if (typeof langEntry !== "string") {
266
+ continue;
267
+ }
268
+
269
+ // "*" matches any language
270
+ if (langEntry === "*") {
271
+ return true;
272
+ }
273
+
274
+ // Direct match
275
+ if (langEntry === configLanguageName) {
276
+ return true;
277
+ }
278
+
279
+ const { pluginName: rulePluginPart, objectName: ruleLangPart } =
280
+ splitPluginIdentifier(langEntry);
281
+
282
+ // "plugin/*" wildcard - matches any language from that plugin (by name or namespace)
283
+ if (ruleLangPart === "*") {
284
+ if (validPluginNames.includes(rulePluginPart)) {
285
+ return true;
286
+ }
287
+ } else {
288
+ // Match by plugin name or namespace, with exact language part
289
+ if (
290
+ validPluginNames.includes(rulePluginPart) &&
291
+ ruleLangPart === configLangPart
292
+ ) {
293
+ return true;
294
+ }
295
+ }
296
+ }
297
+
298
+ return false;
299
+ }
300
+
231
301
  /**
232
302
  * Returns the name of an object in the config by reading its `meta` key.
233
303
  * @param {Object} object The object to check.
@@ -572,12 +642,30 @@ class Config {
572
642
  * @throws {TypeError} If the rulesConfig is not provided or is invalid.
573
643
  * @throws {InvalidRuleOptionsSchemaError} If a rule's `meta.schema` is invalid.
574
644
  * @throws {TypeError} If a rule is not found in the plugins.
645
+ * @throws {TypeError} If a rule does not support the current language.
575
646
  */
576
647
  validateRulesConfig(rulesConfig) {
577
648
  if (!rulesConfig) {
578
649
  throw new TypeError("Config is required for validation.");
579
650
  }
580
651
 
652
+ // Normalize "@/" prefix to "js/" for matching and user-facing messages
653
+ const normalizedLanguageName = normalizeLanguageName(
654
+ this.#languageName,
655
+ );
656
+
657
+ // Compute valid plugin name aliases for the config's language plugin once
658
+ const { pluginName: configPluginName } = splitPluginIdentifier(
659
+ normalizedLanguageName,
660
+ );
661
+ const configPlugin =
662
+ this.plugins[configPluginName] ??
663
+ (configPluginName === "js" ? this.plugins["@"] : void 0);
664
+ const validPluginNames = configPlugin?.meta?.namespace
665
+ ? [configPluginName, configPlugin.meta.namespace]
666
+ : [configPluginName];
667
+ const unsupportedLanguageRules = [];
668
+
581
669
  for (const [ruleId, ruleOptions] of Object.entries(rulesConfig)) {
582
670
  // check for edge case
583
671
  if (ruleId === "__proto__") {
@@ -603,6 +691,34 @@ class Config {
603
691
  throwRuleNotFoundError(parseRuleId(ruleId), this);
604
692
  }
605
693
 
694
+ // Validate meta.languages structure if present (only for enabled rules)
695
+ if (rule.meta?.languages !== void 0) {
696
+ if (!Array.isArray(rule.meta.languages)) {
697
+ throw new TypeError(
698
+ `Key "rules": Key "${ruleId}": Key "meta": Key "languages": Expected an array.`,
699
+ );
700
+ }
701
+
702
+ for (const lang of rule.meta.languages) {
703
+ if (typeof lang !== "string") {
704
+ throw new TypeError(
705
+ `Key "rules": Key "${ruleId}": Key "meta": Key "languages": Expected each element to be a string.`,
706
+ );
707
+ }
708
+ }
709
+ }
710
+
711
+ // Check if the rule supports the current language
712
+ if (
713
+ !doesRuleSupportLanguage(
714
+ rule.meta?.languages,
715
+ normalizedLanguageName,
716
+ validPluginNames,
717
+ )
718
+ ) {
719
+ unsupportedLanguageRules.push(ruleId);
720
+ }
721
+
606
722
  const validateRule = getOrCreateValidator(rule, ruleId);
607
723
 
608
724
  if (validateRule) {
@@ -634,6 +750,19 @@ class Config {
634
750
  }
635
751
  }
636
752
  }
753
+
754
+ if (unsupportedLanguageRules.length > 0) {
755
+ const error = new TypeError(
756
+ `Key "rules": The following rules do not support the language "${normalizedLanguageName}":\n${unsupportedLanguageRules.map(ruleId => `\t- "${ruleId}"`).join("\n")}`,
757
+ );
758
+
759
+ error.messageTemplate = "rule-unsupported-language";
760
+ error.messageData = {
761
+ ruleIds: unsupportedLanguageRules,
762
+ language: normalizedLanguageName,
763
+ };
764
+ throw error;
765
+ }
637
766
  }
638
767
 
639
768
  /**
@@ -1202,6 +1202,22 @@ class Linter {
1202
1202
  throw err;
1203
1203
  }
1204
1204
 
1205
+ /*
1206
+ * If the rule does not support the current language, report a
1207
+ * specific, actionable error message.
1208
+ */
1209
+ if (
1210
+ err.messageTemplate ===
1211
+ "rule-unsupported-language"
1212
+ ) {
1213
+ report.addError({
1214
+ ruleId,
1215
+ message: `Inline configuration for rule "${ruleId}" is invalid:\n\tRule does not support the language "${err.messageData.language}". Use a config block with "files" to apply the rule only to supported files, or disable it.\n`,
1216
+ loc,
1217
+ });
1218
+ return;
1219
+ }
1220
+
1205
1221
  let baseMessage = err.message
1206
1222
  .slice(
1207
1223
  err.message.startsWith('Key "rules":')
@@ -20,7 +20,14 @@ const getPropertyName = require("./utils/ast-utils").getStaticPropertyName;
20
20
  // Helpers
21
21
  //------------------------------------------------------------------------------
22
22
 
23
- const nonCallableGlobals = ["Atomics", "JSON", "Math", "Reflect", "Intl"];
23
+ const nonCallableGlobals = [
24
+ "Atomics",
25
+ "JSON",
26
+ "Math",
27
+ "Reflect",
28
+ "Intl",
29
+ "Temporal",
30
+ ];
24
31
 
25
32
  /**
26
33
  * Returns the name of the node to report
@@ -128,6 +128,35 @@ function getEncloseFunctionDeclaration(reference) {
128
128
  return null;
129
129
  }
130
130
 
131
+ /**
132
+ * Checks whether a given modifier is in a loop.
133
+ *
134
+ * Besides checking for the condition being in the loop, this also checks
135
+ * whether the function that this modifier is belonging to is called
136
+ * in the loop.
137
+ * @param {LoopConditionInfo} condition The condition to check.
138
+ * @param {Reference} modifier The modifier to check.
139
+ * @returns {boolean} `true` if the modifier is in a loop.
140
+ */
141
+ function hasModifierInLoop(condition, modifier) {
142
+ if (condition.isInLoop(modifier)) {
143
+ return true;
144
+ }
145
+
146
+ const funcNode = getEncloseFunctionDeclaration(modifier);
147
+
148
+ if (!funcNode) {
149
+ return false;
150
+ }
151
+
152
+ const funcVar = astUtils.getVariableByName(
153
+ modifier.from.upper,
154
+ funcNode.id.name,
155
+ );
156
+
157
+ return Boolean(funcVar && funcVar.references.some(condition.isInLoop));
158
+ }
159
+
131
160
  /**
132
161
  * Updates the "modified" flags of given loop conditions with given modifiers.
133
162
  * @param {LoopConditionInfo[]} conditions The loop conditions to be updated.
@@ -140,26 +169,8 @@ function updateModifiedFlag(conditions, modifiers) {
140
169
 
141
170
  for (let j = 0; !condition.modified && j < modifiers.length; ++j) {
142
171
  const modifier = modifiers[j];
143
- let funcNode, funcVar;
144
172
 
145
- /*
146
- * Besides checking for the condition being in the loop, we want to
147
- * check the function that this modifier is belonging to is called
148
- * in the loop.
149
- * FIXME: This should probably be extracted to a function.
150
- */
151
- const inLoop =
152
- condition.isInLoop(modifier) ||
153
- Boolean(
154
- (funcNode = getEncloseFunctionDeclaration(modifier)) &&
155
- (funcVar = astUtils.getVariableByName(
156
- modifier.from.upper,
157
- funcNode.id.name,
158
- )) &&
159
- funcVar.references.some(condition.isInLoop),
160
- );
161
-
162
- condition.modified = inLoop;
173
+ condition.modified = hasModifierInLoop(condition, modifier);
163
174
  }
164
175
  }
165
176
  }
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+
3
+ module.exports = function ({ ruleIds, language }) {
4
+ return `
5
+ The following rules do not support the language "${language}":
6
+ ${ruleIds.map(id => `\t- "${id}"`).join("\n")}
7
+
8
+ To fix this error, either:
9
+ - Remove the rule from your configuration, or set its severity to "off".
10
+ - Use the "files" option to apply the rule only to files of the supported language, for example:
11
+ {
12
+ files: ["**/*.js"],
13
+ rules: { "${ruleIds[0]}": "error" }
14
+ }
15
+
16
+ See https://eslint.org/docs/latest/use/configure/rules for more information.
17
+ `.trimStart();
18
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint",
3
- "version": "10.1.0",
3
+ "version": "10.2.0",
4
4
  "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
5
5
  "description": "An AST-based pattern checker for JavaScript.",
6
6
  "type": "commonjs",
@@ -119,10 +119,10 @@
119
119
  "dependencies": {
120
120
  "@eslint-community/eslint-utils": "^4.8.0",
121
121
  "@eslint-community/regexpp": "^4.12.2",
122
- "@eslint/config-array": "^0.23.3",
123
- "@eslint/config-helpers": "^0.5.3",
124
- "@eslint/core": "^1.1.1",
125
- "@eslint/plugin-kit": "^0.6.1",
122
+ "@eslint/config-array": "^0.23.4",
123
+ "@eslint/config-helpers": "^0.5.4",
124
+ "@eslint/core": "^1.2.0",
125
+ "@eslint/plugin-kit": "^0.7.0",
126
126
  "@humanfs/node": "^0.16.6",
127
127
  "@humanwhocodes/module-importer": "^1.0.1",
128
128
  "@humanwhocodes/retry": "^0.4.2",
@@ -168,7 +168,7 @@
168
168
  "ejs": "^3.0.2",
169
169
  "eslint": "file:.",
170
170
  "eslint-config-eslint": "file:packages/eslint-config-eslint",
171
- "eslint-plugin-eslint-plugin": "^6.0.0",
171
+ "eslint-plugin-eslint-plugin": "^7.3.2",
172
172
  "eslint-plugin-expect-type": "^0.6.0",
173
173
  "eslint-plugin-yml": "^1.14.0",
174
174
  "eslint-release": "^3.3.0",
@@ -185,7 +185,7 @@
185
185
  "lint-staged": "^11.0.0",
186
186
  "markdown-it": "^12.2.0",
187
187
  "markdown-it-container": "^3.0.0",
188
- "markdownlint-cli2": "^0.21.0",
188
+ "markdownlint-cli2": "^0.22.0",
189
189
  "marked": "^4.0.8",
190
190
  "metascraper": "^5.25.7",
191
191
  "metascraper-description": "^5.25.7",