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 +39 -39
- package/bin/eslint.js +19 -0
- package/lib/cli-engine/cli-engine.js +1 -1
- package/lib/cli.js +1 -1
- package/lib/eslint/eslint.js +6 -9
- package/lib/eslint/legacy-eslint.js +2 -2
- package/lib/languages/js/index.js +4 -3
- package/lib/linter/esquery.js +329 -0
- package/lib/linter/linter.js +8 -9
- package/lib/linter/node-event-generator.js +94 -251
- package/lib/mcp/mcp-server.js +66 -0
- package/lib/options.js +11 -0
- package/lib/rule-tester/rule-tester.js +14 -6
- package/lib/rules/eqeqeq.js +31 -8
- package/lib/rules/index.js +1 -1
- package/lib/rules/no-shadow-restricted-names.js +25 -2
- package/lib/rules/no-unused-expressions.js +64 -1
- package/lib/rules/utils/ast-utils.js +3 -2
- package/lib/rules/utils/lazy-loading-rule-map.js +2 -2
- package/lib/services/processor-service.js +0 -1
- package/lib/shared/serialization.js +29 -6
- package/lib/shared/types.js +0 -17
- package/lib/types/index.d.ts +4 -2
- package/lib/types/rules.d.ts +14 -1
- package/package.json +7 -4
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
|
-
-
|
24
|
-
-
|
25
|
-
-
|
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
|
-
-
|
91
|
-
-
|
92
|
-
-
|
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
|
-
-
|
113
|
-
-
|
114
|
-
-
|
115
|
-
-
|
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
|
-
-
|
178
|
-
-
|
179
|
-
-
|
180
|
-
-
|
181
|
-
-
|
182
|
-
-
|
183
|
-
-
|
184
|
-
-
|
185
|
-
-
|
186
|
-
-
|
187
|
-
-
|
188
|
-
-
|
189
|
-
-
|
190
|
-
-
|
191
|
-
-
|
192
|
-
-
|
193
|
-
-
|
194
|
-
-
|
195
|
-
-
|
196
|
-
-
|
197
|
-
-
|
198
|
-
-
|
199
|
-
-
|
200
|
-
-
|
201
|
-
-
|
202
|
-
-
|
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="
|
303
|
-
|
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://
|
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
|
package/lib/eslint/eslint.js
CHANGED
@@ -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,
|
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
|
-
|
707
|
+
if (existsSync(cacheFilePath)) {
|
708
|
+
await fs.unlink(cacheFilePath);
|
709
|
+
}
|
707
710
|
} catch (error) {
|
708
|
-
|
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 {
|
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 {
|
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 {
|
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
|
+
};
|
package/lib/linter/linter.js
CHANGED
@@ -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("
|
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 {
|
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 {
|
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 {
|
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):
|
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.
|