eslint 9.25.0 → 9.26.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
@@ -20,9 +20,9 @@
20
20
 
21
21
  ESLint is a tool for identifying and reporting on patterns found in ECMAScript/JavaScript code. In many ways, it is similar to JSLint and JSHint with a few exceptions:
22
22
 
23
- - ESLint uses [Espree](https://github.com/eslint/js/tree/main/packages/espree) for JavaScript parsing.
24
- - ESLint uses an AST to evaluate patterns in code.
25
- - ESLint is completely pluggable, every single rule is a plugin and you can add more at runtime.
23
+ - ESLint uses [Espree](https://github.com/eslint/js/tree/main/packages/espree) for JavaScript parsing.
24
+ - ESLint uses an AST to evaluate patterns in code.
25
+ - ESLint is completely pluggable, every single rule is a plugin and you can add more at runtime.
26
26
 
27
27
  ## Table of Contents
28
28
 
@@ -87,9 +87,9 @@ export default defineConfig([
87
87
 
88
88
  The names `"prefer-const"` and `"no-constant-binary-expression"` are the names of [rules](https://eslint.org/docs/rules) in ESLint. The first value is the error level of the rule and can be one of these values:
89
89
 
90
- - `"off"` or `0` - turn the rule off
91
- - `"warn"` or `1` - turn the rule on as a warning (doesn't affect exit code)
92
- - `"error"` or `2` - turn the rule on as an error (exit code will be 1)
90
+ - `"off"` or `0` - turn the rule off
91
+ - `"warn"` or `1` - turn the rule on as a warning (doesn't affect exit code)
92
+ - `"error"` or `2` - turn the rule on as an error (exit code will be 1)
93
93
 
94
94
  The three error levels allow you fine-grained control over how ESLint applies rules (for more configuration options and details, see the [configuration docs](https://eslint.org/docs/latest/use/configure)).
95
95
 
@@ -109,10 +109,10 @@ ESLint adheres to the [OpenJS Foundation Code of Conduct](https://eslint.org/con
109
109
 
110
110
  Before filing an issue, please be sure to read the guidelines for what you're reporting:
111
111
 
112
- - [Bug Report](https://eslint.org/docs/latest/contribute/report-bugs)
113
- - [Propose a New Rule](https://eslint.org/docs/latest/contribute/propose-new-rule)
114
- - [Proposing a Rule Change](https://eslint.org/docs/latest/contribute/propose-rule-change)
115
- - [Request a Change](https://eslint.org/docs/latest/contribute/request-change)
112
+ - [Bug Report](https://eslint.org/docs/latest/contribute/report-bugs)
113
+ - [Propose a New Rule](https://eslint.org/docs/latest/contribute/propose-new-rule)
114
+ - [Proposing a Rule Change](https://eslint.org/docs/latest/contribute/propose-rule-change)
115
+ - [Request a Change](https://eslint.org/docs/latest/contribute/request-change)
116
116
 
117
117
  ## Frequently Asked Questions
118
118
 
@@ -174,32 +174,32 @@ ESLint takes security seriously. We work hard to ensure that ESLint is safe for
174
174
 
175
175
  ESLint follows [semantic versioning](https://semver.org). However, due to the nature of ESLint as a code quality tool, it's not always clear when a minor or major version bump occurs. To help clarify this for everyone, we've defined the following semantic versioning policy for ESLint:
176
176
 
177
- - Patch release (intended to not break your lint build)
178
- - A bug fix in a rule that results in ESLint reporting fewer linting errors.
179
- - A bug fix to the CLI or core (including formatters).
180
- - Improvements to documentation.
181
- - Non-user-facing changes such as refactoring code, adding, deleting, or modifying tests, and increasing test coverage.
182
- - Re-releasing after a failed release (i.e., publishing a release that doesn't work for anyone).
183
- - Minor release (might break your lint build)
184
- - A bug fix in a rule that results in ESLint reporting more linting errors.
185
- - A new rule is created.
186
- - A new option to an existing rule that does not result in ESLint reporting more linting errors by default.
187
- - A new addition to an existing rule to support a newly-added language feature (within the last 12 months) that will result in ESLint reporting more linting errors by default.
188
- - An existing rule is deprecated.
189
- - A new CLI capability is created.
190
- - New capabilities to the public API are added (new classes, new methods, new arguments to existing methods, etc.).
191
- - A new formatter is created.
192
- - `eslint:recommended` is updated and will result in strictly fewer linting errors (e.g., rule removals).
193
- - Major release (likely to break your lint build)
194
- - `eslint:recommended` is updated and may result in new linting errors (e.g., rule additions, most rule option updates).
195
- - A new option to an existing rule that results in ESLint reporting more linting errors by default.
196
- - An existing formatter is removed.
197
- - Part of the public API is removed or changed in an incompatible way. The public API includes:
198
- - Rule schemas
199
- - Configuration schema
200
- - Command-line options
201
- - Node.js API
202
- - Rule, formatter, parser, plugin APIs
177
+ - Patch release (intended to not break your lint build)
178
+ - A bug fix in a rule that results in ESLint reporting fewer linting errors.
179
+ - A bug fix to the CLI or core (including formatters).
180
+ - Improvements to documentation.
181
+ - Non-user-facing changes such as refactoring code, adding, deleting, or modifying tests, and increasing test coverage.
182
+ - Re-releasing after a failed release (i.e., publishing a release that doesn't work for anyone).
183
+ - Minor release (might break your lint build)
184
+ - A bug fix in a rule that results in ESLint reporting more linting errors.
185
+ - A new rule is created.
186
+ - A new option to an existing rule that does not result in ESLint reporting more linting errors by default.
187
+ - A new addition to an existing rule to support a newly-added language feature (within the last 12 months) that will result in ESLint reporting more linting errors by default.
188
+ - An existing rule is deprecated.
189
+ - A new CLI capability is created.
190
+ - New capabilities to the public API are added (new classes, new methods, new arguments to existing methods, etc.).
191
+ - A new formatter is created.
192
+ - `eslint:recommended` is updated and will result in strictly fewer linting errors (e.g., rule removals).
193
+ - Major release (likely to break your lint build)
194
+ - `eslint:recommended` is updated and may result in new linting errors (e.g., rule additions, most rule option updates).
195
+ - A new option to an existing rule that results in ESLint reporting more linting errors by default.
196
+ - An existing formatter is removed.
197
+ - Part of the public API is removed or changed in an incompatible way. The public API includes:
198
+ - Rule schemas
199
+ - Configuration schema
200
+ - Command-line options
201
+ - Node.js API
202
+ - Rule, formatter, parser, plugin APIs
203
203
 
204
204
  According to our policy, any minor update may report more linting errors than the previous release (ex: from a bug fix). As such, we recommend using the tilde (`~`) in `package.json` e.g. `"eslint": "~3.1.0"` to guarantee the results of your builds.
205
205
 
@@ -299,8 +299,8 @@ Amaresh S M
299
299
  </a>
300
300
  </td><td align="center" valign="top" width="11%">
301
301
  <a href="https://github.com/harish-sethuraman">
302
- <img src="https://github.com/harish-sethuraman.png?s=75" width="75" height="75" alt="Strek's Avatar"><br />
303
- Strek
302
+ <img src="https://github.com/harish-sethuraman.png?s=75" width="75" height="75" alt="Harish's Avatar"><br />
303
+ Harish
304
304
  </a>
305
305
  </td><td align="center" valign="top" width="11%">
306
306
  <a href="https://github.com/kecrily">
@@ -324,7 +324,7 @@ to get your logo on our READMEs and [website](https://eslint.org/sponsors).
324
324
  <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>
325
325
  <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>
326
326
  <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>
327
- <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://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://www.gitbook.com"><img src="https://avatars.githubusercontent.com/u/7111340" alt="GitBook" height="32"></a> <a href="https://nolebase.ayaka.io"><img src="https://avatars.githubusercontent.com/u/11081491" alt="Neko" 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>
327
+ <p><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://nolebase.ayaka.io"><img src="https://avatars.githubusercontent.com/u/11081491" alt="Neko" 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>
328
328
  <h3>Technology Sponsors</h3>
329
329
  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.
330
330
  <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/bin/eslint.js CHANGED
@@ -155,6 +155,25 @@ ${getErrorMessage(error)}`;
155
155
  return;
156
156
  }
157
157
 
158
+ // start the MCP server if `--mcp` is present
159
+ if (process.argv.includes("--mcp")) {
160
+ const { mcpServer } = require("../lib/mcp/mcp-server");
161
+ const {
162
+ StdioServerTransport,
163
+ } = require("@modelcontextprotocol/sdk/server/stdio.js");
164
+
165
+ await mcpServer.connect(new StdioServerTransport());
166
+
167
+ // Note: do not use console.log() because stdout is part of the server transport
168
+ console.error(`ESLint MCP server is running. cwd: ${process.cwd()}`);
169
+
170
+ process.on("SIGINT", () => {
171
+ mcpServer.close();
172
+ process.exitCode = 0;
173
+ });
174
+ return;
175
+ }
176
+
158
177
  // Otherwise, call the CLI.
159
178
  const cli = require("../lib/cli");
160
179
  const exitCode = await cli.execute(
@@ -63,10 +63,10 @@ const validFixTypes = new Set(["directive", "problem", "suggestion", "layout"]);
63
63
  /** @typedef {import("../shared/types").LintMessage} LintMessage */
64
64
  /** @typedef {import("../shared/types").SuppressedLintMessage} SuppressedLintMessage */
65
65
  /** @typedef {import("../shared/types").ParserOptions} ParserOptions */
66
- /** @typedef {import("../shared/types").Plugin} Plugin */
67
66
  /** @typedef {import("../shared/types").RuleConf} RuleConf */
68
67
  /** @typedef {import("../types").Rule.RuleModule} Rule */
69
68
  /** @typedef {import("../types").ESLint.FormatterFunction} FormatterFunction */
69
+ /** @typedef {import("../types").ESLint.Plugin} Plugin */
70
70
  /** @typedef {ReturnType<CascadingConfigArrayFactory.getConfigArrayForFile>} ConfigArray */
71
71
  /** @typedef {ReturnType<ConfigArray.extractConfig>} ExtractedConfig */
72
72
 
package/lib/cli.js CHANGED
@@ -44,8 +44,8 @@ const debug = require("debug")("eslint:cli");
44
44
  /** @typedef {import("./eslint/eslint").LintMessage} LintMessage */
45
45
  /** @typedef {import("./eslint/eslint").LintResult} LintResult */
46
46
  /** @typedef {import("./options").ParsedCLIOptions} ParsedCLIOptions */
47
- /** @typedef {import("./shared/types").Plugin} Plugin */
48
47
  /** @typedef {import("./shared/types").ResultsMeta} ResultsMeta */
48
+ /** @typedef {import("./types").ESLint.Plugin} Plugin */
49
49
 
50
50
  //------------------------------------------------------------------------------
51
51
  // Helpers
@@ -57,10 +57,11 @@ const { ConfigLoader, LegacyConfigLoader } = require("../config/config-loader");
57
57
  * @import { CLIEngineLintReport } from "./legacy-eslint.js";
58
58
  * @import { FlatConfigArray } from "../config/flat-config-array.js";
59
59
  * @import { RuleDefinition } from "@eslint/core";
60
- * @import { ConfigData, DeprecatedRuleInfo, LintMessage, LintResult, Plugin, ResultsMeta } from "../shared/types.js";
60
+ * @import { ConfigData, DeprecatedRuleInfo, LintMessage, LintResult, ResultsMeta } from "../shared/types.js";
61
61
  */
62
62
 
63
63
  /** @typedef {ReturnType<ConfigArray.extractConfig>} ExtractedConfig */
64
+ /** @typedef {import("../types").ESLint.Plugin} Plugin */
64
65
 
65
66
  /**
66
67
  * The options with which to configure the ESLint instance.
@@ -703,15 +704,11 @@ class ESLint {
703
704
  debug(`Deleting cache file at ${cacheFilePath}`);
704
705
 
705
706
  try {
706
- await fs.unlink(cacheFilePath);
707
+ if (existsSync(cacheFilePath)) {
708
+ await fs.unlink(cacheFilePath);
709
+ }
707
710
  } catch (error) {
708
- const errorCode = error && error.code;
709
-
710
- // Ignore errors when no such file exists or file system is read only (and cache file does not exist)
711
- if (
712
- errorCode !== "ENOENT" &&
713
- !(errorCode === "EROFS" && !existsSync(cacheFilePath))
714
- ) {
711
+ if (existsSync(cacheFilePath)) {
715
712
  throw error;
716
713
  }
717
714
  }
@@ -34,10 +34,10 @@ const { version } = require("../../package.json");
34
34
  /** @typedef {import("../shared/types").ConfigData} ConfigData */
35
35
  /** @typedef {import("../shared/types").LintMessage} LintMessage */
36
36
  /** @typedef {import("../shared/types").SuppressedLintMessage} SuppressedLintMessage */
37
- /** @typedef {import("../shared/types").Plugin} Plugin */
38
- /** @typedef {import("../types").Rule.RuleModule} Rule */
39
37
  /** @typedef {import("../shared/types").LintResult} LintResult */
40
38
  /** @typedef {import("../shared/types").ResultsMeta} ResultsMeta */
39
+ /** @typedef {import("../types").ESLint.Plugin} Plugin */
40
+ /** @typedef {import("../types").Rule.RuleModule} Rule */
41
41
 
42
42
  /**
43
43
  * The main formatter object.
@@ -25,6 +25,7 @@ const { LATEST_ECMA_VERSION } = require("../../../conf/ecma-version");
25
25
  /** @typedef {import("@eslint/core").File} File */
26
26
  /** @typedef {import("@eslint/core").Language} Language */
27
27
  /** @typedef {import("@eslint/core").OkParseResult} OkParseResult */
28
+ /** @typedef {import("../../types").Linter.LanguageOptions} JSLanguageOptions */
28
29
 
29
30
  //-----------------------------------------------------------------------------
30
31
  // Helpers
@@ -37,7 +38,7 @@ const parserSymbol = Symbol.for("eslint.RuleTester.parser");
37
38
  /**
38
39
  * Analyze scope of the given AST.
39
40
  * @param {ASTNode} ast The `Program` node to analyze.
40
- * @param {LanguageOptions} languageOptions The parser options.
41
+ * @param {JSLanguageOptions} languageOptions The parser options.
41
42
  * @param {Record<string, string[]>} visitorKeys The visitor keys.
42
43
  * @returns {ScopeManager} The analysis result.
43
44
  */
@@ -230,7 +231,7 @@ module.exports = {
230
231
  * Parses the given file into an AST.
231
232
  * @param {File} file The virtual file to parse.
232
233
  * @param {Object} options Additional options passed from ESLint.
233
- * @param {LanguageOptions} options.languageOptions The language options.
234
+ * @param {JSLanguageOptions} options.languageOptions The language options.
234
235
  * @returns {Object} The result of parsing.
235
236
  */
236
237
  parse(file, { languageOptions }) {
@@ -309,7 +310,7 @@ module.exports = {
309
310
  * @param {File} file The virtual file to create a `SourceCode` object from.
310
311
  * @param {OkParseResult} parseResult The result returned from `parse()`.
311
312
  * @param {Object} options Additional options passed from ESLint.
312
- * @param {LanguageOptions} options.languageOptions The language options.
313
+ * @param {JSLanguageOptions} options.languageOptions The language options.
313
314
  * @returns {SourceCode} The new `SourceCode` object.
314
315
  */
315
316
  createSourceCode(file, parseResult, { languageOptions }) {
@@ -0,0 +1,329 @@
1
+ /**
2
+ * @fileoverview ESQuery wrapper for ESLint.
3
+ * @author Nicholas C. Zakas
4
+ */
5
+
6
+ "use strict";
7
+
8
+ //------------------------------------------------------------------------------
9
+ // Requirements
10
+ //------------------------------------------------------------------------------
11
+
12
+ const esquery = require("esquery");
13
+
14
+ //-----------------------------------------------------------------------------
15
+ // Typedefs
16
+ //-----------------------------------------------------------------------------
17
+
18
+ /**
19
+ * @typedef {import("esquery").Selector} ESQuerySelector
20
+ * @typedef {import("esquery").ESQueryOptions} ESQueryOptions
21
+ */
22
+
23
+ //------------------------------------------------------------------------------
24
+ // Classes
25
+ //------------------------------------------------------------------------------
26
+
27
+ /**
28
+ * The result of parsing and analyzing an ESQuery selector.
29
+ */
30
+ class ESQueryParsedSelector {
31
+ /**
32
+ * The raw selector string that was parsed
33
+ * @type {string}
34
+ */
35
+ source;
36
+
37
+ /**
38
+ * Whether this selector is an exit selector
39
+ * @type {boolean}
40
+ */
41
+ isExit;
42
+
43
+ /**
44
+ * An object (from esquery) describing the matching behavior of the selector
45
+ * @type {ESQuerySelector}
46
+ */
47
+ root;
48
+
49
+ /**
50
+ * The node types that could possibly trigger this selector, or `null` if all node types could trigger it
51
+ * @type {string[]|null}
52
+ */
53
+ nodeTypes;
54
+
55
+ /**
56
+ * The number of class, pseudo-class, and attribute queries in this selector
57
+ * @type {number}
58
+ */
59
+ attributeCount;
60
+
61
+ /**
62
+ * The number of identifier queries in this selector
63
+ * @type {number}
64
+ */
65
+ identifierCount;
66
+
67
+ /**
68
+ * Creates a new parsed selector.
69
+ * @param {string} source The raw selector string that was parsed
70
+ * @param {boolean} isExit Whether this selector is an exit selector
71
+ * @param {ESQuerySelector} root An object (from esquery) describing the matching behavior of the selector
72
+ * @param {string[]|null} nodeTypes The node types that could possibly trigger this selector, or `null` if all node types could trigger it
73
+ * @param {number} attributeCount The number of class, pseudo-class, and attribute queries in this selector
74
+ * @param {number} identifierCount The number of identifier queries in this selector
75
+ */
76
+ constructor(
77
+ source,
78
+ isExit,
79
+ root,
80
+ nodeTypes,
81
+ attributeCount,
82
+ identifierCount,
83
+ ) {
84
+ this.source = source;
85
+ this.isExit = isExit;
86
+ this.root = root;
87
+ this.nodeTypes = nodeTypes;
88
+ this.attributeCount = attributeCount;
89
+ this.identifierCount = identifierCount;
90
+ }
91
+
92
+ /**
93
+ * Compares this selector's specifity to another selector for sorting purposes.
94
+ * @param {ESQueryParsedSelector} otherSelector The selector to compare against
95
+ * @returns {number}
96
+ * a value less than 0 if this selector is less specific than otherSelector
97
+ * a value greater than 0 if this selector is more specific than otherSelector
98
+ * a value less than 0 if this selector and otherSelector have the same specificity, and this selector <= otherSelector alphabetically
99
+ * a value greater than 0 if this selector and otherSelector have the same specificity, and this selector > otherSelector alphabetically
100
+ */
101
+ compare(otherSelector) {
102
+ return (
103
+ this.attributeCount - otherSelector.attributeCount ||
104
+ this.identifierCount - otherSelector.identifierCount ||
105
+ (this.source <= otherSelector.source ? -1 : 1)
106
+ );
107
+ }
108
+ }
109
+
110
+ //------------------------------------------------------------------------------
111
+ // Helpers
112
+ //------------------------------------------------------------------------------
113
+
114
+ const selectorCache = new Map();
115
+
116
+ /**
117
+ * Computes the union of one or more arrays
118
+ * @param {...any[]} arrays One or more arrays to union
119
+ * @returns {any[]} The union of the input arrays
120
+ */
121
+ function union(...arrays) {
122
+ return [...new Set(arrays.flat())];
123
+ }
124
+
125
+ /**
126
+ * Computes the intersection of one or more arrays
127
+ * @param {...any[]} arrays One or more arrays to intersect
128
+ * @returns {any[]} The intersection of the input arrays
129
+ */
130
+ function intersection(...arrays) {
131
+ if (arrays.length === 0) {
132
+ return [];
133
+ }
134
+
135
+ let result = [...new Set(arrays[0])];
136
+
137
+ for (const array of arrays.slice(1)) {
138
+ result = result.filter(x => array.includes(x));
139
+ }
140
+ return result;
141
+ }
142
+
143
+ /**
144
+ * Analyzes a parsed selector and returns combined data about it
145
+ * @param {ESQuerySelector} parsedSelector An object (from esquery) describing the matching behavior of the selector
146
+ * @returns {{nodeTypes:string[]|null, attributeCount:number, identifierCount:number}} Object containing selector data.
147
+ */
148
+ function analyzeParsedSelector(parsedSelector) {
149
+ let attributeCount = 0;
150
+ let identifierCount = 0;
151
+
152
+ /**
153
+ * Analyzes a selector and returns the node types that could possibly trigger it.
154
+ * @param {ESQuerySelector} selector The selector to analyze.
155
+ * @returns {string[]|null} The node types that could possibly trigger this selector, or `null` if all node types could trigger it
156
+ */
157
+ function analyzeSelector(selector) {
158
+ switch (selector.type) {
159
+ case "identifier":
160
+ identifierCount++;
161
+ return [selector.value];
162
+
163
+ case "not":
164
+ selector.selectors.map(analyzeSelector);
165
+ return null;
166
+
167
+ case "matches": {
168
+ const typesForComponents =
169
+ selector.selectors.map(analyzeSelector);
170
+
171
+ if (typesForComponents.every(Boolean)) {
172
+ return union(...typesForComponents);
173
+ }
174
+ return null;
175
+ }
176
+
177
+ case "compound": {
178
+ const typesForComponents = selector.selectors
179
+ .map(analyzeSelector)
180
+ .filter(typesForComponent => typesForComponent);
181
+
182
+ // If all of the components could match any type, then the compound could also match any type.
183
+ if (!typesForComponents.length) {
184
+ return null;
185
+ }
186
+
187
+ /*
188
+ * If at least one of the components could only match a particular type, the compound could only match
189
+ * the intersection of those types.
190
+ */
191
+ return intersection(...typesForComponents);
192
+ }
193
+
194
+ case "attribute":
195
+ case "field":
196
+ case "nth-child":
197
+ case "nth-last-child":
198
+ attributeCount++;
199
+ return null;
200
+
201
+ case "child":
202
+ case "descendant":
203
+ case "sibling":
204
+ case "adjacent":
205
+ analyzeSelector(selector.left);
206
+ return analyzeSelector(selector.right);
207
+
208
+ case "class":
209
+ // TODO: abstract into JSLanguage somehow
210
+ if (selector.name === "function") {
211
+ return [
212
+ "FunctionDeclaration",
213
+ "FunctionExpression",
214
+ "ArrowFunctionExpression",
215
+ ];
216
+ }
217
+ return null;
218
+
219
+ default:
220
+ return null;
221
+ }
222
+ }
223
+
224
+ const nodeTypes = analyzeSelector(parsedSelector);
225
+
226
+ return {
227
+ nodeTypes,
228
+ attributeCount,
229
+ identifierCount,
230
+ };
231
+ }
232
+
233
+ /**
234
+ * Tries to parse a simple selector string, such as a single identifier or wildcard.
235
+ * This saves time by avoiding the overhead of esquery parsing for simple cases.
236
+ * @param {string} selector The selector string to parse.
237
+ * @returns {Object|null} An object describing the selector if it is simple, or `null` if it is not.
238
+ */
239
+ function trySimpleParseSelector(selector) {
240
+ if (selector === "*") {
241
+ return {
242
+ type: "wildcard",
243
+ value: "*",
244
+ };
245
+ }
246
+
247
+ if (/^[a-z]+$/iu.test(selector)) {
248
+ return {
249
+ type: "identifier",
250
+ value: selector,
251
+ };
252
+ }
253
+
254
+ return null;
255
+ }
256
+
257
+ /**
258
+ * Parses a raw selector string, and throws a useful error if parsing fails.
259
+ * @param {string} selector The selector string to parse.
260
+ * @returns {Object} An object (from esquery) describing the matching behavior of this selector
261
+ * @throws {Error} An error if the selector is invalid
262
+ */
263
+ function tryParseSelector(selector) {
264
+ try {
265
+ return esquery.parse(selector);
266
+ } catch (err) {
267
+ if (
268
+ err.location &&
269
+ err.location.start &&
270
+ typeof err.location.start.offset === "number"
271
+ ) {
272
+ throw new SyntaxError(
273
+ `Syntax error in selector "${selector}" at position ${err.location.start.offset}: ${err.message}`,
274
+ );
275
+ }
276
+ throw err;
277
+ }
278
+ }
279
+
280
+ /**
281
+ * Parses a raw selector string, and returns the parsed selector along with specificity and type information.
282
+ * @param {string} source A raw AST selector
283
+ * @returns {ESQueryParsedSelector} A selector descriptor
284
+ */
285
+ function parse(source) {
286
+ if (selectorCache.has(source)) {
287
+ return selectorCache.get(source);
288
+ }
289
+
290
+ const cleanSource = source.replace(/:exit$/u, "");
291
+ const parsedSelector =
292
+ trySimpleParseSelector(cleanSource) ?? tryParseSelector(cleanSource);
293
+ const { nodeTypes, attributeCount, identifierCount } =
294
+ analyzeParsedSelector(parsedSelector);
295
+
296
+ const result = new ESQueryParsedSelector(
297
+ source,
298
+ source.endsWith(":exit"),
299
+ parsedSelector,
300
+ nodeTypes,
301
+ attributeCount,
302
+ identifierCount,
303
+ );
304
+
305
+ selectorCache.set(source, result);
306
+ return result;
307
+ }
308
+
309
+ /**
310
+ * Checks if a node matches a given selector.
311
+ * @param {Object} node The node to check against the selector.
312
+ * @param {ESQuerySelector} root The root of the selector to match against.
313
+ * @param {Object[]} ancestry The ancestry of the node being checked, which is an array of nodes from the current node to the root.
314
+ * @param {ESQueryOptions} options The options to use for matching.
315
+ * @returns {boolean} `true` if the node matches the selector, `false` otherwise.
316
+ */
317
+ function matches(node, root, ancestry, options) {
318
+ return esquery.matches(node, root, ancestry, options);
319
+ }
320
+
321
+ //-----------------------------------------------------------------------------
322
+ // Exports
323
+ //-----------------------------------------------------------------------------
324
+
325
+ module.exports = {
326
+ parse,
327
+ matches,
328
+ ESQueryParsedSelector,
329
+ };
@@ -73,20 +73,19 @@ const STEP_KIND_CALL = 2;
73
73
  // Typedefs
74
74
  //------------------------------------------------------------------------------
75
75
 
76
+ /** @import { Language, LanguageOptions, RuleConfig, RuleDefinition, RuleSeverity } from "@eslint/core" */
77
+
76
78
  /** @typedef {import("../shared/types").ConfigData} ConfigData */
77
79
  /** @typedef {import("../shared/types").Environment} Environment */
78
80
  /** @typedef {import("../shared/types").GlobalConf} GlobalConf */
79
81
  /** @typedef {import("../shared/types").LintMessage} LintMessage */
80
82
  /** @typedef {import("../shared/types").SuppressedLintMessage} SuppressedLintMessage */
81
83
  /** @typedef {import("../shared/types").ParserOptions} ParserOptions */
82
- /** @typedef {import("../shared/types").LanguageOptions} LanguageOptions */
83
84
  /** @typedef {import("../shared/types").Processor} Processor */
84
- /** @typedef {import("../types").Rule.RuleModule} Rule */
85
85
  /** @typedef {import("../shared/types").Times} Times */
86
- /** @typedef {import("@eslint/core").Language} Language */
87
- /** @typedef {import("@eslint/core").RuleSeverity} RuleSeverity */
88
- /** @typedef {import("@eslint/core").RuleConfig} RuleConfig */
86
+ /** @typedef {import("../types").Linter.LanguageOptions} JSLanguageOptions */
89
87
  /** @typedef {import("../types").Linter.StringSeverity} StringSeverity */
88
+ /** @typedef {import("../types").Rule.RuleModule} Rule */
90
89
 
91
90
  /* eslint-disable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */
92
91
  /**
@@ -1011,7 +1010,7 @@ function resolveParserOptions(parser, providedOptions, enabledEnvironments) {
1011
1010
  * @param {Object} config.globals Global variable definitions.
1012
1011
  * @param {Parser} config.parser The parser to use.
1013
1012
  * @param {ParserOptions} config.parserOptions The parserOptions to use.
1014
- * @returns {LanguageOptions} The languageOptions equivalent.
1013
+ * @returns {JSLanguageOptions} The languageOptions equivalent.
1015
1014
  */
1016
1015
  function createLanguageOptions({
1017
1016
  globals: configuredGlobals,
@@ -1091,7 +1090,7 @@ function getRuleOptions(ruleConfig, defaultOptions) {
1091
1090
  /**
1092
1091
  * Analyze scope of the given AST.
1093
1092
  * @param {ASTNode} ast The `Program` node to analyze.
1094
- * @param {LanguageOptions} languageOptions The parser options.
1093
+ * @param {JSLanguageOptions} languageOptions The language options.
1095
1094
  * @param {Record<string, string[]>} visitorKeys The visitor keys.
1096
1095
  * @returns {ScopeManager} The analysis result.
1097
1096
  */
@@ -1113,7 +1112,7 @@ function analyzeScope(ast, languageOptions, visitorKeys) {
1113
1112
 
1114
1113
  /**
1115
1114
  * Runs a rule, and gets its listeners
1116
- * @param {Rule} rule A rule object
1115
+ * @param {RuleDefinition} rule A rule object
1117
1116
  * @param {Context} ruleContext The context that should be passed to the rule
1118
1117
  * @throws {TypeError} If `rule` is not an object with a `create` method
1119
1118
  * @throws {any} Any error during the rule's `create`
@@ -1142,7 +1141,7 @@ function createRuleListeners(rule, ruleContext) {
1142
1141
  * Runs the given rules on the given SourceCode object
1143
1142
  * @param {SourceCode} sourceCode A SourceCode object for the given text
1144
1143
  * @param {Object} configuredRules The rules configuration
1145
- * @param {function(string): Rule} ruleMapper A mapper function from rule names to rules
1144
+ * @param {function(string): RuleDefinition} ruleMapper A mapper function from rule names to rules
1146
1145
  * @param {string | undefined} parserName The name of the parser in the config
1147
1146
  * @param {Language} language The language object used for parsing.
1148
1147
  * @param {LanguageOptions} languageOptions The options for parsing the code.