forked from public/fvtt-cthulhu-eternal
Initial import with skill sheet working
This commit is contained in:
19
node_modules/eslint/LICENSE
generated
vendored
Normal file
19
node_modules/eslint/LICENSE
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright OpenJS Foundation and other contributors, <www.openjsf.org>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
310
node_modules/eslint/README.md
generated
vendored
Normal file
310
node_modules/eslint/README.md
generated
vendored
Normal file
@ -0,0 +1,310 @@
|
||||
[](https://www.npmjs.com/package/eslint)
|
||||
[](https://www.npmjs.com/package/eslint)
|
||||
[](https://github.com/eslint/eslint/actions)
|
||||
[](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Feslint%2Feslint?ref=badge_shield)
|
||||
<br />
|
||||
[](https://opencollective.com/eslint)
|
||||
[](https://opencollective.com/eslint)
|
||||
[](https://twitter.com/intent/user?screen_name=geteslint)
|
||||
|
||||
# ESLint
|
||||
|
||||
[Website](https://eslint.org) |
|
||||
[Configure ESLint](https://eslint.org/docs/latest/use/configure) |
|
||||
[Rules](https://eslint.org/docs/rules/) |
|
||||
[Contribute to ESLint](https://eslint.org/docs/latest/contribute) |
|
||||
[Report Bugs](https://eslint.org/docs/latest/contribute/report-bugs) |
|
||||
[Code of Conduct](https://eslint.org/conduct) |
|
||||
[Twitter](https://twitter.com/geteslint) |
|
||||
[Discord](https://eslint.org/chat) |
|
||||
[Mastodon](https://fosstodon.org/@eslint)
|
||||
|
||||
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:
|
||||
|
||||
* ESLint uses [Espree](https://github.com/eslint/js/tree/main/packages/espree) for JavaScript parsing.
|
||||
* ESLint uses an AST to evaluate patterns in code.
|
||||
* ESLint is completely pluggable, every single rule is a plugin and you can add more at runtime.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Installation and Usage](#installation-and-usage)
|
||||
1. [Configuration](#configuration)
|
||||
1. [Version Support](#version-support)
|
||||
1. [Code of Conduct](#code-of-conduct)
|
||||
1. [Filing Issues](#filing-issues)
|
||||
1. [Frequently Asked Questions](#frequently-asked-questions)
|
||||
1. [Releases](#releases)
|
||||
1. [Security Policy](#security-policy)
|
||||
1. [Semantic Versioning Policy](#semantic-versioning-policy)
|
||||
1. [Stylistic Rule Updates](#stylistic-rule-updates)
|
||||
1. [License](#license)
|
||||
1. [Team](#team)
|
||||
1. [Sponsors](#sponsors)
|
||||
1. [Technology Sponsors](#technology-sponsors) <!-- markdownlint-disable-line MD051 -->
|
||||
|
||||
## Installation and Usage
|
||||
|
||||
Prerequisites: [Node.js](https://nodejs.org/) (`^18.18.0`, `^20.9.0`, or `>=21.1.0`) built with SSL support. (If you are using an official Node.js distribution, SSL is always built in.)
|
||||
|
||||
You can install and configure ESLint using this command:
|
||||
|
||||
```shell
|
||||
npm init @eslint/config@latest
|
||||
```
|
||||
|
||||
After that, you can run ESLint on any file or directory like this:
|
||||
|
||||
```shell
|
||||
npx eslint yourfile.js
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
You can configure rules in your `eslint.config.js` files as in this example:
|
||||
|
||||
```js
|
||||
export default [
|
||||
{
|
||||
files: ["**/*.js", "**/*.cjs", "**/*.mjs"],
|
||||
rules: {
|
||||
"prefer-const": "warn",
|
||||
"no-constant-binary-expression": "error"
|
||||
}
|
||||
}
|
||||
];
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
* `"off"` or `0` - turn the rule off
|
||||
* `"warn"` or `1` - turn the rule on as a warning (doesn't affect exit code)
|
||||
* `"error"` or `2` - turn the rule on as an error (exit code will be 1)
|
||||
|
||||
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)).
|
||||
|
||||
## Version Support
|
||||
|
||||
The ESLint team provides ongoing support for the current version and six months of limited support for the previous version. Limited support includes critical bug fixes, security issues, and compatibility issues only.
|
||||
|
||||
ESLint offers commercial support for both current and previous versions through our partners, [Tidelift][tidelift] and [HeroDevs][herodevs].
|
||||
|
||||
See [Version Support](https://eslint.org/version-support) for more details.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
ESLint adheres to the [OpenJS Foundation Code of Conduct](https://eslint.org/conduct).
|
||||
|
||||
## Filing Issues
|
||||
|
||||
Before filing an issue, please be sure to read the guidelines for what you're reporting:
|
||||
|
||||
* [Bug Report](https://eslint.org/docs/latest/contribute/report-bugs)
|
||||
* [Propose a New Rule](https://eslint.org/docs/latest/contribute/propose-new-rule)
|
||||
* [Proposing a Rule Change](https://eslint.org/docs/latest/contribute/propose-rule-change)
|
||||
* [Request a Change](https://eslint.org/docs/latest/contribute/request-change)
|
||||
|
||||
## Frequently Asked Questions
|
||||
|
||||
### Does ESLint support JSX?
|
||||
|
||||
Yes, ESLint natively supports parsing JSX syntax (this must be enabled in [configuration](https://eslint.org/docs/latest/use/configure)). Please note that supporting JSX syntax *is not* the same as supporting React. React applies specific semantics to JSX syntax that ESLint doesn't recognize. We recommend using [eslint-plugin-react](https://www.npmjs.com/package/eslint-plugin-react) if you are using React and want React semantics.
|
||||
|
||||
### Does Prettier replace ESLint?
|
||||
|
||||
No, ESLint and Prettier have different jobs: ESLint is a linter (looking for problematic patterns) and Prettier is a code formatter. Using both tools is common, refer to [Prettier's documentation](https://prettier.io/docs/en/install#eslint-and-other-linters) to learn how to configure them to work well with each other.
|
||||
|
||||
### What ECMAScript versions does ESLint support?
|
||||
|
||||
ESLint has full support for ECMAScript 3, 5, and every year from 2015 up until the most recent stage 4 specification (the default). You can set your desired ECMAScript syntax and other settings (like global variables) through [configuration](https://eslint.org/docs/latest/use/configure).
|
||||
|
||||
### What about experimental features?
|
||||
|
||||
ESLint's parser only officially supports the latest final ECMAScript standard. We will make changes to core rules in order to avoid crashes on stage 3 ECMAScript syntax proposals (as long as they are implemented using the correct experimental ESTree syntax). We may make changes to core rules to better work with language extensions (such as JSX, Flow, and TypeScript) on a case-by-case basis.
|
||||
|
||||
In other cases (including if rules need to warn on more or fewer cases due to new syntax, rather than just not crashing), we recommend you use other parsers and/or rule plugins. If you are using Babel, you can use [@babel/eslint-parser](https://www.npmjs.com/package/@babel/eslint-parser) and [@babel/eslint-plugin](https://www.npmjs.com/package/@babel/eslint-plugin) to use any option available in Babel.
|
||||
|
||||
Once a language feature has been adopted into the ECMAScript standard (stage 4 according to the [TC39 process](https://tc39.github.io/process-document/)), we will accept issues and pull requests related to the new feature, subject to our [contributing guidelines](https://eslint.org/docs/latest/contribute). Until then, please use the appropriate parser and plugin(s) for your experimental feature.
|
||||
|
||||
### Which Node.js versions does ESLint support?
|
||||
|
||||
ESLint updates the supported Node.js versions with each major release of ESLint. At that time, ESLint's supported Node.js versions are updated to be:
|
||||
|
||||
1. The most recent maintenance release of Node.js
|
||||
1. The lowest minor version of the Node.js LTS release that includes the features the ESLint team wants to use.
|
||||
1. The Node.js Current release
|
||||
|
||||
ESLint is also expected to work with Node.js versions released after the Node.js Current release.
|
||||
|
||||
Refer to the [Quick Start Guide](https://eslint.org/docs/latest/use/getting-started#prerequisites) for the officially supported Node.js versions for a given ESLint release.
|
||||
|
||||
### Where to ask for help?
|
||||
|
||||
Open a [discussion](https://github.com/eslint/eslint/discussions) or stop by our [Discord server](https://eslint.org/chat).
|
||||
|
||||
### Why doesn't ESLint lock dependency versions?
|
||||
|
||||
Lock files like `package-lock.json` are helpful for deployed applications. They ensure that dependencies are consistent between environments and across deployments.
|
||||
|
||||
Packages like `eslint` that get published to the npm registry do not include lock files. `npm install eslint` as a user will respect version constraints in ESLint's `package.json`. ESLint and its dependencies will be included in the user's lock file if one exists, but ESLint's own lock file would not be used.
|
||||
|
||||
We intentionally don't lock dependency versions so that we have the latest compatible dependency versions in development and CI that our users get when installing ESLint in a project.
|
||||
|
||||
The Twilio blog has a [deeper dive](https://www.twilio.com/blog/lockfiles-nodejs) to learn more.
|
||||
|
||||
## Releases
|
||||
|
||||
We have scheduled releases every two weeks on Friday or Saturday. You can follow a [release issue](https://github.com/eslint/eslint/issues?q=is%3Aopen+is%3Aissue+label%3Arelease) for updates about the scheduling of any particular release.
|
||||
|
||||
## Security Policy
|
||||
|
||||
ESLint takes security seriously. We work hard to ensure that ESLint is safe for everyone and that security issues are addressed quickly and responsibly. Read the full [security policy](https://github.com/eslint/.github/blob/master/SECURITY.md).
|
||||
|
||||
## Semantic Versioning Policy
|
||||
|
||||
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:
|
||||
|
||||
* Patch release (intended to not break your lint build)
|
||||
* A bug fix in a rule that results in ESLint reporting fewer linting errors.
|
||||
* A bug fix to the CLI or core (including formatters).
|
||||
* Improvements to documentation.
|
||||
* Non-user-facing changes such as refactoring code, adding, deleting, or modifying tests, and increasing test coverage.
|
||||
* Re-releasing after a failed release (i.e., publishing a release that doesn't work for anyone).
|
||||
* Minor release (might break your lint build)
|
||||
* A bug fix in a rule that results in ESLint reporting more linting errors.
|
||||
* A new rule is created.
|
||||
* A new option to an existing rule that does not result in ESLint reporting more linting errors by default.
|
||||
* 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.
|
||||
* An existing rule is deprecated.
|
||||
* A new CLI capability is created.
|
||||
* New capabilities to the public API are added (new classes, new methods, new arguments to existing methods, etc.).
|
||||
* A new formatter is created.
|
||||
* `eslint:recommended` is updated and will result in strictly fewer linting errors (e.g., rule removals).
|
||||
* Major release (likely to break your lint build)
|
||||
* `eslint:recommended` is updated and may result in new linting errors (e.g., rule additions, most rule option updates).
|
||||
* A new option to an existing rule that results in ESLint reporting more linting errors by default.
|
||||
* An existing formatter is removed.
|
||||
* Part of the public API is removed or changed in an incompatible way. The public API includes:
|
||||
* Rule schemas
|
||||
* Configuration schema
|
||||
* Command-line options
|
||||
* Node.js API
|
||||
* Rule, formatter, parser, plugin APIs
|
||||
|
||||
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.
|
||||
|
||||
## Stylistic Rule Updates
|
||||
|
||||
Stylistic rules are frozen according to [our policy](https://eslint.org/blog/2020/05/changes-to-rules-policies) on how we evaluate new rules and rule changes.
|
||||
This means:
|
||||
|
||||
* **Bug fixes**: We will still fix bugs in stylistic rules.
|
||||
* **New ECMAScript features**: We will also make sure stylistic rules are compatible with new ECMAScript features.
|
||||
* **New options**: We will **not** add any new options to stylistic rules unless an option is the only way to fix a bug or support a newly-added ECMAScript feature.
|
||||
|
||||
## License
|
||||
|
||||
[](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Feslint%2Feslint?ref=badge_large)
|
||||
|
||||
## Team
|
||||
|
||||
These folks keep the project moving and are resources for help.
|
||||
|
||||
<!-- NOTE: This section is autogenerated. Do not manually edit.-->
|
||||
|
||||
<!--teamstart-->
|
||||
|
||||
### Technical Steering Committee (TSC)
|
||||
|
||||
The people who manage releases, review feature requests, and meet regularly to ensure ESLint is properly maintained.
|
||||
|
||||
<table><tbody><tr><td align="center" valign="top" width="11%">
|
||||
<a href="https://github.com/nzakas">
|
||||
<img src="https://github.com/nzakas.png?s=75" width="75" height="75" alt="Nicholas C. Zakas's Avatar"><br />
|
||||
Nicholas C. Zakas
|
||||
</a>
|
||||
</td><td align="center" valign="top" width="11%">
|
||||
<a href="https://github.com/fasttime">
|
||||
<img src="https://github.com/fasttime.png?s=75" width="75" height="75" alt="Francesco Trotta's Avatar"><br />
|
||||
Francesco Trotta
|
||||
</a>
|
||||
</td><td align="center" valign="top" width="11%">
|
||||
<a href="https://github.com/mdjermanovic">
|
||||
<img src="https://github.com/mdjermanovic.png?s=75" width="75" height="75" alt="Milos Djermanovic's Avatar"><br />
|
||||
Milos Djermanovic
|
||||
</a>
|
||||
</td></tr></tbody></table>
|
||||
|
||||
### Reviewers
|
||||
|
||||
The people who review and implement new features.
|
||||
|
||||
<table><tbody><tr><td align="center" valign="top" width="11%">
|
||||
<a href="https://github.com/aladdin-add">
|
||||
<img src="https://github.com/aladdin-add.png?s=75" width="75" height="75" alt="唯然's Avatar"><br />
|
||||
唯然
|
||||
</a>
|
||||
</td><td align="center" valign="top" width="11%">
|
||||
<a href="https://github.com/snitin315">
|
||||
<img src="https://github.com/snitin315.png?s=75" width="75" height="75" alt="Nitin Kumar's Avatar"><br />
|
||||
Nitin Kumar
|
||||
</a>
|
||||
</td></tr></tbody></table>
|
||||
|
||||
### Committers
|
||||
|
||||
The people who review and fix bugs and help triage issues.
|
||||
|
||||
<table><tbody><tr><td align="center" valign="top" width="11%">
|
||||
<a href="https://github.com/JoshuaKGoldberg">
|
||||
<img src="https://github.com/JoshuaKGoldberg.png?s=75" width="75" height="75" alt="Josh Goldberg ✨'s Avatar"><br />
|
||||
Josh Goldberg ✨
|
||||
</a>
|
||||
</td><td align="center" valign="top" width="11%">
|
||||
<a href="https://github.com/Tanujkanti4441">
|
||||
<img src="https://github.com/Tanujkanti4441.png?s=75" width="75" height="75" alt="Tanuj Kanti's Avatar"><br />
|
||||
Tanuj Kanti
|
||||
</a>
|
||||
</td></tr></tbody></table>
|
||||
|
||||
### Website Team
|
||||
|
||||
Team members who focus specifically on eslint.org
|
||||
|
||||
<table><tbody><tr><td align="center" valign="top" width="11%">
|
||||
<a href="https://github.com/amareshsm">
|
||||
<img src="https://github.com/amareshsm.png?s=75" width="75" height="75" alt="Amaresh S M's Avatar"><br />
|
||||
Amaresh S M
|
||||
</a>
|
||||
</td><td align="center" valign="top" width="11%">
|
||||
<a href="https://github.com/harish-sethuraman">
|
||||
<img src="https://github.com/harish-sethuraman.png?s=75" width="75" height="75" alt="Strek's Avatar"><br />
|
||||
Strek
|
||||
</a>
|
||||
</td><td align="center" valign="top" width="11%">
|
||||
<a href="https://github.com/kecrily">
|
||||
<img src="https://github.com/kecrily.png?s=75" width="75" height="75" alt="Percy Ma's Avatar"><br />
|
||||
Percy Ma
|
||||
</a>
|
||||
</td></tr></tbody></table>
|
||||
|
||||
<!--teamend-->
|
||||
|
||||
<!-- NOTE: This section is autogenerated. Do not manually edit.-->
|
||||
<!--sponsorsstart-->
|
||||
## Sponsors
|
||||
|
||||
The following companies, organizations, and individuals support ESLint's ongoing maintenance and development. [Become a Sponsor](https://eslint.org/donate)
|
||||
to get your logo on our READMEs and [website](https://eslint.org/sponsors).
|
||||
|
||||
<h3>Platinum Sponsors</h3>
|
||||
<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>
|
||||
<p><a href="https://trunk.io/"><img src="https://images.opencollective.com/trunkio/fb92d60/avatar.png" alt="trunk.io" height="96"></a></p><h3>Silver Sponsors</h3>
|
||||
<p><a href="https://www.serptriumph.com/"><img src="https://images.opencollective.com/serp-triumph5/fea3074/logo.png" alt="SERP Triumph" height="64"></a> <a href="https://www.jetbrains.com/"><img src="https://images.opencollective.com/jetbrains/fe76f99/logo.png" alt="JetBrains" height="64"></a> <a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/5c4fa84/logo.png" alt="Liftoff" height="64"></a> <a href="https://americanexpress.io"><img src="https://avatars.githubusercontent.com/u/3853301?v=4" alt="American Express" height="64"></a> <a href="https://www.workleap.com"><img src="https://avatars.githubusercontent.com/u/53535748?u=d1e55d7661d724bf2281c1bfd33cb8f99fe2465f&v=4" alt="Workleap" height="64"></a></p><h3>Bronze Sponsors</h3>
|
||||
<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://syntax.fm"><img src="https://github.com/syntaxfm.png" alt="Syntax" height="32"></a> <a href="https://www.wordhint.net/"><img src="https://images.opencollective.com/wordhint/be86813/avatar.png" alt="WordHint" 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?v=4" alt="GitBook" height="32"></a> <a href="https://nx.dev"><img src="https://avatars.githubusercontent.com/u/23692104?v=4" alt="Nx" height="32"></a> <a href="https://herocoders.com"><img src="https://avatars.githubusercontent.com/u/37549774?v=4" alt="HeroCoders" height="32"></a></p>
|
||||
<h3>Technology Sponsors</h3>
|
||||
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.
|
||||
<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>
|
||||
<!--sponsorsend-->
|
||||
|
||||
[tidelift]: https://tidelift.com/funding/github/npm/eslint
|
||||
[herodevs]: https://www.herodevs.com/support/eslint-nes?utm_source=ESLintWebsite&utm_medium=ESLintWebsite&utm_campaign=ESLintNES&utm_id=ESLintNES
|
179
node_modules/eslint/bin/eslint.js
generated
vendored
Executable file
179
node_modules/eslint/bin/eslint.js
generated
vendored
Executable file
@ -0,0 +1,179 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* @fileoverview Main CLI that is run via the eslint command.
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
|
||||
/* eslint no-console:off -- CLI */
|
||||
|
||||
"use strict";
|
||||
|
||||
const mod = require("node:module");
|
||||
|
||||
// to use V8's code cache to speed up instantiation time
|
||||
mod.enableCompileCache?.();
|
||||
|
||||
// must do this initialization *before* other requires in order to work
|
||||
if (process.argv.includes("--debug")) {
|
||||
require("debug").enable("eslint:*,-eslint:code-path,eslintrc:*");
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Read data from stdin til the end.
|
||||
*
|
||||
* Note: See
|
||||
* - https://github.com/nodejs/node/blob/master/doc/api/process.md#processstdin
|
||||
* - https://github.com/nodejs/node/blob/master/doc/api/process.md#a-note-on-process-io
|
||||
* - https://lists.gnu.org/archive/html/bug-gnu-emacs/2016-01/msg00419.html
|
||||
* - https://github.com/nodejs/node/issues/7439 (historical)
|
||||
*
|
||||
* On Windows using `fs.readFileSync(STDIN_FILE_DESCRIPTOR, "utf8")` seems
|
||||
* to read 4096 bytes before blocking and never drains to read further data.
|
||||
*
|
||||
* The investigation on the Emacs thread indicates:
|
||||
*
|
||||
* > Emacs on MS-Windows uses pipes to communicate with subprocesses; a
|
||||
* > pipe on Windows has a 4K buffer. So as soon as Emacs writes more than
|
||||
* > 4096 bytes to the pipe, the pipe becomes full, and Emacs then waits for
|
||||
* > the subprocess to read its end of the pipe, at which time Emacs will
|
||||
* > write the rest of the stuff.
|
||||
* @returns {Promise<string>} The read text.
|
||||
*/
|
||||
function readStdin() {
|
||||
return new Promise((resolve, reject) => {
|
||||
let content = "";
|
||||
let chunk = "";
|
||||
|
||||
process.stdin
|
||||
.setEncoding("utf8")
|
||||
.on("readable", () => {
|
||||
while ((chunk = process.stdin.read()) !== null) {
|
||||
content += chunk;
|
||||
}
|
||||
})
|
||||
.on("end", () => resolve(content))
|
||||
.on("error", reject);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error message of a given value.
|
||||
* @param {any} error The value to get.
|
||||
* @returns {string} The error message.
|
||||
*/
|
||||
function getErrorMessage(error) {
|
||||
|
||||
// Lazy loading because this is used only if an error happened.
|
||||
const util = require("node:util");
|
||||
|
||||
// Foolproof -- third-party module might throw non-object.
|
||||
if (typeof error !== "object" || error === null) {
|
||||
return String(error);
|
||||
}
|
||||
|
||||
// Use templates if `error.messageTemplate` is present.
|
||||
if (typeof error.messageTemplate === "string") {
|
||||
try {
|
||||
const template = require(`../messages/${error.messageTemplate}.js`);
|
||||
|
||||
return template(error.messageData || {});
|
||||
} catch {
|
||||
|
||||
// Ignore template error then fallback to use `error.stack`.
|
||||
}
|
||||
}
|
||||
|
||||
// Use the stacktrace if it's an error object.
|
||||
if (typeof error.stack === "string") {
|
||||
return error.stack;
|
||||
}
|
||||
|
||||
// Otherwise, dump the object.
|
||||
return util.format("%o", error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks error messages that are shown to the user so we only ever show the
|
||||
* same message once.
|
||||
* @type {Set<string>}
|
||||
*/
|
||||
const displayedErrors = new Set();
|
||||
|
||||
/**
|
||||
* Tracks whether an unexpected error was caught
|
||||
* @type {boolean}
|
||||
*/
|
||||
let hadFatalError = false;
|
||||
|
||||
/**
|
||||
* Catch and report unexpected error.
|
||||
* @param {any} error The thrown error object.
|
||||
* @returns {void}
|
||||
*/
|
||||
function onFatalError(error) {
|
||||
process.exitCode = 2;
|
||||
hadFatalError = true;
|
||||
|
||||
const { version } = require("../package.json");
|
||||
const message = `
|
||||
Oops! Something went wrong! :(
|
||||
|
||||
ESLint: ${version}
|
||||
|
||||
${getErrorMessage(error)}`;
|
||||
|
||||
if (!displayedErrors.has(message)) {
|
||||
console.error(message);
|
||||
displayedErrors.add(message);
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Execution
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
(async function main() {
|
||||
process.on("uncaughtException", onFatalError);
|
||||
process.on("unhandledRejection", onFatalError);
|
||||
|
||||
// Call the config initializer if `--init` is present.
|
||||
if (process.argv.includes("--init")) {
|
||||
|
||||
// `eslint --init` has been moved to `@eslint/create-config`
|
||||
console.warn("You can also run this command directly using 'npm init @eslint/config@latest'.");
|
||||
|
||||
const spawn = require("cross-spawn");
|
||||
|
||||
spawn.sync("npm", ["init", "@eslint/config@latest"], { encoding: "utf8", stdio: "inherit" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, call the CLI.
|
||||
const cli = require("../lib/cli");
|
||||
const exitCode = await cli.execute(
|
||||
process.argv,
|
||||
process.argv.includes("--stdin") ? await readStdin() : null,
|
||||
true
|
||||
);
|
||||
|
||||
/*
|
||||
* If an uncaught exception or unhandled rejection was detected in the meantime,
|
||||
* keep the fatal exit code 2 that is already assigned to `process.exitCode`.
|
||||
* Without this condition, exit code 2 (unsuccessful execution) could be overwritten with
|
||||
* 1 (successful execution, lint problems found) or even 0 (successful execution, no lint problems found).
|
||||
* This ensures that unexpected errors that seemingly don't affect the success
|
||||
* of the execution will still cause a non-zero exit code, as it's a common
|
||||
* practice and the default behavior of Node.js to exit with non-zero
|
||||
* in case of an uncaught exception or unhandled rejection.
|
||||
*
|
||||
* Otherwise, assign the exit code returned from CLI.
|
||||
*/
|
||||
if (!hadFatalError) {
|
||||
process.exitCode = exitCode;
|
||||
}
|
||||
}()).catch(onFatalError);
|
32
node_modules/eslint/conf/default-cli-options.js
generated
vendored
Normal file
32
node_modules/eslint/conf/default-cli-options.js
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* @fileoverview Default CLIEngineOptions.
|
||||
* @author Ian VanSchooten
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
configFile: null,
|
||||
baseConfig: false,
|
||||
rulePaths: [],
|
||||
useEslintrc: true,
|
||||
envs: [],
|
||||
globals: [],
|
||||
extensions: null,
|
||||
ignore: true,
|
||||
ignorePath: void 0,
|
||||
cache: false,
|
||||
|
||||
/*
|
||||
* in order to honor the cacheFile option if specified
|
||||
* this option should not have a default value otherwise
|
||||
* it will always be used
|
||||
*/
|
||||
cacheLocation: "",
|
||||
cacheFile: ".eslintcache",
|
||||
cacheStrategy: "metadata",
|
||||
fix: false,
|
||||
allowInlineConfig: true,
|
||||
reportUnusedDisableDirectives: void 0,
|
||||
globInputPaths: true
|
||||
};
|
16
node_modules/eslint/conf/ecma-version.js
generated
vendored
Normal file
16
node_modules/eslint/conf/ecma-version.js
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* @fileoverview Configuration related to ECMAScript versions
|
||||
* @author Milos Djermanovic
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* The latest ECMAScript version supported by ESLint.
|
||||
* @type {number} year-based ECMAScript version
|
||||
*/
|
||||
const LATEST_ECMA_VERSION = 2025;
|
||||
|
||||
module.exports = {
|
||||
LATEST_ECMA_VERSION
|
||||
};
|
160
node_modules/eslint/conf/globals.js
generated
vendored
Normal file
160
node_modules/eslint/conf/globals.js
generated
vendored
Normal file
@ -0,0 +1,160 @@
|
||||
/**
|
||||
* @fileoverview Globals for ecmaVersion/sourceType
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Globals
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const commonjs = {
|
||||
exports: true,
|
||||
global: false,
|
||||
module: false,
|
||||
require: false
|
||||
};
|
||||
|
||||
const es3 = {
|
||||
Array: false,
|
||||
Boolean: false,
|
||||
constructor: false,
|
||||
Date: false,
|
||||
decodeURI: false,
|
||||
decodeURIComponent: false,
|
||||
encodeURI: false,
|
||||
encodeURIComponent: false,
|
||||
Error: false,
|
||||
escape: false,
|
||||
eval: false,
|
||||
EvalError: false,
|
||||
Function: false,
|
||||
hasOwnProperty: false,
|
||||
Infinity: false,
|
||||
isFinite: false,
|
||||
isNaN: false,
|
||||
isPrototypeOf: false,
|
||||
Math: false,
|
||||
NaN: false,
|
||||
Number: false,
|
||||
Object: false,
|
||||
parseFloat: false,
|
||||
parseInt: false,
|
||||
propertyIsEnumerable: false,
|
||||
RangeError: false,
|
||||
ReferenceError: false,
|
||||
RegExp: false,
|
||||
String: false,
|
||||
SyntaxError: false,
|
||||
toLocaleString: false,
|
||||
toString: false,
|
||||
TypeError: false,
|
||||
undefined: false,
|
||||
unescape: false,
|
||||
URIError: false,
|
||||
valueOf: false
|
||||
};
|
||||
|
||||
const es5 = {
|
||||
...es3,
|
||||
JSON: false
|
||||
};
|
||||
|
||||
const es2015 = {
|
||||
...es5,
|
||||
ArrayBuffer: false,
|
||||
DataView: false,
|
||||
Float32Array: false,
|
||||
Float64Array: false,
|
||||
Int16Array: false,
|
||||
Int32Array: false,
|
||||
Int8Array: false,
|
||||
Intl: false,
|
||||
Map: false,
|
||||
Promise: false,
|
||||
Proxy: false,
|
||||
Reflect: false,
|
||||
Set: false,
|
||||
Symbol: false,
|
||||
Uint16Array: false,
|
||||
Uint32Array: false,
|
||||
Uint8Array: false,
|
||||
Uint8ClampedArray: false,
|
||||
WeakMap: false,
|
||||
WeakSet: false
|
||||
};
|
||||
|
||||
// no new globals in ES2016
|
||||
const es2016 = {
|
||||
...es2015
|
||||
};
|
||||
|
||||
const es2017 = {
|
||||
...es2016,
|
||||
Atomics: false,
|
||||
SharedArrayBuffer: false
|
||||
};
|
||||
|
||||
// no new globals in ES2018
|
||||
const es2018 = {
|
||||
...es2017
|
||||
};
|
||||
|
||||
// no new globals in ES2019
|
||||
const es2019 = {
|
||||
...es2018
|
||||
};
|
||||
|
||||
const es2020 = {
|
||||
...es2019,
|
||||
BigInt: false,
|
||||
BigInt64Array: false,
|
||||
BigUint64Array: false,
|
||||
globalThis: false
|
||||
};
|
||||
|
||||
const es2021 = {
|
||||
...es2020,
|
||||
AggregateError: false,
|
||||
FinalizationRegistry: false,
|
||||
WeakRef: false
|
||||
};
|
||||
|
||||
const es2022 = {
|
||||
...es2021
|
||||
};
|
||||
|
||||
const es2023 = {
|
||||
...es2022
|
||||
};
|
||||
|
||||
const es2024 = {
|
||||
...es2023
|
||||
};
|
||||
|
||||
const es2025 = {
|
||||
...es2024
|
||||
};
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Exports
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
commonjs,
|
||||
es3,
|
||||
es5,
|
||||
es2015,
|
||||
es2016,
|
||||
es2017,
|
||||
es2018,
|
||||
es2019,
|
||||
es2020,
|
||||
es2021,
|
||||
es2022,
|
||||
es2023,
|
||||
es2024,
|
||||
es2025
|
||||
};
|
22
node_modules/eslint/conf/replacements.json
generated
vendored
Normal file
22
node_modules/eslint/conf/replacements.json
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"rules": {
|
||||
"generator-star": ["generator-star-spacing"],
|
||||
"global-strict": ["strict"],
|
||||
"no-arrow-condition": ["no-confusing-arrow", "no-constant-condition"],
|
||||
"no-comma-dangle": ["comma-dangle"],
|
||||
"no-empty-class": ["no-empty-character-class"],
|
||||
"no-empty-label": ["no-labels"],
|
||||
"no-extra-strict": ["strict"],
|
||||
"no-reserved-keys": ["quote-props"],
|
||||
"no-space-before-semi": ["semi-spacing"],
|
||||
"no-wrap-func": ["no-extra-parens"],
|
||||
"space-after-function-name": ["space-before-function-paren"],
|
||||
"space-after-keywords": ["keyword-spacing"],
|
||||
"space-before-function-parentheses": ["space-before-function-paren"],
|
||||
"space-before-keywords": ["keyword-spacing"],
|
||||
"space-in-brackets": ["object-curly-spacing", "array-bracket-spacing", "computed-property-spacing"],
|
||||
"space-return-throw-case": ["keyword-spacing"],
|
||||
"space-unary-word-ops": ["space-unary-ops"],
|
||||
"spaced-line-comment": ["spaced-comment"]
|
||||
}
|
||||
}
|
30
node_modules/eslint/conf/rule-type-list.json
generated
vendored
Normal file
30
node_modules/eslint/conf/rule-type-list.json
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"types": {
|
||||
"problem": [],
|
||||
"suggestion": [],
|
||||
"layout": []
|
||||
},
|
||||
"deprecated": [],
|
||||
"removed": [
|
||||
{ "removed": "generator-star", "replacedBy": ["generator-star-spacing"] },
|
||||
{ "removed": "global-strict", "replacedBy": ["strict"] },
|
||||
{ "removed": "no-arrow-condition", "replacedBy": ["no-confusing-arrow", "no-constant-condition"] },
|
||||
{ "removed": "no-comma-dangle", "replacedBy": ["comma-dangle"] },
|
||||
{ "removed": "no-empty-class", "replacedBy": ["no-empty-character-class"] },
|
||||
{ "removed": "no-empty-label", "replacedBy": ["no-labels"] },
|
||||
{ "removed": "no-extra-strict", "replacedBy": ["strict"] },
|
||||
{ "removed": "no-reserved-keys", "replacedBy": ["quote-props"] },
|
||||
{ "removed": "no-space-before-semi", "replacedBy": ["semi-spacing"] },
|
||||
{ "removed": "no-wrap-func", "replacedBy": ["no-extra-parens"] },
|
||||
{ "removed": "space-after-function-name", "replacedBy": ["space-before-function-paren"] },
|
||||
{ "removed": "space-after-keywords", "replacedBy": ["keyword-spacing"] },
|
||||
{ "removed": "space-before-function-parentheses", "replacedBy": ["space-before-function-paren"] },
|
||||
{ "removed": "space-before-keywords", "replacedBy": ["keyword-spacing"] },
|
||||
{ "removed": "space-in-brackets", "replacedBy": ["object-curly-spacing", "array-bracket-spacing"] },
|
||||
{ "removed": "space-return-throw-case", "replacedBy": ["keyword-spacing"] },
|
||||
{ "removed": "space-unary-word-ops", "replacedBy": ["space-unary-ops"] },
|
||||
{ "removed": "spaced-line-comment", "replacedBy": ["spaced-comment"] },
|
||||
{ "removed": "valid-jsdoc", "replacedBy": [] },
|
||||
{ "removed": "require-jsdoc", "replacedBy": [] }
|
||||
]
|
||||
}
|
50
node_modules/eslint/lib/api.js
generated
vendored
Normal file
50
node_modules/eslint/lib/api.js
generated
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* @fileoverview Expose out ESLint and CLI to require.
|
||||
* @author Ian Christian Myers
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const { ESLint, shouldUseFlatConfig } = require("./eslint/eslint");
|
||||
const { LegacyESLint } = require("./eslint/legacy-eslint");
|
||||
const { Linter } = require("./linter");
|
||||
const { RuleTester } = require("./rule-tester");
|
||||
const { SourceCode } = require("./languages/js/source-code");
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Functions
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Loads the correct ESLint constructor given the options.
|
||||
* @param {Object} [options] The options object
|
||||
* @param {boolean} [options.useFlatConfig] Whether or not to use a flat config
|
||||
* @returns {Promise<ESLint|LegacyESLint>} The ESLint constructor
|
||||
*/
|
||||
async function loadESLint({ useFlatConfig } = {}) {
|
||||
|
||||
/*
|
||||
* Note: The v8.x version of this function also accepted a `cwd` option, but
|
||||
* it is not used in this implementation so we silently ignore it.
|
||||
*/
|
||||
|
||||
const shouldESLintUseFlatConfig = useFlatConfig ?? (await shouldUseFlatConfig());
|
||||
|
||||
return shouldESLintUseFlatConfig ? ESLint : LegacyESLint;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Exports
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
Linter,
|
||||
loadESLint,
|
||||
ESLint,
|
||||
RuleTester,
|
||||
SourceCode
|
||||
};
|
1089
node_modules/eslint/lib/cli-engine/cli-engine.js
generated
vendored
Normal file
1089
node_modules/eslint/lib/cli-engine/cli-engine.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
547
node_modules/eslint/lib/cli-engine/file-enumerator.js
generated
vendored
Normal file
547
node_modules/eslint/lib/cli-engine/file-enumerator.js
generated
vendored
Normal file
@ -0,0 +1,547 @@
|
||||
/**
|
||||
* @fileoverview `FileEnumerator` class.
|
||||
*
|
||||
* `FileEnumerator` class has two responsibilities:
|
||||
*
|
||||
* 1. Find target files by processing glob patterns.
|
||||
* 2. Tie each target file and appropriate configuration.
|
||||
*
|
||||
* It provides a method:
|
||||
*
|
||||
* - `iterateFiles(patterns)`
|
||||
* Iterate files which are matched by given patterns together with the
|
||||
* corresponded configuration. This is for `CLIEngine#executeOnFiles()`.
|
||||
* While iterating files, it loads the configuration file of each directory
|
||||
* before iterate files on the directory, so we can use the configuration
|
||||
* files to determine target files.
|
||||
*
|
||||
* @example
|
||||
* const enumerator = new FileEnumerator();
|
||||
* const linter = new Linter();
|
||||
*
|
||||
* for (const { config, filePath } of enumerator.iterateFiles(["*.js"])) {
|
||||
* const code = fs.readFileSync(filePath, "utf8");
|
||||
* const messages = linter.verify(code, config, filePath);
|
||||
*
|
||||
* console.log(messages);
|
||||
* }
|
||||
*
|
||||
* @author Toru Nagashima <https://github.com/mysticatea>
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const getGlobParent = require("glob-parent");
|
||||
const isGlob = require("is-glob");
|
||||
const escapeRegExp = require("escape-string-regexp");
|
||||
const { Minimatch } = require("minimatch");
|
||||
|
||||
const {
|
||||
Legacy: {
|
||||
IgnorePattern,
|
||||
CascadingConfigArrayFactory
|
||||
}
|
||||
} = require("@eslint/eslintrc");
|
||||
const debug = require("debug")("eslint:file-enumerator");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const minimatchOpts = { dot: true, matchBase: true };
|
||||
const dotfilesPattern = /(?:(?:^\.)|(?:[/\\]\.))[^/\\.].*/u;
|
||||
const NONE = 0;
|
||||
const IGNORED_SILENTLY = 1;
|
||||
const IGNORED = 2;
|
||||
|
||||
// For VSCode intellisense
|
||||
/** @typedef {ReturnType<CascadingConfigArrayFactory.getConfigArrayForFile>} ConfigArray */
|
||||
|
||||
/**
|
||||
* @typedef {Object} FileEnumeratorOptions
|
||||
* @property {CascadingConfigArrayFactory} [configArrayFactory] The factory for config arrays.
|
||||
* @property {string} [cwd] The base directory to start lookup.
|
||||
* @property {string[]} [extensions] The extensions to match files for directory patterns.
|
||||
* @property {boolean} [globInputPaths] Set to false to skip glob resolution of input file paths to lint (default: true). If false, each input file paths is assumed to be a non-glob path to an existing file.
|
||||
* @property {boolean} [ignore] The flag to check ignored files.
|
||||
* @property {string[]} [rulePaths] The value of `--rulesdir` option.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} FileAndConfig
|
||||
* @property {string} filePath The path to a target file.
|
||||
* @property {ConfigArray} config The config entries of that file.
|
||||
* @property {boolean} ignored If `true` then this file should be ignored and warned because it was directly specified.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} FileEntry
|
||||
* @property {string} filePath The path to a target file.
|
||||
* @property {ConfigArray} config The config entries of that file.
|
||||
* @property {NONE|IGNORED_SILENTLY|IGNORED} flag The flag.
|
||||
* - `NONE` means the file is a target file.
|
||||
* - `IGNORED_SILENTLY` means the file should be ignored silently.
|
||||
* - `IGNORED` means the file should be ignored and warned because it was directly specified.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} FileEnumeratorInternalSlots
|
||||
* @property {CascadingConfigArrayFactory} configArrayFactory The factory for config arrays.
|
||||
* @property {string} cwd The base directory to start lookup.
|
||||
* @property {RegExp|null} extensionRegExp The RegExp to test if a string ends with specific file extensions.
|
||||
* @property {boolean} globInputPaths Set to false to skip glob resolution of input file paths to lint (default: true). If false, each input file paths is assumed to be a non-glob path to an existing file.
|
||||
* @property {boolean} ignoreFlag The flag to check ignored files.
|
||||
* @property {(filePath:string, dot:boolean) => boolean} defaultIgnores The default predicate function to ignore files.
|
||||
*/
|
||||
|
||||
/** @type {WeakMap<FileEnumerator, FileEnumeratorInternalSlots>} */
|
||||
const internalSlotsMap = new WeakMap();
|
||||
|
||||
/**
|
||||
* Check if a string is a glob pattern or not.
|
||||
* @param {string} pattern A glob pattern.
|
||||
* @returns {boolean} `true` if the string is a glob pattern.
|
||||
*/
|
||||
function isGlobPattern(pattern) {
|
||||
return isGlob(path.sep === "\\" ? pattern.replace(/\\/gu, "/") : pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stats of a given path.
|
||||
* @param {string} filePath The path to target file.
|
||||
* @throws {Error} As may be thrown by `fs.statSync`.
|
||||
* @returns {fs.Stats|null} The stats.
|
||||
* @private
|
||||
*/
|
||||
function statSafeSync(filePath) {
|
||||
try {
|
||||
return fs.statSync(filePath);
|
||||
} catch (error) {
|
||||
|
||||
/* c8 ignore next */
|
||||
if (error.code !== "ENOENT") {
|
||||
throw error;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get filenames in a given path to a directory.
|
||||
* @param {string} directoryPath The path to target directory.
|
||||
* @throws {Error} As may be thrown by `fs.readdirSync`.
|
||||
* @returns {import("fs").Dirent[]} The filenames.
|
||||
* @private
|
||||
*/
|
||||
function readdirSafeSync(directoryPath) {
|
||||
try {
|
||||
return fs.readdirSync(directoryPath, { withFileTypes: true });
|
||||
} catch (error) {
|
||||
|
||||
/* c8 ignore next */
|
||||
if (error.code !== "ENOENT") {
|
||||
throw error;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a `RegExp` object to detect extensions.
|
||||
* @param {string[] | null} extensions The extensions to create.
|
||||
* @returns {RegExp | null} The created `RegExp` object or null.
|
||||
*/
|
||||
function createExtensionRegExp(extensions) {
|
||||
if (extensions) {
|
||||
const normalizedExts = extensions.map(ext => escapeRegExp(
|
||||
ext.startsWith(".")
|
||||
? ext.slice(1)
|
||||
: ext
|
||||
));
|
||||
|
||||
return new RegExp(
|
||||
`.\\.(?:${normalizedExts.join("|")})$`,
|
||||
"u"
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The error type when no files match a glob.
|
||||
*/
|
||||
class NoFilesFoundError extends Error {
|
||||
|
||||
/**
|
||||
* @param {string} pattern The glob pattern which was not found.
|
||||
* @param {boolean} globDisabled If `true` then the pattern was a glob pattern, but glob was disabled.
|
||||
*/
|
||||
constructor(pattern, globDisabled) {
|
||||
super(`No files matching '${pattern}' were found${globDisabled ? " (glob was disabled)" : ""}.`);
|
||||
this.messageTemplate = "file-not-found";
|
||||
this.messageData = { pattern, globDisabled };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The error type when there are files matched by a glob, but all of them have been ignored.
|
||||
*/
|
||||
class AllFilesIgnoredError extends Error {
|
||||
|
||||
/**
|
||||
* @param {string} pattern The glob pattern which was not found.
|
||||
*/
|
||||
constructor(pattern) {
|
||||
super(`All files matched by '${pattern}' are ignored.`);
|
||||
this.messageTemplate = "all-files-ignored";
|
||||
this.messageData = { pattern };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class provides the functionality that enumerates every file which is
|
||||
* matched by given glob patterns and that configuration.
|
||||
*/
|
||||
class FileEnumerator {
|
||||
|
||||
/**
|
||||
* Initialize this enumerator.
|
||||
* @param {FileEnumeratorOptions} options The options.
|
||||
*/
|
||||
constructor({
|
||||
cwd = process.cwd(),
|
||||
configArrayFactory = new CascadingConfigArrayFactory({
|
||||
cwd,
|
||||
getEslintRecommendedConfig: () => require("@eslint/js").configs.recommended,
|
||||
getEslintAllConfig: () => require("@eslint/js").configs.all
|
||||
}),
|
||||
extensions = null,
|
||||
globInputPaths = true,
|
||||
errorOnUnmatchedPattern = true,
|
||||
ignore = true
|
||||
} = {}) {
|
||||
internalSlotsMap.set(this, {
|
||||
configArrayFactory,
|
||||
cwd,
|
||||
defaultIgnores: IgnorePattern.createDefaultIgnore(cwd),
|
||||
extensionRegExp: createExtensionRegExp(extensions),
|
||||
globInputPaths,
|
||||
errorOnUnmatchedPattern,
|
||||
ignoreFlag: ignore
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given file is target or not.
|
||||
* @param {string} filePath The path to a candidate file.
|
||||
* @param {ConfigArray} [providedConfig] Optional. The configuration for the file.
|
||||
* @returns {boolean} `true` if the file is a target.
|
||||
*/
|
||||
isTargetPath(filePath, providedConfig) {
|
||||
const {
|
||||
configArrayFactory,
|
||||
extensionRegExp
|
||||
} = internalSlotsMap.get(this);
|
||||
|
||||
// If `--ext` option is present, use it.
|
||||
if (extensionRegExp) {
|
||||
return extensionRegExp.test(filePath);
|
||||
}
|
||||
|
||||
// `.js` file is target by default.
|
||||
if (filePath.endsWith(".js")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// use `overrides[].files` to check additional targets.
|
||||
const config =
|
||||
providedConfig ||
|
||||
configArrayFactory.getConfigArrayForFile(
|
||||
filePath,
|
||||
{ ignoreNotFoundError: true }
|
||||
);
|
||||
|
||||
return config.isAdditionalTargetPath(filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate files which are matched by given glob patterns.
|
||||
* @param {string|string[]} patternOrPatterns The glob patterns to iterate files.
|
||||
* @throws {NoFilesFoundError|AllFilesIgnoredError} On an unmatched pattern.
|
||||
* @returns {IterableIterator<FileAndConfig>} The found files.
|
||||
*/
|
||||
*iterateFiles(patternOrPatterns) {
|
||||
const { globInputPaths, errorOnUnmatchedPattern } = internalSlotsMap.get(this);
|
||||
const patterns = Array.isArray(patternOrPatterns)
|
||||
? patternOrPatterns
|
||||
: [patternOrPatterns];
|
||||
|
||||
debug("Start to iterate files: %o", patterns);
|
||||
|
||||
// The set of paths to remove duplicate.
|
||||
const set = new Set();
|
||||
|
||||
for (const pattern of patterns) {
|
||||
let foundRegardlessOfIgnored = false;
|
||||
let found = false;
|
||||
|
||||
// Skip empty string.
|
||||
if (!pattern) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Iterate files of this pattern.
|
||||
for (const { config, filePath, flag } of this._iterateFiles(pattern)) {
|
||||
foundRegardlessOfIgnored = true;
|
||||
if (flag === IGNORED_SILENTLY) {
|
||||
continue;
|
||||
}
|
||||
found = true;
|
||||
|
||||
// Remove duplicate paths while yielding paths.
|
||||
if (!set.has(filePath)) {
|
||||
set.add(filePath);
|
||||
yield {
|
||||
config,
|
||||
filePath,
|
||||
ignored: flag === IGNORED
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Raise an error if any files were not found.
|
||||
if (errorOnUnmatchedPattern) {
|
||||
if (!foundRegardlessOfIgnored) {
|
||||
throw new NoFilesFoundError(
|
||||
pattern,
|
||||
!globInputPaths && isGlob(pattern)
|
||||
);
|
||||
}
|
||||
if (!found) {
|
||||
throw new AllFilesIgnoredError(pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug(`Complete iterating files: ${JSON.stringify(patterns)}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate files which are matched by a given glob pattern.
|
||||
* @param {string} pattern The glob pattern to iterate files.
|
||||
* @returns {IterableIterator<FileEntry>} The found files.
|
||||
*/
|
||||
_iterateFiles(pattern) {
|
||||
const { cwd, globInputPaths } = internalSlotsMap.get(this);
|
||||
const absolutePath = path.resolve(cwd, pattern);
|
||||
const isDot = dotfilesPattern.test(pattern);
|
||||
const stat = statSafeSync(absolutePath);
|
||||
|
||||
if (stat && stat.isDirectory()) {
|
||||
return this._iterateFilesWithDirectory(absolutePath, isDot);
|
||||
}
|
||||
if (stat && stat.isFile()) {
|
||||
return this._iterateFilesWithFile(absolutePath);
|
||||
}
|
||||
if (globInputPaths && isGlobPattern(pattern)) {
|
||||
return this._iterateFilesWithGlob(pattern, isDot);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate a file which is matched by a given path.
|
||||
* @param {string} filePath The path to the target file.
|
||||
* @returns {IterableIterator<FileEntry>} The found files.
|
||||
* @private
|
||||
*/
|
||||
_iterateFilesWithFile(filePath) {
|
||||
debug(`File: ${filePath}`);
|
||||
|
||||
const { configArrayFactory } = internalSlotsMap.get(this);
|
||||
const config = configArrayFactory.getConfigArrayForFile(filePath);
|
||||
const ignored = this._isIgnoredFile(filePath, { config, direct: true });
|
||||
const flag = ignored ? IGNORED : NONE;
|
||||
|
||||
return [{ config, filePath, flag }];
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate files in a given path.
|
||||
* @param {string} directoryPath The path to the target directory.
|
||||
* @param {boolean} dotfiles If `true` then it doesn't skip dot files by default.
|
||||
* @returns {IterableIterator<FileEntry>} The found files.
|
||||
* @private
|
||||
*/
|
||||
_iterateFilesWithDirectory(directoryPath, dotfiles) {
|
||||
debug(`Directory: ${directoryPath}`);
|
||||
|
||||
return this._iterateFilesRecursive(
|
||||
directoryPath,
|
||||
{ dotfiles, recursive: true, selector: null }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate files which are matched by a given glob pattern.
|
||||
* @param {string} pattern The glob pattern to iterate files.
|
||||
* @param {boolean} dotfiles If `true` then it doesn't skip dot files by default.
|
||||
* @returns {IterableIterator<FileEntry>} The found files.
|
||||
* @private
|
||||
*/
|
||||
_iterateFilesWithGlob(pattern, dotfiles) {
|
||||
debug(`Glob: ${pattern}`);
|
||||
|
||||
const { cwd } = internalSlotsMap.get(this);
|
||||
const directoryPath = path.resolve(cwd, getGlobParent(pattern));
|
||||
const absolutePath = path.resolve(cwd, pattern);
|
||||
const globPart = absolutePath.slice(directoryPath.length + 1);
|
||||
|
||||
/*
|
||||
* recursive if there are `**` or path separators in the glob part.
|
||||
* Otherwise, patterns such as `src/*.js`, it doesn't need recursive.
|
||||
*/
|
||||
const recursive = /\*\*|\/|\\/u.test(globPart);
|
||||
const selector = new Minimatch(absolutePath, minimatchOpts);
|
||||
|
||||
debug(`recursive? ${recursive}`);
|
||||
|
||||
return this._iterateFilesRecursive(
|
||||
directoryPath,
|
||||
{ dotfiles, recursive, selector }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate files in a given path.
|
||||
* @param {string} directoryPath The path to the target directory.
|
||||
* @param {Object} options The options to iterate files.
|
||||
* @param {boolean} [options.dotfiles] If `true` then it doesn't skip dot files by default.
|
||||
* @param {boolean} [options.recursive] If `true` then it dives into sub directories.
|
||||
* @param {InstanceType<Minimatch>} [options.selector] The matcher to choose files.
|
||||
* @returns {IterableIterator<FileEntry>} The found files.
|
||||
* @private
|
||||
*/
|
||||
*_iterateFilesRecursive(directoryPath, options) {
|
||||
debug(`Enter the directory: ${directoryPath}`);
|
||||
const { configArrayFactory } = internalSlotsMap.get(this);
|
||||
|
||||
/** @type {ConfigArray|null} */
|
||||
let config = null;
|
||||
|
||||
// Enumerate the files of this directory.
|
||||
for (const entry of readdirSafeSync(directoryPath)) {
|
||||
const filePath = path.join(directoryPath, entry.name);
|
||||
const fileInfo = entry.isSymbolicLink() ? statSafeSync(filePath) : entry;
|
||||
|
||||
if (!fileInfo) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the file is matched.
|
||||
if (fileInfo.isFile()) {
|
||||
if (!config) {
|
||||
config = configArrayFactory.getConfigArrayForFile(
|
||||
filePath,
|
||||
|
||||
/*
|
||||
* We must ignore `ConfigurationNotFoundError` at this
|
||||
* point because we don't know if target files exist in
|
||||
* this directory.
|
||||
*/
|
||||
{ ignoreNotFoundError: true }
|
||||
);
|
||||
}
|
||||
const matched = options.selector
|
||||
|
||||
// Started with a glob pattern; choose by the pattern.
|
||||
? options.selector.match(filePath)
|
||||
|
||||
// Started with a directory path; choose by file extensions.
|
||||
: this.isTargetPath(filePath, config);
|
||||
|
||||
if (matched) {
|
||||
const ignored = this._isIgnoredFile(filePath, { ...options, config });
|
||||
const flag = ignored ? IGNORED_SILENTLY : NONE;
|
||||
|
||||
debug(`Yield: ${entry.name}${ignored ? " but ignored" : ""}`);
|
||||
yield {
|
||||
config: configArrayFactory.getConfigArrayForFile(filePath),
|
||||
filePath,
|
||||
flag
|
||||
};
|
||||
} else {
|
||||
debug(`Didn't match: ${entry.name}`);
|
||||
}
|
||||
|
||||
// Dive into the sub directory.
|
||||
} else if (options.recursive && fileInfo.isDirectory()) {
|
||||
if (!config) {
|
||||
config = configArrayFactory.getConfigArrayForFile(
|
||||
filePath,
|
||||
{ ignoreNotFoundError: true }
|
||||
);
|
||||
}
|
||||
const ignored = this._isIgnoredFile(
|
||||
filePath + path.sep,
|
||||
{ ...options, config }
|
||||
);
|
||||
|
||||
if (!ignored) {
|
||||
yield* this._iterateFilesRecursive(filePath, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug(`Leave the directory: ${directoryPath}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given file should be ignored.
|
||||
* @param {string} filePath The path to a file to check.
|
||||
* @param {Object} options Options
|
||||
* @param {ConfigArray} [options.config] The config for this file.
|
||||
* @param {boolean} [options.dotfiles] If `true` then this is not ignore dot files by default.
|
||||
* @param {boolean} [options.direct] If `true` then this is a direct specified file.
|
||||
* @returns {boolean} `true` if the file should be ignored.
|
||||
* @private
|
||||
*/
|
||||
_isIgnoredFile(filePath, {
|
||||
config: providedConfig,
|
||||
dotfiles = false,
|
||||
direct = false
|
||||
}) {
|
||||
const {
|
||||
configArrayFactory,
|
||||
defaultIgnores,
|
||||
ignoreFlag
|
||||
} = internalSlotsMap.get(this);
|
||||
|
||||
if (ignoreFlag) {
|
||||
const config =
|
||||
providedConfig ||
|
||||
configArrayFactory.getConfigArrayForFile(
|
||||
filePath,
|
||||
{ ignoreNotFoundError: true }
|
||||
);
|
||||
const ignores =
|
||||
config.extractConfig(filePath).ignores || defaultIgnores;
|
||||
|
||||
return ignores(filePath, dotfiles);
|
||||
}
|
||||
|
||||
return !direct && defaultIgnores(filePath, dotfiles);
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Public Interface
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = { FileEnumerator };
|
18
node_modules/eslint/lib/cli-engine/formatters/formatters-meta.json
generated
vendored
Normal file
18
node_modules/eslint/lib/cli-engine/formatters/formatters-meta.json
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
[
|
||||
{
|
||||
"name": "html",
|
||||
"description": "Outputs results to HTML. The `html` formatter is useful for visual presentation in the browser."
|
||||
},
|
||||
{
|
||||
"name": "json-with-metadata",
|
||||
"description": "Outputs JSON-serialized results. The `json-with-metadata` provides the same linting results as the [`json`](#json) formatter with additional metadata about the rules applied. The linting results are included in the `results` property and the rules metadata is included in the `metadata` property.\n\nAlternatively, you can use the [ESLint Node.js API](../../integrate/nodejs-api) to programmatically use ESLint."
|
||||
},
|
||||
{
|
||||
"name": "json",
|
||||
"description": "Outputs JSON-serialized results. The `json` formatter is useful when you want to programmatically work with the CLI's linting results.\n\nAlternatively, you can use the [ESLint Node.js API](../../integrate/nodejs-api) to programmatically use ESLint."
|
||||
},
|
||||
{
|
||||
"name": "stylish",
|
||||
"description": "Human-readable output format. This is the default formatter."
|
||||
}
|
||||
]
|
351
node_modules/eslint/lib/cli-engine/formatters/html.js
generated
vendored
Normal file
351
node_modules/eslint/lib/cli-engine/formatters/html.js
generated
vendored
Normal file
@ -0,0 +1,351 @@
|
||||
/**
|
||||
* @fileoverview HTML reporter
|
||||
* @author Julian Laval
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const encodeHTML = (function() {
|
||||
const encodeHTMLRules = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'"
|
||||
};
|
||||
const matchHTML = /[&<>"']/ug;
|
||||
|
||||
return function(code) {
|
||||
return code
|
||||
? code.toString().replace(matchHTML, m => encodeHTMLRules[m] || m)
|
||||
: "";
|
||||
};
|
||||
}());
|
||||
|
||||
/**
|
||||
* Get the final HTML document.
|
||||
* @param {Object} it data for the document.
|
||||
* @returns {string} HTML document.
|
||||
*/
|
||||
function pageTemplate(it) {
|
||||
const { reportColor, reportSummary, date, results } = it;
|
||||
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>ESLint Report</title>
|
||||
<link rel="icon" type="image/png" sizes="any" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAHaAAAB2gGFomX7AAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAABD1JREFUWMPFl11sk2UUx3/nbYtjxS1MF7MLMTECMgSTtSSyrQkLhAj7UBPnDSEGoxegGzMwojhXVpmTAA5iYpSoMQa8GBhFOrMFk03buei6yRAlcmOM0SEmU9d90b19jxcM1o5+sGnsc/e+z/l6ztf/HFFVMnns6QieeOCHBePGsHM+wrOtvLG2C4WRVDSSygNV7sCjlspxwDnPB44aols/DXk+mbMBmx/6OseITF1CuOtfevkPh2Uu+/jbdX8lujSScRlT5r7/QDlAfsRmfzmpnkQ/H3H13gf6bBrBn1uqK8WylgEnU8eZmk1repbfchJG1TyKyIKEwuBHFd3lD3naY3O1siiwXsVoBV2VgM1ht/QQUJk2ByqKghsQziYQ8ifKgexIXmuyzC4r67Y7R+xPAfuB/Nn3Cpva+0s7khpQVtZtd4bt51BWxtBYAiciprG7c7D4SixzU9PYalDL6110Ifb/w8W9eY7JqFeFHbO8fPGyLHwwFHJNJTSgwtVTB9oaw9BlQ+tO93vOxypoaQnfEYlI43SeCHDC4TDq9+51/h5fxr33q0ZfV9g04wat9Q943rjJgCp3952W2i8Bi6eDvdsfKj0cK/DYMRyXL4/sUJUmIHd2zYMezsvLaamp4WpcWN3BXSiHpuMwbGbZlnZ8tXY4rgosy+G7oRwQ0cAsd28YGgqfU5UjCZQDLALxDg+Hv/P5Rqvj4hwrS8izXzWb4spwc1GgENFnkpWRzxeuB+ssUHgLdb9UVdt8vpGdKQpze7n7y1U3DBChNRUuqOo9c+0+qpKKxyZqtAIYla7gY4JszAAQri93BSsMRZoyBcUC+w3Q3AyOA4sNhAOZ0q7Iq0b2vUNvK5zPgP+/H8+Zetdoa6uOikhdGurxebwvJY8Iz3V1rTMNAH+opEuQj5KTT/qA1yC+wyUjBm12OidaUtCcPNNX2h0Hx2JG69VulANZAJZJwfU7rzd/FHixuXniTdM0m4GtSQT7bTartqEh9yfImUEzkwKZmTwmo5a5JwkYBfcDL01/RkR5y8iWhtPBknB8ZxwtU9UjwOrrKCeizzc25nTGg1F/turEHoU9wMLpDvWKf8DTmNCAKnd/tqUTF4ElMXJ+A5rWDJS+41WsGWzALhJ+ErBWrLj9g+pqojHxlXJX8HGUg0BsR/x1yhxf3jm4cSzpQFLp6tmi6PEE7g1ZhtZ91ufpSZUAFa6gC+UoQslNaSmypT1U8mHKiUgEKS8KfgF4EpYunFI16tsHin+OG0LcgQK7yj7g6cSzpva2D3hKVNG0Y3mVO1BkqfSlmJrHBQ4uvM12gJHc6ETW8HZVfMRmXvyxxNC1Z/o839zyXlDuCr4nsC11J+MXueaVJWn6yPv+/pJtc9oLTNN4AeTvNGByd3rlhE2x9s5pLwDoHCy+grDzWmOZ95lUtLYj5Bma126Y8eX0/zj/ADxGyViSg4BXAAAAAElFTkSuQmCC">
|
||||
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PScwIDAgMjk0LjgyNSAyNTguOTgyJyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnPg0KPHBhdGggZmlsbD0nIzgwODBGMicgZD0nTTk3LjAyMSw5OS4wMTZsNDguNDMyLTI3Ljk2MmMxLjIxMi0wLjcsMi43MDYtMC43LDMuOTE4LDBsNDguNDMzLDI3Ljk2MiBjMS4yMTEsMC43LDEuOTU5LDEuOTkzLDEuOTU5LDMuMzkzdjU1LjkyNGMwLDEuMzk5LTAuNzQ4LDIuNjkzLTEuOTU5LDMuMzk0bC00OC40MzMsMjcuOTYyYy0xLjIxMiwwLjctMi43MDYsMC43LTMuOTE4LDAgbC00OC40MzItMjcuOTYyYy0xLjIxMi0wLjctMS45NTktMS45OTQtMS45NTktMy4zOTR2LTU1LjkyNEM5NS4wNjMsMTAxLjAwOSw5NS44MSw5OS43MTYsOTcuMDIxLDk5LjAxNicvPg0KPHBhdGggZmlsbD0nIzRCMzJDMycgZD0nTTI3My4zMzYsMTI0LjQ4OEwyMTUuNDY5LDIzLjgxNmMtMi4xMDItMy42NC01Ljk4NS02LjMyNS0xMC4xODgtNi4zMjVIODkuNTQ1IGMtNC4yMDQsMC04LjA4OCwyLjY4NS0xMC4xOSw2LjMyNWwtNTcuODY3LDEwMC40NWMtMi4xMDIsMy42NDEtMi4xMDIsOC4yMzYsMCwxMS44NzdsNTcuODY3LDk5Ljg0NyBjMi4xMDIsMy42NCw1Ljk4Niw1LjUwMSwxMC4xOSw1LjUwMWgxMTUuNzM1YzQuMjAzLDAsOC4wODctMS44MDUsMTAuMTg4LTUuNDQ2bDU3Ljg2Ny0xMDAuMDEgQzI3NS40MzksMTMyLjM5NiwyNzUuNDM5LDEyOC4xMjgsMjczLjMzNiwxMjQuNDg4IE0yMjUuNDE5LDE3Mi44OThjMCwxLjQ4LTAuODkxLDIuODQ5LTIuMTc0LDMuNTlsLTczLjcxLDQyLjUyNyBjLTEuMjgyLDAuNzQtMi44ODgsMC43NC00LjE3LDBsLTczLjc2Ny00Mi41MjdjLTEuMjgyLTAuNzQxLTIuMTc5LTIuMTA5LTIuMTc5LTMuNTlWODcuODQzYzAtMS40ODEsMC44ODQtMi44NDksMi4xNjctMy41OSBsNzMuNzA3LTQyLjUyN2MxLjI4Mi0wLjc0MSwyLjg4Ni0wLjc0MSw0LjE2OCwwbDczLjc3Miw0Mi41MjdjMS4yODMsMC43NDEsMi4xODYsMi4xMDksMi4xODYsMy41OVYxNzIuODk4eicvPg0KPC9zdmc+">
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
#overview {
|
||||
padding: 20px 30px;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
table {
|
||||
margin: 30px;
|
||||
width: calc(100% - 60px);
|
||||
max-width: 1000px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #ddd;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
th {
|
||||
font-weight: 400;
|
||||
font-size: medium;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
td.clr-1,
|
||||
td.clr-2,
|
||||
th span {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
th span {
|
||||
float: right;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
th span::after {
|
||||
content: "";
|
||||
clear: both;
|
||||
display: block;
|
||||
}
|
||||
|
||||
tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
tr td:first-child,
|
||||
tr td:last-child {
|
||||
color: #9da0a4;
|
||||
}
|
||||
|
||||
#overview.bg-0,
|
||||
tr.bg-0 th {
|
||||
color: #468847;
|
||||
background: #dff0d8;
|
||||
border-bottom: 1px solid #d6e9c6;
|
||||
}
|
||||
|
||||
#overview.bg-1,
|
||||
tr.bg-1 th {
|
||||
color: #f0ad4e;
|
||||
background: #fcf8e3;
|
||||
border-bottom: 1px solid #fbeed5;
|
||||
}
|
||||
|
||||
#overview.bg-2,
|
||||
tr.bg-2 th {
|
||||
color: #b94a48;
|
||||
background: #f2dede;
|
||||
border-bottom: 1px solid #eed3d7;
|
||||
}
|
||||
|
||||
td {
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
td.clr-1 {
|
||||
color: #f0ad4e;
|
||||
}
|
||||
|
||||
td.clr-2 {
|
||||
color: #b94a48;
|
||||
}
|
||||
|
||||
td a {
|
||||
color: #3a33d1;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
td a:hover {
|
||||
color: #272296;
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="overview" class="bg-${reportColor}">
|
||||
<h1>ESLint Report</h1>
|
||||
<div>
|
||||
<span>${reportSummary}</span> - Generated on ${date}
|
||||
</div>
|
||||
</div>
|
||||
<table>
|
||||
<tbody>
|
||||
${results}
|
||||
</tbody>
|
||||
</table>
|
||||
<script type="text/javascript">
|
||||
var groups = document.querySelectorAll("tr[data-group]");
|
||||
for (i = 0; i < groups.length; i++) {
|
||||
groups[i].addEventListener("click", function() {
|
||||
var inGroup = document.getElementsByClassName(this.getAttribute("data-group"));
|
||||
this.innerHTML = (this.innerHTML.indexOf("+") > -1) ? this.innerHTML.replace("+", "-") : this.innerHTML.replace("-", "+");
|
||||
for (var j = 0; j < inGroup.length; j++) {
|
||||
inGroup[j].style.display = (inGroup[j].style.display !== "none") ? "none" : "table-row";
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`.trimStart();
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a word and a count, append an s if count is not one.
|
||||
* @param {string} word A word in its singular form.
|
||||
* @param {int} count A number controlling whether word should be pluralized.
|
||||
* @returns {string} The original word with an s on the end if count is not one.
|
||||
*/
|
||||
function pluralize(word, count) {
|
||||
return (count === 1 ? word : `${word}s`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders text along the template of x problems (x errors, x warnings)
|
||||
* @param {string} totalErrors Total errors
|
||||
* @param {string} totalWarnings Total warnings
|
||||
* @returns {string} The formatted string, pluralized where necessary
|
||||
*/
|
||||
function renderSummary(totalErrors, totalWarnings) {
|
||||
const totalProblems = totalErrors + totalWarnings;
|
||||
let renderedText = `${totalProblems} ${pluralize("problem", totalProblems)}`;
|
||||
|
||||
if (totalProblems !== 0) {
|
||||
renderedText += ` (${totalErrors} ${pluralize("error", totalErrors)}, ${totalWarnings} ${pluralize("warning", totalWarnings)})`;
|
||||
}
|
||||
return renderedText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the color based on whether there are errors/warnings...
|
||||
* @param {string} totalErrors Total errors
|
||||
* @param {string} totalWarnings Total warnings
|
||||
* @returns {int} The color code (0 = green, 1 = yellow, 2 = red)
|
||||
*/
|
||||
function renderColor(totalErrors, totalWarnings) {
|
||||
if (totalErrors !== 0) {
|
||||
return 2;
|
||||
}
|
||||
if (totalWarnings !== 0) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get HTML (table row) describing a single message.
|
||||
* @param {Object} it data for the message.
|
||||
* @returns {string} HTML (table row) describing the message.
|
||||
*/
|
||||
function messageTemplate(it) {
|
||||
const {
|
||||
parentIndex,
|
||||
lineNumber,
|
||||
columnNumber,
|
||||
severityNumber,
|
||||
severityName,
|
||||
message,
|
||||
ruleUrl,
|
||||
ruleId
|
||||
} = it;
|
||||
|
||||
return `
|
||||
<tr style="display: none;" class="f-${parentIndex}">
|
||||
<td>${lineNumber}:${columnNumber}</td>
|
||||
<td class="clr-${severityNumber}">${severityName}</td>
|
||||
<td>${encodeHTML(message)}</td>
|
||||
<td>
|
||||
<a href="${ruleUrl ? ruleUrl : ""}" target="_blank" rel="noopener noreferrer">${ruleId ? ruleId : ""}</a>
|
||||
</td>
|
||||
</tr>
|
||||
`.trimStart();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get HTML (table rows) describing the messages.
|
||||
* @param {Array} messages Messages.
|
||||
* @param {int} parentIndex Index of the parent HTML row.
|
||||
* @param {Object} rulesMeta Dictionary containing metadata for each rule executed by the analysis.
|
||||
* @returns {string} HTML (table rows) describing the messages.
|
||||
*/
|
||||
function renderMessages(messages, parentIndex, rulesMeta) {
|
||||
|
||||
/**
|
||||
* Get HTML (table row) describing a message.
|
||||
* @param {Object} message Message.
|
||||
* @returns {string} HTML (table row) describing a message.
|
||||
*/
|
||||
return messages.map(message => {
|
||||
const lineNumber = message.line || 0;
|
||||
const columnNumber = message.column || 0;
|
||||
let ruleUrl;
|
||||
|
||||
if (rulesMeta) {
|
||||
const meta = rulesMeta[message.ruleId];
|
||||
|
||||
if (meta && meta.docs && meta.docs.url) {
|
||||
ruleUrl = meta.docs.url;
|
||||
}
|
||||
}
|
||||
|
||||
return messageTemplate({
|
||||
parentIndex,
|
||||
lineNumber,
|
||||
columnNumber,
|
||||
severityNumber: message.severity,
|
||||
severityName: message.severity === 1 ? "Warning" : "Error",
|
||||
message: message.message,
|
||||
ruleId: message.ruleId,
|
||||
ruleUrl
|
||||
});
|
||||
}).join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get HTML (table row) describing the result for a single file.
|
||||
* @param {Object} it data for the file.
|
||||
* @returns {string} HTML (table row) describing the result for the file.
|
||||
*/
|
||||
function resultTemplate(it) {
|
||||
const { color, index, filePath, summary } = it;
|
||||
|
||||
return `
|
||||
<tr class="bg-${color}" data-group="f-${index}">
|
||||
<th colspan="4">
|
||||
[+] ${encodeHTML(filePath)}
|
||||
<span>${encodeHTML(summary)}</span>
|
||||
</th>
|
||||
</tr>
|
||||
`.trimStart();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the results.
|
||||
* @param {Array} results Test results.
|
||||
* @param {Object} rulesMeta Dictionary containing metadata for each rule executed by the analysis.
|
||||
* @returns {string} HTML string describing the results.
|
||||
*/
|
||||
function renderResults(results, rulesMeta) {
|
||||
return results.map((result, index) => resultTemplate({
|
||||
index,
|
||||
color: renderColor(result.errorCount, result.warningCount),
|
||||
filePath: result.filePath,
|
||||
summary: renderSummary(result.errorCount, result.warningCount)
|
||||
}) + renderMessages(result.messages, index, rulesMeta)).join("\n");
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Public Interface
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = function(results, data) {
|
||||
let totalErrors,
|
||||
totalWarnings;
|
||||
|
||||
const metaData = data ? data.rulesMeta : {};
|
||||
|
||||
totalErrors = 0;
|
||||
totalWarnings = 0;
|
||||
|
||||
// Iterate over results to get totals
|
||||
results.forEach(result => {
|
||||
totalErrors += result.errorCount;
|
||||
totalWarnings += result.warningCount;
|
||||
});
|
||||
|
||||
return pageTemplate({
|
||||
date: new Date(),
|
||||
reportColor: renderColor(totalErrors, totalWarnings),
|
||||
reportSummary: renderSummary(totalErrors, totalWarnings),
|
||||
results: renderResults(results, metaData)
|
||||
});
|
||||
};
|
16
node_modules/eslint/lib/cli-engine/formatters/json-with-metadata.js
generated
vendored
Normal file
16
node_modules/eslint/lib/cli-engine/formatters/json-with-metadata.js
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* @fileoverview JSON reporter, including rules metadata
|
||||
* @author Chris Meyer
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Public Interface
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = function(results, data) {
|
||||
return JSON.stringify({
|
||||
results,
|
||||
metadata: data
|
||||
});
|
||||
};
|
13
node_modules/eslint/lib/cli-engine/formatters/json.js
generated
vendored
Normal file
13
node_modules/eslint/lib/cli-engine/formatters/json.js
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* @fileoverview JSON reporter
|
||||
* @author Burak Yigit Kaya aka BYK
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Public Interface
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = function(results) {
|
||||
return JSON.stringify(results);
|
||||
};
|
101
node_modules/eslint/lib/cli-engine/formatters/stylish.js
generated
vendored
Normal file
101
node_modules/eslint/lib/cli-engine/formatters/stylish.js
generated
vendored
Normal file
@ -0,0 +1,101 @@
|
||||
/**
|
||||
* @fileoverview Stylish reporter
|
||||
* @author Sindre Sorhus
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
const chalk = require("chalk"),
|
||||
util = require("node:util"),
|
||||
table = require("../../shared/text-table");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Given a word and a count, append an s if count is not one.
|
||||
* @param {string} word A word in its singular form.
|
||||
* @param {int} count A number controlling whether word should be pluralized.
|
||||
* @returns {string} The original word with an s on the end if count is not one.
|
||||
*/
|
||||
function pluralize(word, count) {
|
||||
return (count === 1 ? word : `${word}s`);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Public Interface
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = function(results) {
|
||||
|
||||
let output = "\n",
|
||||
errorCount = 0,
|
||||
warningCount = 0,
|
||||
fixableErrorCount = 0,
|
||||
fixableWarningCount = 0,
|
||||
summaryColor = "yellow";
|
||||
|
||||
results.forEach(result => {
|
||||
const messages = result.messages;
|
||||
|
||||
if (messages.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
errorCount += result.errorCount;
|
||||
warningCount += result.warningCount;
|
||||
fixableErrorCount += result.fixableErrorCount;
|
||||
fixableWarningCount += result.fixableWarningCount;
|
||||
|
||||
output += `${chalk.underline(result.filePath)}\n`;
|
||||
|
||||
output += `${table(
|
||||
messages.map(message => {
|
||||
let messageType;
|
||||
|
||||
if (message.fatal || message.severity === 2) {
|
||||
messageType = chalk.red("error");
|
||||
summaryColor = "red";
|
||||
} else {
|
||||
messageType = chalk.yellow("warning");
|
||||
}
|
||||
|
||||
return [
|
||||
"",
|
||||
String(message.line || 0),
|
||||
String(message.column || 0),
|
||||
messageType,
|
||||
message.message.replace(/([^ ])\.$/u, "$1"),
|
||||
chalk.dim(message.ruleId || "")
|
||||
];
|
||||
}),
|
||||
{
|
||||
align: ["", "r", "l"],
|
||||
stringLength(str) {
|
||||
return util.stripVTControlCharacters(str).length;
|
||||
}
|
||||
}
|
||||
).split("\n").map(el => el.replace(/(\d+)\s+(\d+)/u, (m, p1, p2) => chalk.dim(`${p1}:${p2}`))).join("\n")}\n\n`;
|
||||
});
|
||||
|
||||
const total = errorCount + warningCount;
|
||||
|
||||
if (total > 0) {
|
||||
output += chalk[summaryColor].bold([
|
||||
"\u2716 ", total, pluralize(" problem", total),
|
||||
" (", errorCount, pluralize(" error", errorCount), ", ",
|
||||
warningCount, pluralize(" warning", warningCount), ")\n"
|
||||
].join(""));
|
||||
|
||||
if (fixableErrorCount > 0 || fixableWarningCount > 0) {
|
||||
output += chalk[summaryColor].bold([
|
||||
" ", fixableErrorCount, pluralize(" error", fixableErrorCount), " and ",
|
||||
fixableWarningCount, pluralize(" warning", fixableWarningCount),
|
||||
" potentially fixable with the `--fix` option.\n"
|
||||
].join(""));
|
||||
}
|
||||
}
|
||||
|
||||
// Resets output color, for prevent change on top level
|
||||
return total > 0 ? chalk.reset(output) : "";
|
||||
};
|
35
node_modules/eslint/lib/cli-engine/hash.js
generated
vendored
Normal file
35
node_modules/eslint/lib/cli-engine/hash.js
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* @fileoverview Defining the hashing function in one place.
|
||||
* @author Michael Ficarra
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const murmur = require("imurmurhash");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Private
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* hash the given string
|
||||
* @param {string} str the string to hash
|
||||
* @returns {string} the hash
|
||||
*/
|
||||
function hash(str) {
|
||||
return murmur(str).result().toString(36);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Public Interface
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = hash;
|
7
node_modules/eslint/lib/cli-engine/index.js
generated
vendored
Normal file
7
node_modules/eslint/lib/cli-engine/index.js
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
const { CLIEngine } = require("./cli-engine");
|
||||
|
||||
module.exports = {
|
||||
CLIEngine
|
||||
};
|
203
node_modules/eslint/lib/cli-engine/lint-result-cache.js
generated
vendored
Normal file
203
node_modules/eslint/lib/cli-engine/lint-result-cache.js
generated
vendored
Normal file
@ -0,0 +1,203 @@
|
||||
/**
|
||||
* @fileoverview Utility for caching lint results.
|
||||
* @author Kevin Partington
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const fs = require("node:fs");
|
||||
const fileEntryCache = require("file-entry-cache");
|
||||
const stringify = require("json-stable-stringify-without-jsonify");
|
||||
const pkg = require("../../package.json");
|
||||
const assert = require("../shared/assert");
|
||||
const hash = require("./hash");
|
||||
|
||||
const debug = require("debug")("eslint:lint-result-cache");
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const configHashCache = new WeakMap();
|
||||
const nodeVersion = process && process.version;
|
||||
|
||||
const validCacheStrategies = ["metadata", "content"];
|
||||
const invalidCacheStrategyErrorMessage = `Cache strategy must be one of: ${validCacheStrategies
|
||||
.map(strategy => `"${strategy}"`)
|
||||
.join(", ")}`;
|
||||
|
||||
/**
|
||||
* Tests whether a provided cacheStrategy is valid
|
||||
* @param {string} cacheStrategy The cache strategy to use
|
||||
* @returns {boolean} true if `cacheStrategy` is one of `validCacheStrategies`; false otherwise
|
||||
*/
|
||||
function isValidCacheStrategy(cacheStrategy) {
|
||||
return (
|
||||
validCacheStrategies.includes(cacheStrategy)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the hash of the config
|
||||
* @param {ConfigArray} config The config.
|
||||
* @returns {string} The hash of the config
|
||||
*/
|
||||
function hashOfConfigFor(config) {
|
||||
if (!configHashCache.has(config)) {
|
||||
configHashCache.set(config, hash(`${pkg.version}_${nodeVersion}_${stringify(config)}`));
|
||||
}
|
||||
|
||||
return configHashCache.get(config);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Public Interface
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Lint result cache. This wraps around the file-entry-cache module,
|
||||
* transparently removing properties that are difficult or expensive to
|
||||
* serialize and adding them back in on retrieval.
|
||||
*/
|
||||
class LintResultCache {
|
||||
|
||||
/**
|
||||
* Creates a new LintResultCache instance.
|
||||
* @param {string} cacheFileLocation The cache file location.
|
||||
* @param {"metadata" | "content"} cacheStrategy The cache strategy to use.
|
||||
*/
|
||||
constructor(cacheFileLocation, cacheStrategy) {
|
||||
assert(cacheFileLocation, "Cache file location is required");
|
||||
assert(cacheStrategy, "Cache strategy is required");
|
||||
assert(
|
||||
isValidCacheStrategy(cacheStrategy),
|
||||
invalidCacheStrategyErrorMessage
|
||||
);
|
||||
|
||||
debug(`Caching results to ${cacheFileLocation}`);
|
||||
|
||||
const useChecksum = cacheStrategy === "content";
|
||||
|
||||
debug(
|
||||
`Using "${cacheStrategy}" strategy to detect changes`
|
||||
);
|
||||
|
||||
this.fileEntryCache = fileEntryCache.create(
|
||||
cacheFileLocation,
|
||||
void 0,
|
||||
useChecksum
|
||||
);
|
||||
this.cacheFileLocation = cacheFileLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve cached lint results for a given file path, if present in the
|
||||
* cache. If the file is present and has not been changed, rebuild any
|
||||
* missing result information.
|
||||
* @param {string} filePath The file for which to retrieve lint results.
|
||||
* @param {ConfigArray} config The config of the file.
|
||||
* @returns {Object|null} The rebuilt lint results, or null if the file is
|
||||
* changed or not in the filesystem.
|
||||
*/
|
||||
getCachedLintResults(filePath, config) {
|
||||
|
||||
/*
|
||||
* Cached lint results are valid if and only if:
|
||||
* 1. The file is present in the filesystem
|
||||
* 2. The file has not changed since the time it was previously linted
|
||||
* 3. The ESLint configuration has not changed since the time the file
|
||||
* was previously linted
|
||||
* If any of these are not true, we will not reuse the lint results.
|
||||
*/
|
||||
const fileDescriptor = this.fileEntryCache.getFileDescriptor(filePath);
|
||||
const hashOfConfig = hashOfConfigFor(config);
|
||||
const changed =
|
||||
fileDescriptor.changed ||
|
||||
fileDescriptor.meta.hashOfConfig !== hashOfConfig;
|
||||
|
||||
if (fileDescriptor.notFound) {
|
||||
debug(`File not found on the file system: ${filePath}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
debug(`Cache entry not found or no longer valid: ${filePath}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const cachedResults = fileDescriptor.meta.results;
|
||||
|
||||
// Just in case, not sure if this can ever happen.
|
||||
if (!cachedResults) {
|
||||
return cachedResults;
|
||||
}
|
||||
|
||||
/*
|
||||
* Shallow clone the object to ensure that any properties added or modified afterwards
|
||||
* will not be accidentally stored in the cache file when `reconcile()` is called.
|
||||
* https://github.com/eslint/eslint/issues/13507
|
||||
* All intentional changes to the cache file must be done through `setCachedLintResults()`.
|
||||
*/
|
||||
const results = { ...cachedResults };
|
||||
|
||||
// If source is present but null, need to reread the file from the filesystem.
|
||||
if (results.source === null) {
|
||||
debug(`Rereading cached result source from filesystem: ${filePath}`);
|
||||
results.source = fs.readFileSync(filePath, "utf-8");
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the cached lint results for a given file path, after removing any
|
||||
* information that will be both unnecessary and difficult to serialize.
|
||||
* Avoids caching results with an "output" property (meaning fixes were
|
||||
* applied), to prevent potentially incorrect results if fixes are not
|
||||
* written to disk.
|
||||
* @param {string} filePath The file for which to set lint results.
|
||||
* @param {ConfigArray} config The config of the file.
|
||||
* @param {Object} result The lint result to be set for the file.
|
||||
* @returns {void}
|
||||
*/
|
||||
setCachedLintResults(filePath, config, result) {
|
||||
if (result && Object.hasOwn(result, "output")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fileDescriptor = this.fileEntryCache.getFileDescriptor(filePath);
|
||||
|
||||
if (fileDescriptor && !fileDescriptor.notFound) {
|
||||
debug(`Updating cached result: ${filePath}`);
|
||||
|
||||
// Serialize the result, except that we want to remove the file source if present.
|
||||
const resultToSerialize = Object.assign({}, result);
|
||||
|
||||
/*
|
||||
* Set result.source to null.
|
||||
* In `getCachedLintResults`, if source is explicitly null, we will
|
||||
* read the file from the filesystem to set the value again.
|
||||
*/
|
||||
if (Object.hasOwn(resultToSerialize, "source")) {
|
||||
resultToSerialize.source = null;
|
||||
}
|
||||
|
||||
fileDescriptor.meta.results = resultToSerialize;
|
||||
fileDescriptor.meta.hashOfConfig = hashOfConfigFor(config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists the in-memory cache to disk.
|
||||
* @returns {void}
|
||||
*/
|
||||
reconcile() {
|
||||
debug(`Persisting cached results: ${this.cacheFileLocation}`);
|
||||
this.fileEntryCache.reconcile();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LintResultCache;
|
46
node_modules/eslint/lib/cli-engine/load-rules.js
generated
vendored
Normal file
46
node_modules/eslint/lib/cli-engine/load-rules.js
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* @fileoverview Module for loading rules from files and directories.
|
||||
* @author Michael Ficarra
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const fs = require("node:fs"),
|
||||
path = require("node:path");
|
||||
|
||||
const rulesDirCache = {};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Public Interface
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Load all rule modules from specified directory.
|
||||
* @param {string} relativeRulesDir Path to rules directory, may be relative.
|
||||
* @param {string} cwd Current working directory
|
||||
* @returns {Object} Loaded rule modules.
|
||||
*/
|
||||
module.exports = function(relativeRulesDir, cwd) {
|
||||
const rulesDir = path.resolve(cwd, relativeRulesDir);
|
||||
|
||||
// cache will help performance as IO operation are expensive
|
||||
if (rulesDirCache[rulesDir]) {
|
||||
return rulesDirCache[rulesDir];
|
||||
}
|
||||
|
||||
const rules = Object.create(null);
|
||||
|
||||
fs.readdirSync(rulesDir).forEach(file => {
|
||||
if (path.extname(file) !== ".js") {
|
||||
return;
|
||||
}
|
||||
rules[file.slice(0, -3)] = require(path.join(rulesDir, file));
|
||||
});
|
||||
rulesDirCache[rulesDir] = rules;
|
||||
|
||||
return rules;
|
||||
};
|
548
node_modules/eslint/lib/cli.js
generated
vendored
Normal file
548
node_modules/eslint/lib/cli.js
generated
vendored
Normal file
@ -0,0 +1,548 @@
|
||||
/**
|
||||
* @fileoverview Main CLI object.
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
* NOTE: The CLI object should *not* call process.exit() directly. It should only return
|
||||
* exit codes. This allows other programs to use the CLI object and still control
|
||||
* when the program exits.
|
||||
*/
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const fs = require("node:fs"),
|
||||
path = require("node:path"),
|
||||
{ promisify } = require("node:util"),
|
||||
{ LegacyESLint } = require("./eslint"),
|
||||
{ ESLint, shouldUseFlatConfig, locateConfigFileToUse } = require("./eslint/eslint"),
|
||||
createCLIOptions = require("./options"),
|
||||
log = require("./shared/logging"),
|
||||
RuntimeInfo = require("./shared/runtime-info"),
|
||||
{ normalizeSeverityToString } = require("./shared/severity");
|
||||
const { Legacy: { naming } } = require("@eslint/eslintrc");
|
||||
const { ModuleImporter } = require("@humanwhocodes/module-importer");
|
||||
const debug = require("debug")("eslint:cli");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Types
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @typedef {import("./eslint/eslint").ESLintOptions} ESLintOptions */
|
||||
/** @typedef {import("./eslint/eslint").LintMessage} LintMessage */
|
||||
/** @typedef {import("./eslint/eslint").LintResult} LintResult */
|
||||
/** @typedef {import("./options").ParsedCLIOptions} ParsedCLIOptions */
|
||||
/** @typedef {import("./shared/types").Plugin} Plugin */
|
||||
/** @typedef {import("./shared/types").ResultsMeta} ResultsMeta */
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const mkdir = promisify(fs.mkdir);
|
||||
const stat = promisify(fs.stat);
|
||||
const writeFile = promisify(fs.writeFile);
|
||||
|
||||
/**
|
||||
* Loads plugins with the specified names.
|
||||
* @param {{ "import": (name: string) => Promise<any> }} importer An object with an `import` method called once for each plugin.
|
||||
* @param {string[]} pluginNames The names of the plugins to be loaded, with or without the "eslint-plugin-" prefix.
|
||||
* @returns {Promise<Record<string, Plugin>>} A mapping of plugin short names to implementations.
|
||||
*/
|
||||
async function loadPlugins(importer, pluginNames) {
|
||||
const plugins = {};
|
||||
|
||||
await Promise.all(pluginNames.map(async pluginName => {
|
||||
|
||||
const longName = naming.normalizePackageName(pluginName, "eslint-plugin");
|
||||
const module = await importer.import(longName);
|
||||
|
||||
if (!("default" in module)) {
|
||||
throw new Error(`"${longName}" cannot be used with the \`--plugin\` option because its default module does not provide a \`default\` export`);
|
||||
}
|
||||
|
||||
const shortName = naming.getShorthandName(pluginName, "eslint-plugin");
|
||||
|
||||
plugins[shortName] = module.default;
|
||||
}));
|
||||
|
||||
return plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Predicate function for whether or not to apply fixes in quiet mode.
|
||||
* If a message is a warning, do not apply a fix.
|
||||
* @param {LintMessage} message The lint result.
|
||||
* @returns {boolean} True if the lint message is an error (and thus should be
|
||||
* autofixed), false otherwise.
|
||||
*/
|
||||
function quietFixPredicate(message) {
|
||||
return message.severity === 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Predicate function for whether or not to run a rule in quiet mode.
|
||||
* If a rule is set to warning, do not run it.
|
||||
* @param {{ ruleId: string; severity: number; }} rule The rule id and severity.
|
||||
* @returns {boolean} True if the lint rule should run, false otherwise.
|
||||
*/
|
||||
function quietRuleFilter(rule) {
|
||||
return rule.severity === 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates the CLI options into the options expected by the ESLint constructor.
|
||||
* @param {ParsedCLIOptions} cliOptions The CLI options to translate.
|
||||
* @param {"flat"|"eslintrc"} [configType="eslintrc"] The format of the
|
||||
* config to generate.
|
||||
* @returns {Promise<ESLintOptions>} The options object for the ESLint constructor.
|
||||
* @private
|
||||
*/
|
||||
async function translateOptions({
|
||||
cache,
|
||||
cacheFile,
|
||||
cacheLocation,
|
||||
cacheStrategy,
|
||||
config,
|
||||
configLookup,
|
||||
env,
|
||||
errorOnUnmatchedPattern,
|
||||
eslintrc,
|
||||
ext,
|
||||
fix,
|
||||
fixDryRun,
|
||||
fixType,
|
||||
flag,
|
||||
global,
|
||||
ignore,
|
||||
ignorePath,
|
||||
ignorePattern,
|
||||
inlineConfig,
|
||||
parser,
|
||||
parserOptions,
|
||||
plugin,
|
||||
quiet,
|
||||
reportUnusedDisableDirectives,
|
||||
reportUnusedDisableDirectivesSeverity,
|
||||
resolvePluginsRelativeTo,
|
||||
rule,
|
||||
rulesdir,
|
||||
stats,
|
||||
warnIgnored,
|
||||
passOnNoPatterns,
|
||||
maxWarnings
|
||||
}, configType) {
|
||||
|
||||
let overrideConfig, overrideConfigFile;
|
||||
const importer = new ModuleImporter();
|
||||
|
||||
if (configType === "flat") {
|
||||
overrideConfigFile = (typeof config === "string") ? config : !configLookup;
|
||||
if (overrideConfigFile === false) {
|
||||
overrideConfigFile = void 0;
|
||||
}
|
||||
|
||||
const languageOptions = {};
|
||||
|
||||
if (global) {
|
||||
languageOptions.globals = global.reduce((obj, name) => {
|
||||
if (name.endsWith(":true")) {
|
||||
obj[name.slice(0, -5)] = "writable";
|
||||
} else {
|
||||
obj[name] = "readonly";
|
||||
}
|
||||
return obj;
|
||||
}, {});
|
||||
}
|
||||
|
||||
if (parserOptions) {
|
||||
languageOptions.parserOptions = parserOptions;
|
||||
}
|
||||
|
||||
if (parser) {
|
||||
languageOptions.parser = await importer.import(parser);
|
||||
}
|
||||
|
||||
overrideConfig = [{
|
||||
...Object.keys(languageOptions).length > 0 ? { languageOptions } : {},
|
||||
rules: rule ? rule : {}
|
||||
}];
|
||||
|
||||
if (reportUnusedDisableDirectives || reportUnusedDisableDirectivesSeverity !== void 0) {
|
||||
overrideConfig[0].linterOptions = {
|
||||
reportUnusedDisableDirectives: reportUnusedDisableDirectives
|
||||
? "error"
|
||||
: normalizeSeverityToString(reportUnusedDisableDirectivesSeverity)
|
||||
};
|
||||
}
|
||||
|
||||
if (plugin) {
|
||||
overrideConfig[0].plugins = await loadPlugins(importer, plugin);
|
||||
}
|
||||
|
||||
} else {
|
||||
overrideConfigFile = config;
|
||||
|
||||
overrideConfig = {
|
||||
env: env && env.reduce((obj, name) => {
|
||||
obj[name] = true;
|
||||
return obj;
|
||||
}, {}),
|
||||
globals: global && global.reduce((obj, name) => {
|
||||
if (name.endsWith(":true")) {
|
||||
obj[name.slice(0, -5)] = "writable";
|
||||
} else {
|
||||
obj[name] = "readonly";
|
||||
}
|
||||
return obj;
|
||||
}, {}),
|
||||
ignorePatterns: ignorePattern,
|
||||
parser,
|
||||
parserOptions,
|
||||
plugins: plugin,
|
||||
rules: rule
|
||||
};
|
||||
}
|
||||
|
||||
const options = {
|
||||
allowInlineConfig: inlineConfig,
|
||||
cache,
|
||||
cacheLocation: cacheLocation || cacheFile,
|
||||
cacheStrategy,
|
||||
errorOnUnmatchedPattern,
|
||||
fix: (fix || fixDryRun) && (quiet ? quietFixPredicate : true),
|
||||
fixTypes: fixType,
|
||||
ignore,
|
||||
overrideConfig,
|
||||
overrideConfigFile,
|
||||
passOnNoPatterns
|
||||
};
|
||||
|
||||
if (configType === "flat") {
|
||||
options.ignorePatterns = ignorePattern;
|
||||
options.stats = stats;
|
||||
options.warnIgnored = warnIgnored;
|
||||
options.flags = flag;
|
||||
|
||||
/*
|
||||
* For performance reasons rules not marked as 'error' are filtered out in quiet mode. As maxWarnings
|
||||
* requires rules set to 'warn' to be run, we only filter out 'warn' rules if maxWarnings is not specified.
|
||||
*/
|
||||
options.ruleFilter = quiet && maxWarnings === -1 ? quietRuleFilter : () => true;
|
||||
} else {
|
||||
options.resolvePluginsRelativeTo = resolvePluginsRelativeTo;
|
||||
options.rulePaths = rulesdir;
|
||||
options.useEslintrc = eslintrc;
|
||||
options.extensions = ext;
|
||||
options.ignorePath = ignorePath;
|
||||
if (reportUnusedDisableDirectives || reportUnusedDisableDirectivesSeverity !== void 0) {
|
||||
options.reportUnusedDisableDirectives = reportUnusedDisableDirectives
|
||||
? "error"
|
||||
: normalizeSeverityToString(reportUnusedDisableDirectivesSeverity);
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count error messages.
|
||||
* @param {LintResult[]} results The lint results.
|
||||
* @returns {{errorCount:number;fatalErrorCount:number,warningCount:number}} The number of error messages.
|
||||
*/
|
||||
function countErrors(results) {
|
||||
let errorCount = 0;
|
||||
let fatalErrorCount = 0;
|
||||
let warningCount = 0;
|
||||
|
||||
for (const result of results) {
|
||||
errorCount += result.errorCount;
|
||||
fatalErrorCount += result.fatalErrorCount;
|
||||
warningCount += result.warningCount;
|
||||
}
|
||||
|
||||
return { errorCount, fatalErrorCount, warningCount };
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given file path is a directory or not.
|
||||
* @param {string} filePath The path to a file to check.
|
||||
* @returns {Promise<boolean>} `true` if the given path is a directory.
|
||||
*/
|
||||
async function isDirectory(filePath) {
|
||||
try {
|
||||
return (await stat(filePath)).isDirectory();
|
||||
} catch (error) {
|
||||
if (error.code === "ENOENT" || error.code === "ENOTDIR") {
|
||||
return false;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs the results of the linting.
|
||||
* @param {ESLint} engine The ESLint instance to use.
|
||||
* @param {LintResult[]} results The results to print.
|
||||
* @param {string} format The name of the formatter to use or the path to the formatter.
|
||||
* @param {string} outputFile The path for the output file.
|
||||
* @param {ResultsMeta} resultsMeta Warning count and max threshold.
|
||||
* @returns {Promise<boolean>} True if the printing succeeds, false if not.
|
||||
* @private
|
||||
*/
|
||||
async function printResults(engine, results, format, outputFile, resultsMeta) {
|
||||
let formatter;
|
||||
|
||||
try {
|
||||
formatter = await engine.loadFormatter(format);
|
||||
} catch (e) {
|
||||
log.error(e.message);
|
||||
return false;
|
||||
}
|
||||
|
||||
const output = await formatter.format(results, resultsMeta);
|
||||
|
||||
if (outputFile) {
|
||||
const filePath = path.resolve(process.cwd(), outputFile);
|
||||
|
||||
if (await isDirectory(filePath)) {
|
||||
log.error("Cannot write to output file path, it is a directory: %s", outputFile);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await mkdir(path.dirname(filePath), { recursive: true });
|
||||
await writeFile(filePath, output);
|
||||
} catch (ex) {
|
||||
log.error("There was a problem writing the output file:\n%s", ex);
|
||||
return false;
|
||||
}
|
||||
} else if (output) {
|
||||
log.info(output);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Public Interface
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Encapsulates all CLI behavior for eslint. Makes it easier to test as well as
|
||||
* for other Node.js programs to effectively run the CLI.
|
||||
*/
|
||||
const cli = {
|
||||
|
||||
/**
|
||||
* Calculates the command string for the --inspect-config operation.
|
||||
* @param {string} configFile The path to the config file to inspect.
|
||||
* @param {boolean} hasUnstableTSConfigFlag `true` if the `unstable_ts_config` flag is enabled, `false` if it's not.
|
||||
* @returns {Promise<string>} The command string to execute.
|
||||
*/
|
||||
async calculateInspectConfigFlags(configFile, hasUnstableTSConfigFlag) {
|
||||
|
||||
// find the config file
|
||||
const {
|
||||
configFilePath,
|
||||
basePath
|
||||
} = await locateConfigFileToUse({ cwd: process.cwd(), configFile }, hasUnstableTSConfigFlag);
|
||||
|
||||
return ["--config", configFilePath, "--basePath", basePath];
|
||||
},
|
||||
|
||||
/**
|
||||
* Executes the CLI based on an array of arguments that is passed in.
|
||||
* @param {string|Array|Object} args The arguments to process.
|
||||
* @param {string} [text] The text to lint (used for TTY).
|
||||
* @param {boolean} [allowFlatConfig=true] Whether or not to allow flat config.
|
||||
* @returns {Promise<number>} The exit code for the operation.
|
||||
*/
|
||||
async execute(args, text, allowFlatConfig = true) {
|
||||
if (Array.isArray(args)) {
|
||||
debug("CLI args: %o", args.slice(2));
|
||||
}
|
||||
|
||||
/*
|
||||
* Before doing anything, we need to see if we are using a
|
||||
* flat config file. If so, then we need to change the way command
|
||||
* line args are parsed. This is temporary, and when we fully
|
||||
* switch to flat config we can remove this logic.
|
||||
*/
|
||||
|
||||
const usingFlatConfig = allowFlatConfig && await shouldUseFlatConfig();
|
||||
|
||||
debug("Using flat config?", usingFlatConfig);
|
||||
|
||||
if (allowFlatConfig && !usingFlatConfig) {
|
||||
process.emitWarning("You are using an eslintrc configuration file, which is deprecated and support will be removed in v10.0.0. Please migrate to an eslint.config.js file. See https://eslint.org/docs/latest/use/configure/migration-guide for details. An eslintrc configuration file is used because you have the ESLINT_USE_FLAT_CONFIG environment variable set to false. If you want to use an eslint.config.js file, remove the environment variable. If you want to find the location of the eslintrc configuration file, use the --debug flag.", "ESLintRCWarning");
|
||||
}
|
||||
|
||||
const CLIOptions = createCLIOptions(usingFlatConfig);
|
||||
|
||||
/** @type {ParsedCLIOptions} */
|
||||
let options;
|
||||
|
||||
try {
|
||||
options = CLIOptions.parse(args);
|
||||
} catch (error) {
|
||||
debug("Error parsing CLI options:", error.message);
|
||||
|
||||
let errorMessage = error.message;
|
||||
|
||||
if (usingFlatConfig) {
|
||||
errorMessage += "\nYou're using eslint.config.js, some command line flags are no longer available. Please see https://eslint.org/docs/latest/use/command-line-interface for details.";
|
||||
}
|
||||
|
||||
log.error(errorMessage);
|
||||
return 2;
|
||||
}
|
||||
|
||||
const files = options._;
|
||||
const useStdin = typeof text === "string";
|
||||
|
||||
if (options.help) {
|
||||
log.info(CLIOptions.generateHelp());
|
||||
return 0;
|
||||
}
|
||||
if (options.version) {
|
||||
log.info(RuntimeInfo.version());
|
||||
return 0;
|
||||
}
|
||||
if (options.envInfo) {
|
||||
try {
|
||||
log.info(RuntimeInfo.environment());
|
||||
return 0;
|
||||
} catch (err) {
|
||||
debug("Error retrieving environment info");
|
||||
log.error(err.message);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.printConfig) {
|
||||
if (files.length) {
|
||||
log.error("The --print-config option must be used with exactly one file name.");
|
||||
return 2;
|
||||
}
|
||||
if (useStdin) {
|
||||
log.error("The --print-config option is not available for piped-in code.");
|
||||
return 2;
|
||||
}
|
||||
|
||||
const engine = usingFlatConfig
|
||||
? new ESLint(await translateOptions(options, "flat"))
|
||||
: new LegacyESLint(await translateOptions(options));
|
||||
const fileConfig =
|
||||
await engine.calculateConfigForFile(options.printConfig);
|
||||
|
||||
log.info(JSON.stringify(fileConfig, null, " "));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (options.inspectConfig) {
|
||||
|
||||
log.info("You can also run this command directly using 'npx @eslint/config-inspector@latest' in the same directory as your configuration file.");
|
||||
|
||||
try {
|
||||
const flatOptions = await translateOptions(options, "flat");
|
||||
const spawn = require("cross-spawn");
|
||||
const flags = await cli.calculateInspectConfigFlags(flatOptions.overrideConfigFile, flatOptions.flags ? flatOptions.flags.includes("unstable_ts_config") : false);
|
||||
|
||||
spawn.sync("npx", ["@eslint/config-inspector@latest", ...flags], { encoding: "utf8", stdio: "inherit" });
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
debug(`Running on ${useStdin ? "text" : "files"}`);
|
||||
|
||||
if (options.fix && options.fixDryRun) {
|
||||
log.error("The --fix option and the --fix-dry-run option cannot be used together.");
|
||||
return 2;
|
||||
}
|
||||
if (useStdin && options.fix) {
|
||||
log.error("The --fix option is not available for piped-in code; use --fix-dry-run instead.");
|
||||
return 2;
|
||||
}
|
||||
if (options.fixType && !options.fix && !options.fixDryRun) {
|
||||
log.error("The --fix-type option requires either --fix or --fix-dry-run.");
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (options.reportUnusedDisableDirectives && options.reportUnusedDisableDirectivesSeverity !== void 0) {
|
||||
log.error("The --report-unused-disable-directives option and the --report-unused-disable-directives-severity option cannot be used together.");
|
||||
return 2;
|
||||
}
|
||||
|
||||
const ActiveESLint = usingFlatConfig ? ESLint : LegacyESLint;
|
||||
const eslintOptions = await translateOptions(options, usingFlatConfig ? "flat" : "eslintrc");
|
||||
const engine = new ActiveESLint(eslintOptions);
|
||||
let results;
|
||||
|
||||
if (useStdin) {
|
||||
results = await engine.lintText(text, {
|
||||
filePath: options.stdinFilename,
|
||||
|
||||
// flatConfig respects CLI flag and constructor warnIgnored, eslintrc forces true for backwards compatibility
|
||||
warnIgnored: usingFlatConfig ? void 0 : true
|
||||
});
|
||||
} else {
|
||||
results = await engine.lintFiles(files);
|
||||
}
|
||||
|
||||
if (options.fix) {
|
||||
debug("Fix mode enabled - applying fixes");
|
||||
await ActiveESLint.outputFixes(results);
|
||||
}
|
||||
|
||||
let resultsToPrint = results;
|
||||
|
||||
if (options.quiet) {
|
||||
debug("Quiet mode enabled - filtering out warnings");
|
||||
resultsToPrint = ActiveESLint.getErrorResults(resultsToPrint);
|
||||
}
|
||||
|
||||
const resultCounts = countErrors(results);
|
||||
const tooManyWarnings = options.maxWarnings >= 0 && resultCounts.warningCount > options.maxWarnings;
|
||||
const resultsMeta = tooManyWarnings
|
||||
? {
|
||||
maxWarningsExceeded: {
|
||||
maxWarnings: options.maxWarnings,
|
||||
foundWarnings: resultCounts.warningCount
|
||||
}
|
||||
}
|
||||
: {};
|
||||
|
||||
if (await printResults(engine, resultsToPrint, options.format, options.outputFile, resultsMeta)) {
|
||||
|
||||
// Errors and warnings from the original unfiltered results should determine the exit code
|
||||
const shouldExitForFatalErrors =
|
||||
options.exitOnFatalError && resultCounts.fatalErrorCount > 0;
|
||||
|
||||
if (!resultCounts.errorCount && tooManyWarnings) {
|
||||
log.error(
|
||||
"ESLint found too many warnings (maximum: %s).",
|
||||
options.maxWarnings
|
||||
);
|
||||
}
|
||||
|
||||
if (shouldExitForFatalErrors) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
return (resultCounts.errorCount || tooManyWarnings) ? 1 : 0;
|
||||
}
|
||||
|
||||
return 2;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = cli;
|
719
node_modules/eslint/lib/config/config-loader.js
generated
vendored
Normal file
719
node_modules/eslint/lib/config/config-loader.js
generated
vendored
Normal file
@ -0,0 +1,719 @@
|
||||
/**
|
||||
* @fileoverview Utility to load config files
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const path = require("node:path");
|
||||
const fs = require("node:fs/promises");
|
||||
const findUp = require("find-up");
|
||||
const { pathToFileURL } = require("node:url");
|
||||
const debug = require("debug")("eslint:config-loader");
|
||||
const { FlatConfigArray } = require("./flat-config-array");
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Types
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @typedef {import("../shared/types").FlatConfigObject} FlatConfigObject
|
||||
* @typedef {import("../shared/types").FlatConfigArray} FlatConfigArray
|
||||
* @typedef {Object} ConfigLoaderOptions
|
||||
* @property {string|false|undefined} configFile The path to the config file to use.
|
||||
* @property {string} cwd The current working directory.
|
||||
* @property {boolean} ignoreEnabled Indicates if ignore patterns should be honored.
|
||||
* @property {FlatConfigArray} [baseConfig] The base config to use.
|
||||
* @property {Array<FlatConfigObject>} [defaultConfigs] The default configs to use.
|
||||
* @property {Array<string>} [ignorePatterns] The ignore patterns to use.
|
||||
* @property {FlatConfigObject|Array<FlatConfigObject>} overrideConfig The override config to use.
|
||||
* @property {boolean} allowTS Indicates if TypeScript configuration files are allowed.
|
||||
*/
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const FLAT_CONFIG_FILENAMES = [
|
||||
"eslint.config.js",
|
||||
"eslint.config.mjs",
|
||||
"eslint.config.cjs"
|
||||
];
|
||||
|
||||
const TS_FLAT_CONFIG_FILENAMES = [
|
||||
"eslint.config.ts",
|
||||
"eslint.config.mts",
|
||||
"eslint.config.cts"
|
||||
];
|
||||
|
||||
const importedConfigFileModificationTime = new Map();
|
||||
|
||||
/**
|
||||
* Asserts that the given file path is valid.
|
||||
* @param {string} filePath The file path to check.
|
||||
* @returns {void}
|
||||
* @throws {Error} If `filePath` is not a non-empty string.
|
||||
*/
|
||||
function assertValidFilePath(filePath) {
|
||||
if (!filePath || typeof filePath !== "string") {
|
||||
throw new Error("'filePath' must be a non-empty string");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a configuration exists. A configuration exists if any
|
||||
* of the following are true:
|
||||
* - `configFilePath` is defined.
|
||||
* - `useConfigFile` is `false`.
|
||||
* @param {string|undefined} configFilePath The path to the config file.
|
||||
* @param {ConfigLoaderOptions} loaderOptions The options to use when loading configuration files.
|
||||
* @returns {void}
|
||||
* @throws {Error} If no configuration exists.
|
||||
*/
|
||||
function assertConfigurationExists(configFilePath, loaderOptions) {
|
||||
|
||||
const {
|
||||
configFile: useConfigFile
|
||||
} = loaderOptions;
|
||||
|
||||
if (!configFilePath && useConfigFile !== false) {
|
||||
const error = new Error("Could not find config file.");
|
||||
|
||||
error.messageTemplate = "config-file-missing";
|
||||
throw error;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the file is a TypeScript file.
|
||||
* @param {string} filePath The file path to check.
|
||||
* @returns {boolean} `true` if the file is a TypeScript file, `false` if it's not.
|
||||
*/
|
||||
function isFileTS(filePath) {
|
||||
const fileExtension = path.extname(filePath);
|
||||
|
||||
return /^\.[mc]?ts$/u.test(fileExtension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if ESLint is running in Bun.
|
||||
* @returns {boolean} `true` if the ESLint is running Bun, `false` if it's not.
|
||||
*/
|
||||
function isRunningInBun() {
|
||||
return !!globalThis.Bun;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if ESLint is running in Deno.
|
||||
* @returns {boolean} `true` if the ESLint is running in Deno, `false` if it's not.
|
||||
*/
|
||||
function isRunningInDeno() {
|
||||
return !!globalThis.Deno;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the config array from the given filename.
|
||||
* @param {string} filePath The filename to load from.
|
||||
* @param {boolean} allowTS Indicates if TypeScript configuration files are allowed.
|
||||
* @returns {Promise<any>} The config loaded from the config file.
|
||||
*/
|
||||
async function loadConfigFile(filePath, allowTS) {
|
||||
|
||||
debug(`Loading config from ${filePath}`);
|
||||
|
||||
const fileURL = pathToFileURL(filePath);
|
||||
|
||||
debug(`Config file URL is ${fileURL}`);
|
||||
|
||||
const mtime = (await fs.stat(filePath)).mtime.getTime();
|
||||
|
||||
/*
|
||||
* Append a query with the config file's modification time (`mtime`) in order
|
||||
* to import the current version of the config file. Without the query, `import()` would
|
||||
* cache the config file module by the pathname only, and then always return
|
||||
* the same version (the one that was actual when the module was imported for the first time).
|
||||
*
|
||||
* This ensures that the config file module is loaded and executed again
|
||||
* if it has been changed since the last time it was imported.
|
||||
* If it hasn't been changed, `import()` will just return the cached version.
|
||||
*
|
||||
* Note that we should not overuse queries (e.g., by appending the current time
|
||||
* to always reload the config file module) as that could cause memory leaks
|
||||
* because entries are never removed from the import cache.
|
||||
*/
|
||||
fileURL.searchParams.append("mtime", mtime);
|
||||
|
||||
/*
|
||||
* With queries, we can bypass the import cache. However, when import-ing a CJS module,
|
||||
* Node.js uses the require infrastructure under the hood. That includes the require cache,
|
||||
* which caches the config file module by its file path (queries have no effect).
|
||||
* Therefore, we also need to clear the require cache before importing the config file module.
|
||||
* In order to get the same behavior with ESM and CJS config files, in particular - to reload
|
||||
* the config file only if it has been changed, we track file modification times and clear
|
||||
* the require cache only if the file has been changed.
|
||||
*/
|
||||
if (importedConfigFileModificationTime.get(filePath) !== mtime) {
|
||||
delete require.cache[filePath];
|
||||
}
|
||||
|
||||
const isTS = isFileTS(filePath);
|
||||
const isBun = isRunningInBun();
|
||||
const isDeno = isRunningInDeno();
|
||||
|
||||
/*
|
||||
* If we are dealing with a TypeScript file, then we need to use `jiti` to load it
|
||||
* in Node.js. Deno and Bun both allow native importing of TypeScript files.
|
||||
*
|
||||
* When Node.js supports native TypeScript imports, we can remove this check.
|
||||
*/
|
||||
if (allowTS && isTS && !isDeno && !isBun) {
|
||||
|
||||
// eslint-disable-next-line no-use-before-define -- `ConfigLoader.loadJiti` can be overwritten for testing
|
||||
const { createJiti } = await ConfigLoader.loadJiti().catch(() => {
|
||||
throw new Error("The 'jiti' library is required for loading TypeScript configuration files. Make sure to install it.");
|
||||
});
|
||||
|
||||
// `createJiti` was added in jiti v2.
|
||||
if (typeof createJiti !== "function") {
|
||||
throw new Error("You are using an outdated version of the 'jiti' library. Please update to the latest version of 'jiti' to ensure compatibility and access to the latest features.");
|
||||
}
|
||||
|
||||
/*
|
||||
* Disabling `moduleCache` allows us to reload a
|
||||
* config file when the last modified timestamp changes.
|
||||
*/
|
||||
|
||||
const jiti = createJiti(__filename, { moduleCache: false, interopDefault: false });
|
||||
const config = await jiti.import(fileURL.href);
|
||||
|
||||
importedConfigFileModificationTime.set(filePath, mtime);
|
||||
|
||||
return config?.default ?? config;
|
||||
}
|
||||
|
||||
|
||||
// fallback to normal runtime behavior
|
||||
|
||||
const config = (await import(fileURL)).default;
|
||||
|
||||
importedConfigFileModificationTime.set(filePath, mtime);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Exports
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Encapsulates the loading and caching of configuration files when looking up
|
||||
* from the file being linted.
|
||||
*/
|
||||
class ConfigLoader {
|
||||
|
||||
/**
|
||||
* Map of config file paths to the config arrays for those directories.
|
||||
* @type {Map<string, FlatConfigArray|Promise<FlatConfigArray>>}
|
||||
*/
|
||||
#configArrays = new Map();
|
||||
|
||||
/**
|
||||
* Map of absolute directory names to the config file paths for those directories.
|
||||
* @type {Map<string, {configFilePath:string,basePath:string}|Promise<{configFilePath:string,basePath:string}>>}
|
||||
*/
|
||||
#configFilePaths = new Map();
|
||||
|
||||
/**
|
||||
* The options to use when loading configuration files.
|
||||
* @type {ConfigLoaderOptions}
|
||||
*/
|
||||
#options;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {ConfigLoaderOptions} options The options to use when loading configuration files.
|
||||
*/
|
||||
constructor(options) {
|
||||
this.#options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines which config file to use. This is determined by seeing if an
|
||||
* override config file was specified, and if so, using it; otherwise, as long
|
||||
* as override config file is not explicitly set to `false`, it will search
|
||||
* upwards from `fromDirectory` for a file named `eslint.config.js`.
|
||||
* @param {string} fromDirectory The directory from which to start searching.
|
||||
* @returns {Promise<{configFilePath:string|undefined,basePath:string}>} Location information for
|
||||
* the config file.
|
||||
*/
|
||||
async #locateConfigFileToUse(fromDirectory) {
|
||||
|
||||
// check cache first
|
||||
if (this.#configFilePaths.has(fromDirectory)) {
|
||||
return this.#configFilePaths.get(fromDirectory);
|
||||
}
|
||||
|
||||
const resultPromise = ConfigLoader.locateConfigFileToUse({
|
||||
useConfigFile: this.#options.configFile,
|
||||
cwd: this.#options.cwd,
|
||||
fromDirectory,
|
||||
allowTS: this.#options.allowTS
|
||||
});
|
||||
|
||||
// ensure `ConfigLoader.locateConfigFileToUse` is called only once for `fromDirectory`
|
||||
this.#configFilePaths.set(fromDirectory, resultPromise);
|
||||
|
||||
// Unwrap the promise. This is primarily for the sync `getCachedConfigArrayForPath` method.
|
||||
const result = await resultPromise;
|
||||
|
||||
this.#configFilePaths.set(fromDirectory, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the config array for this run based on inputs.
|
||||
* @param {string} configFilePath The absolute path to the config file to use if not overridden.
|
||||
* @param {string} basePath The base path to use for relative paths in the config file.
|
||||
* @returns {Promise<FlatConfigArray>} The config array for `eslint`.
|
||||
*/
|
||||
async #calculateConfigArray(configFilePath, basePath) {
|
||||
|
||||
// check for cached version first
|
||||
if (this.#configArrays.has(configFilePath)) {
|
||||
return this.#configArrays.get(configFilePath);
|
||||
}
|
||||
|
||||
const configsPromise = ConfigLoader.calculateConfigArray(configFilePath, basePath, this.#options);
|
||||
|
||||
// ensure `ConfigLoader.calculateConfigArray` is called only once for `configFilePath`
|
||||
this.#configArrays.set(configFilePath, configsPromise);
|
||||
|
||||
// Unwrap the promise. This is primarily for the sync `getCachedConfigArrayForPath` method.
|
||||
const configs = await configsPromise;
|
||||
|
||||
this.#configArrays.set(configFilePath, configs);
|
||||
|
||||
return configs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the config file path for the given directory or file. This will either use
|
||||
* the override config file that was specified in the constructor options or
|
||||
* search for a config file from the directory.
|
||||
* @param {string} fileOrDirPath The file or directory path to get the config file path for.
|
||||
* @returns {Promise<string|undefined>} The config file path or `undefined` if not found.
|
||||
* @throws {Error} If `fileOrDirPath` is not a non-empty string.
|
||||
* @throws {Error} If `fileOrDirPath` is not an absolute path.
|
||||
*/
|
||||
async findConfigFileForPath(fileOrDirPath) {
|
||||
|
||||
assertValidFilePath(fileOrDirPath);
|
||||
|
||||
const absoluteDirPath = path.resolve(this.#options.cwd, path.dirname(fileOrDirPath));
|
||||
const { configFilePath } = await this.#locateConfigFileToUse(absoluteDirPath);
|
||||
|
||||
return configFilePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a configuration object for the given file based on the CLI options.
|
||||
* This is the same logic used by the ESLint CLI executable to determine
|
||||
* configuration for each file it processes.
|
||||
* @param {string} filePath The path of the file or directory to retrieve config for.
|
||||
* @returns {Promise<ConfigData|undefined>} A configuration object for the file
|
||||
* or `undefined` if there is no configuration data for the file.
|
||||
* @throws {Error} If no configuration for `filePath` exists.
|
||||
*/
|
||||
async loadConfigArrayForFile(filePath) {
|
||||
|
||||
assertValidFilePath(filePath);
|
||||
|
||||
debug(`Calculating config for file ${filePath}`);
|
||||
|
||||
const configFilePath = await this.findConfigFileForPath(filePath);
|
||||
|
||||
assertConfigurationExists(configFilePath, this.#options);
|
||||
|
||||
return this.loadConfigArrayForDirectory(filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a configuration object for the given directory based on the CLI options.
|
||||
* This is the same logic used by the ESLint CLI executable to determine
|
||||
* configuration for each file it processes.
|
||||
* @param {string} dirPath The path of the directory to retrieve config for.
|
||||
* @returns {Promise<ConfigData|undefined>} A configuration object for the directory
|
||||
* or `undefined` if there is no configuration data for the directory.
|
||||
*/
|
||||
async loadConfigArrayForDirectory(dirPath) {
|
||||
|
||||
assertValidFilePath(dirPath);
|
||||
|
||||
debug(`Calculating config for directory ${dirPath}`);
|
||||
|
||||
const absoluteDirPath = path.resolve(this.#options.cwd, path.dirname(dirPath));
|
||||
const { configFilePath, basePath } = await this.#locateConfigFileToUse(absoluteDirPath);
|
||||
|
||||
debug(`Using config file ${configFilePath} and base path ${basePath}`);
|
||||
return this.#calculateConfigArray(configFilePath, basePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a configuration array for the given file based on the CLI options.
|
||||
* This is a synchronous operation and does not read any files from disk. It's
|
||||
* intended to be used in locations where we know the config file has already
|
||||
* been loaded and we just need to get the configuration for a file.
|
||||
* @param {string} filePath The path of the file to retrieve a config object for.
|
||||
* @returns {ConfigData|undefined} A configuration object for the file
|
||||
* or `undefined` if there is no configuration data for the file.
|
||||
* @throws {Error} If `filePath` is not a non-empty string.
|
||||
* @throws {Error} If `filePath` is not an absolute path.
|
||||
* @throws {Error} If the config file was not already loaded.
|
||||
*/
|
||||
getCachedConfigArrayForFile(filePath) {
|
||||
assertValidFilePath(filePath);
|
||||
|
||||
debug(`Looking up cached config for ${filePath}`);
|
||||
|
||||
return this.getCachedConfigArrayForPath(path.dirname(filePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a configuration array for the given directory based on the CLI options.
|
||||
* This is a synchronous operation and does not read any files from disk. It's
|
||||
* intended to be used in locations where we know the config file has already
|
||||
* been loaded and we just need to get the configuration for a file.
|
||||
* @param {string} fileOrDirPath The path of the directory to retrieve a config object for.
|
||||
* @returns {ConfigData|undefined} A configuration object for the directory
|
||||
* or `undefined` if there is no configuration data for the directory.
|
||||
* @throws {Error} If `dirPath` is not a non-empty string.
|
||||
* @throws {Error} If `dirPath` is not an absolute path.
|
||||
* @throws {Error} If the config file was not already loaded.
|
||||
*/
|
||||
getCachedConfigArrayForPath(fileOrDirPath) {
|
||||
assertValidFilePath(fileOrDirPath);
|
||||
|
||||
debug(`Looking up cached config for ${fileOrDirPath}`);
|
||||
|
||||
const absoluteDirPath = path.resolve(this.#options.cwd, fileOrDirPath);
|
||||
|
||||
if (!this.#configFilePaths.has(absoluteDirPath)) {
|
||||
throw new Error(`Could not find config file for ${fileOrDirPath}`);
|
||||
}
|
||||
|
||||
const configFilePathInfo = this.#configFilePaths.get(absoluteDirPath);
|
||||
|
||||
if (typeof configFilePathInfo.then === "function") {
|
||||
throw new Error(`Config file path for ${fileOrDirPath} has not yet been calculated or an error occurred during the calculation`);
|
||||
}
|
||||
|
||||
const { configFilePath } = configFilePathInfo;
|
||||
|
||||
const configArray = this.#configArrays.get(configFilePath);
|
||||
|
||||
if (!configArray || typeof configArray.then === "function") {
|
||||
throw new Error(`Config array for ${fileOrDirPath} has not yet been calculated or an error occurred during the calculation`);
|
||||
}
|
||||
|
||||
return configArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to import the jiti dependency. This method is exposed internally for testing purposes.
|
||||
* @returns {Promise<Record<string, unknown>>} A promise that fulfills with a module object
|
||||
* or rejects with an error if jiti is not found.
|
||||
*/
|
||||
static loadJiti() {
|
||||
return import("jiti");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines which config file to use. This is determined by seeing if an
|
||||
* override config file was specified, and if so, using it; otherwise, as long
|
||||
* as override config file is not explicitly set to `false`, it will search
|
||||
* upwards from `fromDirectory` for a file named `eslint.config.js`.
|
||||
* This method is exposed internally for testing purposes.
|
||||
* @param {Object} [options] the options object
|
||||
* @param {string|false|undefined} options.useConfigFile The path to the config file to use.
|
||||
* @param {string} options.cwd Path to a directory that should be considered as the current working directory.
|
||||
* @param {string} [options.fromDirectory] The directory from which to start searching. Defaults to `cwd`.
|
||||
* @param {boolean} options.allowTS Indicates if TypeScript configuration files are allowed.
|
||||
* @returns {Promise<{configFilePath:string|undefined,basePath:string}>} Location information for
|
||||
* the config file.
|
||||
*/
|
||||
static async locateConfigFileToUse({ useConfigFile, cwd, fromDirectory = cwd, allowTS }) {
|
||||
|
||||
const configFilenames = allowTS
|
||||
? [...FLAT_CONFIG_FILENAMES, ...TS_FLAT_CONFIG_FILENAMES]
|
||||
: FLAT_CONFIG_FILENAMES;
|
||||
|
||||
// determine where to load config file from
|
||||
let configFilePath;
|
||||
let basePath = cwd;
|
||||
|
||||
if (typeof useConfigFile === "string") {
|
||||
debug(`Override config file path is ${useConfigFile}`);
|
||||
configFilePath = path.resolve(cwd, useConfigFile);
|
||||
basePath = cwd;
|
||||
} else if (useConfigFile !== false) {
|
||||
debug("Searching for eslint.config.js");
|
||||
configFilePath = await findUp(
|
||||
configFilenames,
|
||||
{ cwd: fromDirectory }
|
||||
);
|
||||
|
||||
if (configFilePath) {
|
||||
basePath = path.dirname(configFilePath);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
configFilePath,
|
||||
basePath
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the config array for this run based on inputs.
|
||||
* This method is exposed internally for testing purposes.
|
||||
* @param {string} configFilePath The absolute path to the config file to use if not overridden.
|
||||
* @param {string} basePath The base path to use for relative paths in the config file.
|
||||
* @param {ConfigLoaderOptions} options The options to use when loading configuration files.
|
||||
* @returns {Promise<FlatConfigArray>} The config array for `eslint`.
|
||||
*/
|
||||
static async calculateConfigArray(configFilePath, basePath, options) {
|
||||
|
||||
const {
|
||||
cwd,
|
||||
baseConfig,
|
||||
ignoreEnabled,
|
||||
ignorePatterns,
|
||||
overrideConfig,
|
||||
defaultConfigs = [],
|
||||
allowTS
|
||||
} = options;
|
||||
|
||||
debug(`Calculating config array from config file ${configFilePath} and base path ${basePath}`);
|
||||
|
||||
const configs = new FlatConfigArray(baseConfig || [], { basePath, shouldIgnore: ignoreEnabled });
|
||||
|
||||
// load config file
|
||||
if (configFilePath) {
|
||||
|
||||
debug(`Loading config file ${configFilePath}`);
|
||||
const fileConfig = await loadConfigFile(configFilePath, allowTS);
|
||||
|
||||
if (Array.isArray(fileConfig)) {
|
||||
configs.push(...fileConfig);
|
||||
} else {
|
||||
configs.push(fileConfig);
|
||||
}
|
||||
}
|
||||
|
||||
// add in any configured defaults
|
||||
configs.push(...defaultConfigs);
|
||||
|
||||
// append command line ignore patterns
|
||||
if (ignorePatterns && ignorePatterns.length > 0) {
|
||||
|
||||
let relativeIgnorePatterns;
|
||||
|
||||
/*
|
||||
* If the config file basePath is different than the cwd, then
|
||||
* the ignore patterns won't work correctly. Here, we adjust the
|
||||
* ignore pattern to include the correct relative path. Patterns
|
||||
* passed as `ignorePatterns` are relative to the cwd, whereas
|
||||
* the config file basePath can be an ancestor of the cwd.
|
||||
*/
|
||||
if (basePath === cwd) {
|
||||
relativeIgnorePatterns = ignorePatterns;
|
||||
} else {
|
||||
|
||||
// relative path must only have Unix-style separators
|
||||
const relativeIgnorePath = path.relative(basePath, cwd).replace(/\\/gu, "/");
|
||||
|
||||
relativeIgnorePatterns = ignorePatterns.map(pattern => {
|
||||
const negated = pattern.startsWith("!");
|
||||
const basePattern = negated ? pattern.slice(1) : pattern;
|
||||
|
||||
return (negated ? "!" : "") +
|
||||
path.posix.join(relativeIgnorePath, basePattern);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Ignore patterns are added to the end of the config array
|
||||
* so they can override default ignores.
|
||||
*/
|
||||
configs.push({
|
||||
ignores: relativeIgnorePatterns
|
||||
});
|
||||
}
|
||||
|
||||
if (overrideConfig) {
|
||||
if (Array.isArray(overrideConfig)) {
|
||||
configs.push(...overrideConfig);
|
||||
} else {
|
||||
configs.push(overrideConfig);
|
||||
}
|
||||
}
|
||||
|
||||
await configs.normalize();
|
||||
|
||||
return configs;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulates the loading and caching of configuration files when looking up
|
||||
* from the current working directory.
|
||||
*/
|
||||
class LegacyConfigLoader extends ConfigLoader {
|
||||
|
||||
/**
|
||||
* The options to use when loading configuration files.
|
||||
* @type {ConfigLoaderOptions}
|
||||
*/
|
||||
#options;
|
||||
|
||||
/**
|
||||
* The cached config file path for this instance.
|
||||
* @type {Promise<{configFilePath:string,basePath:string}|undefined>}
|
||||
*/
|
||||
#configFilePath;
|
||||
|
||||
/**
|
||||
* The cached config array for this instance.
|
||||
* @type {FlatConfigArray|Promise<FlatConfigArray>}
|
||||
*/
|
||||
#configArray;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {ConfigLoaderOptions} options The options to use when loading configuration files.
|
||||
*/
|
||||
constructor(options) {
|
||||
super(options);
|
||||
this.#options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines which config file to use. This is determined by seeing if an
|
||||
* override config file was specified, and if so, using it; otherwise, as long
|
||||
* as override config file is not explicitly set to `false`, it will search
|
||||
* upwards from the cwd for a file named `eslint.config.js`.
|
||||
* @returns {Promise<{configFilePath:string|undefined,basePath:string}>} Location information for
|
||||
* the config file.
|
||||
*/
|
||||
#locateConfigFileToUse() {
|
||||
if (!this.#configFilePath) {
|
||||
this.#configFilePath = ConfigLoader.locateConfigFileToUse({
|
||||
useConfigFile: this.#options.configFile,
|
||||
cwd: this.#options.cwd,
|
||||
allowTS: this.#options.allowTS
|
||||
});
|
||||
}
|
||||
|
||||
return this.#configFilePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the config array for this run based on inputs.
|
||||
* @param {string} configFilePath The absolute path to the config file to use if not overridden.
|
||||
* @param {string} basePath The base path to use for relative paths in the config file.
|
||||
* @returns {Promise<FlatConfigArray>} The config array for `eslint`.
|
||||
*/
|
||||
async #calculateConfigArray(configFilePath, basePath) {
|
||||
|
||||
// check for cached version first
|
||||
if (this.#configArray) {
|
||||
return this.#configArray;
|
||||
}
|
||||
|
||||
// ensure `ConfigLoader.calculateConfigArray` is called only once
|
||||
this.#configArray = ConfigLoader.calculateConfigArray(configFilePath, basePath, this.#options);
|
||||
|
||||
// Unwrap the promise. This is primarily for the sync `getCachedConfigArrayForPath` method.
|
||||
this.#configArray = await this.#configArray;
|
||||
|
||||
return this.#configArray;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the config file path for the given directory. This will either use
|
||||
* the override config file that was specified in the constructor options or
|
||||
* search for a config file from the directory of the file being linted.
|
||||
* @param {string} dirPath The directory path to get the config file path for.
|
||||
* @returns {Promise<string|undefined>} The config file path or `undefined` if not found.
|
||||
* @throws {Error} If `fileOrDirPath` is not a non-empty string.
|
||||
* @throws {Error} If `fileOrDirPath` is not an absolute path.
|
||||
*/
|
||||
async findConfigFileForPath(dirPath) {
|
||||
|
||||
assertValidFilePath(dirPath);
|
||||
|
||||
const { configFilePath } = await this.#locateConfigFileToUse();
|
||||
|
||||
return configFilePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a configuration object for the given file based on the CLI options.
|
||||
* This is the same logic used by the ESLint CLI executable to determine
|
||||
* configuration for each file it processes.
|
||||
* @param {string} dirPath The path of the directory to retrieve config for.
|
||||
* @returns {Promise<ConfigData|undefined>} A configuration object for the file
|
||||
* or `undefined` if there is no configuration data for the file.
|
||||
*/
|
||||
async loadConfigArrayForDirectory(dirPath) {
|
||||
|
||||
assertValidFilePath(dirPath);
|
||||
|
||||
debug(`[Legacy]: Calculating config for ${dirPath}`);
|
||||
|
||||
const { configFilePath, basePath } = await this.#locateConfigFileToUse();
|
||||
|
||||
debug(`[Legacy]: Using config file ${configFilePath} and base path ${basePath}`);
|
||||
return this.#calculateConfigArray(configFilePath, basePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a configuration array for the given directory based on the CLI options.
|
||||
* This is a synchronous operation and does not read any files from disk. It's
|
||||
* intended to be used in locations where we know the config file has already
|
||||
* been loaded and we just need to get the configuration for a file.
|
||||
* @param {string} dirPath The path of the directory to retrieve a config object for.
|
||||
* @returns {ConfigData|undefined} A configuration object for the file
|
||||
* or `undefined` if there is no configuration data for the file.
|
||||
* @throws {Error} If `dirPath` is not a non-empty string.
|
||||
* @throws {Error} If `dirPath` is not an absolute path.
|
||||
* @throws {Error} If the config file was not already loaded.
|
||||
*/
|
||||
getCachedConfigArrayForPath(dirPath) {
|
||||
assertValidFilePath(dirPath);
|
||||
|
||||
debug(`[Legacy]: Looking up cached config for ${dirPath}`);
|
||||
|
||||
if (!this.#configArray) {
|
||||
throw new Error(`Could not find config file for ${dirPath}`);
|
||||
}
|
||||
|
||||
if (typeof this.#configArray.then === "function") {
|
||||
throw new Error(`Config array for ${dirPath} has not yet been calculated or an error occurred during the calculation`);
|
||||
}
|
||||
|
||||
return this.#configArray;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { ConfigLoader, LegacyConfigLoader };
|
297
node_modules/eslint/lib/config/config.js
generated
vendored
Normal file
297
node_modules/eslint/lib/config/config.js
generated
vendored
Normal file
@ -0,0 +1,297 @@
|
||||
/**
|
||||
* @fileoverview The `Config` class
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const { deepMergeArrays } = require("../shared/deep-merge-arrays");
|
||||
const { getRuleFromConfig } = require("./flat-config-helpers");
|
||||
const { flatConfigSchema, hasMethod } = require("./flat-config-schema");
|
||||
const { RuleValidator } = require("./rule-validator");
|
||||
const { ObjectSchema } = require("@eslint/config-array");
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const ruleValidator = new RuleValidator();
|
||||
|
||||
const severities = new Map([
|
||||
[0, 0],
|
||||
[1, 1],
|
||||
[2, 2],
|
||||
["off", 0],
|
||||
["warn", 1],
|
||||
["error", 2]
|
||||
]);
|
||||
|
||||
/**
|
||||
* Splits a plugin identifier in the form a/b/c into two parts: a/b and c.
|
||||
* @param {string} identifier The identifier to parse.
|
||||
* @returns {{objectName: string, pluginName: string}} The parts of the plugin
|
||||
* name.
|
||||
*/
|
||||
function splitPluginIdentifier(identifier) {
|
||||
const parts = identifier.split("/");
|
||||
|
||||
return {
|
||||
objectName: parts.pop(),
|
||||
pluginName: parts.join("/")
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of an object in the config by reading its `meta` key.
|
||||
* @param {Object} object The object to check.
|
||||
* @returns {string?} The name of the object if found or `null` if there
|
||||
* is no name.
|
||||
*/
|
||||
function getObjectId(object) {
|
||||
|
||||
// first check old-style name
|
||||
let name = object.name;
|
||||
|
||||
if (!name) {
|
||||
|
||||
if (!object.meta) {
|
||||
return null;
|
||||
}
|
||||
|
||||
name = object.meta.name;
|
||||
|
||||
if (!name) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// now check for old-style version
|
||||
let version = object.version;
|
||||
|
||||
if (!version) {
|
||||
version = object.meta && object.meta.version;
|
||||
}
|
||||
|
||||
// if there's a version then append that
|
||||
if (version) {
|
||||
return `${name}@${version}`;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a languageOptions object to a JSON representation.
|
||||
* @param {Record<string, any>} languageOptions The options to create a JSON
|
||||
* representation of.
|
||||
* @param {string} objectKey The key of the object being converted.
|
||||
* @returns {Record<string, any>} The JSON representation of the languageOptions.
|
||||
* @throws {TypeError} If a function is found in the languageOptions.
|
||||
*/
|
||||
function languageOptionsToJSON(languageOptions, objectKey = "languageOptions") {
|
||||
|
||||
const result = {};
|
||||
|
||||
for (const [key, value] of Object.entries(languageOptions)) {
|
||||
if (value) {
|
||||
if (typeof value === "object") {
|
||||
const name = getObjectId(value);
|
||||
|
||||
if (name && hasMethod(value)) {
|
||||
result[key] = name;
|
||||
} else {
|
||||
result[key] = languageOptionsToJSON(value, key);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof value === "function") {
|
||||
throw new TypeError(`Cannot serialize key "${key}" in ${objectKey}: Function values are not supported.`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
result[key] = value;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Exports
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Represents a normalized configuration object.
|
||||
*/
|
||||
class Config {
|
||||
|
||||
/**
|
||||
* The name to use for the language when serializing to JSON.
|
||||
* @type {string|undefined}
|
||||
*/
|
||||
#languageName;
|
||||
|
||||
/**
|
||||
* The name to use for the processor when serializing to JSON.
|
||||
* @type {string|undefined}
|
||||
*/
|
||||
#processorName;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {Object} config The configuration object.
|
||||
*/
|
||||
constructor(config) {
|
||||
|
||||
const { plugins, language, languageOptions, processor, ...otherKeys } = config;
|
||||
|
||||
// Validate config object
|
||||
const schema = new ObjectSchema(flatConfigSchema);
|
||||
|
||||
schema.validate(config);
|
||||
|
||||
// first, copy all the other keys over
|
||||
Object.assign(this, otherKeys);
|
||||
|
||||
// ensure that a language is specified
|
||||
if (!language) {
|
||||
throw new TypeError("Key 'language' is required.");
|
||||
}
|
||||
|
||||
// copy the rest over
|
||||
this.plugins = plugins;
|
||||
this.language = language;
|
||||
|
||||
// Check language value
|
||||
const { pluginName: languagePluginName, objectName: localLanguageName } = splitPluginIdentifier(language);
|
||||
|
||||
this.#languageName = language;
|
||||
|
||||
if (!plugins || !plugins[languagePluginName] || !plugins[languagePluginName].languages || !plugins[languagePluginName].languages[localLanguageName]) {
|
||||
throw new TypeError(`Key "language": Could not find "${localLanguageName}" in plugin "${languagePluginName}".`);
|
||||
}
|
||||
|
||||
this.language = plugins[languagePluginName].languages[localLanguageName];
|
||||
|
||||
if (this.language.defaultLanguageOptions ?? languageOptions) {
|
||||
this.languageOptions = flatConfigSchema.languageOptions.merge(
|
||||
this.language.defaultLanguageOptions,
|
||||
languageOptions
|
||||
);
|
||||
} else {
|
||||
this.languageOptions = {};
|
||||
}
|
||||
|
||||
// Validate language options
|
||||
try {
|
||||
this.language.validateLanguageOptions(this.languageOptions);
|
||||
} catch (error) {
|
||||
throw new TypeError(`Key "languageOptions": ${error.message}`, { cause: error });
|
||||
}
|
||||
|
||||
// Normalize language options if necessary
|
||||
if (this.language.normalizeLanguageOptions) {
|
||||
this.languageOptions = this.language.normalizeLanguageOptions(this.languageOptions);
|
||||
}
|
||||
|
||||
// Check processor value
|
||||
if (processor) {
|
||||
this.processor = processor;
|
||||
|
||||
if (typeof processor === "string") {
|
||||
const { pluginName, objectName: localProcessorName } = splitPluginIdentifier(processor);
|
||||
|
||||
this.#processorName = processor;
|
||||
|
||||
if (!plugins || !plugins[pluginName] || !plugins[pluginName].processors || !plugins[pluginName].processors[localProcessorName]) {
|
||||
throw new TypeError(`Key "processor": Could not find "${localProcessorName}" in plugin "${pluginName}".`);
|
||||
}
|
||||
|
||||
this.processor = plugins[pluginName].processors[localProcessorName];
|
||||
} else if (typeof processor === "object") {
|
||||
this.#processorName = getObjectId(processor);
|
||||
this.processor = processor;
|
||||
} else {
|
||||
throw new TypeError("Key 'processor' must be a string or an object.");
|
||||
}
|
||||
}
|
||||
|
||||
// Process the rules
|
||||
if (this.rules) {
|
||||
this.#normalizeRulesConfig();
|
||||
ruleValidator.validate(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the configuration to a JSON representation.
|
||||
* @returns {Record<string, any>} The JSON representation of the configuration.
|
||||
* @throws {Error} If the configuration cannot be serialized.
|
||||
*/
|
||||
toJSON() {
|
||||
|
||||
if (this.processor && !this.#processorName) {
|
||||
throw new Error("Could not serialize processor object (missing 'meta' object).");
|
||||
}
|
||||
|
||||
if (!this.#languageName) {
|
||||
throw new Error("Could not serialize language object (missing 'meta' object).");
|
||||
}
|
||||
|
||||
return {
|
||||
...this,
|
||||
plugins: Object.entries(this.plugins).map(([namespace, plugin]) => {
|
||||
|
||||
const pluginId = getObjectId(plugin);
|
||||
|
||||
if (!pluginId) {
|
||||
return namespace;
|
||||
}
|
||||
|
||||
return `${namespace}:${pluginId}`;
|
||||
}),
|
||||
language: this.#languageName,
|
||||
languageOptions: languageOptionsToJSON(this.languageOptions),
|
||||
processor: this.#processorName
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the rules configuration. Ensures that each rule config is
|
||||
* an array and that the severity is a number. Applies meta.defaultOptions.
|
||||
* This function modifies `this.rules`.
|
||||
* @returns {void}
|
||||
*/
|
||||
#normalizeRulesConfig() {
|
||||
for (const [ruleId, originalConfig] of Object.entries(this.rules)) {
|
||||
|
||||
// ensure rule config is an array
|
||||
let ruleConfig = Array.isArray(originalConfig)
|
||||
? originalConfig
|
||||
: [originalConfig];
|
||||
|
||||
// normalize severity
|
||||
ruleConfig[0] = severities.get(ruleConfig[0]);
|
||||
|
||||
const rule = getRuleFromConfig(ruleId, this);
|
||||
|
||||
// apply meta.defaultOptions
|
||||
const slicedOptions = ruleConfig.slice(1);
|
||||
const mergedOptions = deepMergeArrays(rule?.meta?.defaultOptions, slicedOptions);
|
||||
|
||||
if (mergedOptions.length) {
|
||||
ruleConfig = [ruleConfig[0], ...mergedOptions];
|
||||
}
|
||||
|
||||
this.rules[ruleId] = ruleConfig;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { Config };
|
69
node_modules/eslint/lib/config/default-config.js
generated
vendored
Normal file
69
node_modules/eslint/lib/config/default-config.js
generated
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
/**
|
||||
* @fileoverview Default configuration
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const Rules = require("../rules");
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
exports.defaultConfig = Object.freeze([
|
||||
{
|
||||
plugins: {
|
||||
"@": {
|
||||
|
||||
languages: {
|
||||
js: require("../languages/js")
|
||||
},
|
||||
|
||||
/*
|
||||
* Because we try to delay loading rules until absolutely
|
||||
* necessary, a proxy allows us to hook into the lazy-loading
|
||||
* aspect of the rules map while still keeping all of the
|
||||
* relevant configuration inside of the config array.
|
||||
*/
|
||||
rules: new Proxy({}, {
|
||||
get(target, property) {
|
||||
return Rules.get(property);
|
||||
},
|
||||
|
||||
has(target, property) {
|
||||
return Rules.has(property);
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
language: "@/js",
|
||||
linterOptions: {
|
||||
reportUnusedDisableDirectives: 1
|
||||
}
|
||||
},
|
||||
|
||||
// default ignores are listed here
|
||||
{
|
||||
ignores: [
|
||||
"**/node_modules/",
|
||||
".git/"
|
||||
]
|
||||
},
|
||||
|
||||
// intentionally empty config to ensure these files are globbed by default
|
||||
{
|
||||
files: ["**/*.js", "**/*.mjs"]
|
||||
},
|
||||
{
|
||||
files: ["**/*.cjs"],
|
||||
languageOptions: {
|
||||
sourceType: "commonjs",
|
||||
ecmaVersion: "latest"
|
||||
}
|
||||
}
|
||||
]);
|
222
node_modules/eslint/lib/config/flat-config-array.js
generated
vendored
Normal file
222
node_modules/eslint/lib/config/flat-config-array.js
generated
vendored
Normal file
@ -0,0 +1,222 @@
|
||||
/**
|
||||
* @fileoverview Flat Config Array
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const { ConfigArray, ConfigArraySymbol } = require("@eslint/config-array");
|
||||
const { flatConfigSchema } = require("./flat-config-schema");
|
||||
const { defaultConfig } = require("./default-config");
|
||||
const { Config } = require("./config");
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Fields that are considered metadata and not part of the config object.
|
||||
*/
|
||||
const META_FIELDS = new Set(["name"]);
|
||||
|
||||
/**
|
||||
* Wraps a config error with details about where the error occurred.
|
||||
* @param {Error} error The original error.
|
||||
* @param {number} originalLength The original length of the config array.
|
||||
* @param {number} baseLength The length of the base config.
|
||||
* @returns {TypeError} The new error with details.
|
||||
*/
|
||||
function wrapConfigErrorWithDetails(error, originalLength, baseLength) {
|
||||
|
||||
let location = "user-defined";
|
||||
let configIndex = error.index;
|
||||
|
||||
/*
|
||||
* A config array is set up in this order:
|
||||
* 1. Base config
|
||||
* 2. Original configs
|
||||
* 3. User-defined configs
|
||||
* 4. CLI-defined configs
|
||||
*
|
||||
* So we need to adjust the index to account for the base config.
|
||||
*
|
||||
* - If the index is less than the base length, it's in the base config
|
||||
* (as specified by `baseConfig` argument to `FlatConfigArray` constructor).
|
||||
* - If the index is greater than the base length but less than the original
|
||||
* length + base length, it's in the original config. The original config
|
||||
* is passed to the `FlatConfigArray` constructor as the first argument.
|
||||
* - Otherwise, it's in the user-defined config, which is loaded from the
|
||||
* config file and merged with any command-line options.
|
||||
*/
|
||||
if (error.index < baseLength) {
|
||||
location = "base";
|
||||
} else if (error.index < originalLength + baseLength) {
|
||||
location = "original";
|
||||
configIndex = error.index - baseLength;
|
||||
} else {
|
||||
configIndex = error.index - originalLength - baseLength;
|
||||
}
|
||||
|
||||
return new TypeError(
|
||||
`${error.message.slice(0, -1)} at ${location} index ${configIndex}.`,
|
||||
{ cause: error }
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const originalBaseConfig = Symbol("originalBaseConfig");
|
||||
const originalLength = Symbol("originalLength");
|
||||
const baseLength = Symbol("baseLength");
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Exports
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Represents an array containing configuration information for ESLint.
|
||||
*/
|
||||
class FlatConfigArray extends ConfigArray {
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {*[]} configs An array of configuration information.
|
||||
* @param {{basePath: string, shouldIgnore: boolean, baseConfig: FlatConfig}} options The options
|
||||
* to use for the config array instance.
|
||||
*/
|
||||
constructor(configs, {
|
||||
basePath,
|
||||
shouldIgnore = true,
|
||||
baseConfig = defaultConfig
|
||||
} = {}) {
|
||||
super(configs, {
|
||||
basePath,
|
||||
schema: flatConfigSchema
|
||||
});
|
||||
|
||||
/**
|
||||
* The original length of the array before any modifications.
|
||||
* @type {number}
|
||||
*/
|
||||
this[originalLength] = this.length;
|
||||
|
||||
if (baseConfig[Symbol.iterator]) {
|
||||
this.unshift(...baseConfig);
|
||||
} else {
|
||||
this.unshift(baseConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* The length of the array after applying the base config.
|
||||
* @type {number}
|
||||
*/
|
||||
this[baseLength] = this.length - this[originalLength];
|
||||
|
||||
/**
|
||||
* The base config used to build the config array.
|
||||
* @type {Array<FlatConfig>}
|
||||
*/
|
||||
this[originalBaseConfig] = baseConfig;
|
||||
Object.defineProperty(this, originalBaseConfig, { writable: false });
|
||||
|
||||
/**
|
||||
* Determines if `ignores` fields should be honored.
|
||||
* If true, then all `ignores` fields are honored.
|
||||
* if false, then only `ignores` fields in the baseConfig are honored.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.shouldIgnore = shouldIgnore;
|
||||
Object.defineProperty(this, "shouldIgnore", { writable: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the array by calling the superclass method and catching/rethrowing
|
||||
* any ConfigError exceptions with additional details.
|
||||
* @param {any} [context] The context to use to normalize the array.
|
||||
* @returns {Promise<FlatConfigArray>} A promise that resolves when the array is normalized.
|
||||
*/
|
||||
normalize(context) {
|
||||
return super.normalize(context)
|
||||
.catch(error => {
|
||||
if (error.name === "ConfigError") {
|
||||
throw wrapConfigErrorWithDetails(error, this[originalLength], this[baseLength]);
|
||||
}
|
||||
|
||||
throw error;
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the array by calling the superclass method and catching/rethrowing
|
||||
* any ConfigError exceptions with additional details.
|
||||
* @param {any} [context] The context to use to normalize the array.
|
||||
* @returns {FlatConfigArray} The current instance.
|
||||
* @throws {TypeError} If the config is invalid.
|
||||
*/
|
||||
normalizeSync(context) {
|
||||
|
||||
try {
|
||||
|
||||
return super.normalizeSync(context);
|
||||
|
||||
} catch (error) {
|
||||
|
||||
if (error.name === "ConfigError") {
|
||||
throw wrapConfigErrorWithDetails(error, this[originalLength], this[baseLength]);
|
||||
}
|
||||
|
||||
throw error;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* eslint-disable class-methods-use-this -- Desired as instance method */
|
||||
/**
|
||||
* Replaces a config with another config to allow us to put strings
|
||||
* in the config array that will be replaced by objects before
|
||||
* normalization.
|
||||
* @param {Object} config The config to preprocess.
|
||||
* @returns {Object} The preprocessed config.
|
||||
*/
|
||||
[ConfigArraySymbol.preprocessConfig](config) {
|
||||
|
||||
/*
|
||||
* If a config object has `ignores` and no other non-meta fields, then it's an object
|
||||
* for global ignores. If `shouldIgnore` is false, that object shouldn't apply,
|
||||
* so we'll remove its `ignores`.
|
||||
*/
|
||||
if (
|
||||
!this.shouldIgnore &&
|
||||
!this[originalBaseConfig].includes(config) &&
|
||||
config.ignores &&
|
||||
Object.keys(config).filter(key => !META_FIELDS.has(key)).length === 1
|
||||
) {
|
||||
/* eslint-disable-next-line no-unused-vars -- need to strip off other keys */
|
||||
const { ignores, ...otherKeys } = config;
|
||||
|
||||
return otherKeys;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalizes the config by replacing plugin references with their objects
|
||||
* and validating rule option schemas.
|
||||
* @param {Object} config The config to finalize.
|
||||
* @returns {Object} The finalized config.
|
||||
* @throws {TypeError} If the config is invalid.
|
||||
*/
|
||||
[ConfigArraySymbol.finalizeConfig](config) {
|
||||
return new Config(config);
|
||||
}
|
||||
/* eslint-enable class-methods-use-this -- Desired as instance method */
|
||||
|
||||
}
|
||||
|
||||
exports.FlatConfigArray = FlatConfigArray;
|
128
node_modules/eslint/lib/config/flat-config-helpers.js
generated
vendored
Normal file
128
node_modules/eslint/lib/config/flat-config-helpers.js
generated
vendored
Normal file
@ -0,0 +1,128 @@
|
||||
/**
|
||||
* @fileoverview Shared functions to work with configs.
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Typedefs
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @typedef {import("../shared/types").Rule} Rule */
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Private Members
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// JSON schema that disallows passing any options
|
||||
const noOptionsSchema = Object.freeze({
|
||||
type: "array",
|
||||
minItems: 0,
|
||||
maxItems: 0
|
||||
});
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Functions
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Parses a ruleId into its plugin and rule parts.
|
||||
* @param {string} ruleId The rule ID to parse.
|
||||
* @returns {{pluginName:string,ruleName:string}} The plugin and rule
|
||||
* parts of the ruleId;
|
||||
*/
|
||||
function parseRuleId(ruleId) {
|
||||
let pluginName, ruleName;
|
||||
|
||||
// distinguish between core rules and plugin rules
|
||||
if (ruleId.includes("/")) {
|
||||
|
||||
// mimic scoped npm packages
|
||||
if (ruleId.startsWith("@")) {
|
||||
pluginName = ruleId.slice(0, ruleId.lastIndexOf("/"));
|
||||
} else {
|
||||
pluginName = ruleId.slice(0, ruleId.indexOf("/"));
|
||||
}
|
||||
|
||||
ruleName = ruleId.slice(pluginName.length + 1);
|
||||
} else {
|
||||
pluginName = "@";
|
||||
ruleName = ruleId;
|
||||
}
|
||||
|
||||
return {
|
||||
pluginName,
|
||||
ruleName
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a rule instance from a given config based on the ruleId.
|
||||
* @param {string} ruleId The rule ID to look for.
|
||||
* @param {FlatConfig} config The config to search.
|
||||
* @returns {import("../shared/types").Rule|undefined} The rule if found
|
||||
* or undefined if not.
|
||||
*/
|
||||
function getRuleFromConfig(ruleId, config) {
|
||||
const { pluginName, ruleName } = parseRuleId(ruleId);
|
||||
|
||||
return config.plugins?.[pluginName]?.rules?.[ruleName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a complete options schema for a rule.
|
||||
* @param {Rule} rule A rule object
|
||||
* @throws {TypeError} If `meta.schema` is specified but is not an array, object or `false`.
|
||||
* @returns {Object|null} JSON Schema for the rule's options. `null` if `meta.schema` is `false`.
|
||||
*/
|
||||
function getRuleOptionsSchema(rule) {
|
||||
|
||||
if (!rule.meta) {
|
||||
return { ...noOptionsSchema }; // default if `meta.schema` is not specified
|
||||
}
|
||||
|
||||
const schema = rule.meta.schema;
|
||||
|
||||
if (typeof schema === "undefined") {
|
||||
return { ...noOptionsSchema }; // default if `meta.schema` is not specified
|
||||
}
|
||||
|
||||
// `schema:false` is an allowed explicit opt-out of options validation for the rule
|
||||
if (schema === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof schema !== "object" || schema === null) {
|
||||
throw new TypeError("Rule's `meta.schema` must be an array or object");
|
||||
}
|
||||
|
||||
// ESLint-specific array form needs to be converted into a valid JSON Schema definition
|
||||
if (Array.isArray(schema)) {
|
||||
if (schema.length) {
|
||||
return {
|
||||
type: "array",
|
||||
items: schema,
|
||||
minItems: 0,
|
||||
maxItems: schema.length
|
||||
};
|
||||
}
|
||||
|
||||
// `schema:[]` is an explicit way to specify that the rule does not accept any options
|
||||
return { ...noOptionsSchema };
|
||||
}
|
||||
|
||||
// `schema:<object>` is assumed to be a valid JSON Schema definition
|
||||
return schema;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Exports
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
parseRuleId,
|
||||
getRuleFromConfig,
|
||||
getRuleOptionsSchema
|
||||
};
|
576
node_modules/eslint/lib/config/flat-config-schema.js
generated
vendored
Normal file
576
node_modules/eslint/lib/config/flat-config-schema.js
generated
vendored
Normal file
@ -0,0 +1,576 @@
|
||||
/**
|
||||
* @fileoverview Flat config schema
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const { normalizeSeverityToNumber } = require("../shared/severity");
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Type Definitions
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @typedef ObjectPropertySchema
|
||||
* @property {Function|string} merge The function or name of the function to call
|
||||
* to merge multiple objects with this property.
|
||||
* @property {Function|string} validate The function or name of the function to call
|
||||
* to validate the value of this property.
|
||||
*/
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const ruleSeverities = new Map([
|
||||
[0, 0], ["off", 0],
|
||||
[1, 1], ["warn", 1],
|
||||
[2, 2], ["error", 2]
|
||||
]);
|
||||
|
||||
/**
|
||||
* Check if a value is a non-null object.
|
||||
* @param {any} value The value to check.
|
||||
* @returns {boolean} `true` if the value is a non-null object.
|
||||
*/
|
||||
function isNonNullObject(value) {
|
||||
return typeof value === "object" && value !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a value is a non-null non-array object.
|
||||
* @param {any} value The value to check.
|
||||
* @returns {boolean} `true` if the value is a non-null non-array object.
|
||||
*/
|
||||
function isNonArrayObject(value) {
|
||||
return isNonNullObject(value) && !Array.isArray(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a value is undefined.
|
||||
* @param {any} value The value to check.
|
||||
* @returns {boolean} `true` if the value is undefined.
|
||||
*/
|
||||
function isUndefined(value) {
|
||||
return typeof value === "undefined";
|
||||
}
|
||||
|
||||
/**
|
||||
* Deeply merges two non-array objects.
|
||||
* @param {Object} first The base object.
|
||||
* @param {Object} second The overrides object.
|
||||
* @param {Map<string, Map<string, Object>>} [mergeMap] Maps the combination of first and second arguments to a merged result.
|
||||
* @returns {Object} An object with properties from both first and second.
|
||||
*/
|
||||
function deepMerge(first, second, mergeMap = new Map()) {
|
||||
|
||||
let secondMergeMap = mergeMap.get(first);
|
||||
|
||||
if (secondMergeMap) {
|
||||
const result = secondMergeMap.get(second);
|
||||
|
||||
if (result) {
|
||||
|
||||
// If this combination of first and second arguments has been already visited, return the previously created result.
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
secondMergeMap = new Map();
|
||||
mergeMap.set(first, secondMergeMap);
|
||||
}
|
||||
|
||||
/*
|
||||
* First create a result object where properties from the second object
|
||||
* overwrite properties from the first. This sets up a baseline to use
|
||||
* later rather than needing to inspect and change every property
|
||||
* individually.
|
||||
*/
|
||||
const result = {
|
||||
...first,
|
||||
...second
|
||||
};
|
||||
|
||||
delete result.__proto__; // eslint-disable-line no-proto -- don't merge own property "__proto__"
|
||||
|
||||
// Store the pending result for this combination of first and second arguments.
|
||||
secondMergeMap.set(second, result);
|
||||
|
||||
for (const key of Object.keys(second)) {
|
||||
|
||||
// avoid hairy edge case
|
||||
if (key === "__proto__" || !Object.prototype.propertyIsEnumerable.call(first, key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const firstValue = first[key];
|
||||
const secondValue = second[key];
|
||||
|
||||
if (isNonArrayObject(firstValue) && isNonArrayObject(secondValue)) {
|
||||
result[key] = deepMerge(firstValue, secondValue, mergeMap);
|
||||
} else if (isUndefined(secondValue)) {
|
||||
result[key] = firstValue;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the rule options config for a given rule by ensuring that
|
||||
* it is an array and that the first item is 0, 1, or 2.
|
||||
* @param {Array|string|number} ruleOptions The rule options config.
|
||||
* @returns {Array} An array of rule options.
|
||||
*/
|
||||
function normalizeRuleOptions(ruleOptions) {
|
||||
|
||||
const finalOptions = Array.isArray(ruleOptions)
|
||||
? ruleOptions.slice(0)
|
||||
: [ruleOptions];
|
||||
|
||||
finalOptions[0] = ruleSeverities.get(finalOptions[0]);
|
||||
return structuredClone(finalOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if an object has any methods.
|
||||
* @param {Object} object The object to check.
|
||||
* @returns {boolean} `true` if the object has any methods.
|
||||
*/
|
||||
function hasMethod(object) {
|
||||
|
||||
for (const key of Object.keys(object)) {
|
||||
|
||||
if (typeof object[key] === "function") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Assertions
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* The error type when a rule's options are configured with an invalid type.
|
||||
*/
|
||||
class InvalidRuleOptionsError extends Error {
|
||||
|
||||
/**
|
||||
* @param {string} ruleId Rule name being configured.
|
||||
* @param {any} value The invalid value.
|
||||
*/
|
||||
constructor(ruleId, value) {
|
||||
super(`Key "${ruleId}": Expected severity of "off", 0, "warn", 1, "error", or 2.`);
|
||||
this.messageTemplate = "invalid-rule-options";
|
||||
this.messageData = { ruleId, value };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that a value is a valid rule options entry.
|
||||
* @param {string} ruleId Rule name being configured.
|
||||
* @param {any} value The value to check.
|
||||
* @returns {void}
|
||||
* @throws {InvalidRuleOptionsError} If the value isn't a valid rule options.
|
||||
*/
|
||||
function assertIsRuleOptions(ruleId, value) {
|
||||
if (typeof value !== "string" && typeof value !== "number" && !Array.isArray(value)) {
|
||||
throw new InvalidRuleOptionsError(ruleId, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The error type when a rule's severity is invalid.
|
||||
*/
|
||||
class InvalidRuleSeverityError extends Error {
|
||||
|
||||
/**
|
||||
* @param {string} ruleId Rule name being configured.
|
||||
* @param {any} value The invalid value.
|
||||
*/
|
||||
constructor(ruleId, value) {
|
||||
super(`Key "${ruleId}": Expected severity of "off", 0, "warn", 1, "error", or 2.`);
|
||||
this.messageTemplate = "invalid-rule-severity";
|
||||
this.messageData = { ruleId, value };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that a value is valid rule severity.
|
||||
* @param {string} ruleId Rule name being configured.
|
||||
* @param {any} value The value to check.
|
||||
* @returns {void}
|
||||
* @throws {InvalidRuleSeverityError} If the value isn't a valid rule severity.
|
||||
*/
|
||||
function assertIsRuleSeverity(ruleId, value) {
|
||||
const severity = ruleSeverities.get(value);
|
||||
|
||||
if (typeof severity === "undefined") {
|
||||
throw new InvalidRuleSeverityError(ruleId, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that a given string is the form pluginName/objectName.
|
||||
* @param {string} value The string to check.
|
||||
* @returns {void}
|
||||
* @throws {TypeError} If the string isn't in the correct format.
|
||||
*/
|
||||
function assertIsPluginMemberName(value) {
|
||||
if (!/[@a-z0-9-_$]+(?:\/(?:[a-z0-9-_$]+))+$/iu.test(value)) {
|
||||
throw new TypeError(`Expected string in the form "pluginName/objectName" but found "${value}".`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that a value is an object.
|
||||
* @param {any} value The value to check.
|
||||
* @returns {void}
|
||||
* @throws {TypeError} If the value isn't an object.
|
||||
*/
|
||||
function assertIsObject(value) {
|
||||
if (!isNonNullObject(value)) {
|
||||
throw new TypeError("Expected an object.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The error type when there's an eslintrc-style options in a flat config.
|
||||
*/
|
||||
class IncompatibleKeyError extends Error {
|
||||
|
||||
/**
|
||||
* @param {string} key The invalid key.
|
||||
*/
|
||||
constructor(key) {
|
||||
super("This appears to be in eslintrc format rather than flat config format.");
|
||||
this.messageTemplate = "eslintrc-incompat";
|
||||
this.messageData = { key };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The error type when there's an eslintrc-style plugins array found.
|
||||
*/
|
||||
class IncompatiblePluginsError extends Error {
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {Array<string>} plugins The plugins array.
|
||||
*/
|
||||
constructor(plugins) {
|
||||
super("This appears to be in eslintrc format (array of strings) rather than flat config format (object).");
|
||||
this.messageTemplate = "eslintrc-plugins";
|
||||
this.messageData = { plugins };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Low-Level Schemas
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/** @type {ObjectPropertySchema} */
|
||||
const booleanSchema = {
|
||||
merge: "replace",
|
||||
validate: "boolean"
|
||||
};
|
||||
|
||||
const ALLOWED_SEVERITIES = new Set(["error", "warn", "off", 2, 1, 0]);
|
||||
|
||||
/** @type {ObjectPropertySchema} */
|
||||
const disableDirectiveSeveritySchema = {
|
||||
merge(first, second) {
|
||||
const value = second === void 0 ? first : second;
|
||||
|
||||
if (typeof value === "boolean") {
|
||||
return value ? "warn" : "off";
|
||||
}
|
||||
|
||||
return normalizeSeverityToNumber(value);
|
||||
},
|
||||
validate(value) {
|
||||
if (!(ALLOWED_SEVERITIES.has(value) || typeof value === "boolean")) {
|
||||
throw new TypeError("Expected one of: \"error\", \"warn\", \"off\", 0, 1, 2, or a boolean.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** @type {ObjectPropertySchema} */
|
||||
const deepObjectAssignSchema = {
|
||||
merge(first = {}, second = {}) {
|
||||
return deepMerge(first, second);
|
||||
},
|
||||
validate: "object"
|
||||
};
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// High-Level Schemas
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/** @type {ObjectPropertySchema} */
|
||||
const languageOptionsSchema = {
|
||||
merge(first = {}, second = {}) {
|
||||
|
||||
const result = deepMerge(first, second);
|
||||
|
||||
for (const [key, value] of Object.entries(result)) {
|
||||
|
||||
/*
|
||||
* Special case: Because the `parser` property is an object, it should
|
||||
* not be deep merged. Instead, it should be replaced if it exists in
|
||||
* the second object. To make this more generic, we just check for
|
||||
* objects with methods and replace them if they exist in the second
|
||||
* object.
|
||||
*/
|
||||
if (isNonArrayObject(value)) {
|
||||
if (hasMethod(value)) {
|
||||
result[key] = second[key] ?? first[key];
|
||||
continue;
|
||||
}
|
||||
|
||||
// for other objects, make sure we aren't reusing the same object
|
||||
result[key] = { ...result[key] };
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
validate: "object"
|
||||
};
|
||||
|
||||
/** @type {ObjectPropertySchema} */
|
||||
const languageSchema = {
|
||||
merge: "replace",
|
||||
validate: assertIsPluginMemberName
|
||||
};
|
||||
|
||||
/** @type {ObjectPropertySchema} */
|
||||
const pluginsSchema = {
|
||||
merge(first = {}, second = {}) {
|
||||
const keys = new Set([...Object.keys(first), ...Object.keys(second)]);
|
||||
const result = {};
|
||||
|
||||
// manually validate that plugins are not redefined
|
||||
for (const key of keys) {
|
||||
|
||||
// avoid hairy edge case
|
||||
if (key === "__proto__") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key in first && key in second && first[key] !== second[key]) {
|
||||
throw new TypeError(`Cannot redefine plugin "${key}".`);
|
||||
}
|
||||
|
||||
result[key] = second[key] || first[key];
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
validate(value) {
|
||||
|
||||
// first check the value to be sure it's an object
|
||||
if (value === null || typeof value !== "object") {
|
||||
throw new TypeError("Expected an object.");
|
||||
}
|
||||
|
||||
// make sure it's not an array, which would mean eslintrc-style is used
|
||||
if (Array.isArray(value)) {
|
||||
throw new IncompatiblePluginsError(value);
|
||||
}
|
||||
|
||||
// second check the keys to make sure they are objects
|
||||
for (const key of Object.keys(value)) {
|
||||
|
||||
// avoid hairy edge case
|
||||
if (key === "__proto__") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (value[key] === null || typeof value[key] !== "object") {
|
||||
throw new TypeError(`Key "${key}": Expected an object.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** @type {ObjectPropertySchema} */
|
||||
const processorSchema = {
|
||||
merge: "replace",
|
||||
validate(value) {
|
||||
if (typeof value === "string") {
|
||||
assertIsPluginMemberName(value);
|
||||
} else if (value && typeof value === "object") {
|
||||
if (typeof value.preprocess !== "function" || typeof value.postprocess !== "function") {
|
||||
throw new TypeError("Object must have a preprocess() and a postprocess() method.");
|
||||
}
|
||||
} else {
|
||||
throw new TypeError("Expected an object or a string.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** @type {ObjectPropertySchema} */
|
||||
const rulesSchema = {
|
||||
merge(first = {}, second = {}) {
|
||||
|
||||
const result = {
|
||||
...first,
|
||||
...second
|
||||
};
|
||||
|
||||
|
||||
for (const ruleId of Object.keys(result)) {
|
||||
|
||||
try {
|
||||
|
||||
// avoid hairy edge case
|
||||
if (ruleId === "__proto__") {
|
||||
|
||||
/* eslint-disable-next-line no-proto -- Though deprecated, may still be present */
|
||||
delete result.__proto__;
|
||||
continue;
|
||||
}
|
||||
|
||||
result[ruleId] = normalizeRuleOptions(result[ruleId]);
|
||||
|
||||
/*
|
||||
* If either rule config is missing, then the correct
|
||||
* config is already present and we just need to normalize
|
||||
* the severity.
|
||||
*/
|
||||
if (!(ruleId in first) || !(ruleId in second)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const firstRuleOptions = normalizeRuleOptions(first[ruleId]);
|
||||
const secondRuleOptions = normalizeRuleOptions(second[ruleId]);
|
||||
|
||||
/*
|
||||
* If the second rule config only has a severity (length of 1),
|
||||
* then use that severity and keep the rest of the options from
|
||||
* the first rule config.
|
||||
*/
|
||||
if (secondRuleOptions.length === 1) {
|
||||
result[ruleId] = [secondRuleOptions[0], ...firstRuleOptions.slice(1)];
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* In any other situation, then the second rule config takes
|
||||
* precedence. That means the value at `result[ruleId]` is
|
||||
* already correct and no further work is necessary.
|
||||
*/
|
||||
} catch (ex) {
|
||||
throw new Error(`Key "${ruleId}": ${ex.message}`, { cause: ex });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
|
||||
},
|
||||
|
||||
validate(value) {
|
||||
assertIsObject(value);
|
||||
|
||||
/*
|
||||
* We are not checking the rule schema here because there is no
|
||||
* guarantee that the rule definition is present at this point. Instead
|
||||
* we wait and check the rule schema during the finalization step
|
||||
* of calculating a config.
|
||||
*/
|
||||
for (const ruleId of Object.keys(value)) {
|
||||
|
||||
// avoid hairy edge case
|
||||
if (ruleId === "__proto__") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const ruleOptions = value[ruleId];
|
||||
|
||||
assertIsRuleOptions(ruleId, ruleOptions);
|
||||
|
||||
if (Array.isArray(ruleOptions)) {
|
||||
assertIsRuleSeverity(ruleId, ruleOptions[0]);
|
||||
} else {
|
||||
assertIsRuleSeverity(ruleId, ruleOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a schema that always throws an error. Useful for warning
|
||||
* about eslintrc-style keys.
|
||||
* @param {string} key The eslintrc key to create a schema for.
|
||||
* @returns {ObjectPropertySchema} The schema.
|
||||
*/
|
||||
function createEslintrcErrorSchema(key) {
|
||||
return {
|
||||
merge: "replace",
|
||||
validate() {
|
||||
throw new IncompatibleKeyError(key);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const eslintrcKeys = [
|
||||
"env",
|
||||
"extends",
|
||||
"globals",
|
||||
"ignorePatterns",
|
||||
"noInlineConfig",
|
||||
"overrides",
|
||||
"parser",
|
||||
"parserOptions",
|
||||
"reportUnusedDisableDirectives",
|
||||
"root"
|
||||
];
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Full schema
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const flatConfigSchema = {
|
||||
|
||||
// eslintrc-style keys that should always error
|
||||
...Object.fromEntries(eslintrcKeys.map(key => [key, createEslintrcErrorSchema(key)])),
|
||||
|
||||
// flat config keys
|
||||
settings: deepObjectAssignSchema,
|
||||
linterOptions: {
|
||||
schema: {
|
||||
noInlineConfig: booleanSchema,
|
||||
reportUnusedDisableDirectives: disableDirectiveSeveritySchema
|
||||
}
|
||||
},
|
||||
language: languageSchema,
|
||||
languageOptions: languageOptionsSchema,
|
||||
processor: processorSchema,
|
||||
plugins: pluginsSchema,
|
||||
rules: rulesSchema
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Exports
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
flatConfigSchema,
|
||||
hasMethod,
|
||||
assertIsRuleSeverity
|
||||
};
|
194
node_modules/eslint/lib/config/rule-validator.js
generated
vendored
Normal file
194
node_modules/eslint/lib/config/rule-validator.js
generated
vendored
Normal file
@ -0,0 +1,194 @@
|
||||
/**
|
||||
* @fileoverview Rule Validator
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const ajvImport = require("../shared/ajv");
|
||||
const ajv = ajvImport();
|
||||
const {
|
||||
parseRuleId,
|
||||
getRuleFromConfig,
|
||||
getRuleOptionsSchema
|
||||
} = require("./flat-config-helpers");
|
||||
const ruleReplacements = require("../../conf/replacements.json");
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Throws a helpful error when a rule cannot be found.
|
||||
* @param {Object} ruleId The rule identifier.
|
||||
* @param {string} ruleId.pluginName The ID of the rule to find.
|
||||
* @param {string} ruleId.ruleName The ID of the rule to find.
|
||||
* @param {Object} config The config to search in.
|
||||
* @throws {TypeError} For missing plugin or rule.
|
||||
* @returns {void}
|
||||
*/
|
||||
function throwRuleNotFoundError({ pluginName, ruleName }, config) {
|
||||
|
||||
const ruleId = pluginName === "@" ? ruleName : `${pluginName}/${ruleName}`;
|
||||
|
||||
const errorMessageHeader = `Key "rules": Key "${ruleId}"`;
|
||||
let errorMessage = `${errorMessageHeader}: Could not find plugin "${pluginName}".`;
|
||||
|
||||
// if the plugin exists then we need to check if the rule exists
|
||||
if (config.plugins && config.plugins[pluginName]) {
|
||||
const replacementRuleName = ruleReplacements.rules[ruleName];
|
||||
|
||||
if (pluginName === "@" && replacementRuleName) {
|
||||
|
||||
errorMessage = `${errorMessageHeader}: Rule "${ruleName}" was removed and replaced by "${replacementRuleName}".`;
|
||||
|
||||
} else {
|
||||
|
||||
errorMessage = `${errorMessageHeader}: Could not find "${ruleName}" in plugin "${pluginName}".`;
|
||||
|
||||
// otherwise, let's see if we can find the rule name elsewhere
|
||||
for (const [otherPluginName, otherPlugin] of Object.entries(config.plugins)) {
|
||||
if (otherPlugin.rules && otherPlugin.rules[ruleName]) {
|
||||
errorMessage += ` Did you mean "${otherPluginName}/${ruleName}"?`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// falls through to throw error
|
||||
}
|
||||
|
||||
throw new TypeError(errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* The error type when a rule has an invalid `meta.schema`.
|
||||
*/
|
||||
class InvalidRuleOptionsSchemaError extends Error {
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {string} ruleId Id of the rule that has an invalid `meta.schema`.
|
||||
* @param {Error} processingError Error caught while processing the `meta.schema`.
|
||||
*/
|
||||
constructor(ruleId, processingError) {
|
||||
super(
|
||||
`Error while processing options validation schema of rule '${ruleId}': ${processingError.message}`,
|
||||
{ cause: processingError }
|
||||
);
|
||||
this.code = "ESLINT_INVALID_RULE_OPTIONS_SCHEMA";
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Exports
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Implements validation functionality for the rules portion of a config.
|
||||
*/
|
||||
class RuleValidator {
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
constructor() {
|
||||
|
||||
/**
|
||||
* A collection of compiled validators for rules that have already
|
||||
* been validated.
|
||||
* @type {WeakMap}
|
||||
*/
|
||||
this.validators = new WeakMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates all of the rule configurations in a config against each
|
||||
* rule's schema.
|
||||
* @param {Object} config The full config to validate. This object must
|
||||
* contain both the rules section and the plugins section.
|
||||
* @returns {void}
|
||||
* @throws {Error} If a rule's configuration does not match its schema.
|
||||
*/
|
||||
validate(config) {
|
||||
|
||||
if (!config.rules) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [ruleId, ruleOptions] of Object.entries(config.rules)) {
|
||||
|
||||
// check for edge case
|
||||
if (ruleId === "__proto__") {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* If a rule is disabled, we don't do any validation. This allows
|
||||
* users to safely set any value to 0 or "off" without worrying
|
||||
* that it will cause a validation error.
|
||||
*
|
||||
* Note: ruleOptions is always an array at this point because
|
||||
* this validation occurs after FlatConfigArray has merged and
|
||||
* normalized values.
|
||||
*/
|
||||
if (ruleOptions[0] === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const rule = getRuleFromConfig(ruleId, config);
|
||||
|
||||
if (!rule) {
|
||||
throwRuleNotFoundError(parseRuleId(ruleId), config);
|
||||
}
|
||||
|
||||
// Precompile and cache validator the first time
|
||||
if (!this.validators.has(rule)) {
|
||||
try {
|
||||
const schema = getRuleOptionsSchema(rule);
|
||||
|
||||
if (schema) {
|
||||
this.validators.set(rule, ajv.compile(schema));
|
||||
}
|
||||
} catch (err) {
|
||||
throw new InvalidRuleOptionsSchemaError(ruleId, err);
|
||||
}
|
||||
}
|
||||
|
||||
const validateRule = this.validators.get(rule);
|
||||
|
||||
if (validateRule) {
|
||||
|
||||
validateRule(ruleOptions.slice(1));
|
||||
|
||||
if (validateRule.errors) {
|
||||
throw new Error(`Key "rules": Key "${ruleId}":\n${
|
||||
validateRule.errors.map(
|
||||
error => {
|
||||
if (
|
||||
error.keyword === "additionalProperties" &&
|
||||
error.schema === false &&
|
||||
typeof error.parentSchema?.properties === "object" &&
|
||||
typeof error.params?.additionalProperty === "string"
|
||||
) {
|
||||
const expectedProperties = Object.keys(error.parentSchema.properties).map(property => `"${property}"`);
|
||||
|
||||
return `\tValue ${JSON.stringify(error.data)} ${error.message}.\n\t\tUnexpected property "${error.params.additionalProperty}". Expected properties: ${expectedProperties.join(", ")}.\n`;
|
||||
}
|
||||
|
||||
return `\tValue ${JSON.stringify(error.data)} ${error.message}.\n`;
|
||||
}
|
||||
).join("")
|
||||
}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.RuleValidator = RuleValidator;
|
965
node_modules/eslint/lib/eslint/eslint-helpers.js
generated
vendored
Normal file
965
node_modules/eslint/lib/eslint/eslint-helpers.js
generated
vendored
Normal file
@ -0,0 +1,965 @@
|
||||
/**
|
||||
* @fileoverview Helper functions for ESLint class
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const path = require("node:path");
|
||||
const fs = require("node:fs");
|
||||
const fsp = fs.promises;
|
||||
const isGlob = require("is-glob");
|
||||
const hash = require("../cli-engine/hash");
|
||||
const minimatch = require("minimatch");
|
||||
const globParent = require("glob-parent");
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Fixup references
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const Minimatch = minimatch.Minimatch;
|
||||
const MINIMATCH_OPTIONS = { dot: true };
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Types
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @typedef {Object} GlobSearch
|
||||
* @property {Array<string>} patterns The normalized patterns to use for a search.
|
||||
* @property {Array<string>} rawPatterns The patterns as entered by the user
|
||||
* before doing any normalization.
|
||||
*/
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Errors
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* The error type when no files match a glob.
|
||||
*/
|
||||
class NoFilesFoundError extends Error {
|
||||
|
||||
/**
|
||||
* @param {string} pattern The glob pattern which was not found.
|
||||
* @param {boolean} globEnabled If `false` then the pattern was a glob pattern, but glob was disabled.
|
||||
*/
|
||||
constructor(pattern, globEnabled) {
|
||||
super(`No files matching '${pattern}' were found${!globEnabled ? " (glob was disabled)" : ""}.`);
|
||||
this.messageTemplate = "file-not-found";
|
||||
this.messageData = { pattern, globDisabled: !globEnabled };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The error type when a search fails to match multiple patterns.
|
||||
*/
|
||||
class UnmatchedSearchPatternsError extends Error {
|
||||
|
||||
/**
|
||||
* @param {Object} options The options for the error.
|
||||
* @param {string} options.basePath The directory that was searched.
|
||||
* @param {Array<string>} options.unmatchedPatterns The glob patterns
|
||||
* which were not found.
|
||||
* @param {Array<string>} options.patterns The glob patterns that were
|
||||
* searched.
|
||||
* @param {Array<string>} options.rawPatterns The raw glob patterns that
|
||||
* were searched.
|
||||
*/
|
||||
constructor({ basePath, unmatchedPatterns, patterns, rawPatterns }) {
|
||||
super(`No files matching '${rawPatterns}' in '${basePath}' were found.`);
|
||||
this.basePath = basePath;
|
||||
this.unmatchedPatterns = unmatchedPatterns;
|
||||
this.patterns = patterns;
|
||||
this.rawPatterns = rawPatterns;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The error type when there are files matched by a glob, but all of them have been ignored.
|
||||
*/
|
||||
class AllFilesIgnoredError extends Error {
|
||||
|
||||
/**
|
||||
* @param {string} pattern The glob pattern which was not found.
|
||||
*/
|
||||
constructor(pattern) {
|
||||
super(`All files matched by '${pattern}' are ignored.`);
|
||||
this.messageTemplate = "all-matched-files-ignored";
|
||||
this.messageData = { pattern };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// General Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Check if a given value is a non-empty string or not.
|
||||
* @param {any} value The value to check.
|
||||
* @returns {boolean} `true` if `value` is a non-empty string.
|
||||
*/
|
||||
function isNonEmptyString(value) {
|
||||
return typeof value === "string" && value.trim() !== "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given value is an array of non-empty strings or not.
|
||||
* @param {any} value The value to check.
|
||||
* @returns {boolean} `true` if `value` is an array of non-empty strings.
|
||||
*/
|
||||
function isArrayOfNonEmptyString(value) {
|
||||
return Array.isArray(value) && value.length && value.every(isNonEmptyString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given value is an empty array or an array of non-empty strings.
|
||||
* @param {any} value The value to check.
|
||||
* @returns {boolean} `true` if `value` is an empty array or an array of non-empty
|
||||
* strings.
|
||||
*/
|
||||
function isEmptyArrayOrArrayOfNonEmptyString(value) {
|
||||
return Array.isArray(value) && value.every(isNonEmptyString);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// File-related Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Normalizes slashes in a file pattern to posix-style.
|
||||
* @param {string} pattern The pattern to replace slashes in.
|
||||
* @returns {string} The pattern with slashes normalized.
|
||||
*/
|
||||
function normalizeToPosix(pattern) {
|
||||
return pattern.replace(/\\/gu, "/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a string is a glob pattern or not.
|
||||
* @param {string} pattern A glob pattern.
|
||||
* @returns {boolean} `true` if the string is a glob pattern.
|
||||
*/
|
||||
function isGlobPattern(pattern) {
|
||||
return isGlob(path.sep === "\\" ? normalizeToPosix(pattern) : pattern);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines if a given glob pattern will return any results.
|
||||
* Used primarily to help with useful error messages.
|
||||
* @param {Object} options The options for the function.
|
||||
* @param {string} options.basePath The directory to search.
|
||||
* @param {string} options.pattern An absolute path glob pattern to match.
|
||||
* @returns {Promise<boolean>} True if there is a glob match, false if not.
|
||||
*/
|
||||
async function globMatch({ basePath, pattern }) {
|
||||
|
||||
let found = false;
|
||||
const { hfs } = await import("@humanfs/node");
|
||||
const patternToUse = normalizeToPosix(path.relative(basePath, pattern));
|
||||
|
||||
const matcher = new Minimatch(patternToUse, MINIMATCH_OPTIONS);
|
||||
|
||||
const walkSettings = {
|
||||
|
||||
directoryFilter(entry) {
|
||||
return !found && matcher.match(entry.path, true);
|
||||
},
|
||||
|
||||
entryFilter(entry) {
|
||||
if (found || entry.isDirectory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (matcher.match(entry.path)) {
|
||||
found = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
if (await hfs.isDirectory(basePath)) {
|
||||
return hfs.walk(basePath, walkSettings).next().then(() => found);
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches a directory looking for matching glob patterns. This uses
|
||||
* the config array's logic to determine if a directory or file should
|
||||
* be ignored, so it is consistent with how ignoring works throughout
|
||||
* ESLint.
|
||||
* @param {Object} options The options for this function.
|
||||
* @param {string} options.basePath The directory to search.
|
||||
* @param {Array<string>} options.patterns An array of absolute path glob patterns
|
||||
* to match.
|
||||
* @param {Array<string>} options.rawPatterns An array of glob patterns
|
||||
* as the user inputted them. Used for errors.
|
||||
* @param {ConfigLoader|LegacyConfigLoader} options.configLoader The config array to use for
|
||||
* determining what to ignore.
|
||||
* @param {boolean} options.errorOnUnmatchedPattern Determines if an error
|
||||
* should be thrown when a pattern is unmatched.
|
||||
* @returns {Promise<Array<string>>} An array of matching file paths
|
||||
* or an empty array if there are no matches.
|
||||
* @throws {UnmatchedSearchPatternsError} If there is a pattern that doesn't
|
||||
* match any files.
|
||||
*/
|
||||
async function globSearch({
|
||||
basePath,
|
||||
patterns,
|
||||
rawPatterns,
|
||||
configLoader,
|
||||
errorOnUnmatchedPattern
|
||||
}) {
|
||||
|
||||
if (patterns.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/*
|
||||
* In this section we are converting the patterns into Minimatch
|
||||
* instances for performance reasons. Because we are doing the same
|
||||
* matches repeatedly, it's best to compile those patterns once and
|
||||
* reuse them multiple times.
|
||||
*
|
||||
* To do that, we convert any patterns with an absolute path into a
|
||||
* relative path and normalize it to Posix-style slashes. We also keep
|
||||
* track of the relative patterns to map them back to the original
|
||||
* patterns, which we need in order to throw an error if there are any
|
||||
* unmatched patterns.
|
||||
*/
|
||||
const relativeToPatterns = new Map();
|
||||
const matchers = patterns.map((pattern, i) => {
|
||||
const patternToUse = normalizeToPosix(path.relative(basePath, pattern));
|
||||
|
||||
relativeToPatterns.set(patternToUse, patterns[i]);
|
||||
|
||||
return new Minimatch(patternToUse, MINIMATCH_OPTIONS);
|
||||
});
|
||||
|
||||
/*
|
||||
* We track unmatched patterns because we may want to throw an error when
|
||||
* they occur. To start, this set is initialized with all of the patterns.
|
||||
* Every time a match occurs, the pattern is removed from the set, making
|
||||
* it easy to tell if we have any unmatched patterns left at the end of
|
||||
* search.
|
||||
*/
|
||||
const unmatchedPatterns = new Set([...relativeToPatterns.keys()]);
|
||||
const { hfs } = await import("@humanfs/node");
|
||||
|
||||
const walk = hfs.walk(
|
||||
basePath,
|
||||
{
|
||||
async directoryFilter(entry) {
|
||||
|
||||
if (!matchers.some(matcher => matcher.match(entry.path, true))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const absolutePath = path.resolve(basePath, entry.path);
|
||||
const configs = await configLoader.loadConfigArrayForDirectory(absolutePath);
|
||||
|
||||
return !configs.isDirectoryIgnored(absolutePath);
|
||||
},
|
||||
async entryFilter(entry) {
|
||||
const absolutePath = path.resolve(basePath, entry.path);
|
||||
|
||||
// entries may be directories or files so filter out directories
|
||||
if (entry.isDirectory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const configs = await configLoader.loadConfigArrayForFile(absolutePath);
|
||||
const config = configs.getConfig(absolutePath);
|
||||
|
||||
/*
|
||||
* Optimization: We need to track when patterns are left unmatched
|
||||
* and so we use `unmatchedPatterns` to do that. There is a bit of
|
||||
* complexity here because the same file can be matched by more than
|
||||
* one pattern. So, when we start, we actually need to test every
|
||||
* pattern against every file. Once we know there are no remaining
|
||||
* unmatched patterns, then we can switch to just looking for the
|
||||
* first matching pattern for improved speed.
|
||||
*/
|
||||
const matchesPattern = unmatchedPatterns.size > 0
|
||||
? matchers.reduce((previousValue, matcher) => {
|
||||
const pathMatches = matcher.match(entry.path);
|
||||
|
||||
/*
|
||||
* We updated the unmatched patterns set only if the path
|
||||
* matches and the file has a config. If the file has no
|
||||
* config, that means there wasn't a match for the
|
||||
* pattern so it should not be removed.
|
||||
*
|
||||
* Performance note: `getConfig()` aggressively caches
|
||||
* results so there is no performance penalty for calling
|
||||
* it multiple times with the same argument.
|
||||
*/
|
||||
if (pathMatches && config) {
|
||||
unmatchedPatterns.delete(matcher.pattern);
|
||||
}
|
||||
|
||||
return pathMatches || previousValue;
|
||||
}, false)
|
||||
: matchers.some(matcher => matcher.match(entry.path));
|
||||
|
||||
return matchesPattern && config !== void 0;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const filePaths = [];
|
||||
|
||||
if (await hfs.isDirectory(basePath)) {
|
||||
for await (const entry of walk) {
|
||||
filePaths.push(path.resolve(basePath, entry.path));
|
||||
}
|
||||
}
|
||||
|
||||
// now check to see if we have any unmatched patterns
|
||||
if (errorOnUnmatchedPattern && unmatchedPatterns.size > 0) {
|
||||
throw new UnmatchedSearchPatternsError({
|
||||
basePath,
|
||||
unmatchedPatterns: [...unmatchedPatterns].map(
|
||||
pattern => relativeToPatterns.get(pattern)
|
||||
),
|
||||
patterns,
|
||||
rawPatterns
|
||||
});
|
||||
}
|
||||
|
||||
return filePaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an error for unmatched patterns. The error will only contain information about the first one.
|
||||
* Checks to see if there are any ignored results for a given search.
|
||||
* @param {Object} options The options for this function.
|
||||
* @param {string} options.basePath The directory to search.
|
||||
* @param {Array<string>} options.patterns An array of glob patterns
|
||||
* that were used in the original search.
|
||||
* @param {Array<string>} options.rawPatterns An array of glob patterns
|
||||
* as the user inputted them. Used for errors.
|
||||
* @param {Array<string>} options.unmatchedPatterns A non-empty array of absolute path glob patterns
|
||||
* that were unmatched in the original search.
|
||||
* @returns {void} Always throws an error.
|
||||
* @throws {NoFilesFoundError} If the first unmatched pattern
|
||||
* doesn't match any files even when there are no ignores.
|
||||
* @throws {AllFilesIgnoredError} If the first unmatched pattern
|
||||
* matches some files when there are no ignores.
|
||||
*/
|
||||
async function throwErrorForUnmatchedPatterns({
|
||||
basePath,
|
||||
patterns,
|
||||
rawPatterns,
|
||||
unmatchedPatterns
|
||||
}) {
|
||||
|
||||
const pattern = unmatchedPatterns[0];
|
||||
const rawPattern = rawPatterns[patterns.indexOf(pattern)];
|
||||
|
||||
const patternHasMatch = await globMatch({
|
||||
basePath,
|
||||
pattern
|
||||
});
|
||||
|
||||
if (patternHasMatch) {
|
||||
throw new AllFilesIgnoredError(rawPattern);
|
||||
}
|
||||
|
||||
// if we get here there are truly no matches
|
||||
throw new NoFilesFoundError(rawPattern, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs multiple glob searches in parallel.
|
||||
* @param {Object} options The options for this function.
|
||||
* @param {Map<string,GlobSearch>} options.searches
|
||||
* A map of absolute path glob patterns to match.
|
||||
* @param {ConfigLoader|LegacyConfigLoader} options.configLoader The config loader to use for
|
||||
* determining what to ignore.
|
||||
* @param {boolean} options.errorOnUnmatchedPattern Determines if an
|
||||
* unmatched glob pattern should throw an error.
|
||||
* @returns {Promise<Array<string>>} An array of matching file paths
|
||||
* or an empty array if there are no matches.
|
||||
*/
|
||||
async function globMultiSearch({ searches, configLoader, errorOnUnmatchedPattern }) {
|
||||
|
||||
/*
|
||||
* For convenience, we normalized the search map into an array of objects.
|
||||
* Next, we filter out all searches that have no patterns. This happens
|
||||
* primarily for the cwd, which is prepopulated in the searches map as an
|
||||
* optimization. However, if it has no patterns, it means all patterns
|
||||
* occur outside of the cwd and we can safely filter out that search.
|
||||
*/
|
||||
const normalizedSearches = [...searches].map(
|
||||
([basePath, { patterns, rawPatterns }]) => ({ basePath, patterns, rawPatterns })
|
||||
).filter(({ patterns }) => patterns.length > 0);
|
||||
|
||||
const results = await Promise.allSettled(
|
||||
normalizedSearches.map(
|
||||
({ basePath, patterns, rawPatterns }) => globSearch({
|
||||
basePath,
|
||||
patterns,
|
||||
rawPatterns,
|
||||
configLoader,
|
||||
errorOnUnmatchedPattern
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
/*
|
||||
* The first loop handles errors from the glob searches. Since we can't
|
||||
* use `await` inside `flatMap`, we process errors separately in this loop.
|
||||
* This results in two iterations over `results`, but since the length is
|
||||
* less than or equal to the number of globs and directories passed on the
|
||||
* command line, the performance impact should be minimal.
|
||||
*/
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
|
||||
const result = results[i];
|
||||
const currentSearch = normalizedSearches[i];
|
||||
|
||||
if (result.status === "fulfilled") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if we make it here then there was an error
|
||||
const error = result.reason;
|
||||
|
||||
// unexpected errors should be re-thrown
|
||||
if (!error.basePath) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (errorOnUnmatchedPattern) {
|
||||
|
||||
await throwErrorForUnmatchedPatterns({
|
||||
...currentSearch,
|
||||
unmatchedPatterns: error.unmatchedPatterns
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// second loop for `fulfulled` results
|
||||
return results.flatMap(result => result.value);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all files matching the options specified.
|
||||
* @param {Object} args The arguments objects.
|
||||
* @param {Array<string>} args.patterns An array of glob patterns.
|
||||
* @param {boolean} args.globInputPaths true to interpret glob patterns,
|
||||
* false to not interpret glob patterns.
|
||||
* @param {string} args.cwd The current working directory to find from.
|
||||
* @param {ConfigLoader|LegacyConfigLoader} args.configLoader The config loeader for the current run.
|
||||
* @param {boolean} args.errorOnUnmatchedPattern Determines if an unmatched pattern
|
||||
* should throw an error.
|
||||
* @returns {Promise<Array<string>>} The fully resolved file paths.
|
||||
* @throws {AllFilesIgnoredError} If there are no results due to an ignore pattern.
|
||||
* @throws {NoFilesFoundError} If no files matched the given patterns.
|
||||
*/
|
||||
async function findFiles({
|
||||
patterns,
|
||||
globInputPaths,
|
||||
cwd,
|
||||
configLoader,
|
||||
errorOnUnmatchedPattern
|
||||
}) {
|
||||
|
||||
const results = [];
|
||||
const missingPatterns = [];
|
||||
let globbyPatterns = [];
|
||||
let rawPatterns = [];
|
||||
const searches = new Map([[cwd, { patterns: globbyPatterns, rawPatterns: [] }]]);
|
||||
|
||||
/*
|
||||
* This part is a bit involved because we need to account for
|
||||
* the different ways that the patterns can match directories.
|
||||
* For each different way, we need to decide if we should look
|
||||
* for a config file or just use the default config. (Directories
|
||||
* without a config file always use the default config.)
|
||||
*
|
||||
* Here are the cases:
|
||||
*
|
||||
* 1. A directory is passed directly (e.g., "subdir"). In this case, we
|
||||
* can assume that the user intends to lint this directory and we should
|
||||
* not look for a config file in the parent directory, because the only
|
||||
* reason to do that would be to ignore this directory (which we already
|
||||
* know we don't want to do). Instead, we use the default config until we
|
||||
* get to the directory that was passed, at which point we start looking
|
||||
* for config files again.
|
||||
*
|
||||
* 2. A dot (".") or star ("*"). In this case, we want to read
|
||||
* the config file in the current directory because the user is
|
||||
* explicitly asking to lint the current directory. Note that "."
|
||||
* will traverse into subdirectories while "*" will not.
|
||||
*
|
||||
* 3. A directory is passed in the form of "subdir/subsubdir".
|
||||
* In this case, we don't want to look for a config file in the
|
||||
* parent directory ("subdir"). We can skip looking for a config
|
||||
* file until `entry.depth` is greater than 1 because there's no
|
||||
* way that the pattern can match `entry.path` yet.
|
||||
*
|
||||
* 4. A directory glob pattern is passed (e.g., "subd*"). We want
|
||||
* this case to act like case 2 because it's unclear whether or not
|
||||
* any particular directory is meant to be traversed.
|
||||
*
|
||||
* 5. A recursive glob pattern is passed (e.g., "**"). We want this
|
||||
* case to act like case 2.
|
||||
*/
|
||||
|
||||
// check to see if we have explicit files and directories
|
||||
const filePaths = patterns.map(filePath => path.resolve(cwd, filePath));
|
||||
const stats = await Promise.all(
|
||||
filePaths.map(
|
||||
filePath => fsp.stat(filePath).catch(() => { })
|
||||
)
|
||||
);
|
||||
|
||||
stats.forEach((stat, index) => {
|
||||
|
||||
const filePath = filePaths[index];
|
||||
const pattern = normalizeToPosix(patterns[index]);
|
||||
|
||||
if (stat) {
|
||||
|
||||
// files are added directly to the list
|
||||
if (stat.isFile()) {
|
||||
results.push(filePath);
|
||||
}
|
||||
|
||||
// directories need extensions attached
|
||||
if (stat.isDirectory()) {
|
||||
|
||||
if (!searches.has(filePath)) {
|
||||
searches.set(filePath, { patterns: [], rawPatterns: [] });
|
||||
}
|
||||
({ patterns: globbyPatterns, rawPatterns } = searches.get(filePath));
|
||||
|
||||
globbyPatterns.push(`${normalizeToPosix(filePath)}/**`);
|
||||
rawPatterns.push(pattern);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// save patterns for later use based on whether globs are enabled
|
||||
if (globInputPaths && isGlobPattern(pattern)) {
|
||||
|
||||
/*
|
||||
* We are grouping patterns by their glob parent. This is done to
|
||||
* make it easier to determine when a config file should be loaded.
|
||||
*/
|
||||
|
||||
const basePath = path.resolve(cwd, globParent(pattern));
|
||||
|
||||
if (!searches.has(basePath)) {
|
||||
searches.set(basePath, { patterns: [], rawPatterns: [] });
|
||||
}
|
||||
({ patterns: globbyPatterns, rawPatterns } = searches.get(basePath));
|
||||
|
||||
globbyPatterns.push(filePath);
|
||||
rawPatterns.push(pattern);
|
||||
} else {
|
||||
missingPatterns.push(pattern);
|
||||
}
|
||||
});
|
||||
|
||||
// there were patterns that didn't match anything, tell the user
|
||||
if (errorOnUnmatchedPattern && missingPatterns.length) {
|
||||
throw new NoFilesFoundError(missingPatterns[0], globInputPaths);
|
||||
}
|
||||
|
||||
// now we are safe to do the search
|
||||
const globbyResults = await globMultiSearch({
|
||||
searches,
|
||||
configLoader,
|
||||
errorOnUnmatchedPattern
|
||||
});
|
||||
|
||||
return [
|
||||
...new Set([
|
||||
...results,
|
||||
...globbyResults
|
||||
])
|
||||
];
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Results-related Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Checks if the given message is an error message.
|
||||
* @param {LintMessage} message The message to check.
|
||||
* @returns {boolean} Whether or not the message is an error message.
|
||||
* @private
|
||||
*/
|
||||
function isErrorMessage(message) {
|
||||
return message.severity === 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns result with warning by ignore settings
|
||||
* @param {string} filePath Absolute file path of checked code
|
||||
* @param {string} baseDir Absolute path of base directory
|
||||
* @param {"ignored"|"external"|"unconfigured"} configStatus A status that determines why the file is ignored
|
||||
* @returns {LintResult} Result with single warning
|
||||
* @private
|
||||
*/
|
||||
function createIgnoreResult(filePath, baseDir, configStatus) {
|
||||
let message;
|
||||
|
||||
switch (configStatus) {
|
||||
case "external":
|
||||
message = "File ignored because outside of base path.";
|
||||
break;
|
||||
case "unconfigured":
|
||||
message = "File ignored because no matching configuration was supplied.";
|
||||
break;
|
||||
default:
|
||||
{
|
||||
const isInNodeModules = baseDir && path.dirname(path.relative(baseDir, filePath)).split(path.sep).includes("node_modules");
|
||||
|
||||
if (isInNodeModules) {
|
||||
message = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning.";
|
||||
} else {
|
||||
message = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning.";
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
filePath,
|
||||
messages: [
|
||||
{
|
||||
ruleId: null,
|
||||
fatal: false,
|
||||
severity: 1,
|
||||
message,
|
||||
nodeType: null
|
||||
}
|
||||
],
|
||||
suppressedMessages: [],
|
||||
errorCount: 0,
|
||||
warningCount: 1,
|
||||
fatalErrorCount: 0,
|
||||
fixableErrorCount: 0,
|
||||
fixableWarningCount: 0
|
||||
};
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Options-related Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
/**
|
||||
* Check if a given value is a valid fix type or not.
|
||||
* @param {any} x The value to check.
|
||||
* @returns {boolean} `true` if `x` is valid fix type.
|
||||
*/
|
||||
function isFixType(x) {
|
||||
return x === "directive" || x === "problem" || x === "suggestion" || x === "layout";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given value is an array of fix types or not.
|
||||
* @param {any} x The value to check.
|
||||
* @returns {boolean} `true` if `x` is an array of fix types.
|
||||
*/
|
||||
function isFixTypeArray(x) {
|
||||
return Array.isArray(x) && x.every(isFixType);
|
||||
}
|
||||
|
||||
/**
|
||||
* The error for invalid options.
|
||||
*/
|
||||
class ESLintInvalidOptionsError extends Error {
|
||||
constructor(messages) {
|
||||
super(`Invalid Options:\n- ${messages.join("\n- ")}`);
|
||||
this.code = "ESLINT_INVALID_OPTIONS";
|
||||
Error.captureStackTrace(this, ESLintInvalidOptionsError);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and normalizes options for the wrapped CLIEngine instance.
|
||||
* @param {ESLintOptions} options The options to process.
|
||||
* @throws {ESLintInvalidOptionsError} If of any of a variety of type errors.
|
||||
* @returns {ESLintOptions} The normalized options.
|
||||
*/
|
||||
function processOptions({
|
||||
allowInlineConfig = true, // ← we cannot use `overrideConfig.noInlineConfig` instead because `allowInlineConfig` has side-effect that suppress warnings that show inline configs are ignored.
|
||||
baseConfig = null,
|
||||
cache = false,
|
||||
cacheLocation = ".eslintcache",
|
||||
cacheStrategy = "metadata",
|
||||
cwd = process.cwd(),
|
||||
errorOnUnmatchedPattern = true,
|
||||
fix = false,
|
||||
fixTypes = null, // ← should be null by default because if it's an array then it suppresses rules that don't have the `meta.type` property.
|
||||
flags = [],
|
||||
globInputPaths = true,
|
||||
ignore = true,
|
||||
ignorePatterns = null,
|
||||
overrideConfig = null,
|
||||
overrideConfigFile = null,
|
||||
plugins = {},
|
||||
stats = false,
|
||||
warnIgnored = true,
|
||||
passOnNoPatterns = false,
|
||||
ruleFilter = () => true,
|
||||
...unknownOptions
|
||||
}) {
|
||||
const errors = [];
|
||||
const unknownOptionKeys = Object.keys(unknownOptions);
|
||||
|
||||
if (unknownOptionKeys.length >= 1) {
|
||||
errors.push(`Unknown options: ${unknownOptionKeys.join(", ")}`);
|
||||
if (unknownOptionKeys.includes("cacheFile")) {
|
||||
errors.push("'cacheFile' has been removed. Please use the 'cacheLocation' option instead.");
|
||||
}
|
||||
if (unknownOptionKeys.includes("configFile")) {
|
||||
errors.push("'configFile' has been removed. Please use the 'overrideConfigFile' option instead.");
|
||||
}
|
||||
if (unknownOptionKeys.includes("envs")) {
|
||||
errors.push("'envs' has been removed.");
|
||||
}
|
||||
if (unknownOptionKeys.includes("extensions")) {
|
||||
errors.push("'extensions' has been removed.");
|
||||
}
|
||||
if (unknownOptionKeys.includes("resolvePluginsRelativeTo")) {
|
||||
errors.push("'resolvePluginsRelativeTo' has been removed.");
|
||||
}
|
||||
if (unknownOptionKeys.includes("globals")) {
|
||||
errors.push("'globals' has been removed. Please use the 'overrideConfig.languageOptions.globals' option instead.");
|
||||
}
|
||||
if (unknownOptionKeys.includes("ignorePath")) {
|
||||
errors.push("'ignorePath' has been removed.");
|
||||
}
|
||||
if (unknownOptionKeys.includes("ignorePattern")) {
|
||||
errors.push("'ignorePattern' has been removed. Please use the 'overrideConfig.ignorePatterns' option instead.");
|
||||
}
|
||||
if (unknownOptionKeys.includes("parser")) {
|
||||
errors.push("'parser' has been removed. Please use the 'overrideConfig.languageOptions.parser' option instead.");
|
||||
}
|
||||
if (unknownOptionKeys.includes("parserOptions")) {
|
||||
errors.push("'parserOptions' has been removed. Please use the 'overrideConfig.languageOptions.parserOptions' option instead.");
|
||||
}
|
||||
if (unknownOptionKeys.includes("rules")) {
|
||||
errors.push("'rules' has been removed. Please use the 'overrideConfig.rules' option instead.");
|
||||
}
|
||||
if (unknownOptionKeys.includes("rulePaths")) {
|
||||
errors.push("'rulePaths' has been removed. Please define your rules using plugins.");
|
||||
}
|
||||
if (unknownOptionKeys.includes("reportUnusedDisableDirectives")) {
|
||||
errors.push("'reportUnusedDisableDirectives' has been removed. Please use the 'overrideConfig.linterOptions.reportUnusedDisableDirectives' option instead.");
|
||||
}
|
||||
}
|
||||
if (typeof allowInlineConfig !== "boolean") {
|
||||
errors.push("'allowInlineConfig' must be a boolean.");
|
||||
}
|
||||
if (typeof baseConfig !== "object") {
|
||||
errors.push("'baseConfig' must be an object or null.");
|
||||
}
|
||||
if (typeof cache !== "boolean") {
|
||||
errors.push("'cache' must be a boolean.");
|
||||
}
|
||||
if (!isNonEmptyString(cacheLocation)) {
|
||||
errors.push("'cacheLocation' must be a non-empty string.");
|
||||
}
|
||||
if (
|
||||
cacheStrategy !== "metadata" &&
|
||||
cacheStrategy !== "content"
|
||||
) {
|
||||
errors.push("'cacheStrategy' must be any of \"metadata\", \"content\".");
|
||||
}
|
||||
if (!isNonEmptyString(cwd) || !path.isAbsolute(cwd)) {
|
||||
errors.push("'cwd' must be an absolute path.");
|
||||
}
|
||||
if (typeof errorOnUnmatchedPattern !== "boolean") {
|
||||
errors.push("'errorOnUnmatchedPattern' must be a boolean.");
|
||||
}
|
||||
if (typeof fix !== "boolean" && typeof fix !== "function") {
|
||||
errors.push("'fix' must be a boolean or a function.");
|
||||
}
|
||||
if (fixTypes !== null && !isFixTypeArray(fixTypes)) {
|
||||
errors.push("'fixTypes' must be an array of any of \"directive\", \"problem\", \"suggestion\", and \"layout\".");
|
||||
}
|
||||
if (!isEmptyArrayOrArrayOfNonEmptyString(flags)) {
|
||||
errors.push("'flags' must be an array of non-empty strings.");
|
||||
}
|
||||
if (typeof globInputPaths !== "boolean") {
|
||||
errors.push("'globInputPaths' must be a boolean.");
|
||||
}
|
||||
if (typeof ignore !== "boolean") {
|
||||
errors.push("'ignore' must be a boolean.");
|
||||
}
|
||||
if (!isEmptyArrayOrArrayOfNonEmptyString(ignorePatterns) && ignorePatterns !== null) {
|
||||
errors.push("'ignorePatterns' must be an array of non-empty strings or null.");
|
||||
}
|
||||
if (typeof overrideConfig !== "object") {
|
||||
errors.push("'overrideConfig' must be an object or null.");
|
||||
}
|
||||
if (!isNonEmptyString(overrideConfigFile) && overrideConfigFile !== null && overrideConfigFile !== true) {
|
||||
errors.push("'overrideConfigFile' must be a non-empty string, null, or true.");
|
||||
}
|
||||
if (typeof passOnNoPatterns !== "boolean") {
|
||||
errors.push("'passOnNoPatterns' must be a boolean.");
|
||||
}
|
||||
if (typeof plugins !== "object") {
|
||||
errors.push("'plugins' must be an object or null.");
|
||||
} else if (plugins !== null && Object.keys(plugins).includes("")) {
|
||||
errors.push("'plugins' must not include an empty string.");
|
||||
}
|
||||
if (Array.isArray(plugins)) {
|
||||
errors.push("'plugins' doesn't add plugins to configuration to load. Please use the 'overrideConfig.plugins' option instead.");
|
||||
}
|
||||
if (typeof stats !== "boolean") {
|
||||
errors.push("'stats' must be a boolean.");
|
||||
}
|
||||
if (typeof warnIgnored !== "boolean") {
|
||||
errors.push("'warnIgnored' must be a boolean.");
|
||||
}
|
||||
if (typeof ruleFilter !== "function") {
|
||||
errors.push("'ruleFilter' must be a function.");
|
||||
}
|
||||
if (errors.length > 0) {
|
||||
throw new ESLintInvalidOptionsError(errors);
|
||||
}
|
||||
|
||||
return {
|
||||
allowInlineConfig,
|
||||
baseConfig,
|
||||
cache,
|
||||
cacheLocation,
|
||||
cacheStrategy,
|
||||
|
||||
// when overrideConfigFile is true that means don't do config file lookup
|
||||
configFile: overrideConfigFile === true ? false : overrideConfigFile,
|
||||
overrideConfig,
|
||||
cwd: path.normalize(cwd),
|
||||
errorOnUnmatchedPattern,
|
||||
fix,
|
||||
fixTypes,
|
||||
flags: [...flags],
|
||||
globInputPaths,
|
||||
ignore,
|
||||
ignorePatterns,
|
||||
stats,
|
||||
passOnNoPatterns,
|
||||
warnIgnored,
|
||||
ruleFilter
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Cache-related helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* return the cacheFile to be used by eslint, based on whether the provided parameter is
|
||||
* a directory or looks like a directory (ends in `path.sep`), in which case the file
|
||||
* name will be the `cacheFile/.cache_hashOfCWD`
|
||||
*
|
||||
* if cacheFile points to a file or looks like a file then in will just use that file
|
||||
* @param {string} cacheFile The name of file to be used to store the cache
|
||||
* @param {string} cwd Current working directory
|
||||
* @returns {string} the resolved path to the cache file
|
||||
*/
|
||||
function getCacheFile(cacheFile, cwd) {
|
||||
|
||||
/*
|
||||
* make sure the path separators are normalized for the environment/os
|
||||
* keeping the trailing path separator if present
|
||||
*/
|
||||
const normalizedCacheFile = path.normalize(cacheFile);
|
||||
|
||||
const resolvedCacheFile = path.resolve(cwd, normalizedCacheFile);
|
||||
const looksLikeADirectory = normalizedCacheFile.slice(-1) === path.sep;
|
||||
|
||||
/**
|
||||
* return the name for the cache file in case the provided parameter is a directory
|
||||
* @returns {string} the resolved path to the cacheFile
|
||||
*/
|
||||
function getCacheFileForDirectory() {
|
||||
return path.join(resolvedCacheFile, `.cache_${hash(cwd)}`);
|
||||
}
|
||||
|
||||
let fileStats;
|
||||
|
||||
try {
|
||||
fileStats = fs.lstatSync(resolvedCacheFile);
|
||||
} catch {
|
||||
fileStats = null;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* in case the file exists we need to verify if the provided path
|
||||
* is a directory or a file. If it is a directory we want to create a file
|
||||
* inside that directory
|
||||
*/
|
||||
if (fileStats) {
|
||||
|
||||
/*
|
||||
* is a directory or is a file, but the original file the user provided
|
||||
* looks like a directory but `path.resolve` removed the `last path.sep`
|
||||
* so we need to still treat this like a directory
|
||||
*/
|
||||
if (fileStats.isDirectory() || looksLikeADirectory) {
|
||||
return getCacheFileForDirectory();
|
||||
}
|
||||
|
||||
// is file so just use that file
|
||||
return resolvedCacheFile;
|
||||
}
|
||||
|
||||
/*
|
||||
* here we known the file or directory doesn't exist,
|
||||
* so we will try to infer if its a directory if it looks like a directory
|
||||
* for the current operating system.
|
||||
*/
|
||||
|
||||
// if the last character passed is a path separator we assume is a directory
|
||||
if (looksLikeADirectory) {
|
||||
return getCacheFileForDirectory();
|
||||
}
|
||||
|
||||
return resolvedCacheFile;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Exports
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
findFiles,
|
||||
|
||||
isNonEmptyString,
|
||||
isArrayOfNonEmptyString,
|
||||
|
||||
createIgnoreResult,
|
||||
isErrorMessage,
|
||||
|
||||
processOptions,
|
||||
|
||||
getCacheFile
|
||||
};
|
1121
node_modules/eslint/lib/eslint/eslint.js
generated
vendored
Normal file
1121
node_modules/eslint/lib/eslint/eslint.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
9
node_modules/eslint/lib/eslint/index.js
generated
vendored
Normal file
9
node_modules/eslint/lib/eslint/index.js
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
"use strict";
|
||||
|
||||
const { ESLint } = require("./eslint");
|
||||
const { LegacyESLint } = require("./legacy-eslint");
|
||||
|
||||
module.exports = {
|
||||
ESLint,
|
||||
LegacyESLint
|
||||
};
|
742
node_modules/eslint/lib/eslint/legacy-eslint.js
generated
vendored
Normal file
742
node_modules/eslint/lib/eslint/legacy-eslint.js
generated
vendored
Normal file
@ -0,0 +1,742 @@
|
||||
/**
|
||||
* @fileoverview Main API Class
|
||||
* @author Kai Cataldo
|
||||
* @author Toru Nagashima
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const path = require("node:path");
|
||||
const fs = require("node:fs");
|
||||
const { promisify } = require("node:util");
|
||||
const { CLIEngine, getCLIEngineInternalSlots } = require("../cli-engine/cli-engine");
|
||||
const BuiltinRules = require("../rules");
|
||||
const {
|
||||
Legacy: {
|
||||
ConfigOps: {
|
||||
getRuleSeverity
|
||||
}
|
||||
}
|
||||
} = require("@eslint/eslintrc");
|
||||
const { version } = require("../../package.json");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Typedefs
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @typedef {import("../cli-engine/cli-engine").LintReport} CLIEngineLintReport */
|
||||
/** @typedef {import("../shared/types").DeprecatedRuleInfo} DeprecatedRuleInfo */
|
||||
/** @typedef {import("../shared/types").ConfigData} ConfigData */
|
||||
/** @typedef {import("../shared/types").LintMessage} LintMessage */
|
||||
/** @typedef {import("../shared/types").SuppressedLintMessage} SuppressedLintMessage */
|
||||
/** @typedef {import("../shared/types").Plugin} Plugin */
|
||||
/** @typedef {import("../shared/types").Rule} Rule */
|
||||
/** @typedef {import("../shared/types").LintResult} LintResult */
|
||||
/** @typedef {import("../shared/types").ResultsMeta} ResultsMeta */
|
||||
|
||||
/**
|
||||
* The main formatter object.
|
||||
* @typedef LoadedFormatter
|
||||
* @property {(results: LintResult[], resultsMeta: ResultsMeta) => string | Promise<string>} format format function.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The options with which to configure the LegacyESLint instance.
|
||||
* @typedef {Object} LegacyESLintOptions
|
||||
* @property {boolean} [allowInlineConfig] Enable or disable inline configuration comments.
|
||||
* @property {ConfigData} [baseConfig] Base config object, extended by all configs used with this instance
|
||||
* @property {boolean} [cache] Enable result caching.
|
||||
* @property {string} [cacheLocation] The cache file to use instead of .eslintcache.
|
||||
* @property {"metadata" | "content"} [cacheStrategy] The strategy used to detect changed files.
|
||||
* @property {string} [cwd] The value to use for the current working directory.
|
||||
* @property {boolean} [errorOnUnmatchedPattern] If `false` then `ESLint#lintFiles()` doesn't throw even if no target files found. Defaults to `true`.
|
||||
* @property {string[]} [extensions] An array of file extensions to check.
|
||||
* @property {boolean|Function} [fix] Execute in autofix mode. If a function, should return a boolean.
|
||||
* @property {string[]} [fixTypes] Array of rule types to apply fixes for.
|
||||
* @property {boolean} [globInputPaths] Set to false to skip glob resolution of input file paths to lint (default: true). If false, each input file paths is assumed to be a non-glob path to an existing file.
|
||||
* @property {boolean} [ignore] False disables use of .eslintignore.
|
||||
* @property {string} [ignorePath] The ignore file to use instead of .eslintignore.
|
||||
* @property {ConfigData} [overrideConfig] Override config object, overrides all configs used with this instance
|
||||
* @property {string} [overrideConfigFile] The configuration file to use.
|
||||
* @property {Record<string,Plugin>|null} [plugins] Preloaded plugins. This is a map-like object, keys are plugin IDs and each value is implementation.
|
||||
* @property {"error" | "warn" | "off"} [reportUnusedDisableDirectives] the severity to report unused eslint-disable directives.
|
||||
* @property {string} [resolvePluginsRelativeTo] The folder where plugins should be resolved from, defaulting to the CWD.
|
||||
* @property {string[]} [rulePaths] An array of directories to load custom rules from.
|
||||
* @property {boolean} [useEslintrc] False disables looking for .eslintrc.* files.
|
||||
* @property {boolean} [passOnNoPatterns=false] When set to true, missing patterns cause
|
||||
* the linting operation to short circuit and not report any failures.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A rules metadata object.
|
||||
* @typedef {Object} RulesMeta
|
||||
* @property {string} id The plugin ID.
|
||||
* @property {Object} definition The plugin definition.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Private members for the `ESLint` instance.
|
||||
* @typedef {Object} ESLintPrivateMembers
|
||||
* @property {CLIEngine} cliEngine The wrapped CLIEngine instance.
|
||||
* @property {LegacyESLintOptions} options The options used to instantiate the ESLint instance.
|
||||
*/
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const writeFile = promisify(fs.writeFile);
|
||||
|
||||
/**
|
||||
* The map with which to store private class members.
|
||||
* @type {WeakMap<ESLint, ESLintPrivateMembers>}
|
||||
*/
|
||||
const privateMembersMap = new WeakMap();
|
||||
|
||||
/**
|
||||
* Check if a given value is a non-empty string or not.
|
||||
* @param {any} value The value to check.
|
||||
* @returns {boolean} `true` if `value` is a non-empty string.
|
||||
*/
|
||||
function isNonEmptyString(value) {
|
||||
return typeof value === "string" && value.trim() !== "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given value is an array of non-empty strings or not.
|
||||
* @param {any} value The value to check.
|
||||
* @returns {boolean} `true` if `value` is an array of non-empty strings.
|
||||
*/
|
||||
function isArrayOfNonEmptyString(value) {
|
||||
return Array.isArray(value) && value.length && value.every(isNonEmptyString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given value is an empty array or an array of non-empty strings.
|
||||
* @param {any} value The value to check.
|
||||
* @returns {boolean} `true` if `value` is an empty array or an array of non-empty
|
||||
* strings.
|
||||
*/
|
||||
function isEmptyArrayOrArrayOfNonEmptyString(value) {
|
||||
return Array.isArray(value) && value.every(isNonEmptyString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given value is a valid fix type or not.
|
||||
* @param {any} value The value to check.
|
||||
* @returns {boolean} `true` if `value` is valid fix type.
|
||||
*/
|
||||
function isFixType(value) {
|
||||
return value === "directive" || value === "problem" || value === "suggestion" || value === "layout";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given value is an array of fix types or not.
|
||||
* @param {any} value The value to check.
|
||||
* @returns {boolean} `true` if `value` is an array of fix types.
|
||||
*/
|
||||
function isFixTypeArray(value) {
|
||||
return Array.isArray(value) && value.every(isFixType);
|
||||
}
|
||||
|
||||
/**
|
||||
* The error for invalid options.
|
||||
*/
|
||||
class ESLintInvalidOptionsError extends Error {
|
||||
constructor(messages) {
|
||||
super(`Invalid Options:\n- ${messages.join("\n- ")}`);
|
||||
this.code = "ESLINT_INVALID_OPTIONS";
|
||||
Error.captureStackTrace(this, ESLintInvalidOptionsError);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and normalizes options for the wrapped CLIEngine instance.
|
||||
* @param {LegacyESLintOptions} options The options to process.
|
||||
* @throws {ESLintInvalidOptionsError} If of any of a variety of type errors.
|
||||
* @returns {LegacyESLintOptions} The normalized options.
|
||||
*/
|
||||
function processOptions({
|
||||
allowInlineConfig = true, // ← we cannot use `overrideConfig.noInlineConfig` instead because `allowInlineConfig` has side-effect that suppress warnings that show inline configs are ignored.
|
||||
baseConfig = null,
|
||||
cache = false,
|
||||
cacheLocation = ".eslintcache",
|
||||
cacheStrategy = "metadata",
|
||||
cwd = process.cwd(),
|
||||
errorOnUnmatchedPattern = true,
|
||||
extensions = null, // ← should be null by default because if it's an array then it suppresses RFC20 feature.
|
||||
fix = false,
|
||||
fixTypes = null, // ← should be null by default because if it's an array then it suppresses rules that don't have the `meta.type` property.
|
||||
flags, /* eslint-disable-line no-unused-vars -- leaving for compatibility with ESLint#hasFlag */
|
||||
globInputPaths = true,
|
||||
ignore = true,
|
||||
ignorePath = null, // ← should be null by default because if it's a string then it may throw ENOENT.
|
||||
overrideConfig = null,
|
||||
overrideConfigFile = null,
|
||||
plugins = {},
|
||||
reportUnusedDisableDirectives = null, // ← should be null by default because if it's a string then it overrides the 'reportUnusedDisableDirectives' setting in config files. And we cannot use `overrideConfig.reportUnusedDisableDirectives` instead because we cannot configure the `error` severity with that.
|
||||
resolvePluginsRelativeTo = null, // ← should be null by default because if it's a string then it suppresses RFC47 feature.
|
||||
rulePaths = [],
|
||||
useEslintrc = true,
|
||||
passOnNoPatterns = false,
|
||||
...unknownOptions
|
||||
}) {
|
||||
const errors = [];
|
||||
const unknownOptionKeys = Object.keys(unknownOptions);
|
||||
|
||||
if (unknownOptionKeys.length >= 1) {
|
||||
errors.push(`Unknown options: ${unknownOptionKeys.join(", ")}`);
|
||||
if (unknownOptionKeys.includes("cacheFile")) {
|
||||
errors.push("'cacheFile' has been removed. Please use the 'cacheLocation' option instead.");
|
||||
}
|
||||
if (unknownOptionKeys.includes("configFile")) {
|
||||
errors.push("'configFile' has been removed. Please use the 'overrideConfigFile' option instead.");
|
||||
}
|
||||
if (unknownOptionKeys.includes("envs")) {
|
||||
errors.push("'envs' has been removed. Please use the 'overrideConfig.env' option instead.");
|
||||
}
|
||||
if (unknownOptionKeys.includes("globals")) {
|
||||
errors.push("'globals' has been removed. Please use the 'overrideConfig.globals' option instead.");
|
||||
}
|
||||
if (unknownOptionKeys.includes("ignorePattern")) {
|
||||
errors.push("'ignorePattern' has been removed. Please use the 'overrideConfig.ignorePatterns' option instead.");
|
||||
}
|
||||
if (unknownOptionKeys.includes("parser")) {
|
||||
errors.push("'parser' has been removed. Please use the 'overrideConfig.parser' option instead.");
|
||||
}
|
||||
if (unknownOptionKeys.includes("parserOptions")) {
|
||||
errors.push("'parserOptions' has been removed. Please use the 'overrideConfig.parserOptions' option instead.");
|
||||
}
|
||||
if (unknownOptionKeys.includes("rules")) {
|
||||
errors.push("'rules' has been removed. Please use the 'overrideConfig.rules' option instead.");
|
||||
}
|
||||
}
|
||||
if (typeof allowInlineConfig !== "boolean") {
|
||||
errors.push("'allowInlineConfig' must be a boolean.");
|
||||
}
|
||||
if (typeof baseConfig !== "object") {
|
||||
errors.push("'baseConfig' must be an object or null.");
|
||||
}
|
||||
if (typeof cache !== "boolean") {
|
||||
errors.push("'cache' must be a boolean.");
|
||||
}
|
||||
if (!isNonEmptyString(cacheLocation)) {
|
||||
errors.push("'cacheLocation' must be a non-empty string.");
|
||||
}
|
||||
if (
|
||||
cacheStrategy !== "metadata" &&
|
||||
cacheStrategy !== "content"
|
||||
) {
|
||||
errors.push("'cacheStrategy' must be any of \"metadata\", \"content\".");
|
||||
}
|
||||
if (!isNonEmptyString(cwd) || !path.isAbsolute(cwd)) {
|
||||
errors.push("'cwd' must be an absolute path.");
|
||||
}
|
||||
if (typeof errorOnUnmatchedPattern !== "boolean") {
|
||||
errors.push("'errorOnUnmatchedPattern' must be a boolean.");
|
||||
}
|
||||
if (!isEmptyArrayOrArrayOfNonEmptyString(extensions) && extensions !== null) {
|
||||
errors.push("'extensions' must be an array of non-empty strings or null.");
|
||||
}
|
||||
if (typeof fix !== "boolean" && typeof fix !== "function") {
|
||||
errors.push("'fix' must be a boolean or a function.");
|
||||
}
|
||||
if (fixTypes !== null && !isFixTypeArray(fixTypes)) {
|
||||
errors.push("'fixTypes' must be an array of any of \"directive\", \"problem\", \"suggestion\", and \"layout\".");
|
||||
}
|
||||
if (typeof globInputPaths !== "boolean") {
|
||||
errors.push("'globInputPaths' must be a boolean.");
|
||||
}
|
||||
if (typeof ignore !== "boolean") {
|
||||
errors.push("'ignore' must be a boolean.");
|
||||
}
|
||||
if (!isNonEmptyString(ignorePath) && ignorePath !== null) {
|
||||
errors.push("'ignorePath' must be a non-empty string or null.");
|
||||
}
|
||||
if (typeof overrideConfig !== "object") {
|
||||
errors.push("'overrideConfig' must be an object or null.");
|
||||
}
|
||||
if (!isNonEmptyString(overrideConfigFile) && overrideConfigFile !== null) {
|
||||
errors.push("'overrideConfigFile' must be a non-empty string or null.");
|
||||
}
|
||||
if (typeof plugins !== "object") {
|
||||
errors.push("'plugins' must be an object or null.");
|
||||
} else if (plugins !== null && Object.keys(plugins).includes("")) {
|
||||
errors.push("'plugins' must not include an empty string.");
|
||||
}
|
||||
if (Array.isArray(plugins)) {
|
||||
errors.push("'plugins' doesn't add plugins to configuration to load. Please use the 'overrideConfig.plugins' option instead.");
|
||||
}
|
||||
if (
|
||||
reportUnusedDisableDirectives !== "error" &&
|
||||
reportUnusedDisableDirectives !== "warn" &&
|
||||
reportUnusedDisableDirectives !== "off" &&
|
||||
reportUnusedDisableDirectives !== null
|
||||
) {
|
||||
errors.push("'reportUnusedDisableDirectives' must be any of \"error\", \"warn\", \"off\", and null.");
|
||||
}
|
||||
if (
|
||||
!isNonEmptyString(resolvePluginsRelativeTo) &&
|
||||
resolvePluginsRelativeTo !== null
|
||||
) {
|
||||
errors.push("'resolvePluginsRelativeTo' must be a non-empty string or null.");
|
||||
}
|
||||
if (!isEmptyArrayOrArrayOfNonEmptyString(rulePaths)) {
|
||||
errors.push("'rulePaths' must be an array of non-empty strings.");
|
||||
}
|
||||
if (typeof useEslintrc !== "boolean") {
|
||||
errors.push("'useEslintrc' must be a boolean.");
|
||||
}
|
||||
if (typeof passOnNoPatterns !== "boolean") {
|
||||
errors.push("'passOnNoPatterns' must be a boolean.");
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new ESLintInvalidOptionsError(errors);
|
||||
}
|
||||
|
||||
return {
|
||||
allowInlineConfig,
|
||||
baseConfig,
|
||||
cache,
|
||||
cacheLocation,
|
||||
cacheStrategy,
|
||||
configFile: overrideConfigFile,
|
||||
cwd: path.normalize(cwd),
|
||||
errorOnUnmatchedPattern,
|
||||
extensions,
|
||||
fix,
|
||||
fixTypes,
|
||||
flags: [], // LegacyESLint does not support flags, so just ignore them.
|
||||
globInputPaths,
|
||||
ignore,
|
||||
ignorePath,
|
||||
reportUnusedDisableDirectives,
|
||||
resolvePluginsRelativeTo,
|
||||
rulePaths,
|
||||
useEslintrc,
|
||||
passOnNoPatterns
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a value has one or more properties and that value is not undefined.
|
||||
* @param {any} obj The value to check.
|
||||
* @returns {boolean} `true` if `obj` has one or more properties that that value is not undefined.
|
||||
*/
|
||||
function hasDefinedProperty(obj) {
|
||||
if (typeof obj === "object" && obj !== null) {
|
||||
for (const key in obj) {
|
||||
if (typeof obj[key] !== "undefined") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create rulesMeta object.
|
||||
* @param {Map<string,Rule>} rules a map of rules from which to generate the object.
|
||||
* @returns {Object} metadata for all enabled rules.
|
||||
*/
|
||||
function createRulesMeta(rules) {
|
||||
return Array.from(rules).reduce((retVal, [id, rule]) => {
|
||||
retVal[id] = rule.meta;
|
||||
return retVal;
|
||||
}, {});
|
||||
}
|
||||
|
||||
/** @type {WeakMap<ExtractedConfig, DeprecatedRuleInfo[]>} */
|
||||
const usedDeprecatedRulesCache = new WeakMap();
|
||||
|
||||
/**
|
||||
* Create used deprecated rule list.
|
||||
* @param {CLIEngine} cliEngine The CLIEngine instance.
|
||||
* @param {string} maybeFilePath The absolute path to a lint target file or `"<text>"`.
|
||||
* @returns {DeprecatedRuleInfo[]} The used deprecated rule list.
|
||||
*/
|
||||
function getOrFindUsedDeprecatedRules(cliEngine, maybeFilePath) {
|
||||
const {
|
||||
configArrayFactory,
|
||||
options: { cwd }
|
||||
} = getCLIEngineInternalSlots(cliEngine);
|
||||
const filePath = path.isAbsolute(maybeFilePath)
|
||||
? maybeFilePath
|
||||
: path.join(cwd, "__placeholder__.js");
|
||||
const configArray = configArrayFactory.getConfigArrayForFile(filePath);
|
||||
const config = configArray.extractConfig(filePath);
|
||||
|
||||
// Most files use the same config, so cache it.
|
||||
if (!usedDeprecatedRulesCache.has(config)) {
|
||||
const pluginRules = configArray.pluginRules;
|
||||
const retv = [];
|
||||
|
||||
for (const [ruleId, ruleConf] of Object.entries(config.rules)) {
|
||||
if (getRuleSeverity(ruleConf) === 0) {
|
||||
continue;
|
||||
}
|
||||
const rule = pluginRules.get(ruleId) || BuiltinRules.get(ruleId);
|
||||
const meta = rule && rule.meta;
|
||||
|
||||
if (meta && meta.deprecated) {
|
||||
retv.push({ ruleId, replacedBy: meta.replacedBy || [] });
|
||||
}
|
||||
}
|
||||
|
||||
usedDeprecatedRulesCache.set(config, Object.freeze(retv));
|
||||
}
|
||||
|
||||
return usedDeprecatedRulesCache.get(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the linting results generated by a CLIEngine linting report to
|
||||
* match the ESLint class's API.
|
||||
* @param {CLIEngine} cliEngine The CLIEngine instance.
|
||||
* @param {CLIEngineLintReport} report The CLIEngine linting report to process.
|
||||
* @returns {LintResult[]} The processed linting results.
|
||||
*/
|
||||
function processCLIEngineLintReport(cliEngine, { results }) {
|
||||
const descriptor = {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get() {
|
||||
return getOrFindUsedDeprecatedRules(cliEngine, this.filePath);
|
||||
}
|
||||
};
|
||||
|
||||
for (const result of results) {
|
||||
Object.defineProperty(result, "usedDeprecatedRules", descriptor);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* An Array.prototype.sort() compatible compare function to order results by their file path.
|
||||
* @param {LintResult} a The first lint result.
|
||||
* @param {LintResult} b The second lint result.
|
||||
* @returns {number} An integer representing the order in which the two results should occur.
|
||||
*/
|
||||
function compareResultsByFilePath(a, b) {
|
||||
if (a.filePath < b.filePath) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (a.filePath > b.filePath) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main API.
|
||||
*/
|
||||
class LegacyESLint {
|
||||
|
||||
/**
|
||||
* The type of configuration used by this class.
|
||||
* @type {string}
|
||||
*/
|
||||
static configType = "eslintrc";
|
||||
|
||||
/**
|
||||
* Creates a new instance of the main ESLint API.
|
||||
* @param {LegacyESLintOptions} options The options for this instance.
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
const processedOptions = processOptions(options);
|
||||
const cliEngine = new CLIEngine(processedOptions, { preloadedPlugins: options.plugins });
|
||||
const {
|
||||
configArrayFactory,
|
||||
lastConfigArrays
|
||||
} = getCLIEngineInternalSlots(cliEngine);
|
||||
let updated = false;
|
||||
|
||||
/*
|
||||
* Address `overrideConfig` to set override config.
|
||||
* Operate the `configArrayFactory` internal slot directly because this
|
||||
* functionality doesn't exist as the public API of CLIEngine.
|
||||
*/
|
||||
if (hasDefinedProperty(options.overrideConfig)) {
|
||||
configArrayFactory.setOverrideConfig(options.overrideConfig);
|
||||
updated = true;
|
||||
}
|
||||
|
||||
// Update caches.
|
||||
if (updated) {
|
||||
configArrayFactory.clearCache();
|
||||
lastConfigArrays[0] = configArrayFactory.getConfigArrayForFile();
|
||||
}
|
||||
|
||||
// Initialize private properties.
|
||||
privateMembersMap.set(this, {
|
||||
cliEngine,
|
||||
options: processedOptions
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The version text.
|
||||
* @type {string}
|
||||
*/
|
||||
static get version() {
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs fixes from the given results to files.
|
||||
* @param {LintResult[]} results The lint results.
|
||||
* @returns {Promise<void>} Returns a promise that is used to track side effects.
|
||||
*/
|
||||
static async outputFixes(results) {
|
||||
if (!Array.isArray(results)) {
|
||||
throw new Error("'results' must be an array");
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
results
|
||||
.filter(result => {
|
||||
if (typeof result !== "object" || result === null) {
|
||||
throw new Error("'results' must include only objects");
|
||||
}
|
||||
return (
|
||||
typeof result.output === "string" &&
|
||||
path.isAbsolute(result.filePath)
|
||||
);
|
||||
})
|
||||
.map(r => writeFile(r.filePath, r.output))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns results that only contains errors.
|
||||
* @param {LintResult[]} results The results to filter.
|
||||
* @returns {LintResult[]} The filtered results.
|
||||
*/
|
||||
static getErrorResults(results) {
|
||||
return CLIEngine.getErrorResults(results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns meta objects for each rule represented in the lint results.
|
||||
* @param {LintResult[]} results The results to fetch rules meta for.
|
||||
* @returns {Object} A mapping of ruleIds to rule meta objects.
|
||||
*/
|
||||
getRulesMetaForResults(results) {
|
||||
|
||||
const resultRuleIds = new Set();
|
||||
|
||||
// first gather all ruleIds from all results
|
||||
|
||||
for (const result of results) {
|
||||
for (const { ruleId } of result.messages) {
|
||||
resultRuleIds.add(ruleId);
|
||||
}
|
||||
for (const { ruleId } of result.suppressedMessages) {
|
||||
resultRuleIds.add(ruleId);
|
||||
}
|
||||
}
|
||||
|
||||
// create a map of all rules in the results
|
||||
|
||||
const { cliEngine } = privateMembersMap.get(this);
|
||||
const rules = cliEngine.getRules();
|
||||
const resultRules = new Map();
|
||||
|
||||
for (const [ruleId, rule] of rules) {
|
||||
if (resultRuleIds.has(ruleId)) {
|
||||
resultRules.set(ruleId, rule);
|
||||
}
|
||||
}
|
||||
|
||||
return createRulesMeta(resultRules);
|
||||
|
||||
}
|
||||
|
||||
/* eslint-disable no-unused-vars, class-methods-use-this -- leaving for compatibility with ESLint#hasFlag */
|
||||
/**
|
||||
* Indicates if the given feature flag is enabled for this instance. For this
|
||||
* class, this always returns `false` because it does not support feature flags.
|
||||
* @param {string} flag The feature flag to check.
|
||||
* @returns {boolean} Always false.
|
||||
*/
|
||||
hasFlag(flag) {
|
||||
return false;
|
||||
}
|
||||
/* eslint-enable no-unused-vars, class-methods-use-this -- reenable rules for the rest of the file */
|
||||
|
||||
/**
|
||||
* Executes the current configuration on an array of file and directory names.
|
||||
* @param {string[]} patterns An array of file and directory names.
|
||||
* @returns {Promise<LintResult[]>} The results of linting the file patterns given.
|
||||
*/
|
||||
async lintFiles(patterns) {
|
||||
const { cliEngine, options } = privateMembersMap.get(this);
|
||||
|
||||
if (options.passOnNoPatterns && (patterns === "" || (Array.isArray(patterns) && patterns.length === 0))) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!isNonEmptyString(patterns) && !isArrayOfNonEmptyString(patterns)) {
|
||||
throw new Error("'patterns' must be a non-empty string or an array of non-empty strings");
|
||||
}
|
||||
|
||||
return processCLIEngineLintReport(
|
||||
cliEngine,
|
||||
cliEngine.executeOnFiles(patterns)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the current configuration on text.
|
||||
* @param {string} code A string of JavaScript code to lint.
|
||||
* @param {Object} [options] The options.
|
||||
* @param {string} [options.filePath] The path to the file of the source code.
|
||||
* @param {boolean} [options.warnIgnored] When set to true, warn if given filePath is an ignored path.
|
||||
* @returns {Promise<LintResult[]>} The results of linting the string of code given.
|
||||
*/
|
||||
async lintText(code, options = {}) {
|
||||
if (typeof code !== "string") {
|
||||
throw new Error("'code' must be a string");
|
||||
}
|
||||
if (typeof options !== "object") {
|
||||
throw new Error("'options' must be an object, null, or undefined");
|
||||
}
|
||||
const {
|
||||
filePath,
|
||||
warnIgnored = false,
|
||||
...unknownOptions
|
||||
} = options || {};
|
||||
|
||||
const unknownOptionKeys = Object.keys(unknownOptions);
|
||||
|
||||
if (unknownOptionKeys.length > 0) {
|
||||
throw new Error(`'options' must not include the unknown option(s): ${unknownOptionKeys.join(", ")}`);
|
||||
}
|
||||
|
||||
if (filePath !== void 0 && !isNonEmptyString(filePath)) {
|
||||
throw new Error("'options.filePath' must be a non-empty string or undefined");
|
||||
}
|
||||
if (typeof warnIgnored !== "boolean") {
|
||||
throw new Error("'options.warnIgnored' must be a boolean or undefined");
|
||||
}
|
||||
|
||||
const { cliEngine } = privateMembersMap.get(this);
|
||||
|
||||
return processCLIEngineLintReport(
|
||||
cliEngine,
|
||||
cliEngine.executeOnText(code, filePath, warnIgnored)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the formatter representing the given formatter name.
|
||||
* @param {string} [name] The name of the formatter to load.
|
||||
* The following values are allowed:
|
||||
* - `undefined` ... Load `stylish` builtin formatter.
|
||||
* - A builtin formatter name ... Load the builtin formatter.
|
||||
* - A third-party formatter name:
|
||||
* - `foo` → `eslint-formatter-foo`
|
||||
* - `@foo` → `@foo/eslint-formatter`
|
||||
* - `@foo/bar` → `@foo/eslint-formatter-bar`
|
||||
* - A file path ... Load the file.
|
||||
* @returns {Promise<LoadedFormatter>} A promise resolving to the formatter object.
|
||||
* This promise will be rejected if the given formatter was not found or not
|
||||
* a function.
|
||||
*/
|
||||
async loadFormatter(name = "stylish") {
|
||||
if (typeof name !== "string") {
|
||||
throw new Error("'name' must be a string");
|
||||
}
|
||||
|
||||
const { cliEngine, options } = privateMembersMap.get(this);
|
||||
const formatter = cliEngine.getFormatter(name);
|
||||
|
||||
if (typeof formatter !== "function") {
|
||||
throw new Error(`Formatter must be a function, but got a ${typeof formatter}.`);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
/**
|
||||
* The main formatter method.
|
||||
* @param {LintResult[]} results The lint results to format.
|
||||
* @param {ResultsMeta} resultsMeta Warning count and max threshold.
|
||||
* @returns {string | Promise<string>} The formatted lint results.
|
||||
*/
|
||||
format(results, resultsMeta) {
|
||||
let rulesMeta = null;
|
||||
|
||||
results.sort(compareResultsByFilePath);
|
||||
|
||||
return formatter(results, {
|
||||
...resultsMeta,
|
||||
get cwd() {
|
||||
return options.cwd;
|
||||
},
|
||||
get rulesMeta() {
|
||||
if (!rulesMeta) {
|
||||
rulesMeta = createRulesMeta(cliEngine.getRules());
|
||||
}
|
||||
|
||||
return rulesMeta;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a configuration object for the given file based on the CLI options.
|
||||
* This is the same logic used by the ESLint CLI executable to determine
|
||||
* configuration for each file it processes.
|
||||
* @param {string} filePath The path of the file to retrieve a config object for.
|
||||
* @returns {Promise<ConfigData>} A configuration object for the file.
|
||||
*/
|
||||
async calculateConfigForFile(filePath) {
|
||||
if (!isNonEmptyString(filePath)) {
|
||||
throw new Error("'filePath' must be a non-empty string");
|
||||
}
|
||||
const { cliEngine } = privateMembersMap.get(this);
|
||||
|
||||
return cliEngine.getConfigForFile(filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given path is ignored by ESLint.
|
||||
* @param {string} filePath The path of the file to check.
|
||||
* @returns {Promise<boolean>} Whether or not the given path is ignored.
|
||||
*/
|
||||
async isPathIgnored(filePath) {
|
||||
if (!isNonEmptyString(filePath)) {
|
||||
throw new Error("'filePath' must be a non-empty string");
|
||||
}
|
||||
const { cliEngine } = privateMembersMap.get(this);
|
||||
|
||||
return cliEngine.isPathIgnored(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Public Interface
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
LegacyESLint,
|
||||
|
||||
/**
|
||||
* Get the private class members of a given ESLint instance for tests.
|
||||
* @param {ESLint} instance The ESLint instance to get.
|
||||
* @returns {ESLintPrivateMembers} The instance's private class members.
|
||||
*/
|
||||
getESLintPrivateMembers(instance) {
|
||||
return privateMembersMap.get(instance);
|
||||
}
|
||||
};
|
336
node_modules/eslint/lib/languages/js/index.js
generated
vendored
Normal file
336
node_modules/eslint/lib/languages/js/index.js
generated
vendored
Normal file
@ -0,0 +1,336 @@
|
||||
/**
|
||||
* @fileoverview JavaScript Language Object
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const { SourceCode } = require("./source-code");
|
||||
const createDebug = require("debug");
|
||||
const astUtils = require("../../shared/ast-utils");
|
||||
const espree = require("espree");
|
||||
const eslintScope = require("eslint-scope");
|
||||
const evk = require("eslint-visitor-keys");
|
||||
const { validateLanguageOptions } = require("./validate-language-options");
|
||||
const { LATEST_ECMA_VERSION } = require("../../../conf/ecma-version");
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Type Definitions
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/** @typedef {import("@eslint/core").File} File */
|
||||
/** @typedef {import("@eslint/core").Language} Language */
|
||||
/** @typedef {import("@eslint/core").OkParseResult} OkParseResult */
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const debug = createDebug("eslint:languages:js");
|
||||
const DEFAULT_ECMA_VERSION = 5;
|
||||
const parserSymbol = Symbol.for("eslint.RuleTester.parser");
|
||||
|
||||
/**
|
||||
* Analyze scope of the given AST.
|
||||
* @param {ASTNode} ast The `Program` node to analyze.
|
||||
* @param {LanguageOptions} languageOptions The parser options.
|
||||
* @param {Record<string, string[]>} visitorKeys The visitor keys.
|
||||
* @returns {ScopeManager} The analysis result.
|
||||
*/
|
||||
function analyzeScope(ast, languageOptions, visitorKeys) {
|
||||
const parserOptions = languageOptions.parserOptions;
|
||||
const ecmaFeatures = parserOptions.ecmaFeatures || {};
|
||||
const ecmaVersion = languageOptions.ecmaVersion || DEFAULT_ECMA_VERSION;
|
||||
|
||||
return eslintScope.analyze(ast, {
|
||||
ignoreEval: true,
|
||||
nodejsScope: ecmaFeatures.globalReturn,
|
||||
impliedStrict: ecmaFeatures.impliedStrict,
|
||||
ecmaVersion: typeof ecmaVersion === "number" ? ecmaVersion : 6,
|
||||
sourceType: languageOptions.sourceType || "script",
|
||||
childVisitorKeys: visitorKeys || evk.KEYS,
|
||||
fallback: evk.getKeys
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a given object is Espree.
|
||||
* @param {Object} parser The parser to check.
|
||||
* @returns {boolean} True if the parser is Espree or false if not.
|
||||
*/
|
||||
function isEspree(parser) {
|
||||
return !!(parser === espree || parser[parserSymbol] === espree);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize ECMAScript version from the initial config into languageOptions (year)
|
||||
* format.
|
||||
* @param {any} [ecmaVersion] ECMAScript version from the initial config
|
||||
* @returns {number} normalized ECMAScript version
|
||||
*/
|
||||
function normalizeEcmaVersionForLanguageOptions(ecmaVersion) {
|
||||
|
||||
switch (ecmaVersion) {
|
||||
case 3:
|
||||
return 3;
|
||||
|
||||
// void 0 = no ecmaVersion specified so use the default
|
||||
case 5:
|
||||
case void 0:
|
||||
return 5;
|
||||
|
||||
default:
|
||||
if (typeof ecmaVersion === "number") {
|
||||
return ecmaVersion >= 2015 ? ecmaVersion : ecmaVersion + 2009;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We default to the latest supported ecmaVersion for everything else.
|
||||
* Remember, this is for languageOptions.ecmaVersion, which sets the version
|
||||
* that is used for a number of processes inside of ESLint. It's normally
|
||||
* safe to assume people want the latest unless otherwise specified.
|
||||
*/
|
||||
return LATEST_ECMA_VERSION;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Exports
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @type {Language}
|
||||
*/
|
||||
module.exports = {
|
||||
|
||||
fileType: "text",
|
||||
lineStart: 1,
|
||||
columnStart: 0,
|
||||
nodeTypeKey: "type",
|
||||
visitorKeys: evk.KEYS,
|
||||
|
||||
defaultLanguageOptions: {
|
||||
sourceType: "module",
|
||||
ecmaVersion: "latest",
|
||||
parser: espree,
|
||||
parserOptions: {}
|
||||
},
|
||||
|
||||
validateLanguageOptions,
|
||||
|
||||
/**
|
||||
* Normalizes the language options.
|
||||
* @param {Object} languageOptions The language options to normalize.
|
||||
* @returns {Object} The normalized language options.
|
||||
*/
|
||||
normalizeLanguageOptions(languageOptions) {
|
||||
|
||||
languageOptions.ecmaVersion = normalizeEcmaVersionForLanguageOptions(
|
||||
languageOptions.ecmaVersion
|
||||
);
|
||||
|
||||
// Espree expects this information to be passed in
|
||||
if (isEspree(languageOptions.parser)) {
|
||||
const parserOptions = languageOptions.parserOptions;
|
||||
|
||||
if (languageOptions.sourceType) {
|
||||
|
||||
parserOptions.sourceType = languageOptions.sourceType;
|
||||
|
||||
if (
|
||||
parserOptions.sourceType === "module" &&
|
||||
parserOptions.ecmaFeatures &&
|
||||
parserOptions.ecmaFeatures.globalReturn
|
||||
) {
|
||||
parserOptions.ecmaFeatures.globalReturn = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return languageOptions;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines if a given node matches a given selector class.
|
||||
* @param {string} className The class name to check.
|
||||
* @param {ASTNode} node The node to check.
|
||||
* @param {Array<ASTNode>} ancestry The ancestry of the node.
|
||||
* @returns {boolean} True if there's a match, false if not.
|
||||
* @throws {Error} When an unknown class name is passed.
|
||||
*/
|
||||
matchesSelectorClass(className, node, ancestry) {
|
||||
|
||||
/*
|
||||
* Copyright (c) 2013, Joel Feenstra
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the ESQuery nor the names of its contributors may
|
||||
* be used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL JOEL FEENSTRA BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
switch (className.toLowerCase()) {
|
||||
|
||||
case "statement":
|
||||
if (node.type.slice(-9) === "Statement") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// fallthrough: interface Declaration <: Statement { }
|
||||
|
||||
case "declaration":
|
||||
return node.type.slice(-11) === "Declaration";
|
||||
|
||||
case "pattern":
|
||||
if (node.type.slice(-7) === "Pattern") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// fallthrough: interface Expression <: Node, Pattern { }
|
||||
|
||||
case "expression":
|
||||
return node.type.slice(-10) === "Expression" ||
|
||||
node.type.slice(-7) === "Literal" ||
|
||||
(
|
||||
node.type === "Identifier" &&
|
||||
(ancestry.length === 0 || ancestry[0].type !== "MetaProperty")
|
||||
) ||
|
||||
node.type === "MetaProperty";
|
||||
|
||||
case "function":
|
||||
return node.type === "FunctionDeclaration" ||
|
||||
node.type === "FunctionExpression" ||
|
||||
node.type === "ArrowFunctionExpression";
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown class name: ${className}`);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Parses the given file into an AST.
|
||||
* @param {File} file The virtual file to parse.
|
||||
* @param {Object} options Additional options passed from ESLint.
|
||||
* @param {LanguageOptions} options.languageOptions The language options.
|
||||
* @returns {Object} The result of parsing.
|
||||
*/
|
||||
parse(file, { languageOptions }) {
|
||||
|
||||
// Note: BOM already removed
|
||||
const { body: text, path: filePath } = file;
|
||||
const textToParse = text.replace(astUtils.shebangPattern, (match, captured) => `//${captured}`);
|
||||
const { ecmaVersion, sourceType, parser } = languageOptions;
|
||||
const parserOptions = Object.assign(
|
||||
{ ecmaVersion, sourceType },
|
||||
languageOptions.parserOptions,
|
||||
{
|
||||
loc: true,
|
||||
range: true,
|
||||
raw: true,
|
||||
tokens: true,
|
||||
comment: true,
|
||||
eslintVisitorKeys: true,
|
||||
eslintScopeManager: true,
|
||||
filePath
|
||||
}
|
||||
);
|
||||
|
||||
/*
|
||||
* Check for parsing errors first. If there's a parsing error, nothing
|
||||
* else can happen. However, a parsing error does not throw an error
|
||||
* from this method - it's just considered a fatal error message, a
|
||||
* problem that ESLint identified just like any other.
|
||||
*/
|
||||
try {
|
||||
debug("Parsing:", filePath);
|
||||
const parseResult = (typeof parser.parseForESLint === "function")
|
||||
? parser.parseForESLint(textToParse, parserOptions)
|
||||
: { ast: parser.parse(textToParse, parserOptions) };
|
||||
|
||||
debug("Parsing successful:", filePath);
|
||||
|
||||
const {
|
||||
ast,
|
||||
services: parserServices = {},
|
||||
visitorKeys = evk.KEYS,
|
||||
scopeManager
|
||||
} = parseResult;
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
ast,
|
||||
parserServices,
|
||||
visitorKeys,
|
||||
scopeManager
|
||||
};
|
||||
} catch (ex) {
|
||||
|
||||
// If the message includes a leading line number, strip it:
|
||||
const message = ex.message.replace(/^line \d+:/iu, "").trim();
|
||||
|
||||
debug("%s\n%s", message, ex.stack);
|
||||
|
||||
return {
|
||||
ok: false,
|
||||
errors: [{
|
||||
message,
|
||||
line: ex.lineNumber,
|
||||
column: ex.column
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a new `SourceCode` object from the given information.
|
||||
* @param {File} file The virtual file to create a `SourceCode` object from.
|
||||
* @param {OkParseResult} parseResult The result returned from `parse()`.
|
||||
* @param {Object} options Additional options passed from ESLint.
|
||||
* @param {LanguageOptions} options.languageOptions The language options.
|
||||
* @returns {SourceCode} The new `SourceCode` object.
|
||||
*/
|
||||
createSourceCode(file, parseResult, { languageOptions }) {
|
||||
|
||||
const { body: text, path: filePath, bom: hasBOM } = file;
|
||||
const { ast, parserServices, visitorKeys } = parseResult;
|
||||
|
||||
debug("Scope analysis:", filePath);
|
||||
const scopeManager = parseResult.scopeManager || analyzeScope(ast, languageOptions, visitorKeys);
|
||||
|
||||
debug("Scope analysis successful:", filePath);
|
||||
|
||||
return new SourceCode({
|
||||
text,
|
||||
ast,
|
||||
hasBOM,
|
||||
parserServices,
|
||||
scopeManager,
|
||||
visitorKeys
|
||||
});
|
||||
}
|
||||
|
||||
};
|
7
node_modules/eslint/lib/languages/js/source-code/index.js
generated
vendored
Normal file
7
node_modules/eslint/lib/languages/js/source-code/index.js
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
const SourceCode = require("./source-code");
|
||||
|
||||
module.exports = {
|
||||
SourceCode
|
||||
};
|
1202
node_modules/eslint/lib/languages/js/source-code/source-code.js
generated
vendored
Normal file
1202
node_modules/eslint/lib/languages/js/source-code/source-code.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
57
node_modules/eslint/lib/languages/js/source-code/token-store/backward-token-comment-cursor.js
generated
vendored
Normal file
57
node_modules/eslint/lib/languages/js/source-code/token-store/backward-token-comment-cursor.js
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* @fileoverview Define the cursor which iterates tokens and comments in reverse.
|
||||
* @author Toru Nagashima
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const Cursor = require("./cursor");
|
||||
const utils = require("./utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Exports
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* The cursor which iterates tokens and comments in reverse.
|
||||
*/
|
||||
module.exports = class BackwardTokenCommentCursor extends Cursor {
|
||||
|
||||
/**
|
||||
* Initializes this cursor.
|
||||
* @param {Token[]} tokens The array of tokens.
|
||||
* @param {Comment[]} comments The array of comments.
|
||||
* @param {Object} indexMap The map from locations to indices in `tokens`.
|
||||
* @param {number} startLoc The start location of the iteration range.
|
||||
* @param {number} endLoc The end location of the iteration range.
|
||||
*/
|
||||
constructor(tokens, comments, indexMap, startLoc, endLoc) {
|
||||
super();
|
||||
this.tokens = tokens;
|
||||
this.comments = comments;
|
||||
this.tokenIndex = utils.getLastIndex(tokens, indexMap, endLoc);
|
||||
this.commentIndex = utils.search(comments, endLoc) - 1;
|
||||
this.border = startLoc;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
moveNext() {
|
||||
const token = (this.tokenIndex >= 0) ? this.tokens[this.tokenIndex] : null;
|
||||
const comment = (this.commentIndex >= 0) ? this.comments[this.commentIndex] : null;
|
||||
|
||||
if (token && (!comment || token.range[1] > comment.range[1])) {
|
||||
this.current = token;
|
||||
this.tokenIndex -= 1;
|
||||
} else if (comment) {
|
||||
this.current = comment;
|
||||
this.commentIndex -= 1;
|
||||
} else {
|
||||
this.current = null;
|
||||
}
|
||||
|
||||
return Boolean(this.current) && (this.border === -1 || this.current.range[0] >= this.border);
|
||||
}
|
||||
};
|
58
node_modules/eslint/lib/languages/js/source-code/token-store/backward-token-cursor.js
generated
vendored
Normal file
58
node_modules/eslint/lib/languages/js/source-code/token-store/backward-token-cursor.js
generated
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
/**
|
||||
* @fileoverview Define the cursor which iterates tokens only in reverse.
|
||||
* @author Toru Nagashima
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const Cursor = require("./cursor");
|
||||
const { getLastIndex, getFirstIndex } = require("./utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Exports
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* The cursor which iterates tokens only in reverse.
|
||||
*/
|
||||
module.exports = class BackwardTokenCursor extends Cursor {
|
||||
|
||||
/**
|
||||
* Initializes this cursor.
|
||||
* @param {Token[]} tokens The array of tokens.
|
||||
* @param {Comment[]} comments The array of comments.
|
||||
* @param {Object} indexMap The map from locations to indices in `tokens`.
|
||||
* @param {number} startLoc The start location of the iteration range.
|
||||
* @param {number} endLoc The end location of the iteration range.
|
||||
*/
|
||||
constructor(tokens, comments, indexMap, startLoc, endLoc) {
|
||||
super();
|
||||
this.tokens = tokens;
|
||||
this.index = getLastIndex(tokens, indexMap, endLoc);
|
||||
this.indexEnd = getFirstIndex(tokens, indexMap, startLoc);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
moveNext() {
|
||||
if (this.index >= this.indexEnd) {
|
||||
this.current = this.tokens[this.index];
|
||||
this.index -= 1;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* Shorthand for performance.
|
||||
*
|
||||
*/
|
||||
|
||||
/** @inheritdoc */
|
||||
getOneToken() {
|
||||
return (this.index >= this.indexEnd) ? this.tokens[this.index] : null;
|
||||
}
|
||||
};
|
76
node_modules/eslint/lib/languages/js/source-code/token-store/cursor.js
generated
vendored
Normal file
76
node_modules/eslint/lib/languages/js/source-code/token-store/cursor.js
generated
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
/**
|
||||
* @fileoverview Define the abstract class about cursors which iterate tokens.
|
||||
* @author Toru Nagashima
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Exports
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* The abstract class about cursors which iterate tokens.
|
||||
*
|
||||
* This class has 2 abstract methods.
|
||||
*
|
||||
* - `current: Token | Comment | null` ... The current token.
|
||||
* - `moveNext(): boolean` ... Moves this cursor to the next token. If the next token didn't exist, it returns `false`.
|
||||
*
|
||||
* This is similar to ES2015 Iterators.
|
||||
* However, Iterators were slow (at 2017-01), so I created this class as similar to C# IEnumerable.
|
||||
*
|
||||
* There are the following known sub classes.
|
||||
*
|
||||
* - ForwardTokenCursor .......... The cursor which iterates tokens only.
|
||||
* - BackwardTokenCursor ......... The cursor which iterates tokens only in reverse.
|
||||
* - ForwardTokenCommentCursor ... The cursor which iterates tokens and comments.
|
||||
* - BackwardTokenCommentCursor .. The cursor which iterates tokens and comments in reverse.
|
||||
* - DecorativeCursor
|
||||
* - FilterCursor ............ The cursor which ignores the specified tokens.
|
||||
* - SkipCursor .............. The cursor which ignores the first few tokens.
|
||||
* - LimitCursor ............. The cursor which limits the count of tokens.
|
||||
*
|
||||
*/
|
||||
module.exports = class Cursor {
|
||||
|
||||
/**
|
||||
* Initializes this cursor.
|
||||
*/
|
||||
constructor() {
|
||||
this.current = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first token.
|
||||
* This consumes this cursor.
|
||||
* @returns {Token|Comment} The first token or null.
|
||||
*/
|
||||
getOneToken() {
|
||||
return this.moveNext() ? this.current : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first tokens.
|
||||
* This consumes this cursor.
|
||||
* @returns {(Token|Comment)[]} All tokens.
|
||||
*/
|
||||
getAllTokens() {
|
||||
const tokens = [];
|
||||
|
||||
while (this.moveNext()) {
|
||||
tokens.push(this.current);
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves this cursor to the next token.
|
||||
* @returns {boolean} `true` if the next token exists.
|
||||
* @abstract
|
||||
*/
|
||||
/* c8 ignore next */
|
||||
moveNext() { // eslint-disable-line class-methods-use-this -- Unused
|
||||
throw new Error("Not implemented.");
|
||||
}
|
||||
};
|
92
node_modules/eslint/lib/languages/js/source-code/token-store/cursors.js
generated
vendored
Normal file
92
node_modules/eslint/lib/languages/js/source-code/token-store/cursors.js
generated
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
/**
|
||||
* @fileoverview Define 2 token factories; forward and backward.
|
||||
* @author Toru Nagashima
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const BackwardTokenCommentCursor = require("./backward-token-comment-cursor");
|
||||
const BackwardTokenCursor = require("./backward-token-cursor");
|
||||
const FilterCursor = require("./filter-cursor");
|
||||
const ForwardTokenCommentCursor = require("./forward-token-comment-cursor");
|
||||
const ForwardTokenCursor = require("./forward-token-cursor");
|
||||
const LimitCursor = require("./limit-cursor");
|
||||
const SkipCursor = require("./skip-cursor");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* The cursor factory.
|
||||
* @private
|
||||
*/
|
||||
class CursorFactory {
|
||||
|
||||
/**
|
||||
* Initializes this cursor.
|
||||
* @param {Function} TokenCursor The class of the cursor which iterates tokens only.
|
||||
* @param {Function} TokenCommentCursor The class of the cursor which iterates the mix of tokens and comments.
|
||||
*/
|
||||
constructor(TokenCursor, TokenCommentCursor) {
|
||||
this.TokenCursor = TokenCursor;
|
||||
this.TokenCommentCursor = TokenCommentCursor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a base cursor instance that can be decorated by createCursor.
|
||||
* @param {Token[]} tokens The array of tokens.
|
||||
* @param {Comment[]} comments The array of comments.
|
||||
* @param {Object} indexMap The map from locations to indices in `tokens`.
|
||||
* @param {number} startLoc The start location of the iteration range.
|
||||
* @param {number} endLoc The end location of the iteration range.
|
||||
* @param {boolean} includeComments The flag to iterate comments as well.
|
||||
* @returns {Cursor} The created base cursor.
|
||||
*/
|
||||
createBaseCursor(tokens, comments, indexMap, startLoc, endLoc, includeComments) {
|
||||
const Cursor = includeComments ? this.TokenCommentCursor : this.TokenCursor;
|
||||
|
||||
return new Cursor(tokens, comments, indexMap, startLoc, endLoc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a cursor that iterates tokens with normalized options.
|
||||
* @param {Token[]} tokens The array of tokens.
|
||||
* @param {Comment[]} comments The array of comments.
|
||||
* @param {Object} indexMap The map from locations to indices in `tokens`.
|
||||
* @param {number} startLoc The start location of the iteration range.
|
||||
* @param {number} endLoc The end location of the iteration range.
|
||||
* @param {boolean} includeComments The flag to iterate comments as well.
|
||||
* @param {Function|null} filter The predicate function to choose tokens.
|
||||
* @param {number} skip The count of tokens the cursor skips.
|
||||
* @param {number} count The maximum count of tokens the cursor iterates. Zero is no iteration for backward compatibility.
|
||||
* @returns {Cursor} The created cursor.
|
||||
*/
|
||||
createCursor(tokens, comments, indexMap, startLoc, endLoc, includeComments, filter, skip, count) {
|
||||
let cursor = this.createBaseCursor(tokens, comments, indexMap, startLoc, endLoc, includeComments);
|
||||
|
||||
if (filter) {
|
||||
cursor = new FilterCursor(cursor, filter);
|
||||
}
|
||||
if (skip >= 1) {
|
||||
cursor = new SkipCursor(cursor, skip);
|
||||
}
|
||||
if (count >= 0) {
|
||||
cursor = new LimitCursor(cursor, count);
|
||||
}
|
||||
|
||||
return cursor;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Exports
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
forward: new CursorFactory(ForwardTokenCursor, ForwardTokenCommentCursor),
|
||||
backward: new CursorFactory(BackwardTokenCursor, BackwardTokenCommentCursor)
|
||||
};
|
39
node_modules/eslint/lib/languages/js/source-code/token-store/decorative-cursor.js
generated
vendored
Normal file
39
node_modules/eslint/lib/languages/js/source-code/token-store/decorative-cursor.js
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @fileoverview Define the abstract class about cursors which manipulate another cursor.
|
||||
* @author Toru Nagashima
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const Cursor = require("./cursor");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Exports
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* The abstract class about cursors which manipulate another cursor.
|
||||
*/
|
||||
module.exports = class DecorativeCursor extends Cursor {
|
||||
|
||||
/**
|
||||
* Initializes this cursor.
|
||||
* @param {Cursor} cursor The cursor to be decorated.
|
||||
*/
|
||||
constructor(cursor) {
|
||||
super();
|
||||
this.cursor = cursor;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
moveNext() {
|
||||
const retv = this.cursor.moveNext();
|
||||
|
||||
this.current = this.cursor.current;
|
||||
|
||||
return retv;
|
||||
}
|
||||
};
|
43
node_modules/eslint/lib/languages/js/source-code/token-store/filter-cursor.js
generated
vendored
Normal file
43
node_modules/eslint/lib/languages/js/source-code/token-store/filter-cursor.js
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* @fileoverview Define the cursor which ignores specified tokens.
|
||||
* @author Toru Nagashima
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const DecorativeCursor = require("./decorative-cursor");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Exports
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* The decorative cursor which ignores specified tokens.
|
||||
*/
|
||||
module.exports = class FilterCursor extends DecorativeCursor {
|
||||
|
||||
/**
|
||||
* Initializes this cursor.
|
||||
* @param {Cursor} cursor The cursor to be decorated.
|
||||
* @param {Function} predicate The predicate function to decide tokens this cursor iterates.
|
||||
*/
|
||||
constructor(cursor, predicate) {
|
||||
super(cursor);
|
||||
this.predicate = predicate;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
moveNext() {
|
||||
const predicate = this.predicate;
|
||||
|
||||
while (super.moveNext()) {
|
||||
if (predicate(this.current)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
57
node_modules/eslint/lib/languages/js/source-code/token-store/forward-token-comment-cursor.js
generated
vendored
Normal file
57
node_modules/eslint/lib/languages/js/source-code/token-store/forward-token-comment-cursor.js
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* @fileoverview Define the cursor which iterates tokens and comments.
|
||||
* @author Toru Nagashima
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const Cursor = require("./cursor");
|
||||
const { getFirstIndex, search } = require("./utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Exports
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* The cursor which iterates tokens and comments.
|
||||
*/
|
||||
module.exports = class ForwardTokenCommentCursor extends Cursor {
|
||||
|
||||
/**
|
||||
* Initializes this cursor.
|
||||
* @param {Token[]} tokens The array of tokens.
|
||||
* @param {Comment[]} comments The array of comments.
|
||||
* @param {Object} indexMap The map from locations to indices in `tokens`.
|
||||
* @param {number} startLoc The start location of the iteration range.
|
||||
* @param {number} endLoc The end location of the iteration range.
|
||||
*/
|
||||
constructor(tokens, comments, indexMap, startLoc, endLoc) {
|
||||
super();
|
||||
this.tokens = tokens;
|
||||
this.comments = comments;
|
||||
this.tokenIndex = getFirstIndex(tokens, indexMap, startLoc);
|
||||
this.commentIndex = search(comments, startLoc);
|
||||
this.border = endLoc;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
moveNext() {
|
||||
const token = (this.tokenIndex < this.tokens.length) ? this.tokens[this.tokenIndex] : null;
|
||||
const comment = (this.commentIndex < this.comments.length) ? this.comments[this.commentIndex] : null;
|
||||
|
||||
if (token && (!comment || token.range[0] < comment.range[0])) {
|
||||
this.current = token;
|
||||
this.tokenIndex += 1;
|
||||
} else if (comment) {
|
||||
this.current = comment;
|
||||
this.commentIndex += 1;
|
||||
} else {
|
||||
this.current = null;
|
||||
}
|
||||
|
||||
return Boolean(this.current) && (this.border === -1 || this.current.range[1] <= this.border);
|
||||
}
|
||||
};
|
63
node_modules/eslint/lib/languages/js/source-code/token-store/forward-token-cursor.js
generated
vendored
Normal file
63
node_modules/eslint/lib/languages/js/source-code/token-store/forward-token-cursor.js
generated
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* @fileoverview Define the cursor which iterates tokens only.
|
||||
* @author Toru Nagashima
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const Cursor = require("./cursor");
|
||||
const { getFirstIndex, getLastIndex } = require("./utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Exports
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* The cursor which iterates tokens only.
|
||||
*/
|
||||
module.exports = class ForwardTokenCursor extends Cursor {
|
||||
|
||||
/**
|
||||
* Initializes this cursor.
|
||||
* @param {Token[]} tokens The array of tokens.
|
||||
* @param {Comment[]} comments The array of comments.
|
||||
* @param {Object} indexMap The map from locations to indices in `tokens`.
|
||||
* @param {number} startLoc The start location of the iteration range.
|
||||
* @param {number} endLoc The end location of the iteration range.
|
||||
*/
|
||||
constructor(tokens, comments, indexMap, startLoc, endLoc) {
|
||||
super();
|
||||
this.tokens = tokens;
|
||||
this.index = getFirstIndex(tokens, indexMap, startLoc);
|
||||
this.indexEnd = getLastIndex(tokens, indexMap, endLoc);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
moveNext() {
|
||||
if (this.index <= this.indexEnd) {
|
||||
this.current = this.tokens[this.index];
|
||||
this.index += 1;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* Shorthand for performance.
|
||||
*
|
||||
*/
|
||||
|
||||
/** @inheritdoc */
|
||||
getOneToken() {
|
||||
return (this.index <= this.indexEnd) ? this.tokens[this.index] : null;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
getAllTokens() {
|
||||
return this.tokens.slice(this.index, this.indexEnd + 1);
|
||||
}
|
||||
};
|
627
node_modules/eslint/lib/languages/js/source-code/token-store/index.js
generated
vendored
Normal file
627
node_modules/eslint/lib/languages/js/source-code/token-store/index.js
generated
vendored
Normal file
@ -0,0 +1,627 @@
|
||||
/**
|
||||
* @fileoverview Object to handle access and retrieval of tokens.
|
||||
* @author Brandon Mills
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const { isCommentToken } = require("@eslint-community/eslint-utils");
|
||||
const assert = require("../../../../shared/assert");
|
||||
const cursors = require("./cursors");
|
||||
const ForwardTokenCursor = require("./forward-token-cursor");
|
||||
const PaddedTokenCursor = require("./padded-token-cursor");
|
||||
const utils = require("./utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const TOKENS = Symbol("tokens");
|
||||
const COMMENTS = Symbol("comments");
|
||||
const INDEX_MAP = Symbol("indexMap");
|
||||
|
||||
/**
|
||||
* Creates the map from locations to indices in `tokens`.
|
||||
*
|
||||
* The first/last location of tokens is mapped to the index of the token.
|
||||
* The first/last location of comments is mapped to the index of the next token of each comment.
|
||||
* @param {Token[]} tokens The array of tokens.
|
||||
* @param {Comment[]} comments The array of comments.
|
||||
* @returns {Object} The map from locations to indices in `tokens`.
|
||||
* @private
|
||||
*/
|
||||
function createIndexMap(tokens, comments) {
|
||||
const map = Object.create(null);
|
||||
let tokenIndex = 0;
|
||||
let commentIndex = 0;
|
||||
let nextStart;
|
||||
let range;
|
||||
|
||||
while (tokenIndex < tokens.length || commentIndex < comments.length) {
|
||||
nextStart = (commentIndex < comments.length) ? comments[commentIndex].range[0] : Number.MAX_SAFE_INTEGER;
|
||||
while (tokenIndex < tokens.length && (range = tokens[tokenIndex].range)[0] < nextStart) {
|
||||
map[range[0]] = tokenIndex;
|
||||
map[range[1] - 1] = tokenIndex;
|
||||
tokenIndex += 1;
|
||||
}
|
||||
|
||||
nextStart = (tokenIndex < tokens.length) ? tokens[tokenIndex].range[0] : Number.MAX_SAFE_INTEGER;
|
||||
while (commentIndex < comments.length && (range = comments[commentIndex].range)[0] < nextStart) {
|
||||
map[range[0]] = tokenIndex;
|
||||
map[range[1] - 1] = tokenIndex;
|
||||
commentIndex += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the cursor iterates tokens with options.
|
||||
* @param {CursorFactory} factory The cursor factory to initialize cursor.
|
||||
* @param {Token[]} tokens The array of tokens.
|
||||
* @param {Comment[]} comments The array of comments.
|
||||
* @param {Object} indexMap The map from locations to indices in `tokens`.
|
||||
* @param {number} startLoc The start location of the iteration range.
|
||||
* @param {number} endLoc The end location of the iteration range.
|
||||
* @param {number|Function|Object} [opts=0] The option object. If this is a number then it's `opts.skip`. If this is a function then it's `opts.filter`.
|
||||
* @param {boolean} [opts.includeComments=false] The flag to iterate comments as well.
|
||||
* @param {Function|null} [opts.filter=null] The predicate function to choose tokens.
|
||||
* @param {number} [opts.skip=0] The count of tokens the cursor skips.
|
||||
* @returns {Cursor} The created cursor.
|
||||
* @private
|
||||
*/
|
||||
function createCursorWithSkip(factory, tokens, comments, indexMap, startLoc, endLoc, opts) {
|
||||
let includeComments = false;
|
||||
let skip = 0;
|
||||
let filter = null;
|
||||
|
||||
if (typeof opts === "number") {
|
||||
skip = opts | 0;
|
||||
} else if (typeof opts === "function") {
|
||||
filter = opts;
|
||||
} else if (opts) {
|
||||
includeComments = !!opts.includeComments;
|
||||
skip = opts.skip | 0;
|
||||
filter = opts.filter || null;
|
||||
}
|
||||
assert(skip >= 0, "options.skip should be zero or a positive integer.");
|
||||
assert(!filter || typeof filter === "function", "options.filter should be a function.");
|
||||
|
||||
return factory.createCursor(tokens, comments, indexMap, startLoc, endLoc, includeComments, filter, skip, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the cursor iterates tokens with options.
|
||||
* @param {CursorFactory} factory The cursor factory to initialize cursor.
|
||||
* @param {Token[]} tokens The array of tokens.
|
||||
* @param {Comment[]} comments The array of comments.
|
||||
* @param {Object} indexMap The map from locations to indices in `tokens`.
|
||||
* @param {number} startLoc The start location of the iteration range.
|
||||
* @param {number} endLoc The end location of the iteration range.
|
||||
* @param {number|Function|Object} [opts=0] The option object. If this is a number then it's `opts.count`. If this is a function then it's `opts.filter`.
|
||||
* @param {boolean} [opts.includeComments] The flag to iterate comments as well.
|
||||
* @param {Function|null} [opts.filter=null] The predicate function to choose tokens.
|
||||
* @param {number} [opts.count=0] The maximum count of tokens the cursor iterates. Zero is no iteration for backward compatibility.
|
||||
* @returns {Cursor} The created cursor.
|
||||
* @private
|
||||
*/
|
||||
function createCursorWithCount(factory, tokens, comments, indexMap, startLoc, endLoc, opts) {
|
||||
let includeComments = false;
|
||||
let count = 0;
|
||||
let countExists = false;
|
||||
let filter = null;
|
||||
|
||||
if (typeof opts === "number") {
|
||||
count = opts | 0;
|
||||
countExists = true;
|
||||
} else if (typeof opts === "function") {
|
||||
filter = opts;
|
||||
} else if (opts) {
|
||||
includeComments = !!opts.includeComments;
|
||||
count = opts.count | 0;
|
||||
countExists = typeof opts.count === "number";
|
||||
filter = opts.filter || null;
|
||||
}
|
||||
assert(count >= 0, "options.count should be zero or a positive integer.");
|
||||
assert(!filter || typeof filter === "function", "options.filter should be a function.");
|
||||
|
||||
return factory.createCursor(tokens, comments, indexMap, startLoc, endLoc, includeComments, filter, 0, countExists ? count : -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the cursor iterates tokens with options.
|
||||
* This is overload function of the below.
|
||||
* @param {Token[]} tokens The array of tokens.
|
||||
* @param {Comment[]} comments The array of comments.
|
||||
* @param {Object} indexMap The map from locations to indices in `tokens`.
|
||||
* @param {number} startLoc The start location of the iteration range.
|
||||
* @param {number} endLoc The end location of the iteration range.
|
||||
* @param {Function|Object} opts The option object. If this is a function then it's `opts.filter`.
|
||||
* @param {boolean} [opts.includeComments] The flag to iterate comments as well.
|
||||
* @param {Function|null} [opts.filter=null] The predicate function to choose tokens.
|
||||
* @param {number} [opts.count=0] The maximum count of tokens the cursor iterates. Zero is no iteration for backward compatibility.
|
||||
* @returns {Cursor} The created cursor.
|
||||
* @private
|
||||
*/
|
||||
/**
|
||||
* Creates the cursor iterates tokens with options.
|
||||
* @param {Token[]} tokens The array of tokens.
|
||||
* @param {Comment[]} comments The array of comments.
|
||||
* @param {Object} indexMap The map from locations to indices in `tokens`.
|
||||
* @param {number} startLoc The start location of the iteration range.
|
||||
* @param {number} endLoc The end location of the iteration range.
|
||||
* @param {number} [beforeCount=0] The number of tokens before the node to retrieve.
|
||||
* @param {boolean} [afterCount=0] The number of tokens after the node to retrieve.
|
||||
* @returns {Cursor} The created cursor.
|
||||
* @private
|
||||
*/
|
||||
function createCursorWithPadding(tokens, comments, indexMap, startLoc, endLoc, beforeCount, afterCount) {
|
||||
if (typeof beforeCount === "undefined" && typeof afterCount === "undefined") {
|
||||
return new ForwardTokenCursor(tokens, comments, indexMap, startLoc, endLoc);
|
||||
}
|
||||
if (typeof beforeCount === "number" || typeof beforeCount === "undefined") {
|
||||
return new PaddedTokenCursor(tokens, comments, indexMap, startLoc, endLoc, beforeCount | 0, afterCount | 0);
|
||||
}
|
||||
return createCursorWithCount(cursors.forward, tokens, comments, indexMap, startLoc, endLoc, beforeCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets comment tokens that are adjacent to the current cursor position.
|
||||
* @param {Cursor} cursor A cursor instance.
|
||||
* @returns {Array} An array of comment tokens adjacent to the current cursor position.
|
||||
* @private
|
||||
*/
|
||||
function getAdjacentCommentTokensFromCursor(cursor) {
|
||||
const tokens = [];
|
||||
let currentToken = cursor.getOneToken();
|
||||
|
||||
while (currentToken && isCommentToken(currentToken)) {
|
||||
tokens.push(currentToken);
|
||||
currentToken = cursor.getOneToken();
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Exports
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* The token store.
|
||||
*
|
||||
* This class provides methods to get tokens by locations as fast as possible.
|
||||
* The methods are a part of public API, so we should be careful if it changes this class.
|
||||
*
|
||||
* People can get tokens in O(1) by the hash map which is mapping from the location of tokens/comments to tokens.
|
||||
* Also people can get a mix of tokens and comments in O(log k), the k is the number of comments.
|
||||
* Assuming that comments to be much fewer than tokens, this does not make hash map from token's locations to comments to reduce memory cost.
|
||||
* This uses binary-searching instead for comments.
|
||||
*/
|
||||
module.exports = class TokenStore {
|
||||
|
||||
/**
|
||||
* Initializes this token store.
|
||||
* @param {Token[]} tokens The array of tokens.
|
||||
* @param {Comment[]} comments The array of comments.
|
||||
*/
|
||||
constructor(tokens, comments) {
|
||||
this[TOKENS] = tokens;
|
||||
this[COMMENTS] = comments;
|
||||
this[INDEX_MAP] = createIndexMap(tokens, comments);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Gets single token.
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Gets the token starting at the specified index.
|
||||
* @param {number} offset Index of the start of the token's range.
|
||||
* @param {Object} [options=0] The option object.
|
||||
* @param {boolean} [options.includeComments=false] The flag to iterate comments as well.
|
||||
* @returns {Token|null} The token starting at index, or null if no such token.
|
||||
*/
|
||||
getTokenByRangeStart(offset, options) {
|
||||
const includeComments = options && options.includeComments;
|
||||
const token = cursors.forward.createBaseCursor(
|
||||
this[TOKENS],
|
||||
this[COMMENTS],
|
||||
this[INDEX_MAP],
|
||||
offset,
|
||||
-1,
|
||||
includeComments
|
||||
).getOneToken();
|
||||
|
||||
if (token && token.range[0] === offset) {
|
||||
return token;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first token of the given node.
|
||||
* @param {ASTNode} node The AST node.
|
||||
* @param {number|Function|Object} [options=0] The option object. If this is a number then it's `options.skip`. If this is a function then it's `options.filter`.
|
||||
* @param {boolean} [options.includeComments=false] The flag to iterate comments as well.
|
||||
* @param {Function|null} [options.filter=null] The predicate function to choose tokens.
|
||||
* @param {number} [options.skip=0] The count of tokens the cursor skips.
|
||||
* @returns {Token|null} An object representing the token.
|
||||
*/
|
||||
getFirstToken(node, options) {
|
||||
return createCursorWithSkip(
|
||||
cursors.forward,
|
||||
this[TOKENS],
|
||||
this[COMMENTS],
|
||||
this[INDEX_MAP],
|
||||
node.range[0],
|
||||
node.range[1],
|
||||
options
|
||||
).getOneToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last token of the given node.
|
||||
* @param {ASTNode} node The AST node.
|
||||
* @param {number|Function|Object} [options=0] The option object. Same options as getFirstToken()
|
||||
* @returns {Token|null} An object representing the token.
|
||||
*/
|
||||
getLastToken(node, options) {
|
||||
return createCursorWithSkip(
|
||||
cursors.backward,
|
||||
this[TOKENS],
|
||||
this[COMMENTS],
|
||||
this[INDEX_MAP],
|
||||
node.range[0],
|
||||
node.range[1],
|
||||
options
|
||||
).getOneToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the token that precedes a given node or token.
|
||||
* @param {ASTNode|Token|Comment} node The AST node or token.
|
||||
* @param {number|Function|Object} [options=0] The option object. Same options as getFirstToken()
|
||||
* @returns {Token|null} An object representing the token.
|
||||
*/
|
||||
getTokenBefore(node, options) {
|
||||
return createCursorWithSkip(
|
||||
cursors.backward,
|
||||
this[TOKENS],
|
||||
this[COMMENTS],
|
||||
this[INDEX_MAP],
|
||||
-1,
|
||||
node.range[0],
|
||||
options
|
||||
).getOneToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the token that follows a given node or token.
|
||||
* @param {ASTNode|Token|Comment} node The AST node or token.
|
||||
* @param {number|Function|Object} [options=0] The option object. Same options as getFirstToken()
|
||||
* @returns {Token|null} An object representing the token.
|
||||
*/
|
||||
getTokenAfter(node, options) {
|
||||
return createCursorWithSkip(
|
||||
cursors.forward,
|
||||
this[TOKENS],
|
||||
this[COMMENTS],
|
||||
this[INDEX_MAP],
|
||||
node.range[1],
|
||||
-1,
|
||||
options
|
||||
).getOneToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first token between two non-overlapping nodes.
|
||||
* @param {ASTNode|Token|Comment} left Node before the desired token range.
|
||||
* @param {ASTNode|Token|Comment} right Node after the desired token range.
|
||||
* @param {number|Function|Object} [options=0] The option object. Same options as getFirstToken()
|
||||
* @returns {Token|null} An object representing the token.
|
||||
*/
|
||||
getFirstTokenBetween(left, right, options) {
|
||||
return createCursorWithSkip(
|
||||
cursors.forward,
|
||||
this[TOKENS],
|
||||
this[COMMENTS],
|
||||
this[INDEX_MAP],
|
||||
left.range[1],
|
||||
right.range[0],
|
||||
options
|
||||
).getOneToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last token between two non-overlapping nodes.
|
||||
* @param {ASTNode|Token|Comment} left Node before the desired token range.
|
||||
* @param {ASTNode|Token|Comment} right Node after the desired token range.
|
||||
* @param {number|Function|Object} [options=0] The option object. Same options as getFirstToken()
|
||||
* @returns {Token|null} An object representing the token.
|
||||
*/
|
||||
getLastTokenBetween(left, right, options) {
|
||||
return createCursorWithSkip(
|
||||
cursors.backward,
|
||||
this[TOKENS],
|
||||
this[COMMENTS],
|
||||
this[INDEX_MAP],
|
||||
left.range[1],
|
||||
right.range[0],
|
||||
options
|
||||
).getOneToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the token that precedes a given node or token in the token stream.
|
||||
* This is defined for backward compatibility. Use `includeComments` option instead.
|
||||
* TODO: We have a plan to remove this in a future major version.
|
||||
* @param {ASTNode|Token|Comment} node The AST node or token.
|
||||
* @param {number} [skip=0] A number of tokens to skip.
|
||||
* @returns {Token|null} An object representing the token.
|
||||
* @deprecated
|
||||
*/
|
||||
getTokenOrCommentBefore(node, skip) {
|
||||
return this.getTokenBefore(node, { includeComments: true, skip });
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the token that follows a given node or token in the token stream.
|
||||
* This is defined for backward compatibility. Use `includeComments` option instead.
|
||||
* TODO: We have a plan to remove this in a future major version.
|
||||
* @param {ASTNode|Token|Comment} node The AST node or token.
|
||||
* @param {number} [skip=0] A number of tokens to skip.
|
||||
* @returns {Token|null} An object representing the token.
|
||||
* @deprecated
|
||||
*/
|
||||
getTokenOrCommentAfter(node, skip) {
|
||||
return this.getTokenAfter(node, { includeComments: true, skip });
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Gets multiple tokens.
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Gets the first `count` tokens of the given node.
|
||||
* @param {ASTNode} node The AST node.
|
||||
* @param {number|Function|Object} [options=0] The option object. If this is a number then it's `options.count`. If this is a function then it's `options.filter`.
|
||||
* @param {boolean} [options.includeComments=false] The flag to iterate comments as well.
|
||||
* @param {Function|null} [options.filter=null] The predicate function to choose tokens.
|
||||
* @param {number} [options.count=0] The maximum count of tokens the cursor iterates.
|
||||
* @returns {Token[]} Tokens.
|
||||
*/
|
||||
getFirstTokens(node, options) {
|
||||
return createCursorWithCount(
|
||||
cursors.forward,
|
||||
this[TOKENS],
|
||||
this[COMMENTS],
|
||||
this[INDEX_MAP],
|
||||
node.range[0],
|
||||
node.range[1],
|
||||
options
|
||||
).getAllTokens();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last `count` tokens of the given node.
|
||||
* @param {ASTNode} node The AST node.
|
||||
* @param {number|Function|Object} [options=0] The option object. Same options as getFirstTokens()
|
||||
* @returns {Token[]} Tokens.
|
||||
*/
|
||||
getLastTokens(node, options) {
|
||||
return createCursorWithCount(
|
||||
cursors.backward,
|
||||
this[TOKENS],
|
||||
this[COMMENTS],
|
||||
this[INDEX_MAP],
|
||||
node.range[0],
|
||||
node.range[1],
|
||||
options
|
||||
).getAllTokens().reverse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `count` tokens that precedes a given node or token.
|
||||
* @param {ASTNode|Token|Comment} node The AST node or token.
|
||||
* @param {number|Function|Object} [options=0] The option object. Same options as getFirstTokens()
|
||||
* @returns {Token[]} Tokens.
|
||||
*/
|
||||
getTokensBefore(node, options) {
|
||||
return createCursorWithCount(
|
||||
cursors.backward,
|
||||
this[TOKENS],
|
||||
this[COMMENTS],
|
||||
this[INDEX_MAP],
|
||||
-1,
|
||||
node.range[0],
|
||||
options
|
||||
).getAllTokens().reverse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `count` tokens that follows a given node or token.
|
||||
* @param {ASTNode|Token|Comment} node The AST node or token.
|
||||
* @param {number|Function|Object} [options=0] The option object. Same options as getFirstTokens()
|
||||
* @returns {Token[]} Tokens.
|
||||
*/
|
||||
getTokensAfter(node, options) {
|
||||
return createCursorWithCount(
|
||||
cursors.forward,
|
||||
this[TOKENS],
|
||||
this[COMMENTS],
|
||||
this[INDEX_MAP],
|
||||
node.range[1],
|
||||
-1,
|
||||
options
|
||||
).getAllTokens();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first `count` tokens between two non-overlapping nodes.
|
||||
* @param {ASTNode|Token|Comment} left Node before the desired token range.
|
||||
* @param {ASTNode|Token|Comment} right Node after the desired token range.
|
||||
* @param {number|Function|Object} [options=0] The option object. Same options as getFirstTokens()
|
||||
* @returns {Token[]} Tokens between left and right.
|
||||
*/
|
||||
getFirstTokensBetween(left, right, options) {
|
||||
return createCursorWithCount(
|
||||
cursors.forward,
|
||||
this[TOKENS],
|
||||
this[COMMENTS],
|
||||
this[INDEX_MAP],
|
||||
left.range[1],
|
||||
right.range[0],
|
||||
options
|
||||
).getAllTokens();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last `count` tokens between two non-overlapping nodes.
|
||||
* @param {ASTNode|Token|Comment} left Node before the desired token range.
|
||||
* @param {ASTNode|Token|Comment} right Node after the desired token range.
|
||||
* @param {number|Function|Object} [options=0] The option object. Same options as getFirstTokens()
|
||||
* @returns {Token[]} Tokens between left and right.
|
||||
*/
|
||||
getLastTokensBetween(left, right, options) {
|
||||
return createCursorWithCount(
|
||||
cursors.backward,
|
||||
this[TOKENS],
|
||||
this[COMMENTS],
|
||||
this[INDEX_MAP],
|
||||
left.range[1],
|
||||
right.range[0],
|
||||
options
|
||||
).getAllTokens().reverse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all tokens that are related to the given node.
|
||||
* @param {ASTNode} node The AST node.
|
||||
* @param {Function|Object} options The option object. If this is a function then it's `options.filter`.
|
||||
* @param {boolean} [options.includeComments=false] The flag to iterate comments as well.
|
||||
* @param {Function|null} [options.filter=null] The predicate function to choose tokens.
|
||||
* @param {number} [options.count=0] The maximum count of tokens the cursor iterates.
|
||||
* @returns {Token[]} Array of objects representing tokens.
|
||||
*/
|
||||
/**
|
||||
* Gets all tokens that are related to the given node.
|
||||
* @param {ASTNode} node The AST node.
|
||||
* @param {int} [beforeCount=0] The number of tokens before the node to retrieve.
|
||||
* @param {int} [afterCount=0] The number of tokens after the node to retrieve.
|
||||
* @returns {Token[]} Array of objects representing tokens.
|
||||
*/
|
||||
getTokens(node, beforeCount, afterCount) {
|
||||
return createCursorWithPadding(
|
||||
this[TOKENS],
|
||||
this[COMMENTS],
|
||||
this[INDEX_MAP],
|
||||
node.range[0],
|
||||
node.range[1],
|
||||
beforeCount,
|
||||
afterCount
|
||||
).getAllTokens();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all of the tokens between two non-overlapping nodes.
|
||||
* @param {ASTNode|Token|Comment} left Node before the desired token range.
|
||||
* @param {ASTNode|Token|Comment} right Node after the desired token range.
|
||||
* @param {Function|Object} options The option object. If this is a function then it's `options.filter`.
|
||||
* @param {boolean} [options.includeComments=false] The flag to iterate comments as well.
|
||||
* @param {Function|null} [options.filter=null] The predicate function to choose tokens.
|
||||
* @param {number} [options.count=0] The maximum count of tokens the cursor iterates.
|
||||
* @returns {Token[]} Tokens between left and right.
|
||||
*/
|
||||
/**
|
||||
* Gets all of the tokens between two non-overlapping nodes.
|
||||
* @param {ASTNode|Token|Comment} left Node before the desired token range.
|
||||
* @param {ASTNode|Token|Comment} right Node after the desired token range.
|
||||
* @param {int} [padding=0] Number of extra tokens on either side of center.
|
||||
* @returns {Token[]} Tokens between left and right.
|
||||
*/
|
||||
getTokensBetween(left, right, padding) {
|
||||
return createCursorWithPadding(
|
||||
this[TOKENS],
|
||||
this[COMMENTS],
|
||||
this[INDEX_MAP],
|
||||
left.range[1],
|
||||
right.range[0],
|
||||
padding,
|
||||
padding
|
||||
).getAllTokens();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Others.
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Checks whether any comments exist or not between the given 2 nodes.
|
||||
* @param {ASTNode} left The node to check.
|
||||
* @param {ASTNode} right The node to check.
|
||||
* @returns {boolean} `true` if one or more comments exist.
|
||||
*/
|
||||
commentsExistBetween(left, right) {
|
||||
const index = utils.search(this[COMMENTS], left.range[1]);
|
||||
|
||||
return (
|
||||
index < this[COMMENTS].length &&
|
||||
this[COMMENTS][index].range[1] <= right.range[0]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all comment tokens directly before the given node or token.
|
||||
* @param {ASTNode|token} nodeOrToken The AST node or token to check for adjacent comment tokens.
|
||||
* @returns {Array} An array of comments in occurrence order.
|
||||
*/
|
||||
getCommentsBefore(nodeOrToken) {
|
||||
const cursor = createCursorWithCount(
|
||||
cursors.backward,
|
||||
this[TOKENS],
|
||||
this[COMMENTS],
|
||||
this[INDEX_MAP],
|
||||
-1,
|
||||
nodeOrToken.range[0],
|
||||
{ includeComments: true }
|
||||
);
|
||||
|
||||
return getAdjacentCommentTokensFromCursor(cursor).reverse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all comment tokens directly after the given node or token.
|
||||
* @param {ASTNode|token} nodeOrToken The AST node or token to check for adjacent comment tokens.
|
||||
* @returns {Array} An array of comments in occurrence order.
|
||||
*/
|
||||
getCommentsAfter(nodeOrToken) {
|
||||
const cursor = createCursorWithCount(
|
||||
cursors.forward,
|
||||
this[TOKENS],
|
||||
this[COMMENTS],
|
||||
this[INDEX_MAP],
|
||||
nodeOrToken.range[1],
|
||||
-1,
|
||||
{ includeComments: true }
|
||||
);
|
||||
|
||||
return getAdjacentCommentTokensFromCursor(cursor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all comment tokens inside the given node.
|
||||
* @param {ASTNode} node The AST node to get the comments for.
|
||||
* @returns {Array} An array of comments in occurrence order.
|
||||
*/
|
||||
getCommentsInside(node) {
|
||||
return this.getTokens(node, {
|
||||
includeComments: true,
|
||||
filter: isCommentToken
|
||||
});
|
||||
}
|
||||
};
|
40
node_modules/eslint/lib/languages/js/source-code/token-store/limit-cursor.js
generated
vendored
Normal file
40
node_modules/eslint/lib/languages/js/source-code/token-store/limit-cursor.js
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* @fileoverview Define the cursor which limits the number of tokens.
|
||||
* @author Toru Nagashima
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const DecorativeCursor = require("./decorative-cursor");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Exports
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* The decorative cursor which limits the number of tokens.
|
||||
*/
|
||||
module.exports = class LimitCursor extends DecorativeCursor {
|
||||
|
||||
/**
|
||||
* Initializes this cursor.
|
||||
* @param {Cursor} cursor The cursor to be decorated.
|
||||
* @param {number} count The count of tokens this cursor iterates.
|
||||
*/
|
||||
constructor(cursor, count) {
|
||||
super(cursor);
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
moveNext() {
|
||||
if (this.count > 0) {
|
||||
this.count -= 1;
|
||||
return super.moveNext();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
38
node_modules/eslint/lib/languages/js/source-code/token-store/padded-token-cursor.js
generated
vendored
Normal file
38
node_modules/eslint/lib/languages/js/source-code/token-store/padded-token-cursor.js
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @fileoverview Define the cursor which iterates tokens only, with inflated range.
|
||||
* @author Toru Nagashima
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const ForwardTokenCursor = require("./forward-token-cursor");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Exports
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* The cursor which iterates tokens only, with inflated range.
|
||||
* This is for the backward compatibility of padding options.
|
||||
*/
|
||||
module.exports = class PaddedTokenCursor extends ForwardTokenCursor {
|
||||
|
||||
/**
|
||||
* Initializes this cursor.
|
||||
* @param {Token[]} tokens The array of tokens.
|
||||
* @param {Comment[]} comments The array of comments.
|
||||
* @param {Object} indexMap The map from locations to indices in `tokens`.
|
||||
* @param {number} startLoc The start location of the iteration range.
|
||||
* @param {number} endLoc The end location of the iteration range.
|
||||
* @param {number} beforeCount The number of tokens this cursor iterates before start.
|
||||
* @param {number} afterCount The number of tokens this cursor iterates after end.
|
||||
*/
|
||||
constructor(tokens, comments, indexMap, startLoc, endLoc, beforeCount, afterCount) {
|
||||
super(tokens, comments, indexMap, startLoc, endLoc);
|
||||
this.index = Math.max(0, this.index - beforeCount);
|
||||
this.indexEnd = Math.min(tokens.length - 1, this.indexEnd + afterCount);
|
||||
}
|
||||
};
|
42
node_modules/eslint/lib/languages/js/source-code/token-store/skip-cursor.js
generated
vendored
Normal file
42
node_modules/eslint/lib/languages/js/source-code/token-store/skip-cursor.js
generated
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* @fileoverview Define the cursor which ignores the first few tokens.
|
||||
* @author Toru Nagashima
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const DecorativeCursor = require("./decorative-cursor");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Exports
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* The decorative cursor which ignores the first few tokens.
|
||||
*/
|
||||
module.exports = class SkipCursor extends DecorativeCursor {
|
||||
|
||||
/**
|
||||
* Initializes this cursor.
|
||||
* @param {Cursor} cursor The cursor to be decorated.
|
||||
* @param {number} count The count of tokens this cursor skips.
|
||||
*/
|
||||
constructor(cursor, count) {
|
||||
super(cursor);
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
moveNext() {
|
||||
while (this.count > 0) {
|
||||
this.count -= 1;
|
||||
if (!super.moveNext()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return super.moveNext();
|
||||
}
|
||||
};
|
107
node_modules/eslint/lib/languages/js/source-code/token-store/utils.js
generated
vendored
Normal file
107
node_modules/eslint/lib/languages/js/source-code/token-store/utils.js
generated
vendored
Normal file
@ -0,0 +1,107 @@
|
||||
/**
|
||||
* @fileoverview Define utility functions for token store.
|
||||
* @author Toru Nagashima
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Exports
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Finds the index of the first token which is after the given location.
|
||||
* If it was not found, this returns `tokens.length`.
|
||||
* @param {(Token|Comment)[]} tokens It searches the token in this list.
|
||||
* @param {number} location The location to search.
|
||||
* @returns {number} The found index or `tokens.length`.
|
||||
*/
|
||||
exports.search = function search(tokens, location) {
|
||||
for (let minIndex = 0, maxIndex = tokens.length - 1; minIndex <= maxIndex;) {
|
||||
|
||||
/*
|
||||
* Calculate the index in the middle between minIndex and maxIndex.
|
||||
* `| 0` is used to round a fractional value down to the nearest integer: this is similar to
|
||||
* using `Math.trunc()` or `Math.floor()`, but performance tests have shown this method to
|
||||
* be faster.
|
||||
*/
|
||||
const index = (minIndex + maxIndex) / 2 | 0;
|
||||
const token = tokens[index];
|
||||
const tokenStartLocation = token.range[0];
|
||||
|
||||
if (location <= tokenStartLocation) {
|
||||
if (index === minIndex) {
|
||||
return index;
|
||||
}
|
||||
maxIndex = index;
|
||||
} else {
|
||||
minIndex = index + 1;
|
||||
}
|
||||
}
|
||||
return tokens.length;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the index of the `startLoc` in `tokens`.
|
||||
* `startLoc` can be the value of `node.range[1]`, so this checks about `startLoc - 1` as well.
|
||||
* @param {(Token|Comment)[]} tokens The tokens to find an index.
|
||||
* @param {Object} indexMap The map from locations to indices.
|
||||
* @param {number} startLoc The location to get an index.
|
||||
* @returns {number} The index.
|
||||
*/
|
||||
exports.getFirstIndex = function getFirstIndex(tokens, indexMap, startLoc) {
|
||||
if (startLoc in indexMap) {
|
||||
return indexMap[startLoc];
|
||||
}
|
||||
if ((startLoc - 1) in indexMap) {
|
||||
const index = indexMap[startLoc - 1];
|
||||
const token = tokens[index];
|
||||
|
||||
// If the mapped index is out of bounds, the returned cursor index will point after the end of the tokens array.
|
||||
if (!token) {
|
||||
return tokens.length;
|
||||
}
|
||||
|
||||
/*
|
||||
* For the map of "comment's location -> token's index", it points the next token of a comment.
|
||||
* In that case, +1 is unnecessary.
|
||||
*/
|
||||
if (token.range[0] >= startLoc) {
|
||||
return index;
|
||||
}
|
||||
return index + 1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the index of the `endLoc` in `tokens`.
|
||||
* The information of end locations are recorded at `endLoc - 1` in `indexMap`, so this checks about `endLoc - 1` as well.
|
||||
* @param {(Token|Comment)[]} tokens The tokens to find an index.
|
||||
* @param {Object} indexMap The map from locations to indices.
|
||||
* @param {number} endLoc The location to get an index.
|
||||
* @returns {number} The index.
|
||||
*/
|
||||
exports.getLastIndex = function getLastIndex(tokens, indexMap, endLoc) {
|
||||
if (endLoc in indexMap) {
|
||||
return indexMap[endLoc] - 1;
|
||||
}
|
||||
if ((endLoc - 1) in indexMap) {
|
||||
const index = indexMap[endLoc - 1];
|
||||
const token = tokens[index];
|
||||
|
||||
// If the mapped index is out of bounds, the returned cursor index will point before the end of the tokens array.
|
||||
if (!token) {
|
||||
return tokens.length - 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* For the map of "comment's location -> token's index", it points the next token of a comment.
|
||||
* In that case, -1 is necessary.
|
||||
*/
|
||||
if (token.range[1] > endLoc) {
|
||||
return index - 1;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
return tokens.length - 1;
|
||||
};
|
181
node_modules/eslint/lib/languages/js/validate-language-options.js
generated
vendored
Normal file
181
node_modules/eslint/lib/languages/js/validate-language-options.js
generated
vendored
Normal file
@ -0,0 +1,181 @@
|
||||
/**
|
||||
* @fileoverview The schema to validate language options
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Data
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const globalVariablesValues = new Set([
|
||||
true, "true", "writable", "writeable",
|
||||
false, "false", "readonly", "readable", null,
|
||||
"off"
|
||||
]);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Check if a value is a non-null object.
|
||||
* @param {any} value The value to check.
|
||||
* @returns {boolean} `true` if the value is a non-null object.
|
||||
*/
|
||||
function isNonNullObject(value) {
|
||||
return typeof value === "object" && value !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a value is a non-null non-array object.
|
||||
* @param {any} value The value to check.
|
||||
* @returns {boolean} `true` if the value is a non-null non-array object.
|
||||
*/
|
||||
function isNonArrayObject(value) {
|
||||
return isNonNullObject(value) && !Array.isArray(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a value is undefined.
|
||||
* @param {any} value The value to check.
|
||||
* @returns {boolean} `true` if the value is undefined.
|
||||
*/
|
||||
function isUndefined(value) {
|
||||
return typeof value === "undefined";
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Schemas
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Validates the ecmaVersion property.
|
||||
* @param {string|number} ecmaVersion The value to check.
|
||||
* @returns {void}
|
||||
* @throws {TypeError} If the value is invalid.
|
||||
*/
|
||||
function validateEcmaVersion(ecmaVersion) {
|
||||
|
||||
if (isUndefined(ecmaVersion)) {
|
||||
throw new TypeError("Key \"ecmaVersion\": Expected an \"ecmaVersion\" property.");
|
||||
}
|
||||
|
||||
if (typeof ecmaVersion !== "number" && ecmaVersion !== "latest") {
|
||||
throw new TypeError("Key \"ecmaVersion\": Expected a number or \"latest\".");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the sourceType property.
|
||||
* @param {string} sourceType The value to check.
|
||||
* @returns {void}
|
||||
* @throws {TypeError} If the value is invalid.
|
||||
*/
|
||||
function validateSourceType(sourceType) {
|
||||
|
||||
if (typeof sourceType !== "string" || !/^(?:script|module|commonjs)$/u.test(sourceType)) {
|
||||
throw new TypeError("Key \"sourceType\": Expected \"script\", \"module\", or \"commonjs\".");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the globals property.
|
||||
* @param {Object} globals The value to check.
|
||||
* @returns {void}
|
||||
* @throws {TypeError} If the value is invalid.
|
||||
*/
|
||||
function validateGlobals(globals) {
|
||||
|
||||
if (!isNonArrayObject(globals)) {
|
||||
throw new TypeError("Key \"globals\": Expected an object.");
|
||||
}
|
||||
|
||||
for (const key of Object.keys(globals)) {
|
||||
|
||||
// avoid hairy edge case
|
||||
if (key === "__proto__") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key !== key.trim()) {
|
||||
throw new TypeError(`Key "globals": Global "${key}" has leading or trailing whitespace.`);
|
||||
}
|
||||
|
||||
if (!globalVariablesValues.has(globals[key])) {
|
||||
throw new TypeError(`Key "globals": Key "${key}": Expected "readonly", "writable", or "off".`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the parser property.
|
||||
* @param {Object} parser The value to check.
|
||||
* @returns {void}
|
||||
* @throws {TypeError} If the value is invalid.
|
||||
*/
|
||||
function validateParser(parser) {
|
||||
|
||||
if (!parser || typeof parser !== "object" ||
|
||||
(typeof parser.parse !== "function" && typeof parser.parseForESLint !== "function")
|
||||
) {
|
||||
throw new TypeError("Key \"parser\": Expected object with parse() or parseForESLint() method.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the language options.
|
||||
* @param {Object} languageOptions The language options to validate.
|
||||
* @returns {void}
|
||||
* @throws {TypeError} If the language options are invalid.
|
||||
*/
|
||||
function validateLanguageOptions(languageOptions) {
|
||||
|
||||
if (!isNonArrayObject(languageOptions)) {
|
||||
throw new TypeError("Expected an object.");
|
||||
}
|
||||
|
||||
const {
|
||||
ecmaVersion,
|
||||
sourceType,
|
||||
globals,
|
||||
parser,
|
||||
parserOptions,
|
||||
...otherOptions
|
||||
} = languageOptions;
|
||||
|
||||
if ("ecmaVersion" in languageOptions) {
|
||||
validateEcmaVersion(ecmaVersion);
|
||||
}
|
||||
|
||||
if ("sourceType" in languageOptions) {
|
||||
validateSourceType(sourceType);
|
||||
}
|
||||
|
||||
if ("globals" in languageOptions) {
|
||||
validateGlobals(globals);
|
||||
}
|
||||
|
||||
if ("parser" in languageOptions) {
|
||||
validateParser(parser);
|
||||
}
|
||||
|
||||
if ("parserOptions" in languageOptions) {
|
||||
if (!isNonArrayObject(parserOptions)) {
|
||||
throw new TypeError("Key \"parserOptions\": Expected an object.");
|
||||
}
|
||||
}
|
||||
|
||||
const otherOptionKeys = Object.keys(otherOptions);
|
||||
|
||||
if (otherOptionKeys.length > 0) {
|
||||
throw new TypeError(`Unexpected key "${otherOptionKeys[0]}" found.`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = { validateLanguageOptions };
|
502
node_modules/eslint/lib/linter/apply-disable-directives.js
generated
vendored
Normal file
502
node_modules/eslint/lib/linter/apply-disable-directives.js
generated
vendored
Normal file
@ -0,0 +1,502 @@
|
||||
/**
|
||||
* @fileoverview A module that filters reported problems based on `eslint-disable` and `eslint-enable` comments
|
||||
* @author Teddy Katz
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Typedefs
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @typedef {import("../shared/types").LintMessage} LintMessage */
|
||||
/** @typedef {import("@eslint/core").Language} Language */
|
||||
/** @typedef {import("@eslint/core").Position} Position */
|
||||
/** @typedef {import("@eslint/core").RulesConfig} RulesConfig */
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Module Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const escapeRegExp = require("escape-string-regexp");
|
||||
const {
|
||||
Legacy: {
|
||||
ConfigOps
|
||||
}
|
||||
} = require("@eslint/eslintrc/universal");
|
||||
|
||||
/**
|
||||
* Compares the locations of two objects in a source file
|
||||
* @param {Position} itemA The first object
|
||||
* @param {Position} itemB The second object
|
||||
* @returns {number} A value less than 1 if itemA appears before itemB in the source file, greater than 1 if
|
||||
* itemA appears after itemB in the source file, or 0 if itemA and itemB have the same location.
|
||||
*/
|
||||
function compareLocations(itemA, itemB) {
|
||||
return itemA.line - itemB.line || itemA.column - itemB.column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Groups a set of directives into sub-arrays by their parent comment.
|
||||
* @param {Iterable<Directive>} directives Unused directives to be removed.
|
||||
* @returns {Directive[][]} Directives grouped by their parent comment.
|
||||
*/
|
||||
function groupByParentDirective(directives) {
|
||||
const groups = new Map();
|
||||
|
||||
for (const directive of directives) {
|
||||
const { unprocessedDirective: { parentDirective } } = directive;
|
||||
|
||||
if (groups.has(parentDirective)) {
|
||||
groups.get(parentDirective).push(directive);
|
||||
} else {
|
||||
groups.set(parentDirective, [directive]);
|
||||
}
|
||||
}
|
||||
|
||||
return [...groups.values()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates removal details for a set of directives within the same comment.
|
||||
* @param {Directive[]} directives Unused directives to be removed.
|
||||
* @param {{node: Token, value: string}} parentDirective Data about the backing directive.
|
||||
* @param {SourceCode} sourceCode The source code object for the file being linted.
|
||||
* @returns {{ description, fix, unprocessedDirective }[]} Details for later creation of output Problems.
|
||||
*/
|
||||
function createIndividualDirectivesRemoval(directives, parentDirective, sourceCode) {
|
||||
|
||||
/*
|
||||
* Get the list of the rules text without any surrounding whitespace. In order to preserve the original
|
||||
* formatting, we don't want to change that whitespace.
|
||||
*
|
||||
* // eslint-disable-line rule-one , rule-two , rule-three -- comment
|
||||
* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
*/
|
||||
const listText = parentDirective.value.trim();
|
||||
|
||||
// Calculate where it starts in the source code text
|
||||
const listStart = sourceCode.text.indexOf(listText, sourceCode.getRange(parentDirective.node)[0]);
|
||||
|
||||
/*
|
||||
* We can assume that `listText` contains multiple elements.
|
||||
* Otherwise, this function wouldn't be called - if there is
|
||||
* only one rule in the list, then the whole comment must be removed.
|
||||
*/
|
||||
|
||||
return directives.map(directive => {
|
||||
const { ruleId } = directive;
|
||||
|
||||
const regex = new RegExp(String.raw`(?:^|\s*,\s*)(?<quote>['"]?)${escapeRegExp(ruleId)}\k<quote>(?:\s*,\s*|$)`, "u");
|
||||
const match = regex.exec(listText);
|
||||
const matchedText = match[0];
|
||||
const matchStart = listStart + match.index;
|
||||
const matchEnd = matchStart + matchedText.length;
|
||||
|
||||
const firstIndexOfComma = matchedText.indexOf(",");
|
||||
const lastIndexOfComma = matchedText.lastIndexOf(",");
|
||||
|
||||
let removalStart, removalEnd;
|
||||
|
||||
if (firstIndexOfComma !== lastIndexOfComma) {
|
||||
|
||||
/*
|
||||
* Since there are two commas, this must one of the elements in the middle of the list.
|
||||
* Matched range starts where the previous rule name ends, and ends where the next rule name starts.
|
||||
*
|
||||
* // eslint-disable-line rule-one , rule-two , rule-three -- comment
|
||||
* ^^^^^^^^^^^^^^
|
||||
*
|
||||
* We want to remove only the content between the two commas, and also one of the commas.
|
||||
*
|
||||
* // eslint-disable-line rule-one , rule-two , rule-three -- comment
|
||||
* ^^^^^^^^^^^
|
||||
*/
|
||||
removalStart = matchStart + firstIndexOfComma;
|
||||
removalEnd = matchStart + lastIndexOfComma;
|
||||
|
||||
} else {
|
||||
|
||||
/*
|
||||
* This is either the first element or the last element.
|
||||
*
|
||||
* If this is the first element, matched range starts where the first rule name starts
|
||||
* and ends where the second rule name starts. This is exactly the range we want
|
||||
* to remove so that the second rule name will start where the first one was starting
|
||||
* and thus preserve the original formatting.
|
||||
*
|
||||
* // eslint-disable-line rule-one , rule-two , rule-three -- comment
|
||||
* ^^^^^^^^^^^
|
||||
*
|
||||
* Similarly, if this is the last element, we've already matched the range we want to
|
||||
* remove. The previous rule name will end where the last one was ending, relative
|
||||
* to the content on the right side.
|
||||
*
|
||||
* // eslint-disable-line rule-one , rule-two , rule-three -- comment
|
||||
* ^^^^^^^^^^^^^
|
||||
*/
|
||||
removalStart = matchStart;
|
||||
removalEnd = matchEnd;
|
||||
}
|
||||
|
||||
return {
|
||||
description: `'${ruleId}'`,
|
||||
fix: {
|
||||
range: [
|
||||
removalStart,
|
||||
removalEnd
|
||||
],
|
||||
text: ""
|
||||
},
|
||||
unprocessedDirective: directive.unprocessedDirective
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a description of deleting an entire unused disable directive.
|
||||
* @param {Directive[]} directives Unused directives to be removed.
|
||||
* @param {Token} node The backing Comment token.
|
||||
* @param {SourceCode} sourceCode The source code object for the file being linted.
|
||||
* @returns {{ description, fix, unprocessedDirective }} Details for later creation of an output problem.
|
||||
*/
|
||||
function createDirectiveRemoval(directives, node, sourceCode) {
|
||||
const range = sourceCode.getRange(node);
|
||||
const ruleIds = directives.filter(directive => directive.ruleId).map(directive => `'${directive.ruleId}'`);
|
||||
|
||||
return {
|
||||
description: ruleIds.length <= 2
|
||||
? ruleIds.join(" or ")
|
||||
: `${ruleIds.slice(0, ruleIds.length - 1).join(", ")}, or ${ruleIds.at(-1)}`,
|
||||
fix: {
|
||||
range,
|
||||
text: " "
|
||||
},
|
||||
unprocessedDirective: directives[0].unprocessedDirective
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses details from directives to create output Problems.
|
||||
* @param {Iterable<Directive>} allDirectives Unused directives to be removed.
|
||||
* @param {SourceCode} sourceCode The source code object for the file being linted.
|
||||
* @returns {{ description, fix, unprocessedDirective }[]} Details for later creation of output Problems.
|
||||
*/
|
||||
function processUnusedDirectives(allDirectives, sourceCode) {
|
||||
const directiveGroups = groupByParentDirective(allDirectives);
|
||||
|
||||
return directiveGroups.flatMap(
|
||||
directives => {
|
||||
const { parentDirective } = directives[0].unprocessedDirective;
|
||||
const remainingRuleIds = new Set(parentDirective.ruleIds);
|
||||
|
||||
for (const directive of directives) {
|
||||
remainingRuleIds.delete(directive.ruleId);
|
||||
}
|
||||
|
||||
return remainingRuleIds.size
|
||||
? createIndividualDirectivesRemoval(directives, parentDirective, sourceCode)
|
||||
: [createDirectiveRemoval(directives, parentDirective.node, sourceCode)];
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect eslint-enable comments that are removing suppressions by eslint-disable comments.
|
||||
* @param {Directive[]} directives The directives to check.
|
||||
* @returns {Set<Directive>} The used eslint-enable comments
|
||||
*/
|
||||
function collectUsedEnableDirectives(directives) {
|
||||
|
||||
/**
|
||||
* A Map of `eslint-enable` keyed by ruleIds that may be marked as used.
|
||||
* If `eslint-enable` does not have a ruleId, the key will be `null`.
|
||||
* @type {Map<string|null, Directive>}
|
||||
*/
|
||||
const enabledRules = new Map();
|
||||
|
||||
/**
|
||||
* A Set of `eslint-enable` marked as used.
|
||||
* It is also the return value of `collectUsedEnableDirectives` function.
|
||||
* @type {Set<Directive>}
|
||||
*/
|
||||
const usedEnableDirectives = new Set();
|
||||
|
||||
/*
|
||||
* Checks the directives backwards to see if the encountered `eslint-enable` is used by the previous `eslint-disable`,
|
||||
* and if so, stores the `eslint-enable` in `usedEnableDirectives`.
|
||||
*/
|
||||
for (let index = directives.length - 1; index >= 0; index--) {
|
||||
const directive = directives[index];
|
||||
|
||||
if (directive.type === "disable") {
|
||||
if (enabledRules.size === 0) {
|
||||
continue;
|
||||
}
|
||||
if (directive.ruleId === null) {
|
||||
|
||||
// If encounter `eslint-disable` without ruleId,
|
||||
// mark all `eslint-enable` currently held in enabledRules as used.
|
||||
// e.g.
|
||||
// /* eslint-disable */ <- current directive
|
||||
// /* eslint-enable rule-id1 */ <- used
|
||||
// /* eslint-enable rule-id2 */ <- used
|
||||
// /* eslint-enable */ <- used
|
||||
for (const enableDirective of enabledRules.values()) {
|
||||
usedEnableDirectives.add(enableDirective);
|
||||
}
|
||||
enabledRules.clear();
|
||||
} else {
|
||||
const enableDirective = enabledRules.get(directive.ruleId);
|
||||
|
||||
if (enableDirective) {
|
||||
|
||||
// If encounter `eslint-disable` with ruleId, and there is an `eslint-enable` with the same ruleId in enabledRules,
|
||||
// mark `eslint-enable` with ruleId as used.
|
||||
// e.g.
|
||||
// /* eslint-disable rule-id */ <- current directive
|
||||
// /* eslint-enable rule-id */ <- used
|
||||
usedEnableDirectives.add(enableDirective);
|
||||
} else {
|
||||
const enabledDirectiveWithoutRuleId = enabledRules.get(null);
|
||||
|
||||
if (enabledDirectiveWithoutRuleId) {
|
||||
|
||||
// If encounter `eslint-disable` with ruleId, and there is no `eslint-enable` with the same ruleId in enabledRules,
|
||||
// mark `eslint-enable` without ruleId as used.
|
||||
// e.g.
|
||||
// /* eslint-disable rule-id */ <- current directive
|
||||
// /* eslint-enable */ <- used
|
||||
usedEnableDirectives.add(enabledDirectiveWithoutRuleId);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (directive.type === "enable") {
|
||||
if (directive.ruleId === null) {
|
||||
|
||||
// If encounter `eslint-enable` without ruleId, the `eslint-enable` that follows it are unused.
|
||||
// So clear enabledRules.
|
||||
// e.g.
|
||||
// /* eslint-enable */ <- current directive
|
||||
// /* eslint-enable rule-id *// <- unused
|
||||
// /* eslint-enable */ <- unused
|
||||
enabledRules.clear();
|
||||
enabledRules.set(null, directive);
|
||||
} else {
|
||||
enabledRules.set(directive.ruleId, directive);
|
||||
}
|
||||
}
|
||||
}
|
||||
return usedEnableDirectives;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the same as the exported function, except that it
|
||||
* doesn't handle disable-line and disable-next-line directives, and it always reports unused
|
||||
* disable directives.
|
||||
* @param {Object} options options for applying directives. This is the same as the options
|
||||
* for the exported function, except that `reportUnusedDisableDirectives` is not supported
|
||||
* (this function always reports unused disable directives).
|
||||
* @returns {{problems: LintMessage[], unusedDirectives: LintMessage[]}} An object with a list
|
||||
* of problems (including suppressed ones) and unused eslint-disable directives
|
||||
*/
|
||||
function applyDirectives(options) {
|
||||
const problems = [];
|
||||
const usedDisableDirectives = new Set();
|
||||
const { sourceCode } = options;
|
||||
|
||||
for (const problem of options.problems) {
|
||||
let disableDirectivesForProblem = [];
|
||||
let nextDirectiveIndex = 0;
|
||||
|
||||
while (
|
||||
nextDirectiveIndex < options.directives.length &&
|
||||
compareLocations(options.directives[nextDirectiveIndex], problem) <= 0
|
||||
) {
|
||||
const directive = options.directives[nextDirectiveIndex++];
|
||||
|
||||
if (directive.ruleId === null || directive.ruleId === problem.ruleId) {
|
||||
switch (directive.type) {
|
||||
case "disable":
|
||||
disableDirectivesForProblem.push(directive);
|
||||
break;
|
||||
|
||||
case "enable":
|
||||
disableDirectivesForProblem = [];
|
||||
break;
|
||||
|
||||
// no default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (disableDirectivesForProblem.length > 0) {
|
||||
const suppressions = disableDirectivesForProblem.map(directive => ({
|
||||
kind: "directive",
|
||||
justification: directive.unprocessedDirective.justification
|
||||
}));
|
||||
|
||||
if (problem.suppressions) {
|
||||
problem.suppressions = problem.suppressions.concat(suppressions);
|
||||
} else {
|
||||
problem.suppressions = suppressions;
|
||||
usedDisableDirectives.add(disableDirectivesForProblem.at(-1));
|
||||
}
|
||||
}
|
||||
|
||||
problems.push(problem);
|
||||
}
|
||||
|
||||
const unusedDisableDirectivesToReport = options.directives
|
||||
.filter(directive => directive.type === "disable" && !usedDisableDirectives.has(directive) && !options.rulesToIgnore.has(directive.ruleId));
|
||||
|
||||
|
||||
const unusedEnableDirectivesToReport = new Set(
|
||||
options.directives.filter(directive => directive.unprocessedDirective.type === "enable" && !options.rulesToIgnore.has(directive.ruleId))
|
||||
);
|
||||
|
||||
/*
|
||||
* If directives has the eslint-enable directive,
|
||||
* check whether the eslint-enable comment is used.
|
||||
*/
|
||||
if (unusedEnableDirectivesToReport.size > 0) {
|
||||
for (const directive of collectUsedEnableDirectives(options.directives)) {
|
||||
unusedEnableDirectivesToReport.delete(directive);
|
||||
}
|
||||
}
|
||||
|
||||
const processed = processUnusedDirectives(unusedDisableDirectivesToReport, sourceCode)
|
||||
.concat(processUnusedDirectives(unusedEnableDirectivesToReport, sourceCode));
|
||||
const columnOffset = options.language.columnStart === 1 ? 0 : 1;
|
||||
const lineOffset = options.language.lineStart === 1 ? 0 : 1;
|
||||
|
||||
const unusedDirectives = processed
|
||||
.map(({ description, fix, unprocessedDirective }) => {
|
||||
const { parentDirective, type, line, column } = unprocessedDirective;
|
||||
|
||||
let message;
|
||||
|
||||
if (type === "enable") {
|
||||
message = description
|
||||
? `Unused eslint-enable directive (no matching eslint-disable directives were found for ${description}).`
|
||||
: "Unused eslint-enable directive (no matching eslint-disable directives were found).";
|
||||
} else {
|
||||
message = description
|
||||
? `Unused eslint-disable directive (no problems were reported from ${description}).`
|
||||
: "Unused eslint-disable directive (no problems were reported).";
|
||||
}
|
||||
|
||||
const loc = sourceCode.getLoc(parentDirective.node);
|
||||
|
||||
return {
|
||||
ruleId: null,
|
||||
message,
|
||||
line: type === "disable-next-line" ? loc.start.line + lineOffset : line,
|
||||
column: type === "disable-next-line" ? loc.start.column + columnOffset : column,
|
||||
severity: options.reportUnusedDisableDirectives === "warn" ? 1 : 2,
|
||||
nodeType: null,
|
||||
...options.disableFixes ? {} : { fix }
|
||||
};
|
||||
});
|
||||
|
||||
return { problems, unusedDirectives };
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a list of directive comments (i.e. metadata about eslint-disable and eslint-enable comments) and a list
|
||||
* of reported problems, adds the suppression information to the problems.
|
||||
* @param {Object} options Information about directives and problems
|
||||
* @param {Language} options.language The language being linted.
|
||||
* @param {SourceCode} options.sourceCode The source code object for the file being linted.
|
||||
* @param {{
|
||||
* type: ("disable"|"enable"|"disable-line"|"disable-next-line"),
|
||||
* ruleId: (string|null),
|
||||
* line: number,
|
||||
* column: number,
|
||||
* justification: string
|
||||
* }} options.directives Directive comments found in the file, with one-based columns.
|
||||
* Two directive comments can only have the same location if they also have the same type (e.g. a single eslint-disable
|
||||
* comment for two different rules is represented as two directives).
|
||||
* @param {{ruleId: (string|null), line: number, column: number}[]} options.problems
|
||||
* A list of problems reported by rules, sorted by increasing location in the file, with one-based columns.
|
||||
* @param {"off" | "warn" | "error"} options.reportUnusedDisableDirectives If `"warn"` or `"error"`, adds additional problems for unused directives
|
||||
* @param {RulesConfig} options.configuredRules The rules configuration.
|
||||
* @param {Function} options.ruleFilter A predicate function to filter which rules should be executed.
|
||||
* @param {boolean} options.disableFixes If true, it doesn't make `fix` properties.
|
||||
* @returns {{ruleId: (string|null), line: number, column: number, suppressions?: {kind: string, justification: string}}[]}
|
||||
* An object with a list of reported problems, the suppressed of which contain the suppression information.
|
||||
*/
|
||||
module.exports = ({ language, sourceCode, directives, disableFixes, problems, configuredRules, ruleFilter, reportUnusedDisableDirectives = "off" }) => {
|
||||
const blockDirectives = directives
|
||||
.filter(directive => directive.type === "disable" || directive.type === "enable")
|
||||
.map(directive => Object.assign({}, directive, { unprocessedDirective: directive }))
|
||||
.sort(compareLocations);
|
||||
|
||||
const lineDirectives = directives.flatMap(directive => {
|
||||
switch (directive.type) {
|
||||
case "disable":
|
||||
case "enable":
|
||||
return [];
|
||||
|
||||
case "disable-line":
|
||||
return [
|
||||
{ type: "disable", line: directive.line, column: 1, ruleId: directive.ruleId, unprocessedDirective: directive },
|
||||
{ type: "enable", line: directive.line + 1, column: 0, ruleId: directive.ruleId, unprocessedDirective: directive }
|
||||
];
|
||||
|
||||
case "disable-next-line":
|
||||
return [
|
||||
{ type: "disable", line: directive.line + 1, column: 1, ruleId: directive.ruleId, unprocessedDirective: directive },
|
||||
{ type: "enable", line: directive.line + 2, column: 0, ruleId: directive.ruleId, unprocessedDirective: directive }
|
||||
];
|
||||
|
||||
default:
|
||||
throw new TypeError(`Unrecognized directive type '${directive.type}'`);
|
||||
}
|
||||
}).sort(compareLocations);
|
||||
|
||||
// This determines a list of rules that are not being run by the given ruleFilter, if present.
|
||||
const rulesToIgnore = configuredRules && ruleFilter
|
||||
? new Set(Object.keys(configuredRules).filter(ruleId => {
|
||||
const severity = ConfigOps.getRuleSeverity(configuredRules[ruleId]);
|
||||
|
||||
// Ignore for disabled rules.
|
||||
if (severity === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !ruleFilter({ severity, ruleId });
|
||||
}))
|
||||
: new Set();
|
||||
|
||||
// If no ruleId is supplied that means this directive is applied to all rules, so we can't determine if it's unused if any rules are filtered out.
|
||||
if (rulesToIgnore.size > 0) {
|
||||
rulesToIgnore.add(null);
|
||||
}
|
||||
|
||||
const blockDirectivesResult = applyDirectives({
|
||||
language,
|
||||
sourceCode,
|
||||
problems,
|
||||
directives: blockDirectives,
|
||||
disableFixes,
|
||||
reportUnusedDisableDirectives,
|
||||
rulesToIgnore
|
||||
});
|
||||
const lineDirectivesResult = applyDirectives({
|
||||
language,
|
||||
sourceCode,
|
||||
problems: blockDirectivesResult.problems,
|
||||
directives: lineDirectives,
|
||||
disableFixes,
|
||||
reportUnusedDisableDirectives,
|
||||
rulesToIgnore
|
||||
});
|
||||
|
||||
return reportUnusedDisableDirectives !== "off"
|
||||
? lineDirectivesResult.problems
|
||||
.concat(blockDirectivesResult.unusedDirectives)
|
||||
.concat(lineDirectivesResult.unusedDirectives)
|
||||
.sort(compareLocations)
|
||||
: lineDirectivesResult.problems;
|
||||
};
|
851
node_modules/eslint/lib/linter/code-path-analysis/code-path-analyzer.js
generated
vendored
Normal file
851
node_modules/eslint/lib/linter/code-path-analysis/code-path-analyzer.js
generated
vendored
Normal file
@ -0,0 +1,851 @@
|
||||
/**
|
||||
* @fileoverview A class of the code path analyzer.
|
||||
* @author Toru Nagashima
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const assert = require("../../shared/assert"),
|
||||
{ breakableTypePattern } = require("../../shared/ast-utils"),
|
||||
CodePath = require("./code-path"),
|
||||
CodePathSegment = require("./code-path-segment"),
|
||||
IdGenerator = require("./id-generator"),
|
||||
debug = require("./debug-helpers");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Checks whether or not a given node is a `case` node (not `default` node).
|
||||
* @param {ASTNode} node A `SwitchCase` node to check.
|
||||
* @returns {boolean} `true` if the node is a `case` node (not `default` node).
|
||||
*/
|
||||
function isCaseNode(node) {
|
||||
return Boolean(node.test);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given node appears as the value of a PropertyDefinition node.
|
||||
* @param {ASTNode} node THe node to check.
|
||||
* @returns {boolean} `true` if the node is a PropertyDefinition value,
|
||||
* false if not.
|
||||
*/
|
||||
function isPropertyDefinitionValue(node) {
|
||||
const parent = node.parent;
|
||||
|
||||
return parent && parent.type === "PropertyDefinition" && parent.value === node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given logical operator is taken into account for the code
|
||||
* path analysis.
|
||||
* @param {string} operator The operator found in the LogicalExpression node
|
||||
* @returns {boolean} `true` if the operator is "&&" or "||" or "??"
|
||||
*/
|
||||
function isHandledLogicalOperator(operator) {
|
||||
return operator === "&&" || operator === "||" || operator === "??";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given assignment operator is a logical assignment operator.
|
||||
* Logical assignments are taken into account for the code path analysis
|
||||
* because of their short-circuiting semantics.
|
||||
* @param {string} operator The operator found in the AssignmentExpression node
|
||||
* @returns {boolean} `true` if the operator is "&&=" or "||=" or "??="
|
||||
*/
|
||||
function isLogicalAssignmentOperator(operator) {
|
||||
return operator === "&&=" || operator === "||=" || operator === "??=";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the label if the parent node of a given node is a LabeledStatement.
|
||||
* @param {ASTNode} node A node to get.
|
||||
* @returns {string|null} The label or `null`.
|
||||
*/
|
||||
function getLabel(node) {
|
||||
if (node.parent.type === "LabeledStatement") {
|
||||
return node.parent.label.name;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not a given logical expression node goes different path
|
||||
* between the `true` case and the `false` case.
|
||||
* @param {ASTNode} node A node to check.
|
||||
* @returns {boolean} `true` if the node is a test of a choice statement.
|
||||
*/
|
||||
function isForkingByTrueOrFalse(node) {
|
||||
const parent = node.parent;
|
||||
|
||||
switch (parent.type) {
|
||||
case "ConditionalExpression":
|
||||
case "IfStatement":
|
||||
case "WhileStatement":
|
||||
case "DoWhileStatement":
|
||||
case "ForStatement":
|
||||
return parent.test === node;
|
||||
|
||||
case "LogicalExpression":
|
||||
return isHandledLogicalOperator(parent.operator);
|
||||
|
||||
case "AssignmentExpression":
|
||||
return isLogicalAssignmentOperator(parent.operator);
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the boolean value of a given literal node.
|
||||
*
|
||||
* This is used to detect infinity loops (e.g. `while (true) {}`).
|
||||
* Statements preceded by an infinity loop are unreachable if the loop didn't
|
||||
* have any `break` statement.
|
||||
* @param {ASTNode} node A node to get.
|
||||
* @returns {boolean|undefined} a boolean value if the node is a Literal node,
|
||||
* otherwise `undefined`.
|
||||
*/
|
||||
function getBooleanValueIfSimpleConstant(node) {
|
||||
if (node.type === "Literal") {
|
||||
return Boolean(node.value);
|
||||
}
|
||||
return void 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that a given identifier node is a reference or not.
|
||||
*
|
||||
* This is used to detect the first throwable node in a `try` block.
|
||||
* @param {ASTNode} node An Identifier node to check.
|
||||
* @returns {boolean} `true` if the node is a reference.
|
||||
*/
|
||||
function isIdentifierReference(node) {
|
||||
const parent = node.parent;
|
||||
|
||||
switch (parent.type) {
|
||||
case "LabeledStatement":
|
||||
case "BreakStatement":
|
||||
case "ContinueStatement":
|
||||
case "ArrayPattern":
|
||||
case "RestElement":
|
||||
case "ImportSpecifier":
|
||||
case "ImportDefaultSpecifier":
|
||||
case "ImportNamespaceSpecifier":
|
||||
case "CatchClause":
|
||||
return false;
|
||||
|
||||
case "FunctionDeclaration":
|
||||
case "FunctionExpression":
|
||||
case "ArrowFunctionExpression":
|
||||
case "ClassDeclaration":
|
||||
case "ClassExpression":
|
||||
case "VariableDeclarator":
|
||||
return parent.id !== node;
|
||||
|
||||
case "Property":
|
||||
case "PropertyDefinition":
|
||||
case "MethodDefinition":
|
||||
return (
|
||||
parent.key !== node ||
|
||||
parent.computed ||
|
||||
parent.shorthand
|
||||
);
|
||||
|
||||
case "AssignmentPattern":
|
||||
return parent.key !== node;
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current segment with the head segment.
|
||||
* This is similar to local branches and tracking branches of git.
|
||||
*
|
||||
* To separate the current and the head is in order to not make useless segments.
|
||||
*
|
||||
* In this process, both "onCodePathSegmentStart" and "onCodePathSegmentEnd"
|
||||
* events are fired.
|
||||
* @param {CodePathAnalyzer} analyzer The instance.
|
||||
* @param {ASTNode} node The current AST node.
|
||||
* @returns {void}
|
||||
*/
|
||||
function forwardCurrentToHead(analyzer, node) {
|
||||
const codePath = analyzer.codePath;
|
||||
const state = CodePath.getState(codePath);
|
||||
const currentSegments = state.currentSegments;
|
||||
const headSegments = state.headSegments;
|
||||
const end = Math.max(currentSegments.length, headSegments.length);
|
||||
let i, currentSegment, headSegment;
|
||||
|
||||
// Fires leaving events.
|
||||
for (i = 0; i < end; ++i) {
|
||||
currentSegment = currentSegments[i];
|
||||
headSegment = headSegments[i];
|
||||
|
||||
if (currentSegment !== headSegment && currentSegment) {
|
||||
|
||||
const eventName = currentSegment.reachable
|
||||
? "onCodePathSegmentEnd"
|
||||
: "onUnreachableCodePathSegmentEnd";
|
||||
|
||||
debug.dump(`${eventName} ${currentSegment.id}`);
|
||||
|
||||
analyzer.emitter.emit(
|
||||
eventName,
|
||||
currentSegment,
|
||||
node
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Update state.
|
||||
state.currentSegments = headSegments;
|
||||
|
||||
// Fires entering events.
|
||||
for (i = 0; i < end; ++i) {
|
||||
currentSegment = currentSegments[i];
|
||||
headSegment = headSegments[i];
|
||||
|
||||
if (currentSegment !== headSegment && headSegment) {
|
||||
|
||||
const eventName = headSegment.reachable
|
||||
? "onCodePathSegmentStart"
|
||||
: "onUnreachableCodePathSegmentStart";
|
||||
|
||||
debug.dump(`${eventName} ${headSegment.id}`);
|
||||
CodePathSegment.markUsed(headSegment);
|
||||
analyzer.emitter.emit(
|
||||
eventName,
|
||||
headSegment,
|
||||
node
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current segment with empty.
|
||||
* This is called at the last of functions or the program.
|
||||
* @param {CodePathAnalyzer} analyzer The instance.
|
||||
* @param {ASTNode} node The current AST node.
|
||||
* @returns {void}
|
||||
*/
|
||||
function leaveFromCurrentSegment(analyzer, node) {
|
||||
const state = CodePath.getState(analyzer.codePath);
|
||||
const currentSegments = state.currentSegments;
|
||||
|
||||
for (let i = 0; i < currentSegments.length; ++i) {
|
||||
const currentSegment = currentSegments[i];
|
||||
const eventName = currentSegment.reachable
|
||||
? "onCodePathSegmentEnd"
|
||||
: "onUnreachableCodePathSegmentEnd";
|
||||
|
||||
debug.dump(`${eventName} ${currentSegment.id}`);
|
||||
|
||||
analyzer.emitter.emit(
|
||||
eventName,
|
||||
currentSegment,
|
||||
node
|
||||
);
|
||||
}
|
||||
|
||||
state.currentSegments = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the code path due to the position of a given node in the parent node
|
||||
* thereof.
|
||||
*
|
||||
* For example, if the node is `parent.consequent`, this creates a fork from the
|
||||
* current path.
|
||||
* @param {CodePathAnalyzer} analyzer The instance.
|
||||
* @param {ASTNode} node The current AST node.
|
||||
* @returns {void}
|
||||
*/
|
||||
function preprocess(analyzer, node) {
|
||||
const codePath = analyzer.codePath;
|
||||
const state = CodePath.getState(codePath);
|
||||
const parent = node.parent;
|
||||
|
||||
switch (parent.type) {
|
||||
|
||||
// The `arguments.length == 0` case is in `postprocess` function.
|
||||
case "CallExpression":
|
||||
if (parent.optional === true && parent.arguments.length >= 1 && parent.arguments[0] === node) {
|
||||
state.makeOptionalRight();
|
||||
}
|
||||
break;
|
||||
case "MemberExpression":
|
||||
if (parent.optional === true && parent.property === node) {
|
||||
state.makeOptionalRight();
|
||||
}
|
||||
break;
|
||||
|
||||
case "LogicalExpression":
|
||||
if (
|
||||
parent.right === node &&
|
||||
isHandledLogicalOperator(parent.operator)
|
||||
) {
|
||||
state.makeLogicalRight();
|
||||
}
|
||||
break;
|
||||
|
||||
case "AssignmentExpression":
|
||||
if (
|
||||
parent.right === node &&
|
||||
isLogicalAssignmentOperator(parent.operator)
|
||||
) {
|
||||
state.makeLogicalRight();
|
||||
}
|
||||
break;
|
||||
|
||||
case "ConditionalExpression":
|
||||
case "IfStatement":
|
||||
|
||||
/*
|
||||
* Fork if this node is at `consequent`/`alternate`.
|
||||
* `popForkContext()` exists at `IfStatement:exit` and
|
||||
* `ConditionalExpression:exit`.
|
||||
*/
|
||||
if (parent.consequent === node) {
|
||||
state.makeIfConsequent();
|
||||
} else if (parent.alternate === node) {
|
||||
state.makeIfAlternate();
|
||||
}
|
||||
break;
|
||||
|
||||
case "SwitchCase":
|
||||
if (parent.consequent[0] === node) {
|
||||
state.makeSwitchCaseBody(false, !parent.test);
|
||||
}
|
||||
break;
|
||||
|
||||
case "TryStatement":
|
||||
if (parent.handler === node) {
|
||||
state.makeCatchBlock();
|
||||
} else if (parent.finalizer === node) {
|
||||
state.makeFinallyBlock();
|
||||
}
|
||||
break;
|
||||
|
||||
case "WhileStatement":
|
||||
if (parent.test === node) {
|
||||
state.makeWhileTest(getBooleanValueIfSimpleConstant(node));
|
||||
} else {
|
||||
assert(parent.body === node);
|
||||
state.makeWhileBody();
|
||||
}
|
||||
break;
|
||||
|
||||
case "DoWhileStatement":
|
||||
if (parent.body === node) {
|
||||
state.makeDoWhileBody();
|
||||
} else {
|
||||
assert(parent.test === node);
|
||||
state.makeDoWhileTest(getBooleanValueIfSimpleConstant(node));
|
||||
}
|
||||
break;
|
||||
|
||||
case "ForStatement":
|
||||
if (parent.test === node) {
|
||||
state.makeForTest(getBooleanValueIfSimpleConstant(node));
|
||||
} else if (parent.update === node) {
|
||||
state.makeForUpdate();
|
||||
} else if (parent.body === node) {
|
||||
state.makeForBody();
|
||||
}
|
||||
break;
|
||||
|
||||
case "ForInStatement":
|
||||
case "ForOfStatement":
|
||||
if (parent.left === node) {
|
||||
state.makeForInOfLeft();
|
||||
} else if (parent.right === node) {
|
||||
state.makeForInOfRight();
|
||||
} else {
|
||||
assert(parent.body === node);
|
||||
state.makeForInOfBody();
|
||||
}
|
||||
break;
|
||||
|
||||
case "AssignmentPattern":
|
||||
|
||||
/*
|
||||
* Fork if this node is at `right`.
|
||||
* `left` is executed always, so it uses the current path.
|
||||
* `popForkContext()` exists at `AssignmentPattern:exit`.
|
||||
*/
|
||||
if (parent.right === node) {
|
||||
state.pushForkContext();
|
||||
state.forkBypassPath();
|
||||
state.forkPath();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the code path due to the type of a given node in entering.
|
||||
* @param {CodePathAnalyzer} analyzer The instance.
|
||||
* @param {ASTNode} node The current AST node.
|
||||
* @returns {void}
|
||||
*/
|
||||
function processCodePathToEnter(analyzer, node) {
|
||||
let codePath = analyzer.codePath;
|
||||
let state = codePath && CodePath.getState(codePath);
|
||||
const parent = node.parent;
|
||||
|
||||
/**
|
||||
* Creates a new code path and trigger the onCodePathStart event
|
||||
* based on the currently selected node.
|
||||
* @param {string} origin The reason the code path was started.
|
||||
* @returns {void}
|
||||
*/
|
||||
function startCodePath(origin) {
|
||||
if (codePath) {
|
||||
|
||||
// Emits onCodePathSegmentStart events if updated.
|
||||
forwardCurrentToHead(analyzer, node);
|
||||
debug.dumpState(node, state, false);
|
||||
}
|
||||
|
||||
// Create the code path of this scope.
|
||||
codePath = analyzer.codePath = new CodePath({
|
||||
id: analyzer.idGenerator.next(),
|
||||
origin,
|
||||
upper: codePath,
|
||||
onLooped: analyzer.onLooped
|
||||
});
|
||||
state = CodePath.getState(codePath);
|
||||
|
||||
// Emits onCodePathStart events.
|
||||
debug.dump(`onCodePathStart ${codePath.id}`);
|
||||
analyzer.emitter.emit("onCodePathStart", codePath, node);
|
||||
}
|
||||
|
||||
/*
|
||||
* Special case: The right side of class field initializer is considered
|
||||
* to be its own function, so we need to start a new code path in this
|
||||
* case.
|
||||
*/
|
||||
if (isPropertyDefinitionValue(node)) {
|
||||
startCodePath("class-field-initializer");
|
||||
|
||||
/*
|
||||
* Intentional fall through because `node` needs to also be
|
||||
* processed by the code below. For example, if we have:
|
||||
*
|
||||
* class Foo {
|
||||
* a = () => {}
|
||||
* }
|
||||
*
|
||||
* In this case, we also need start a second code path.
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
switch (node.type) {
|
||||
case "Program":
|
||||
startCodePath("program");
|
||||
break;
|
||||
|
||||
case "FunctionDeclaration":
|
||||
case "FunctionExpression":
|
||||
case "ArrowFunctionExpression":
|
||||
startCodePath("function");
|
||||
break;
|
||||
|
||||
case "StaticBlock":
|
||||
startCodePath("class-static-block");
|
||||
break;
|
||||
|
||||
case "ChainExpression":
|
||||
state.pushChainContext();
|
||||
break;
|
||||
case "CallExpression":
|
||||
if (node.optional === true) {
|
||||
state.makeOptionalNode();
|
||||
}
|
||||
break;
|
||||
case "MemberExpression":
|
||||
if (node.optional === true) {
|
||||
state.makeOptionalNode();
|
||||
}
|
||||
break;
|
||||
|
||||
case "LogicalExpression":
|
||||
if (isHandledLogicalOperator(node.operator)) {
|
||||
state.pushChoiceContext(
|
||||
node.operator,
|
||||
isForkingByTrueOrFalse(node)
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case "AssignmentExpression":
|
||||
if (isLogicalAssignmentOperator(node.operator)) {
|
||||
state.pushChoiceContext(
|
||||
node.operator.slice(0, -1), // removes `=` from the end
|
||||
isForkingByTrueOrFalse(node)
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case "ConditionalExpression":
|
||||
case "IfStatement":
|
||||
state.pushChoiceContext("test", false);
|
||||
break;
|
||||
|
||||
case "SwitchStatement":
|
||||
state.pushSwitchContext(
|
||||
node.cases.some(isCaseNode),
|
||||
getLabel(node)
|
||||
);
|
||||
break;
|
||||
|
||||
case "TryStatement":
|
||||
state.pushTryContext(Boolean(node.finalizer));
|
||||
break;
|
||||
|
||||
case "SwitchCase":
|
||||
|
||||
/*
|
||||
* Fork if this node is after the 2st node in `cases`.
|
||||
* It's similar to `else` blocks.
|
||||
* The next `test` node is processed in this path.
|
||||
*/
|
||||
if (parent.discriminant !== node && parent.cases[0] !== node) {
|
||||
state.forkPath();
|
||||
}
|
||||
break;
|
||||
|
||||
case "WhileStatement":
|
||||
case "DoWhileStatement":
|
||||
case "ForStatement":
|
||||
case "ForInStatement":
|
||||
case "ForOfStatement":
|
||||
state.pushLoopContext(node.type, getLabel(node));
|
||||
break;
|
||||
|
||||
case "LabeledStatement":
|
||||
if (!breakableTypePattern.test(node.body.type)) {
|
||||
state.pushBreakContext(false, node.label.name);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Emits onCodePathSegmentStart events if updated.
|
||||
forwardCurrentToHead(analyzer, node);
|
||||
debug.dumpState(node, state, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the code path due to the type of a given node in leaving.
|
||||
* @param {CodePathAnalyzer} analyzer The instance.
|
||||
* @param {ASTNode} node The current AST node.
|
||||
* @returns {void}
|
||||
*/
|
||||
function processCodePathToExit(analyzer, node) {
|
||||
|
||||
const codePath = analyzer.codePath;
|
||||
const state = CodePath.getState(codePath);
|
||||
let dontForward = false;
|
||||
|
||||
switch (node.type) {
|
||||
case "ChainExpression":
|
||||
state.popChainContext();
|
||||
break;
|
||||
|
||||
case "IfStatement":
|
||||
case "ConditionalExpression":
|
||||
state.popChoiceContext();
|
||||
break;
|
||||
|
||||
case "LogicalExpression":
|
||||
if (isHandledLogicalOperator(node.operator)) {
|
||||
state.popChoiceContext();
|
||||
}
|
||||
break;
|
||||
|
||||
case "AssignmentExpression":
|
||||
if (isLogicalAssignmentOperator(node.operator)) {
|
||||
state.popChoiceContext();
|
||||
}
|
||||
break;
|
||||
|
||||
case "SwitchStatement":
|
||||
state.popSwitchContext();
|
||||
break;
|
||||
|
||||
case "SwitchCase":
|
||||
|
||||
/*
|
||||
* This is the same as the process at the 1st `consequent` node in
|
||||
* `preprocess` function.
|
||||
* Must do if this `consequent` is empty.
|
||||
*/
|
||||
if (node.consequent.length === 0) {
|
||||
state.makeSwitchCaseBody(true, !node.test);
|
||||
}
|
||||
if (state.forkContext.reachable) {
|
||||
dontForward = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case "TryStatement":
|
||||
state.popTryContext();
|
||||
break;
|
||||
|
||||
case "BreakStatement":
|
||||
forwardCurrentToHead(analyzer, node);
|
||||
state.makeBreak(node.label && node.label.name);
|
||||
dontForward = true;
|
||||
break;
|
||||
|
||||
case "ContinueStatement":
|
||||
forwardCurrentToHead(analyzer, node);
|
||||
state.makeContinue(node.label && node.label.name);
|
||||
dontForward = true;
|
||||
break;
|
||||
|
||||
case "ReturnStatement":
|
||||
forwardCurrentToHead(analyzer, node);
|
||||
state.makeReturn();
|
||||
dontForward = true;
|
||||
break;
|
||||
|
||||
case "ThrowStatement":
|
||||
forwardCurrentToHead(analyzer, node);
|
||||
state.makeThrow();
|
||||
dontForward = true;
|
||||
break;
|
||||
|
||||
case "Identifier":
|
||||
if (isIdentifierReference(node)) {
|
||||
state.makeFirstThrowablePathInTryBlock();
|
||||
dontForward = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case "CallExpression":
|
||||
case "ImportExpression":
|
||||
case "MemberExpression":
|
||||
case "NewExpression":
|
||||
case "YieldExpression":
|
||||
state.makeFirstThrowablePathInTryBlock();
|
||||
break;
|
||||
|
||||
case "WhileStatement":
|
||||
case "DoWhileStatement":
|
||||
case "ForStatement":
|
||||
case "ForInStatement":
|
||||
case "ForOfStatement":
|
||||
state.popLoopContext();
|
||||
break;
|
||||
|
||||
case "AssignmentPattern":
|
||||
state.popForkContext();
|
||||
break;
|
||||
|
||||
case "LabeledStatement":
|
||||
if (!breakableTypePattern.test(node.body.type)) {
|
||||
state.popBreakContext();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Emits onCodePathSegmentStart events if updated.
|
||||
if (!dontForward) {
|
||||
forwardCurrentToHead(analyzer, node);
|
||||
}
|
||||
debug.dumpState(node, state, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the code path to finalize the current code path.
|
||||
* @param {CodePathAnalyzer} analyzer The instance.
|
||||
* @param {ASTNode} node The current AST node.
|
||||
* @returns {void}
|
||||
*/
|
||||
function postprocess(analyzer, node) {
|
||||
|
||||
/**
|
||||
* Ends the code path for the current node.
|
||||
* @returns {void}
|
||||
*/
|
||||
function endCodePath() {
|
||||
let codePath = analyzer.codePath;
|
||||
|
||||
// Mark the current path as the final node.
|
||||
CodePath.getState(codePath).makeFinal();
|
||||
|
||||
// Emits onCodePathSegmentEnd event of the current segments.
|
||||
leaveFromCurrentSegment(analyzer, node);
|
||||
|
||||
// Emits onCodePathEnd event of this code path.
|
||||
debug.dump(`onCodePathEnd ${codePath.id}`);
|
||||
analyzer.emitter.emit("onCodePathEnd", codePath, node);
|
||||
debug.dumpDot(codePath);
|
||||
|
||||
codePath = analyzer.codePath = analyzer.codePath.upper;
|
||||
if (codePath) {
|
||||
debug.dumpState(node, CodePath.getState(codePath), true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
switch (node.type) {
|
||||
case "Program":
|
||||
case "FunctionDeclaration":
|
||||
case "FunctionExpression":
|
||||
case "ArrowFunctionExpression":
|
||||
case "StaticBlock": {
|
||||
endCodePath();
|
||||
break;
|
||||
}
|
||||
|
||||
// The `arguments.length >= 1` case is in `preprocess` function.
|
||||
case "CallExpression":
|
||||
if (node.optional === true && node.arguments.length === 0) {
|
||||
CodePath.getState(analyzer.codePath).makeOptionalRight();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Special case: The right side of class field initializer is considered
|
||||
* to be its own function, so we need to end a code path in this
|
||||
* case.
|
||||
*
|
||||
* We need to check after the other checks in order to close the
|
||||
* code paths in the correct order for code like this:
|
||||
*
|
||||
*
|
||||
* class Foo {
|
||||
* a = () => {}
|
||||
* }
|
||||
*
|
||||
* In this case, The ArrowFunctionExpression code path is closed first
|
||||
* and then we need to close the code path for the PropertyDefinition
|
||||
* value.
|
||||
*/
|
||||
if (isPropertyDefinitionValue(node)) {
|
||||
endCodePath();
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Public Interface
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* The class to analyze code paths.
|
||||
* This class implements the EventGenerator interface.
|
||||
*/
|
||||
class CodePathAnalyzer {
|
||||
|
||||
/**
|
||||
* @param {EventGenerator} eventGenerator An event generator to wrap.
|
||||
*/
|
||||
constructor(eventGenerator) {
|
||||
this.original = eventGenerator;
|
||||
this.emitter = eventGenerator.emitter;
|
||||
this.codePath = null;
|
||||
this.idGenerator = new IdGenerator("s");
|
||||
this.currentNode = null;
|
||||
this.onLooped = this.onLooped.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the process to enter a given AST node.
|
||||
* This updates state of analysis and calls `enterNode` of the wrapped.
|
||||
* @param {ASTNode} node A node which is entering.
|
||||
* @returns {void}
|
||||
*/
|
||||
enterNode(node) {
|
||||
this.currentNode = node;
|
||||
|
||||
// Updates the code path due to node's position in its parent node.
|
||||
if (node.parent) {
|
||||
preprocess(this, node);
|
||||
}
|
||||
|
||||
/*
|
||||
* Updates the code path.
|
||||
* And emits onCodePathStart/onCodePathSegmentStart events.
|
||||
*/
|
||||
processCodePathToEnter(this, node);
|
||||
|
||||
// Emits node events.
|
||||
this.original.enterNode(node);
|
||||
|
||||
this.currentNode = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the process to leave a given AST node.
|
||||
* This updates state of analysis and calls `leaveNode` of the wrapped.
|
||||
* @param {ASTNode} node A node which is leaving.
|
||||
* @returns {void}
|
||||
*/
|
||||
leaveNode(node) {
|
||||
this.currentNode = node;
|
||||
|
||||
/*
|
||||
* Updates the code path.
|
||||
* And emits onCodePathStart/onCodePathSegmentStart events.
|
||||
*/
|
||||
processCodePathToExit(this, node);
|
||||
|
||||
// Emits node events.
|
||||
this.original.leaveNode(node);
|
||||
|
||||
// Emits the last onCodePathStart/onCodePathSegmentStart events.
|
||||
postprocess(this, node);
|
||||
|
||||
this.currentNode = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called on a code path looped.
|
||||
* Then this raises a looped event.
|
||||
* @param {CodePathSegment} fromSegment A segment of prev.
|
||||
* @param {CodePathSegment} toSegment A segment of next.
|
||||
* @returns {void}
|
||||
*/
|
||||
onLooped(fromSegment, toSegment) {
|
||||
if (fromSegment.reachable && toSegment.reachable) {
|
||||
debug.dump(`onCodePathSegmentLoop ${fromSegment.id} -> ${toSegment.id}`);
|
||||
this.emitter.emit(
|
||||
"onCodePathSegmentLoop",
|
||||
fromSegment,
|
||||
toSegment,
|
||||
this.currentNode
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CodePathAnalyzer;
|
263
node_modules/eslint/lib/linter/code-path-analysis/code-path-segment.js
generated
vendored
Normal file
263
node_modules/eslint/lib/linter/code-path-analysis/code-path-segment.js
generated
vendored
Normal file
@ -0,0 +1,263 @@
|
||||
/**
|
||||
* @fileoverview The CodePathSegment class.
|
||||
* @author Toru Nagashima
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const debug = require("./debug-helpers");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Checks whether or not a given segment is reachable.
|
||||
* @param {CodePathSegment} segment A segment to check.
|
||||
* @returns {boolean} `true` if the segment is reachable.
|
||||
*/
|
||||
function isReachable(segment) {
|
||||
return segment.reachable;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Public Interface
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* A code path segment.
|
||||
*
|
||||
* Each segment is arranged in a series of linked lists (implemented by arrays)
|
||||
* that keep track of the previous and next segments in a code path. In this way,
|
||||
* you can navigate between all segments in any code path so long as you have a
|
||||
* reference to any segment in that code path.
|
||||
*
|
||||
* When first created, the segment is in a detached state, meaning that it knows the
|
||||
* segments that came before it but those segments don't know that this new segment
|
||||
* follows it. Only when `CodePathSegment#markUsed()` is called on a segment does it
|
||||
* officially become part of the code path by updating the previous segments to know
|
||||
* that this new segment follows.
|
||||
*/
|
||||
class CodePathSegment {
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {string} id An identifier.
|
||||
* @param {CodePathSegment[]} allPrevSegments An array of the previous segments.
|
||||
* This array includes unreachable segments.
|
||||
* @param {boolean} reachable A flag which shows this is reachable.
|
||||
*/
|
||||
constructor(id, allPrevSegments, reachable) {
|
||||
|
||||
/**
|
||||
* The identifier of this code path.
|
||||
* Rules use it to store additional information of each rule.
|
||||
* @type {string}
|
||||
*/
|
||||
this.id = id;
|
||||
|
||||
/**
|
||||
* An array of the next reachable segments.
|
||||
* @type {CodePathSegment[]}
|
||||
*/
|
||||
this.nextSegments = [];
|
||||
|
||||
/**
|
||||
* An array of the previous reachable segments.
|
||||
* @type {CodePathSegment[]}
|
||||
*/
|
||||
this.prevSegments = allPrevSegments.filter(isReachable);
|
||||
|
||||
/**
|
||||
* An array of all next segments including reachable and unreachable.
|
||||
* @type {CodePathSegment[]}
|
||||
*/
|
||||
this.allNextSegments = [];
|
||||
|
||||
/**
|
||||
* An array of all previous segments including reachable and unreachable.
|
||||
* @type {CodePathSegment[]}
|
||||
*/
|
||||
this.allPrevSegments = allPrevSegments;
|
||||
|
||||
/**
|
||||
* A flag which shows this is reachable.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.reachable = reachable;
|
||||
|
||||
// Internal data.
|
||||
Object.defineProperty(this, "internal", {
|
||||
value: {
|
||||
|
||||
// determines if the segment has been attached to the code path
|
||||
used: false,
|
||||
|
||||
// array of previous segments coming from the end of a loop
|
||||
loopedPrevSegments: []
|
||||
}
|
||||
});
|
||||
|
||||
/* c8 ignore start */
|
||||
if (debug.enabled) {
|
||||
this.internal.nodes = [];
|
||||
}/* c8 ignore stop */
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a given previous segment is coming from the end of a loop.
|
||||
* @param {CodePathSegment} segment A previous segment to check.
|
||||
* @returns {boolean} `true` if the segment is coming from the end of a loop.
|
||||
*/
|
||||
isLoopedPrevSegment(segment) {
|
||||
return this.internal.loopedPrevSegments.includes(segment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the root segment.
|
||||
* @param {string} id An identifier.
|
||||
* @returns {CodePathSegment} The created segment.
|
||||
*/
|
||||
static newRoot(id) {
|
||||
return new CodePathSegment(id, [], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new segment and appends it after the given segments.
|
||||
* @param {string} id An identifier.
|
||||
* @param {CodePathSegment[]} allPrevSegments An array of the previous segments
|
||||
* to append to.
|
||||
* @returns {CodePathSegment} The created segment.
|
||||
*/
|
||||
static newNext(id, allPrevSegments) {
|
||||
return new CodePathSegment(
|
||||
id,
|
||||
CodePathSegment.flattenUnusedSegments(allPrevSegments),
|
||||
allPrevSegments.some(isReachable)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an unreachable segment and appends it after the given segments.
|
||||
* @param {string} id An identifier.
|
||||
* @param {CodePathSegment[]} allPrevSegments An array of the previous segments.
|
||||
* @returns {CodePathSegment} The created segment.
|
||||
*/
|
||||
static newUnreachable(id, allPrevSegments) {
|
||||
const segment = new CodePathSegment(id, CodePathSegment.flattenUnusedSegments(allPrevSegments), false);
|
||||
|
||||
/*
|
||||
* In `if (a) return a; foo();` case, the unreachable segment preceded by
|
||||
* the return statement is not used but must not be removed.
|
||||
*/
|
||||
CodePathSegment.markUsed(segment);
|
||||
|
||||
return segment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a segment that follows given segments.
|
||||
* This factory method does not connect with `allPrevSegments`.
|
||||
* But this inherits `reachable` flag.
|
||||
* @param {string} id An identifier.
|
||||
* @param {CodePathSegment[]} allPrevSegments An array of the previous segments.
|
||||
* @returns {CodePathSegment} The created segment.
|
||||
*/
|
||||
static newDisconnected(id, allPrevSegments) {
|
||||
return new CodePathSegment(id, [], allPrevSegments.some(isReachable));
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a given segment as used.
|
||||
*
|
||||
* And this function registers the segment into the previous segments as a next.
|
||||
* @param {CodePathSegment} segment A segment to mark.
|
||||
* @returns {void}
|
||||
*/
|
||||
static markUsed(segment) {
|
||||
if (segment.internal.used) {
|
||||
return;
|
||||
}
|
||||
segment.internal.used = true;
|
||||
|
||||
let i;
|
||||
|
||||
if (segment.reachable) {
|
||||
|
||||
/*
|
||||
* If the segment is reachable, then it's officially part of the
|
||||
* code path. This loops through all previous segments to update
|
||||
* their list of next segments. Because the segment is reachable,
|
||||
* it's added to both `nextSegments` and `allNextSegments`.
|
||||
*/
|
||||
for (i = 0; i < segment.allPrevSegments.length; ++i) {
|
||||
const prevSegment = segment.allPrevSegments[i];
|
||||
|
||||
prevSegment.allNextSegments.push(segment);
|
||||
prevSegment.nextSegments.push(segment);
|
||||
}
|
||||
} else {
|
||||
|
||||
/*
|
||||
* If the segment is not reachable, then it's not officially part of the
|
||||
* code path. This loops through all previous segments to update
|
||||
* their list of next segments. Because the segment is not reachable,
|
||||
* it's added only to `allNextSegments`.
|
||||
*/
|
||||
for (i = 0; i < segment.allPrevSegments.length; ++i) {
|
||||
segment.allPrevSegments[i].allNextSegments.push(segment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a previous segment as looped.
|
||||
* @param {CodePathSegment} segment A segment.
|
||||
* @param {CodePathSegment} prevSegment A previous segment to mark.
|
||||
* @returns {void}
|
||||
*/
|
||||
static markPrevSegmentAsLooped(segment, prevSegment) {
|
||||
segment.internal.loopedPrevSegments.push(prevSegment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new array based on an array of segments. If any segment in the
|
||||
* array is unused, then it is replaced by all of its previous segments.
|
||||
* All used segments are returned as-is without replacement.
|
||||
* @param {CodePathSegment[]} segments The array of segments to flatten.
|
||||
* @returns {CodePathSegment[]} The flattened array.
|
||||
*/
|
||||
static flattenUnusedSegments(segments) {
|
||||
const done = new Set();
|
||||
|
||||
for (let i = 0; i < segments.length; ++i) {
|
||||
const segment = segments[i];
|
||||
|
||||
// Ignores duplicated.
|
||||
if (done.has(segment)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Use previous segments if unused.
|
||||
if (!segment.internal.used) {
|
||||
for (let j = 0; j < segment.allPrevSegments.length; ++j) {
|
||||
const prevSegment = segment.allPrevSegments[j];
|
||||
|
||||
if (!done.has(prevSegment)) {
|
||||
done.add(prevSegment);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
done.add(segment);
|
||||
}
|
||||
}
|
||||
|
||||
return [...done];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CodePathSegment;
|
2348
node_modules/eslint/lib/linter/code-path-analysis/code-path-state.js
generated
vendored
Normal file
2348
node_modules/eslint/lib/linter/code-path-analysis/code-path-state.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
344
node_modules/eslint/lib/linter/code-path-analysis/code-path.js
generated
vendored
Normal file
344
node_modules/eslint/lib/linter/code-path-analysis/code-path.js
generated
vendored
Normal file
@ -0,0 +1,344 @@
|
||||
/**
|
||||
* @fileoverview A class of the code path.
|
||||
* @author Toru Nagashima
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const CodePathState = require("./code-path-state");
|
||||
const IdGenerator = require("./id-generator");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Public Interface
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* A code path.
|
||||
*/
|
||||
class CodePath {
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {Object} options Options for the function (see below).
|
||||
* @param {string} options.id An identifier.
|
||||
* @param {string} options.origin The type of code path origin.
|
||||
* @param {CodePath|null} options.upper The code path of the upper function scope.
|
||||
* @param {Function} options.onLooped A callback function to notify looping.
|
||||
*/
|
||||
constructor({ id, origin, upper, onLooped }) {
|
||||
|
||||
/**
|
||||
* The identifier of this code path.
|
||||
* Rules use it to store additional information of each rule.
|
||||
* @type {string}
|
||||
*/
|
||||
this.id = id;
|
||||
|
||||
/**
|
||||
* The reason that this code path was started. May be "program",
|
||||
* "function", "class-field-initializer", or "class-static-block".
|
||||
* @type {string}
|
||||
*/
|
||||
this.origin = origin;
|
||||
|
||||
/**
|
||||
* The code path of the upper function scope.
|
||||
* @type {CodePath|null}
|
||||
*/
|
||||
this.upper = upper;
|
||||
|
||||
/**
|
||||
* The code paths of nested function scopes.
|
||||
* @type {CodePath[]}
|
||||
*/
|
||||
this.childCodePaths = [];
|
||||
|
||||
// Initializes internal state.
|
||||
Object.defineProperty(
|
||||
this,
|
||||
"internal",
|
||||
{ value: new CodePathState(new IdGenerator(`${id}_`), onLooped) }
|
||||
);
|
||||
|
||||
// Adds this into `childCodePaths` of `upper`.
|
||||
if (upper) {
|
||||
upper.childCodePaths.push(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the state of a given code path.
|
||||
* @param {CodePath} codePath A code path to get.
|
||||
* @returns {CodePathState} The state of the code path.
|
||||
*/
|
||||
static getState(codePath) {
|
||||
return codePath.internal;
|
||||
}
|
||||
|
||||
/**
|
||||
* The initial code path segment. This is the segment that is at the head
|
||||
* of the code path.
|
||||
* This is a passthrough to the underlying `CodePathState`.
|
||||
* @type {CodePathSegment}
|
||||
*/
|
||||
get initialSegment() {
|
||||
return this.internal.initialSegment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Final code path segments. These are the terminal (tail) segments in the
|
||||
* code path, which is the combination of `returnedSegments` and `thrownSegments`.
|
||||
* All segments in this array are reachable.
|
||||
* This is a passthrough to the underlying `CodePathState`.
|
||||
* @type {CodePathSegment[]}
|
||||
*/
|
||||
get finalSegments() {
|
||||
return this.internal.finalSegments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Final code path segments that represent normal completion of the code path.
|
||||
* For functions, this means both explicit `return` statements and implicit returns,
|
||||
* such as the last reachable segment in a function that does not have an
|
||||
* explicit `return` as this implicitly returns `undefined`. For scripts,
|
||||
* modules, class field initializers, and class static blocks, this means
|
||||
* all lines of code have been executed.
|
||||
* These segments are also present in `finalSegments`.
|
||||
* This is a passthrough to the underlying `CodePathState`.
|
||||
* @type {CodePathSegment[]}
|
||||
*/
|
||||
get returnedSegments() {
|
||||
return this.internal.returnedForkContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Final code path segments that represent `throw` statements.
|
||||
* This is a passthrough to the underlying `CodePathState`.
|
||||
* These segments are also present in `finalSegments`.
|
||||
* @type {CodePathSegment[]}
|
||||
*/
|
||||
get thrownSegments() {
|
||||
return this.internal.thrownForkContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses all segments in this code path.
|
||||
*
|
||||
* codePath.traverseSegments((segment, controller) => {
|
||||
* // do something.
|
||||
* });
|
||||
*
|
||||
* This method enumerates segments in order from the head.
|
||||
*
|
||||
* The `controller` argument has two methods:
|
||||
*
|
||||
* - `skip()` - skips the following segments in this branch
|
||||
* - `break()` - skips all following segments in the traversal
|
||||
*
|
||||
* A note on the parameters: the `options` argument is optional. This means
|
||||
* the first argument might be an options object or the callback function.
|
||||
* @param {Object} [optionsOrCallback] Optional first and last segments to traverse.
|
||||
* @param {CodePathSegment} [optionsOrCallback.first] The first segment to traverse.
|
||||
* @param {CodePathSegment} [optionsOrCallback.last] The last segment to traverse.
|
||||
* @param {Function} callback A callback function.
|
||||
* @returns {void}
|
||||
*/
|
||||
traverseSegments(optionsOrCallback, callback) {
|
||||
|
||||
// normalize the arguments into a callback and options
|
||||
let resolvedOptions;
|
||||
let resolvedCallback;
|
||||
|
||||
if (typeof optionsOrCallback === "function") {
|
||||
resolvedCallback = optionsOrCallback;
|
||||
resolvedOptions = {};
|
||||
} else {
|
||||
resolvedOptions = optionsOrCallback || {};
|
||||
resolvedCallback = callback;
|
||||
}
|
||||
|
||||
// determine where to start traversing from based on the options
|
||||
const startSegment = resolvedOptions.first || this.internal.initialSegment;
|
||||
const lastSegment = resolvedOptions.last;
|
||||
|
||||
// set up initial location information
|
||||
let record;
|
||||
let index;
|
||||
let end;
|
||||
let segment = null;
|
||||
|
||||
// segments that have already been visited during traversal
|
||||
const visited = new Set();
|
||||
|
||||
// tracks the traversal steps
|
||||
const stack = [[startSegment, 0]];
|
||||
|
||||
// segments that have been skipped during traversal
|
||||
const skipped = new Set();
|
||||
|
||||
// indicates if we exited early from the traversal
|
||||
let broken = false;
|
||||
|
||||
/**
|
||||
* Maintains traversal state.
|
||||
*/
|
||||
const controller = {
|
||||
|
||||
/**
|
||||
* Skip the following segments in this branch.
|
||||
* @returns {void}
|
||||
*/
|
||||
skip() {
|
||||
skipped.add(segment);
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop traversal completely - do not traverse to any
|
||||
* other segments.
|
||||
* @returns {void}
|
||||
*/
|
||||
break() {
|
||||
broken = true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if a given previous segment has been visited.
|
||||
* @param {CodePathSegment} prevSegment A previous segment to check.
|
||||
* @returns {boolean} `true` if the segment has been visited.
|
||||
*/
|
||||
function isVisited(prevSegment) {
|
||||
return (
|
||||
visited.has(prevSegment) ||
|
||||
segment.isLoopedPrevSegment(prevSegment)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given previous segment has been skipped.
|
||||
* @param {CodePathSegment} prevSegment A previous segment to check.
|
||||
* @returns {boolean} `true` if the segment has been skipped.
|
||||
*/
|
||||
function isSkipped(prevSegment) {
|
||||
return (
|
||||
skipped.has(prevSegment) ||
|
||||
segment.isLoopedPrevSegment(prevSegment)
|
||||
);
|
||||
}
|
||||
|
||||
// the traversal
|
||||
while (stack.length > 0) {
|
||||
|
||||
/*
|
||||
* This isn't a pure stack. We use the top record all the time
|
||||
* but don't always pop it off. The record is popped only if
|
||||
* one of the following is true:
|
||||
*
|
||||
* 1) We have already visited the segment.
|
||||
* 2) We have not visited *all* of the previous segments.
|
||||
* 3) We have traversed past the available next segments.
|
||||
*
|
||||
* Otherwise, we just read the value and sometimes modify the
|
||||
* record as we traverse.
|
||||
*/
|
||||
record = stack.at(-1);
|
||||
segment = record[0];
|
||||
index = record[1];
|
||||
|
||||
if (index === 0) {
|
||||
|
||||
// Skip if this segment has been visited already.
|
||||
if (visited.has(segment)) {
|
||||
stack.pop();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip if all previous segments have not been visited.
|
||||
if (segment !== startSegment &&
|
||||
segment.prevSegments.length > 0 &&
|
||||
!segment.prevSegments.every(isVisited)
|
||||
) {
|
||||
stack.pop();
|
||||
continue;
|
||||
}
|
||||
|
||||
visited.add(segment);
|
||||
|
||||
|
||||
// Skips the segment if all previous segments have been skipped.
|
||||
const shouldSkip = (
|
||||
skipped.size > 0 &&
|
||||
segment.prevSegments.length > 0 &&
|
||||
segment.prevSegments.every(isSkipped)
|
||||
);
|
||||
|
||||
/*
|
||||
* If the most recent segment hasn't been skipped, then we call
|
||||
* the callback, passing in the segment and the controller.
|
||||
*/
|
||||
if (!shouldSkip) {
|
||||
resolvedCallback.call(this, segment, controller);
|
||||
|
||||
// exit if we're at the last segment
|
||||
if (segment === lastSegment) {
|
||||
controller.skip();
|
||||
}
|
||||
|
||||
/*
|
||||
* If the previous statement was executed, or if the callback
|
||||
* called a method on the controller, we might need to exit the
|
||||
* loop, so check for that and break accordingly.
|
||||
*/
|
||||
if (broken) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
|
||||
// If the most recent segment has been skipped, then mark it as skipped.
|
||||
skipped.add(segment);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the stack.
|
||||
end = segment.nextSegments.length - 1;
|
||||
if (index < end) {
|
||||
|
||||
/*
|
||||
* If we haven't yet visited all of the next segments, update
|
||||
* the current top record on the stack to the next index to visit
|
||||
* and then push a record for the current segment on top.
|
||||
*
|
||||
* Setting the current top record's index lets us know how many
|
||||
* times we've been here and ensures that the segment won't be
|
||||
* reprocessed (because we only process segments with an index
|
||||
* of 0).
|
||||
*/
|
||||
record[1] += 1;
|
||||
stack.push([segment.nextSegments[index], 0]);
|
||||
} else if (index === end) {
|
||||
|
||||
/*
|
||||
* If we are at the last next segment, then reset the top record
|
||||
* in the stack to next segment and set its index to 0 so it will
|
||||
* be processed next.
|
||||
*/
|
||||
record[0] = segment.nextSegments[index];
|
||||
record[1] = 0;
|
||||
} else {
|
||||
|
||||
/*
|
||||
* If index > end, that means we have no more segments that need
|
||||
* processing. So, we pop that record off of the stack in order to
|
||||
* continue traversing at the next level up.
|
||||
*/
|
||||
stack.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CodePath;
|
203
node_modules/eslint/lib/linter/code-path-analysis/debug-helpers.js
generated
vendored
Normal file
203
node_modules/eslint/lib/linter/code-path-analysis/debug-helpers.js
generated
vendored
Normal file
@ -0,0 +1,203 @@
|
||||
/**
|
||||
* @fileoverview Helpers to debug for code path analysis.
|
||||
* @author Toru Nagashima
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const debug = require("debug")("eslint:code-path");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Gets id of a given segment.
|
||||
* @param {CodePathSegment} segment A segment to get.
|
||||
* @returns {string} Id of the segment.
|
||||
*/
|
||||
/* c8 ignore next */
|
||||
function getId(segment) { // eslint-disable-line jsdoc/require-jsdoc -- Ignoring
|
||||
return segment.id + (segment.reachable ? "" : "!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get string for the given node and operation.
|
||||
* @param {ASTNode} node The node to convert.
|
||||
* @param {"enter" | "exit" | undefined} label The operation label.
|
||||
* @returns {string} The string representation.
|
||||
*/
|
||||
function nodeToString(node, label) {
|
||||
const suffix = label ? `:${label}` : "";
|
||||
|
||||
switch (node.type) {
|
||||
case "Identifier": return `${node.type}${suffix} (${node.name})`;
|
||||
case "Literal": return `${node.type}${suffix} (${node.value})`;
|
||||
default: return `${node.type}${suffix}`;
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Public Interface
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
|
||||
/**
|
||||
* A flag that debug dumping is enabled or not.
|
||||
* @type {boolean}
|
||||
*/
|
||||
enabled: debug.enabled,
|
||||
|
||||
/**
|
||||
* Dumps given objects.
|
||||
* @param {...any} args objects to dump.
|
||||
* @returns {void}
|
||||
*/
|
||||
dump: debug,
|
||||
|
||||
/**
|
||||
* Dumps the current analyzing state.
|
||||
* @param {ASTNode} node A node to dump.
|
||||
* @param {CodePathState} state A state to dump.
|
||||
* @param {boolean} leaving A flag whether or not it's leaving
|
||||
* @returns {void}
|
||||
*/
|
||||
dumpState: !debug.enabled ? debug : /* c8 ignore next */ function(node, state, leaving) {
|
||||
for (let i = 0; i < state.currentSegments.length; ++i) {
|
||||
const segInternal = state.currentSegments[i].internal;
|
||||
|
||||
if (leaving) {
|
||||
const last = segInternal.nodes.length - 1;
|
||||
|
||||
if (last >= 0 && segInternal.nodes[last] === nodeToString(node, "enter")) {
|
||||
segInternal.nodes[last] = nodeToString(node, void 0);
|
||||
} else {
|
||||
segInternal.nodes.push(nodeToString(node, "exit"));
|
||||
}
|
||||
} else {
|
||||
segInternal.nodes.push(nodeToString(node, "enter"));
|
||||
}
|
||||
}
|
||||
|
||||
debug([
|
||||
`${state.currentSegments.map(getId).join(",")})`,
|
||||
`${node.type}${leaving ? ":exit" : ""}`
|
||||
].join(" "));
|
||||
},
|
||||
|
||||
/**
|
||||
* Dumps a DOT code of a given code path.
|
||||
* The DOT code can be visualized with Graphvis.
|
||||
* @param {CodePath} codePath A code path to dump.
|
||||
* @returns {void}
|
||||
* @see http://www.graphviz.org
|
||||
* @see http://www.webgraphviz.com
|
||||
*/
|
||||
dumpDot: !debug.enabled ? debug : /* c8 ignore next */ function(codePath) {
|
||||
let text =
|
||||
"\n" +
|
||||
"digraph {\n" +
|
||||
"node[shape=box,style=\"rounded,filled\",fillcolor=white];\n" +
|
||||
"initial[label=\"\",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];\n";
|
||||
|
||||
if (codePath.returnedSegments.length > 0) {
|
||||
text += "final[label=\"\",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];\n";
|
||||
}
|
||||
if (codePath.thrownSegments.length > 0) {
|
||||
text += "thrown[label=\"✘\",shape=circle,width=0.3,height=0.3,fixedsize=true];\n";
|
||||
}
|
||||
|
||||
const traceMap = Object.create(null);
|
||||
const arrows = this.makeDotArrows(codePath, traceMap);
|
||||
|
||||
for (const id in traceMap) { // eslint-disable-line guard-for-in -- Want ability to traverse prototype
|
||||
const segment = traceMap[id];
|
||||
|
||||
text += `${id}[`;
|
||||
|
||||
if (segment.reachable) {
|
||||
text += "label=\"";
|
||||
} else {
|
||||
text += "style=\"rounded,dashed,filled\",fillcolor=\"#FF9800\",label=\"<<unreachable>>\\n";
|
||||
}
|
||||
|
||||
if (segment.internal.nodes.length > 0) {
|
||||
text += segment.internal.nodes.join("\\n");
|
||||
} else {
|
||||
text += "????";
|
||||
}
|
||||
|
||||
text += "\"];\n";
|
||||
}
|
||||
|
||||
text += `${arrows}\n`;
|
||||
text += "}";
|
||||
debug("DOT", text);
|
||||
},
|
||||
|
||||
/**
|
||||
* Makes a DOT code of a given code path.
|
||||
* The DOT code can be visualized with Graphvis.
|
||||
* @param {CodePath} codePath A code path to make DOT.
|
||||
* @param {Object} traceMap Optional. A map to check whether or not segments had been done.
|
||||
* @returns {string} A DOT code of the code path.
|
||||
*/
|
||||
makeDotArrows(codePath, traceMap) {
|
||||
const stack = [[codePath.initialSegment, 0]];
|
||||
const done = traceMap || Object.create(null);
|
||||
let lastId = codePath.initialSegment.id;
|
||||
let text = `initial->${codePath.initialSegment.id}`;
|
||||
|
||||
while (stack.length > 0) {
|
||||
const item = stack.pop();
|
||||
const segment = item[0];
|
||||
const index = item[1];
|
||||
|
||||
if (done[segment.id] && index === 0) {
|
||||
continue;
|
||||
}
|
||||
done[segment.id] = segment;
|
||||
|
||||
const nextSegment = segment.allNextSegments[index];
|
||||
|
||||
if (!nextSegment) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (lastId === segment.id) {
|
||||
text += `->${nextSegment.id}`;
|
||||
} else {
|
||||
text += `;\n${segment.id}->${nextSegment.id}`;
|
||||
}
|
||||
lastId = nextSegment.id;
|
||||
|
||||
stack.unshift([segment, 1 + index]);
|
||||
stack.push([nextSegment, 0]);
|
||||
}
|
||||
|
||||
codePath.returnedSegments.forEach(finalSegment => {
|
||||
if (lastId === finalSegment.id) {
|
||||
text += "->final";
|
||||
} else {
|
||||
text += `;\n${finalSegment.id}->final`;
|
||||
}
|
||||
lastId = null;
|
||||
});
|
||||
|
||||
codePath.thrownSegments.forEach(finalSegment => {
|
||||
if (lastId === finalSegment.id) {
|
||||
text += "->thrown";
|
||||
} else {
|
||||
text += `;\n${finalSegment.id}->thrown`;
|
||||
}
|
||||
lastId = null;
|
||||
});
|
||||
|
||||
return `${text};`;
|
||||
}
|
||||
};
|
349
node_modules/eslint/lib/linter/code-path-analysis/fork-context.js
generated
vendored
Normal file
349
node_modules/eslint/lib/linter/code-path-analysis/fork-context.js
generated
vendored
Normal file
@ -0,0 +1,349 @@
|
||||
/**
|
||||
* @fileoverview A class to operate forking.
|
||||
*
|
||||
* This is state of forking.
|
||||
* This has a fork list and manages it.
|
||||
*
|
||||
* @author Toru Nagashima
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const assert = require("../../shared/assert"),
|
||||
CodePathSegment = require("./code-path-segment");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Determines whether or not a given segment is reachable.
|
||||
* @param {CodePathSegment} segment The segment to check.
|
||||
* @returns {boolean} `true` if the segment is reachable.
|
||||
*/
|
||||
function isReachable(segment) {
|
||||
return segment.reachable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new segment for each fork in the given context and appends it
|
||||
* to the end of the specified range of segments. Ultimately, this ends up calling
|
||||
* `new CodePathSegment()` for each of the forks using the `create` argument
|
||||
* as a wrapper around special behavior.
|
||||
*
|
||||
* The `startIndex` and `endIndex` arguments specify a range of segments in
|
||||
* `context` that should become `allPrevSegments` for the newly created
|
||||
* `CodePathSegment` objects.
|
||||
*
|
||||
* When `context.segmentsList` is `[[a, b], [c, d], [e, f]]`, `begin` is `0`, and
|
||||
* `end` is `-1`, this creates two new segments, `[g, h]`. This `g` is appended to
|
||||
* the end of the path from `a`, `c`, and `e`. This `h` is appended to the end of
|
||||
* `b`, `d`, and `f`.
|
||||
* @param {ForkContext} context An instance from which the previous segments
|
||||
* will be obtained.
|
||||
* @param {number} startIndex The index of the first segment in the context
|
||||
* that should be specified as previous segments for the newly created segments.
|
||||
* @param {number} endIndex The index of the last segment in the context
|
||||
* that should be specified as previous segments for the newly created segments.
|
||||
* @param {Function} create A function that creates new `CodePathSegment`
|
||||
* instances in a particular way. See the `CodePathSegment.new*` methods.
|
||||
* @returns {Array<CodePathSegment>} An array of the newly-created segments.
|
||||
*/
|
||||
function createSegments(context, startIndex, endIndex, create) {
|
||||
|
||||
/** @type {Array<Array<CodePathSegment>>} */
|
||||
const list = context.segmentsList;
|
||||
|
||||
/*
|
||||
* Both `startIndex` and `endIndex` work the same way: if the number is zero
|
||||
* or more, then the number is used as-is. If the number is negative,
|
||||
* then that number is added to the length of the segments list to
|
||||
* determine the index to use. That means -1 for either argument
|
||||
* is the last element, -2 is the second to last, and so on.
|
||||
*
|
||||
* So if `startIndex` is 0, `endIndex` is -1, and `list.length` is 3, the
|
||||
* effective `startIndex` is 0 and the effective `endIndex` is 2, so this function
|
||||
* will include items at indices 0, 1, and 2.
|
||||
*
|
||||
* Therefore, if `startIndex` is -1 and `endIndex` is -1, that means we'll only
|
||||
* be using the last segment in `list`.
|
||||
*/
|
||||
const normalizedBegin = startIndex >= 0 ? startIndex : list.length + startIndex;
|
||||
const normalizedEnd = endIndex >= 0 ? endIndex : list.length + endIndex;
|
||||
|
||||
/** @type {Array<CodePathSegment>} */
|
||||
const segments = [];
|
||||
|
||||
for (let i = 0; i < context.count; ++i) {
|
||||
|
||||
// this is passed into `new CodePathSegment` to add to code path.
|
||||
const allPrevSegments = [];
|
||||
|
||||
for (let j = normalizedBegin; j <= normalizedEnd; ++j) {
|
||||
allPrevSegments.push(list[j][i]);
|
||||
}
|
||||
|
||||
// note: `create` is just a wrapper that augments `new CodePathSegment`.
|
||||
segments.push(create(context.idGenerator.next(), allPrevSegments));
|
||||
}
|
||||
|
||||
return segments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inside of a `finally` block we end up with two parallel paths. If the code path
|
||||
* exits by a control statement (such as `break` or `continue`) from the `finally`
|
||||
* block, then we need to merge the remaining parallel paths back into one.
|
||||
* @param {ForkContext} context The fork context to work on.
|
||||
* @param {Array<CodePathSegment>} segments Segments to merge.
|
||||
* @returns {Array<CodePathSegment>} The merged segments.
|
||||
*/
|
||||
function mergeExtraSegments(context, segments) {
|
||||
let currentSegments = segments;
|
||||
|
||||
/*
|
||||
* We need to ensure that the array returned from this function contains no more
|
||||
* than the number of segments that the context allows. `context.count` indicates
|
||||
* how many items should be in the returned array to ensure that the new segment
|
||||
* entries will line up with the already existing segment entries.
|
||||
*/
|
||||
while (currentSegments.length > context.count) {
|
||||
const merged = [];
|
||||
|
||||
/*
|
||||
* Because `context.count` is a factor of 2 inside of a `finally` block,
|
||||
* we can divide the segment count by 2 to merge the paths together.
|
||||
* This loops through each segment in the list and creates a new `CodePathSegment`
|
||||
* that has the segment and the segment two slots away as previous segments.
|
||||
*
|
||||
* If `currentSegments` is [a,b,c,d], this will create new segments e and f, such
|
||||
* that:
|
||||
*
|
||||
* When `i` is 0:
|
||||
* a->e
|
||||
* c->e
|
||||
*
|
||||
* When `i` is 1:
|
||||
* b->f
|
||||
* d->f
|
||||
*/
|
||||
for (let i = 0, length = Math.floor(currentSegments.length / 2); i < length; ++i) {
|
||||
merged.push(CodePathSegment.newNext(
|
||||
context.idGenerator.next(),
|
||||
[currentSegments[i], currentSegments[i + length]]
|
||||
));
|
||||
}
|
||||
|
||||
/*
|
||||
* Go through the loop condition one more time to see if we have the
|
||||
* number of segments for the context. If not, we'll keep merging paths
|
||||
* of the merged segments until we get there.
|
||||
*/
|
||||
currentSegments = merged;
|
||||
}
|
||||
|
||||
return currentSegments;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Public Interface
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Manages the forking of code paths.
|
||||
*/
|
||||
class ForkContext {
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {IdGenerator} idGenerator An identifier generator for segments.
|
||||
* @param {ForkContext|null} upper The preceding fork context.
|
||||
* @param {number} count The number of parallel segments in each element
|
||||
* of `segmentsList`.
|
||||
*/
|
||||
constructor(idGenerator, upper, count) {
|
||||
|
||||
/**
|
||||
* The ID generator that will generate segment IDs for any new
|
||||
* segments that are created.
|
||||
* @type {IdGenerator}
|
||||
*/
|
||||
this.idGenerator = idGenerator;
|
||||
|
||||
/**
|
||||
* The preceding fork context.
|
||||
* @type {ForkContext|null}
|
||||
*/
|
||||
this.upper = upper;
|
||||
|
||||
/**
|
||||
* The number of elements in each element of `segmentsList`. In most
|
||||
* cases, this is 1 but can be 2 when there is a `finally` present,
|
||||
* which forks the code path outside of normal flow. In the case of nested
|
||||
* `finally` blocks, this can be a multiple of 2.
|
||||
* @type {number}
|
||||
*/
|
||||
this.count = count;
|
||||
|
||||
/**
|
||||
* The segments within this context. Each element in this array has
|
||||
* `count` elements that represent one step in each fork. For example,
|
||||
* when `segmentsList` is `[[a, b], [c, d], [e, f]]`, there is one path
|
||||
* a->c->e and one path b->d->f, and `count` is 2 because each element
|
||||
* is an array with two elements.
|
||||
* @type {Array<Array<CodePathSegment>>}
|
||||
*/
|
||||
this.segmentsList = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* The segments that begin this fork context.
|
||||
* @type {Array<CodePathSegment>}
|
||||
*/
|
||||
get head() {
|
||||
const list = this.segmentsList;
|
||||
|
||||
return list.length === 0 ? [] : list.at(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the context contains no segments.
|
||||
* @type {boolean}
|
||||
*/
|
||||
get empty() {
|
||||
return this.segmentsList.length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if there are any segments that are reachable.
|
||||
* @type {boolean}
|
||||
*/
|
||||
get reachable() {
|
||||
const segments = this.head;
|
||||
|
||||
return segments.length > 0 && segments.some(isReachable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new segments in this context and appends them to the end of the
|
||||
* already existing `CodePathSegment`s specified by `startIndex` and
|
||||
* `endIndex`.
|
||||
* @param {number} startIndex The index of the first segment in the context
|
||||
* that should be specified as previous segments for the newly created segments.
|
||||
* @param {number} endIndex The index of the last segment in the context
|
||||
* that should be specified as previous segments for the newly created segments.
|
||||
* @returns {Array<CodePathSegment>} An array of the newly created segments.
|
||||
*/
|
||||
makeNext(startIndex, endIndex) {
|
||||
return createSegments(this, startIndex, endIndex, CodePathSegment.newNext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new unreachable segments in this context and appends them to the end of the
|
||||
* already existing `CodePathSegment`s specified by `startIndex` and
|
||||
* `endIndex`.
|
||||
* @param {number} startIndex The index of the first segment in the context
|
||||
* that should be specified as previous segments for the newly created segments.
|
||||
* @param {number} endIndex The index of the last segment in the context
|
||||
* that should be specified as previous segments for the newly created segments.
|
||||
* @returns {Array<CodePathSegment>} An array of the newly created segments.
|
||||
*/
|
||||
makeUnreachable(startIndex, endIndex) {
|
||||
return createSegments(this, startIndex, endIndex, CodePathSegment.newUnreachable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new segments in this context and does not append them to the end
|
||||
* of the already existing `CodePathSegment`s specified by `startIndex` and
|
||||
* `endIndex`. The `startIndex` and `endIndex` are only used to determine if
|
||||
* the new segments should be reachable. If any of the segments in this range
|
||||
* are reachable then the new segments are also reachable; otherwise, the new
|
||||
* segments are unreachable.
|
||||
* @param {number} startIndex The index of the first segment in the context
|
||||
* that should be considered for reachability.
|
||||
* @param {number} endIndex The index of the last segment in the context
|
||||
* that should be considered for reachability.
|
||||
* @returns {Array<CodePathSegment>} An array of the newly created segments.
|
||||
*/
|
||||
makeDisconnected(startIndex, endIndex) {
|
||||
return createSegments(this, startIndex, endIndex, CodePathSegment.newDisconnected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds segments to the head of this context.
|
||||
* @param {Array<CodePathSegment>} segments The segments to add.
|
||||
* @returns {void}
|
||||
*/
|
||||
add(segments) {
|
||||
assert(segments.length >= this.count, `${segments.length} >= ${this.count}`);
|
||||
this.segmentsList.push(mergeExtraSegments(this, segments));
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the head segments with the given segments.
|
||||
* The current head segments are removed.
|
||||
* @param {Array<CodePathSegment>} replacementHeadSegments The new head segments.
|
||||
* @returns {void}
|
||||
*/
|
||||
replaceHead(replacementHeadSegments) {
|
||||
assert(
|
||||
replacementHeadSegments.length >= this.count,
|
||||
`${replacementHeadSegments.length} >= ${this.count}`
|
||||
);
|
||||
this.segmentsList.splice(-1, 1, mergeExtraSegments(this, replacementHeadSegments));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all segments of a given fork context into this context.
|
||||
* @param {ForkContext} otherForkContext The fork context to add from.
|
||||
* @returns {void}
|
||||
*/
|
||||
addAll(otherForkContext) {
|
||||
assert(otherForkContext.count === this.count);
|
||||
this.segmentsList.push(...otherForkContext.segmentsList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all segments in this context.
|
||||
* @returns {void}
|
||||
*/
|
||||
clear() {
|
||||
this.segmentsList = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new root context, meaning that there are no parent
|
||||
* fork contexts.
|
||||
* @param {IdGenerator} idGenerator An identifier generator for segments.
|
||||
* @returns {ForkContext} New fork context.
|
||||
*/
|
||||
static newRoot(idGenerator) {
|
||||
const context = new ForkContext(idGenerator, null, 1);
|
||||
|
||||
context.add([CodePathSegment.newRoot(idGenerator.next())]);
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an empty fork context preceded by a given context.
|
||||
* @param {ForkContext} parentContext The parent fork context.
|
||||
* @param {boolean} shouldForkLeavingPath Indicates that we are inside of
|
||||
* a `finally` block and should therefore fork the path that leaves
|
||||
* `finally`.
|
||||
* @returns {ForkContext} New fork context.
|
||||
*/
|
||||
static newEmpty(parentContext, shouldForkLeavingPath) {
|
||||
return new ForkContext(
|
||||
parentContext.idGenerator,
|
||||
parentContext,
|
||||
(shouldForkLeavingPath ? 2 : 1) * parentContext.count
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ForkContext;
|
45
node_modules/eslint/lib/linter/code-path-analysis/id-generator.js
generated
vendored
Normal file
45
node_modules/eslint/lib/linter/code-path-analysis/id-generator.js
generated
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* @fileoverview A class of identifiers generator for code path segments.
|
||||
*
|
||||
* Each rule uses the identifier of code path segments to store additional
|
||||
* information of the code path.
|
||||
*
|
||||
* @author Toru Nagashima
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Public Interface
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* A generator for unique ids.
|
||||
*/
|
||||
class IdGenerator {
|
||||
|
||||
/**
|
||||
* @param {string} prefix Optional. A prefix of generated ids.
|
||||
*/
|
||||
constructor(prefix) {
|
||||
this.prefix = String(prefix);
|
||||
this.n = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates id.
|
||||
* @returns {string} A generated id.
|
||||
*/
|
||||
next() {
|
||||
this.n = 1 + this.n | 0;
|
||||
|
||||
/* c8 ignore start */
|
||||
if (this.n < 0) {
|
||||
this.n = 1;
|
||||
}/* c8 ignore stop */
|
||||
|
||||
return this.prefix + this.n;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = IdGenerator;
|
134
node_modules/eslint/lib/linter/file-context.js
generated
vendored
Normal file
134
node_modules/eslint/lib/linter/file-context.js
generated
vendored
Normal file
@ -0,0 +1,134 @@
|
||||
/**
|
||||
* @fileoverview The FileContext class.
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Represents a file context that the linter can use to lint a file.
|
||||
*/
|
||||
class FileContext {
|
||||
|
||||
/**
|
||||
* The current working directory.
|
||||
* @type {string}
|
||||
*/
|
||||
cwd;
|
||||
|
||||
/**
|
||||
* The filename of the file being linted.
|
||||
* @type {string}
|
||||
*/
|
||||
filename;
|
||||
|
||||
/**
|
||||
* The physical filename of the file being linted.
|
||||
* @type {string}
|
||||
*/
|
||||
physicalFilename;
|
||||
|
||||
/**
|
||||
* The source code of the file being linted.
|
||||
* @type {SourceCode}
|
||||
*/
|
||||
sourceCode;
|
||||
|
||||
/**
|
||||
* The parser options for the file being linted.
|
||||
* @type {Record<string, unknown>}
|
||||
* @deprecated Use `languageOptions` instead.
|
||||
*/
|
||||
parserOptions;
|
||||
|
||||
/**
|
||||
* The path to the parser used to parse this file.
|
||||
* @type {string}
|
||||
* @deprecated No longer supported.
|
||||
*/
|
||||
parserPath;
|
||||
|
||||
/**
|
||||
* The language options used when parsing this file.
|
||||
* @type {Record<string, unknown>}
|
||||
*/
|
||||
languageOptions;
|
||||
|
||||
/**
|
||||
* The settings for the file being linted.
|
||||
* @type {Record<string, unknown>}
|
||||
*/
|
||||
settings;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {Object} config The configuration object for the file context.
|
||||
* @param {string} config.cwd The current working directory.
|
||||
* @param {string} config.filename The filename of the file being linted.
|
||||
* @param {string} config.physicalFilename The physical filename of the file being linted.
|
||||
* @param {SourceCode} config.sourceCode The source code of the file being linted.
|
||||
* @param {Record<string, unknown>} config.parserOptions The parser options for the file being linted.
|
||||
* @param {string} config.parserPath The path to the parser used to parse this file.
|
||||
* @param {Record<string, unknown>} config.languageOptions The language options used when parsing this file.
|
||||
* @param {Record<string, unknown>} config.settings The settings for the file being linted.
|
||||
*/
|
||||
constructor({
|
||||
cwd,
|
||||
filename,
|
||||
physicalFilename,
|
||||
sourceCode,
|
||||
parserOptions,
|
||||
parserPath,
|
||||
languageOptions,
|
||||
settings
|
||||
}) {
|
||||
this.cwd = cwd;
|
||||
this.filename = filename;
|
||||
this.physicalFilename = physicalFilename;
|
||||
this.sourceCode = sourceCode;
|
||||
this.parserOptions = parserOptions;
|
||||
this.parserPath = parserPath;
|
||||
this.languageOptions = languageOptions;
|
||||
this.settings = settings;
|
||||
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current working directory.
|
||||
* @returns {string} The current working directory.
|
||||
* @deprecated Use `cwd` instead.
|
||||
*/
|
||||
getCwd() {
|
||||
return this.cwd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the filename of the file being linted.
|
||||
* @returns {string} The filename of the file being linted.
|
||||
* @deprecated Use `filename` instead.
|
||||
*/
|
||||
getFilename() {
|
||||
return this.filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the physical filename of the file being linted.
|
||||
* @returns {string} The physical filename of the file being linted.
|
||||
* @deprecated Use `physicalFilename` instead.
|
||||
*/
|
||||
getPhysicalFilename() {
|
||||
return this.physicalFilename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the source code of the file being linted.
|
||||
* @returns {SourceCode} The source code of the file being linted.
|
||||
* @deprecated Use `sourceCode` instead.
|
||||
*/
|
||||
getSourceCode() {
|
||||
return this.sourceCode;
|
||||
}
|
||||
}
|
||||
|
||||
exports.FileContext = FileContext;
|
11
node_modules/eslint/lib/linter/index.js
generated
vendored
Normal file
11
node_modules/eslint/lib/linter/index.js
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
"use strict";
|
||||
|
||||
const { Linter } = require("./linter");
|
||||
const SourceCodeFixer = require("./source-code-fixer");
|
||||
|
||||
module.exports = {
|
||||
Linter,
|
||||
|
||||
// For testers.
|
||||
SourceCodeFixer
|
||||
};
|
50
node_modules/eslint/lib/linter/interpolate.js
generated
vendored
Normal file
50
node_modules/eslint/lib/linter/interpolate.js
generated
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* @fileoverview Interpolate keys from an object into a string with {{ }} markers.
|
||||
* @author Jed Fox
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Public Interface
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns a global expression matching placeholders in messages.
|
||||
* @returns {RegExp} Global regular expression matching placeholders
|
||||
*/
|
||||
function getPlaceholderMatcher() {
|
||||
return /\{\{([^{}]+?)\}\}/gu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces {{ placeholders }} in the message with the provided data.
|
||||
* Does not replace placeholders not available in the data.
|
||||
* @param {string} text Original message with potential placeholders
|
||||
* @param {Record<string, string>} data Map of placeholder name to its value
|
||||
* @returns {string} Message with replaced placeholders
|
||||
*/
|
||||
function interpolate(text, data) {
|
||||
if (!data) {
|
||||
return text;
|
||||
}
|
||||
|
||||
const matcher = getPlaceholderMatcher();
|
||||
|
||||
// Substitution content for any {{ }} markers.
|
||||
return text.replace(matcher, (fullMatch, termWithWhitespace) => {
|
||||
const term = termWithWhitespace.trim();
|
||||
|
||||
if (term in data) {
|
||||
return data[term];
|
||||
}
|
||||
|
||||
// Preserve old behavior: If parameter name not provided, don't replace it.
|
||||
return fullMatch;
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getPlaceholderMatcher,
|
||||
interpolate
|
||||
};
|
2407
node_modules/eslint/lib/linter/linter.js
generated
vendored
Normal file
2407
node_modules/eslint/lib/linter/linter.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
352
node_modules/eslint/lib/linter/node-event-generator.js
generated
vendored
Normal file
352
node_modules/eslint/lib/linter/node-event-generator.js
generated
vendored
Normal file
@ -0,0 +1,352 @@
|
||||
/**
|
||||
* @fileoverview The event generator for AST nodes.
|
||||
* @author Toru Nagashima
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const esquery = require("esquery");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Typedefs
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* An object describing an AST selector
|
||||
* @typedef {Object} ASTSelector
|
||||
* @property {string} rawSelector The string that was parsed into this selector
|
||||
* @property {boolean} isExit `true` if this should be emitted when exiting the node rather than when entering
|
||||
* @property {Object} parsedSelector An object (from esquery) describing the matching behavior of the selector
|
||||
* @property {string[]|null} listenerTypes A list of node types that could possibly cause the selector to match,
|
||||
* or `null` if all node types could cause a match
|
||||
* @property {number} attributeCount The total number of classes, pseudo-classes, and attribute queries in this selector
|
||||
* @property {number} identifierCount The total number of identifier queries in this selector
|
||||
*/
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Computes the union of one or more arrays
|
||||
* @param {...any[]} arrays One or more arrays to union
|
||||
* @returns {any[]} The union of the input arrays
|
||||
*/
|
||||
function union(...arrays) {
|
||||
return [...new Set(arrays.flat())];
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the intersection of one or more arrays
|
||||
* @param {...any[]} arrays One or more arrays to intersect
|
||||
* @returns {any[]} The intersection of the input arrays
|
||||
*/
|
||||
function intersection(...arrays) {
|
||||
if (arrays.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let result = [...new Set(arrays[0])];
|
||||
|
||||
for (const array of arrays.slice(1)) {
|
||||
result = result.filter(x => array.includes(x));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the possible types of a selector
|
||||
* @param {Object} parsedSelector An object (from esquery) describing the matching behavior of the selector
|
||||
* @returns {string[]|null} The node types that could possibly trigger this selector, or `null` if all node types could trigger it
|
||||
*/
|
||||
function getPossibleTypes(parsedSelector) {
|
||||
switch (parsedSelector.type) {
|
||||
case "identifier":
|
||||
return [parsedSelector.value];
|
||||
|
||||
case "matches": {
|
||||
const typesForComponents = parsedSelector.selectors.map(getPossibleTypes);
|
||||
|
||||
if (typesForComponents.every(Boolean)) {
|
||||
return union(...typesForComponents);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
case "compound": {
|
||||
const typesForComponents = parsedSelector.selectors.map(getPossibleTypes).filter(typesForComponent => typesForComponent);
|
||||
|
||||
// If all of the components could match any type, then the compound could also match any type.
|
||||
if (!typesForComponents.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* If at least one of the components could only match a particular type, the compound could only match
|
||||
* the intersection of those types.
|
||||
*/
|
||||
return intersection(...typesForComponents);
|
||||
}
|
||||
|
||||
case "child":
|
||||
case "descendant":
|
||||
case "sibling":
|
||||
case "adjacent":
|
||||
return getPossibleTypes(parsedSelector.right);
|
||||
|
||||
case "class":
|
||||
if (parsedSelector.name === "function") {
|
||||
return ["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"];
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
default:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of class, pseudo-class, and attribute queries in this selector
|
||||
* @param {Object} parsedSelector An object (from esquery) describing the selector's matching behavior
|
||||
* @returns {number} The number of class, pseudo-class, and attribute queries in this selector
|
||||
*/
|
||||
function countClassAttributes(parsedSelector) {
|
||||
switch (parsedSelector.type) {
|
||||
case "child":
|
||||
case "descendant":
|
||||
case "sibling":
|
||||
case "adjacent":
|
||||
return countClassAttributes(parsedSelector.left) + countClassAttributes(parsedSelector.right);
|
||||
|
||||
case "compound":
|
||||
case "not":
|
||||
case "matches":
|
||||
return parsedSelector.selectors.reduce((sum, childSelector) => sum + countClassAttributes(childSelector), 0);
|
||||
|
||||
case "attribute":
|
||||
case "field":
|
||||
case "nth-child":
|
||||
case "nth-last-child":
|
||||
return 1;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of identifier queries in this selector
|
||||
* @param {Object} parsedSelector An object (from esquery) describing the selector's matching behavior
|
||||
* @returns {number} The number of identifier queries
|
||||
*/
|
||||
function countIdentifiers(parsedSelector) {
|
||||
switch (parsedSelector.type) {
|
||||
case "child":
|
||||
case "descendant":
|
||||
case "sibling":
|
||||
case "adjacent":
|
||||
return countIdentifiers(parsedSelector.left) + countIdentifiers(parsedSelector.right);
|
||||
|
||||
case "compound":
|
||||
case "not":
|
||||
case "matches":
|
||||
return parsedSelector.selectors.reduce((sum, childSelector) => sum + countIdentifiers(childSelector), 0);
|
||||
|
||||
case "identifier":
|
||||
return 1;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the specificity of two selector objects, with CSS-like rules.
|
||||
* @param {ASTSelector} selectorA An AST selector descriptor
|
||||
* @param {ASTSelector} selectorB Another AST selector descriptor
|
||||
* @returns {number}
|
||||
* a value less than 0 if selectorA is less specific than selectorB
|
||||
* a value greater than 0 if selectorA is more specific than selectorB
|
||||
* a value less than 0 if selectorA and selectorB have the same specificity, and selectorA <= selectorB alphabetically
|
||||
* a value greater than 0 if selectorA and selectorB have the same specificity, and selectorA > selectorB alphabetically
|
||||
*/
|
||||
function compareSpecificity(selectorA, selectorB) {
|
||||
return selectorA.attributeCount - selectorB.attributeCount ||
|
||||
selectorA.identifierCount - selectorB.identifierCount ||
|
||||
(selectorA.rawSelector <= selectorB.rawSelector ? -1 : 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a raw selector string, and throws a useful error if parsing fails.
|
||||
* @param {string} rawSelector A raw AST selector
|
||||
* @returns {Object} An object (from esquery) describing the matching behavior of this selector
|
||||
* @throws {Error} An error if the selector is invalid
|
||||
*/
|
||||
function tryParseSelector(rawSelector) {
|
||||
try {
|
||||
return esquery.parse(rawSelector.replace(/:exit$/u, ""));
|
||||
} catch (err) {
|
||||
if (err.location && err.location.start && typeof err.location.start.offset === "number") {
|
||||
throw new SyntaxError(`Syntax error in selector "${rawSelector}" at position ${err.location.start.offset}: ${err.message}`);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
const selectorCache = new Map();
|
||||
|
||||
/**
|
||||
* Parses a raw selector string, and returns the parsed selector along with specificity and type information.
|
||||
* @param {string} rawSelector A raw AST selector
|
||||
* @returns {ASTSelector} A selector descriptor
|
||||
*/
|
||||
function parseSelector(rawSelector) {
|
||||
if (selectorCache.has(rawSelector)) {
|
||||
return selectorCache.get(rawSelector);
|
||||
}
|
||||
|
||||
const parsedSelector = tryParseSelector(rawSelector);
|
||||
|
||||
const result = {
|
||||
rawSelector,
|
||||
isExit: rawSelector.endsWith(":exit"),
|
||||
parsedSelector,
|
||||
listenerTypes: getPossibleTypes(parsedSelector),
|
||||
attributeCount: countClassAttributes(parsedSelector),
|
||||
identifierCount: countIdentifiers(parsedSelector)
|
||||
};
|
||||
|
||||
selectorCache.set(rawSelector, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Public Interface
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* The event generator for AST nodes.
|
||||
* This implements below interface.
|
||||
*
|
||||
* ```ts
|
||||
* interface EventGenerator {
|
||||
* emitter: SafeEmitter;
|
||||
* enterNode(node: ASTNode): void;
|
||||
* leaveNode(node: ASTNode): void;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
class NodeEventGenerator {
|
||||
|
||||
/**
|
||||
* @param {SafeEmitter} emitter
|
||||
* An SafeEmitter which is the destination of events. This emitter must already
|
||||
* have registered listeners for all of the events that it needs to listen for.
|
||||
* (See lib/linter/safe-emitter.js for more details on `SafeEmitter`.)
|
||||
* @param {ESQueryOptions} esqueryOptions `esquery` options for traversing custom nodes.
|
||||
* @returns {NodeEventGenerator} new instance
|
||||
*/
|
||||
constructor(emitter, esqueryOptions) {
|
||||
this.emitter = emitter;
|
||||
this.esqueryOptions = esqueryOptions;
|
||||
this.currentAncestry = [];
|
||||
this.enterSelectorsByNodeType = new Map();
|
||||
this.exitSelectorsByNodeType = new Map();
|
||||
this.anyTypeEnterSelectors = [];
|
||||
this.anyTypeExitSelectors = [];
|
||||
|
||||
emitter.eventNames().forEach(rawSelector => {
|
||||
const selector = parseSelector(rawSelector);
|
||||
|
||||
if (selector.listenerTypes) {
|
||||
const typeMap = selector.isExit ? this.exitSelectorsByNodeType : this.enterSelectorsByNodeType;
|
||||
|
||||
selector.listenerTypes.forEach(nodeType => {
|
||||
if (!typeMap.has(nodeType)) {
|
||||
typeMap.set(nodeType, []);
|
||||
}
|
||||
typeMap.get(nodeType).push(selector);
|
||||
});
|
||||
return;
|
||||
}
|
||||
const selectors = selector.isExit ? this.anyTypeExitSelectors : this.anyTypeEnterSelectors;
|
||||
|
||||
selectors.push(selector);
|
||||
});
|
||||
|
||||
this.anyTypeEnterSelectors.sort(compareSpecificity);
|
||||
this.anyTypeExitSelectors.sort(compareSpecificity);
|
||||
this.enterSelectorsByNodeType.forEach(selectorList => selectorList.sort(compareSpecificity));
|
||||
this.exitSelectorsByNodeType.forEach(selectorList => selectorList.sort(compareSpecificity));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a selector against a node, and emits it if it matches
|
||||
* @param {ASTNode} node The node to check
|
||||
* @param {ASTSelector} selector An AST selector descriptor
|
||||
* @returns {void}
|
||||
*/
|
||||
applySelector(node, selector) {
|
||||
if (esquery.matches(node, selector.parsedSelector, this.currentAncestry, this.esqueryOptions)) {
|
||||
this.emitter.emit(selector.rawSelector, node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies all appropriate selectors to a node, in specificity order
|
||||
* @param {ASTNode} node The node to check
|
||||
* @param {boolean} isExit `false` if the node is currently being entered, `true` if it's currently being exited
|
||||
* @returns {void}
|
||||
*/
|
||||
applySelectors(node, isExit) {
|
||||
const selectorsByNodeType = (isExit ? this.exitSelectorsByNodeType : this.enterSelectorsByNodeType).get(node.type) || [];
|
||||
const anyTypeSelectors = isExit ? this.anyTypeExitSelectors : this.anyTypeEnterSelectors;
|
||||
|
||||
/*
|
||||
* selectorsByNodeType and anyTypeSelectors were already sorted by specificity in the constructor.
|
||||
* Iterate through each of them, applying selectors in the right order.
|
||||
*/
|
||||
let selectorsByTypeIndex = 0;
|
||||
let anyTypeSelectorsIndex = 0;
|
||||
|
||||
while (selectorsByTypeIndex < selectorsByNodeType.length || anyTypeSelectorsIndex < anyTypeSelectors.length) {
|
||||
if (
|
||||
selectorsByTypeIndex >= selectorsByNodeType.length ||
|
||||
anyTypeSelectorsIndex < anyTypeSelectors.length &&
|
||||
compareSpecificity(anyTypeSelectors[anyTypeSelectorsIndex], selectorsByNodeType[selectorsByTypeIndex]) < 0
|
||||
) {
|
||||
this.applySelector(node, anyTypeSelectors[anyTypeSelectorsIndex++]);
|
||||
} else {
|
||||
this.applySelector(node, selectorsByNodeType[selectorsByTypeIndex++]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event of entering AST node.
|
||||
* @param {ASTNode} node A node which was entered.
|
||||
* @returns {void}
|
||||
*/
|
||||
enterNode(node) {
|
||||
this.applySelectors(node, false);
|
||||
this.currentAncestry.unshift(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event of leaving AST node.
|
||||
* @param {ASTNode} node A node which was left.
|
||||
* @returns {void}
|
||||
*/
|
||||
leaveNode(node) {
|
||||
this.currentAncestry.shift();
|
||||
this.applySelectors(node, true);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NodeEventGenerator;
|
376
node_modules/eslint/lib/linter/report-translator.js
generated
vendored
Normal file
376
node_modules/eslint/lib/linter/report-translator.js
generated
vendored
Normal file
@ -0,0 +1,376 @@
|
||||
/**
|
||||
* @fileoverview A helper that translates context.report() calls from the rule API into generic problem objects
|
||||
* @author Teddy Katz
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const assert = require("../shared/assert");
|
||||
const { RuleFixer } = require("./rule-fixer");
|
||||
const { interpolate } = require("./interpolate");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Typedefs
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @typedef {import("../shared/types").LintMessage} LintMessage */
|
||||
|
||||
/**
|
||||
* An error message description
|
||||
* @typedef {Object} MessageDescriptor
|
||||
* @property {ASTNode} [node] The reported node
|
||||
* @property {Location} loc The location of the problem.
|
||||
* @property {string} message The problem message.
|
||||
* @property {Object} [data] Optional data to use to fill in placeholders in the
|
||||
* message.
|
||||
* @property {Function} [fix] The function to call that creates a fix command.
|
||||
* @property {Array<{desc?: string, messageId?: string, fix: Function}>} suggest Suggestion descriptions and functions to create a the associated fixes.
|
||||
*/
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Module Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
/**
|
||||
* Translates a multi-argument context.report() call into a single object argument call
|
||||
* @param {...*} args A list of arguments passed to `context.report`
|
||||
* @returns {MessageDescriptor} A normalized object containing report information
|
||||
*/
|
||||
function normalizeMultiArgReportCall(...args) {
|
||||
|
||||
// If there is one argument, it is considered to be a new-style call already.
|
||||
if (args.length === 1) {
|
||||
|
||||
// Shallow clone the object to avoid surprises if reusing the descriptor
|
||||
return Object.assign({}, args[0]);
|
||||
}
|
||||
|
||||
// If the second argument is a string, the arguments are interpreted as [node, message, data, fix].
|
||||
if (typeof args[1] === "string") {
|
||||
return {
|
||||
node: args[0],
|
||||
message: args[1],
|
||||
data: args[2],
|
||||
fix: args[3]
|
||||
};
|
||||
}
|
||||
|
||||
// Otherwise, the arguments are interpreted as [node, loc, message, data, fix].
|
||||
return {
|
||||
node: args[0],
|
||||
loc: args[1],
|
||||
message: args[2],
|
||||
data: args[3],
|
||||
fix: args[4]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that either a loc or a node was provided, and the node is valid if it was provided.
|
||||
* @param {MessageDescriptor} descriptor A descriptor to validate
|
||||
* @returns {void}
|
||||
* @throws AssertionError if neither a node nor a loc was provided, or if the node is not an object
|
||||
*/
|
||||
function assertValidNodeInfo(descriptor) {
|
||||
if (descriptor.node) {
|
||||
assert(typeof descriptor.node === "object", "Node must be an object");
|
||||
} else {
|
||||
assert(descriptor.loc, "Node must be provided when reporting error if location is not provided");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a MessageDescriptor to always have a `loc` with `start` and `end` properties
|
||||
* @param {MessageDescriptor} descriptor A descriptor for the report from a rule.
|
||||
* @returns {{start: Location, end: (Location|null)}} An updated location that infers the `start` and `end` properties
|
||||
* from the `node` of the original descriptor, or infers the `start` from the `loc` of the original descriptor.
|
||||
*/
|
||||
function normalizeReportLoc(descriptor) {
|
||||
if (descriptor.loc.start) {
|
||||
return descriptor.loc;
|
||||
}
|
||||
return { start: descriptor.loc, end: null };
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones the given fix object.
|
||||
* @param {Fix|null} fix The fix to clone.
|
||||
* @returns {Fix|null} Deep cloned fix object or `null` if `null` or `undefined` was passed in.
|
||||
*/
|
||||
function cloneFix(fix) {
|
||||
if (!fix) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
range: [fix.range[0], fix.range[1]],
|
||||
text: fix.text
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that a fix has a valid range.
|
||||
* @param {Fix|null} fix The fix to validate.
|
||||
* @returns {void}
|
||||
*/
|
||||
function assertValidFix(fix) {
|
||||
if (fix) {
|
||||
assert(fix.range && typeof fix.range[0] === "number" && typeof fix.range[1] === "number", `Fix has invalid range: ${JSON.stringify(fix, null, 2)}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares items in a fixes array by range.
|
||||
* @param {Fix} a The first message.
|
||||
* @param {Fix} b The second message.
|
||||
* @returns {int} -1 if a comes before b, 1 if a comes after b, 0 if equal.
|
||||
* @private
|
||||
*/
|
||||
function compareFixesByRange(a, b) {
|
||||
return a.range[0] - b.range[0] || a.range[1] - b.range[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the given fixes array into one.
|
||||
* @param {Fix[]} fixes The fixes to merge.
|
||||
* @param {SourceCode} sourceCode The source code object to get the text between fixes.
|
||||
* @returns {{text: string, range: number[]}} The merged fixes
|
||||
*/
|
||||
function mergeFixes(fixes, sourceCode) {
|
||||
for (const fix of fixes) {
|
||||
assertValidFix(fix);
|
||||
}
|
||||
|
||||
if (fixes.length === 0) {
|
||||
return null;
|
||||
}
|
||||
if (fixes.length === 1) {
|
||||
return cloneFix(fixes[0]);
|
||||
}
|
||||
|
||||
fixes.sort(compareFixesByRange);
|
||||
|
||||
const originalText = sourceCode.text;
|
||||
const start = fixes[0].range[0];
|
||||
const end = fixes.at(-1).range[1];
|
||||
let text = "";
|
||||
let lastPos = Number.MIN_SAFE_INTEGER;
|
||||
|
||||
for (const fix of fixes) {
|
||||
assert(fix.range[0] >= lastPos, "Fix objects must not be overlapped in a report.");
|
||||
|
||||
if (fix.range[0] >= 0) {
|
||||
text += originalText.slice(Math.max(0, start, lastPos), fix.range[0]);
|
||||
}
|
||||
text += fix.text;
|
||||
lastPos = fix.range[1];
|
||||
}
|
||||
text += originalText.slice(Math.max(0, start, lastPos), end);
|
||||
|
||||
return { range: [start, end], text };
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets one fix object from the given descriptor.
|
||||
* If the descriptor retrieves multiple fixes, this merges those to one.
|
||||
* @param {MessageDescriptor} descriptor The report descriptor.
|
||||
* @param {SourceCode} sourceCode The source code object to get text between fixes.
|
||||
* @returns {({text: string, range: number[]}|null)} The fix for the descriptor
|
||||
*/
|
||||
function normalizeFixes(descriptor, sourceCode) {
|
||||
if (typeof descriptor.fix !== "function") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const ruleFixer = new RuleFixer({ sourceCode });
|
||||
|
||||
// @type {null | Fix | Fix[] | IterableIterator<Fix>}
|
||||
const fix = descriptor.fix(ruleFixer);
|
||||
|
||||
// Merge to one.
|
||||
if (fix && Symbol.iterator in fix) {
|
||||
return mergeFixes(Array.from(fix), sourceCode);
|
||||
}
|
||||
|
||||
assertValidFix(fix);
|
||||
return cloneFix(fix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of suggestion objects from the given descriptor.
|
||||
* @param {MessageDescriptor} descriptor The report descriptor.
|
||||
* @param {SourceCode} sourceCode The source code object to get text between fixes.
|
||||
* @param {Object} messages Object of meta messages for the rule.
|
||||
* @returns {Array<SuggestionResult>} The suggestions for the descriptor
|
||||
*/
|
||||
function mapSuggestions(descriptor, sourceCode, messages) {
|
||||
if (!descriptor.suggest || !Array.isArray(descriptor.suggest)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return descriptor.suggest
|
||||
.map(suggestInfo => {
|
||||
const computedDesc = suggestInfo.desc || messages[suggestInfo.messageId];
|
||||
|
||||
return {
|
||||
...suggestInfo,
|
||||
desc: interpolate(computedDesc, suggestInfo.data),
|
||||
fix: normalizeFixes(suggestInfo, sourceCode)
|
||||
};
|
||||
})
|
||||
|
||||
// Remove suggestions that didn't provide a fix
|
||||
.filter(({ fix }) => fix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates information about the report from a descriptor
|
||||
* @param {Object} options Information about the problem
|
||||
* @param {string} options.ruleId Rule ID
|
||||
* @param {(0|1|2)} options.severity Rule severity
|
||||
* @param {(ASTNode|null)} options.node Node
|
||||
* @param {string} options.message Error message
|
||||
* @param {string} [options.messageId] The error message ID.
|
||||
* @param {{start: SourceLocation, end: (SourceLocation|null)}} options.loc Start and end location
|
||||
* @param {{text: string, range: (number[]|null)}} options.fix The fix object
|
||||
* @param {Array<{text: string, range: (number[]|null)}>} options.suggestions The array of suggestions objects
|
||||
* @param {Language} [options.language] The language to use to adjust line and column offsets.
|
||||
* @returns {LintMessage} Information about the report
|
||||
*/
|
||||
function createProblem(options) {
|
||||
const { language } = options;
|
||||
|
||||
// calculate offsets based on the language in use
|
||||
const columnOffset = language.columnStart === 1 ? 0 : 1;
|
||||
const lineOffset = language.lineStart === 1 ? 0 : 1;
|
||||
|
||||
const problem = {
|
||||
ruleId: options.ruleId,
|
||||
severity: options.severity,
|
||||
message: options.message,
|
||||
line: options.loc.start.line + lineOffset,
|
||||
column: options.loc.start.column + columnOffset,
|
||||
nodeType: options.node && options.node.type || null
|
||||
};
|
||||
|
||||
/*
|
||||
* If this isn’t in the conditional, some of the tests fail
|
||||
* because `messageId` is present in the problem object
|
||||
*/
|
||||
if (options.messageId) {
|
||||
problem.messageId = options.messageId;
|
||||
}
|
||||
|
||||
if (options.loc.end) {
|
||||
problem.endLine = options.loc.end.line + lineOffset;
|
||||
problem.endColumn = options.loc.end.column + columnOffset;
|
||||
}
|
||||
|
||||
if (options.fix) {
|
||||
problem.fix = options.fix;
|
||||
}
|
||||
|
||||
if (options.suggestions && options.suggestions.length > 0) {
|
||||
problem.suggestions = options.suggestions;
|
||||
}
|
||||
|
||||
return problem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that suggestions are properly defined. Throws if an error is detected.
|
||||
* @param {Array<{ desc?: string, messageId?: string }>} suggest The incoming suggest data.
|
||||
* @param {Object} messages Object of meta messages for the rule.
|
||||
* @returns {void}
|
||||
*/
|
||||
function validateSuggestions(suggest, messages) {
|
||||
if (suggest && Array.isArray(suggest)) {
|
||||
suggest.forEach(suggestion => {
|
||||
if (suggestion.messageId) {
|
||||
const { messageId } = suggestion;
|
||||
|
||||
if (!messages) {
|
||||
throw new TypeError(`context.report() called with a suggest option with a messageId '${messageId}', but no messages were present in the rule metadata.`);
|
||||
}
|
||||
|
||||
if (!messages[messageId]) {
|
||||
throw new TypeError(`context.report() called with a suggest option with a messageId '${messageId}' which is not present in the 'messages' config: ${JSON.stringify(messages, null, 2)}`);
|
||||
}
|
||||
|
||||
if (suggestion.desc) {
|
||||
throw new TypeError("context.report() called with a suggest option that defines both a 'messageId' and an 'desc'. Please only pass one.");
|
||||
}
|
||||
} else if (!suggestion.desc) {
|
||||
throw new TypeError("context.report() called with a suggest option that doesn't have either a `desc` or `messageId`");
|
||||
}
|
||||
|
||||
if (typeof suggestion.fix !== "function") {
|
||||
throw new TypeError(`context.report() called with a suggest option without a fix function. See: ${suggestion}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a function that converts the arguments of a `context.report` call from a rule into a reported
|
||||
* problem for the Node.js API.
|
||||
* @param {{ruleId: string, severity: number, sourceCode: SourceCode, messageIds: Object, disableFixes: boolean, language:Language}} metadata Metadata for the reported problem
|
||||
* @returns {function(...args): LintMessage} Function that returns information about the report
|
||||
*/
|
||||
|
||||
module.exports = function createReportTranslator(metadata) {
|
||||
|
||||
/*
|
||||
* `createReportTranslator` gets called once per enabled rule per file. It needs to be very performant.
|
||||
* The report translator itself (i.e. the function that `createReportTranslator` returns) gets
|
||||
* called every time a rule reports a problem, which happens much less frequently (usually, the vast
|
||||
* majority of rules don't report any problems for a given file).
|
||||
*/
|
||||
return (...args) => {
|
||||
const descriptor = normalizeMultiArgReportCall(...args);
|
||||
const messages = metadata.messageIds;
|
||||
const { sourceCode } = metadata;
|
||||
|
||||
assertValidNodeInfo(descriptor);
|
||||
|
||||
let computedMessage;
|
||||
|
||||
if (descriptor.messageId) {
|
||||
if (!messages) {
|
||||
throw new TypeError("context.report() called with a messageId, but no messages were present in the rule metadata.");
|
||||
}
|
||||
const id = descriptor.messageId;
|
||||
|
||||
if (descriptor.message) {
|
||||
throw new TypeError("context.report() called with a message and a messageId. Please only pass one.");
|
||||
}
|
||||
if (!messages || !Object.hasOwn(messages, id)) {
|
||||
throw new TypeError(`context.report() called with a messageId of '${id}' which is not present in the 'messages' config: ${JSON.stringify(messages, null, 2)}`);
|
||||
}
|
||||
computedMessage = messages[id];
|
||||
} else if (descriptor.message) {
|
||||
computedMessage = descriptor.message;
|
||||
} else {
|
||||
throw new TypeError("Missing `message` property in report() call; add a message that describes the linting problem.");
|
||||
}
|
||||
|
||||
validateSuggestions(descriptor.suggest, messages);
|
||||
|
||||
return createProblem({
|
||||
ruleId: metadata.ruleId,
|
||||
severity: metadata.severity,
|
||||
node: descriptor.node,
|
||||
message: interpolate(computedMessage, descriptor.data),
|
||||
messageId: descriptor.messageId,
|
||||
loc: descriptor.loc ? normalizeReportLoc(descriptor) : sourceCode.getLoc(descriptor.node),
|
||||
fix: metadata.disableFixes ? null : normalizeFixes(descriptor, sourceCode),
|
||||
suggestions: metadata.disableFixes ? [] : mapSuggestions(descriptor, sourceCode, messages),
|
||||
language: metadata.language
|
||||
});
|
||||
};
|
||||
};
|
163
node_modules/eslint/lib/linter/rule-fixer.js
generated
vendored
Normal file
163
node_modules/eslint/lib/linter/rule-fixer.js
generated
vendored
Normal file
@ -0,0 +1,163 @@
|
||||
/**
|
||||
* @fileoverview An object that creates fix commands for rules.
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
/* eslint class-methods-use-this: off -- Methods desired on instance */
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// none!
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Creates a fix command that inserts text at the specified index in the source text.
|
||||
* @param {int} index The 0-based index at which to insert the new text.
|
||||
* @param {string} text The text to insert.
|
||||
* @returns {Object} The fix command.
|
||||
* @private
|
||||
*/
|
||||
function insertTextAt(index, text) {
|
||||
return {
|
||||
range: [index, index],
|
||||
text
|
||||
};
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Public Interface
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Creates code fixing commands for rules.
|
||||
*/
|
||||
class RuleFixer {
|
||||
|
||||
/**
|
||||
* The source code object representing the text to be fixed.
|
||||
* @type {SourceCode}
|
||||
*/
|
||||
#sourceCode;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {Object} options The options for the fixer.
|
||||
* @param {SourceCode} options.sourceCode The source code object representing the text to be fixed.
|
||||
*/
|
||||
constructor({ sourceCode }) {
|
||||
this.#sourceCode = sourceCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a fix command that inserts text after the given node or token.
|
||||
* The fix is not applied until applyFixes() is called.
|
||||
* @param {ASTNode|Token} nodeOrToken The node or token to insert after.
|
||||
* @param {string} text The text to insert.
|
||||
* @returns {Object} The fix command.
|
||||
*/
|
||||
insertTextAfter(nodeOrToken, text) {
|
||||
const range = this.#sourceCode.getRange(nodeOrToken);
|
||||
|
||||
return this.insertTextAfterRange(range, text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a fix command that inserts text after the specified range in the source text.
|
||||
* The fix is not applied until applyFixes() is called.
|
||||
* @param {int[]} range The range to replace, first item is start of range, second
|
||||
* is end of range.
|
||||
* @param {string} text The text to insert.
|
||||
* @returns {Object} The fix command.
|
||||
*/
|
||||
insertTextAfterRange(range, text) {
|
||||
return insertTextAt(range[1], text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a fix command that inserts text before the given node or token.
|
||||
* The fix is not applied until applyFixes() is called.
|
||||
* @param {ASTNode|Token} nodeOrToken The node or token to insert before.
|
||||
* @param {string} text The text to insert.
|
||||
* @returns {Object} The fix command.
|
||||
*/
|
||||
insertTextBefore(nodeOrToken, text) {
|
||||
const range = this.#sourceCode.getRange(nodeOrToken);
|
||||
|
||||
return this.insertTextBeforeRange(range, text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a fix command that inserts text before the specified range in the source text.
|
||||
* The fix is not applied until applyFixes() is called.
|
||||
* @param {int[]} range The range to replace, first item is start of range, second
|
||||
* is end of range.
|
||||
* @param {string} text The text to insert.
|
||||
* @returns {Object} The fix command.
|
||||
*/
|
||||
insertTextBeforeRange(range, text) {
|
||||
return insertTextAt(range[0], text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a fix command that replaces text at the node or token.
|
||||
* The fix is not applied until applyFixes() is called.
|
||||
* @param {ASTNode|Token} nodeOrToken The node or token to remove.
|
||||
* @param {string} text The text to insert.
|
||||
* @returns {Object} The fix command.
|
||||
*/
|
||||
replaceText(nodeOrToken, text) {
|
||||
const range = this.#sourceCode.getRange(nodeOrToken);
|
||||
|
||||
return this.replaceTextRange(range, text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a fix command that replaces text at the specified range in the source text.
|
||||
* The fix is not applied until applyFixes() is called.
|
||||
* @param {int[]} range The range to replace, first item is start of range, second
|
||||
* is end of range.
|
||||
* @param {string} text The text to insert.
|
||||
* @returns {Object} The fix command.
|
||||
*/
|
||||
replaceTextRange(range, text) {
|
||||
return {
|
||||
range,
|
||||
text
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a fix command that removes the node or token from the source.
|
||||
* The fix is not applied until applyFixes() is called.
|
||||
* @param {ASTNode|Token} nodeOrToken The node or token to remove.
|
||||
* @returns {Object} The fix command.
|
||||
*/
|
||||
remove(nodeOrToken) {
|
||||
const range = this.#sourceCode.getRange(nodeOrToken);
|
||||
|
||||
return this.removeRange(range);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a fix command that removes the specified range of text from the source.
|
||||
* The fix is not applied until applyFixes() is called.
|
||||
* @param {int[]} range The range to remove, first item is start of range, second
|
||||
* is end of range.
|
||||
* @returns {Object} The fix command.
|
||||
*/
|
||||
removeRange(range) {
|
||||
return {
|
||||
range,
|
||||
text: ""
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = { RuleFixer };
|
71
node_modules/eslint/lib/linter/rules.js
generated
vendored
Normal file
71
node_modules/eslint/lib/linter/rules.js
generated
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
/**
|
||||
* @fileoverview Defines a storage for rules.
|
||||
* @author Nicholas C. Zakas
|
||||
* @author aladdin-add
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const builtInRules = require("../rules");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Typedefs
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @typedef {import("../shared/types").Rule} Rule */
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Public Interface
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* A storage for rules.
|
||||
*/
|
||||
class Rules {
|
||||
constructor() {
|
||||
this._rules = Object.create(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a rule module for rule id in storage.
|
||||
* @param {string} ruleId Rule id (file name).
|
||||
* @param {Rule} rule Rule object.
|
||||
* @returns {void}
|
||||
*/
|
||||
define(ruleId, rule) {
|
||||
this._rules[ruleId] = rule;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access rule handler by id (file name).
|
||||
* @param {string} ruleId Rule id (file name).
|
||||
* @returns {Rule} Rule object.
|
||||
*/
|
||||
get(ruleId) {
|
||||
if (typeof this._rules[ruleId] === "string") {
|
||||
this.define(ruleId, require(this._rules[ruleId]));
|
||||
}
|
||||
if (this._rules[ruleId]) {
|
||||
return this._rules[ruleId];
|
||||
}
|
||||
if (builtInRules.has(ruleId)) {
|
||||
return builtInRules.get(ruleId);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
*[Symbol.iterator]() {
|
||||
yield* builtInRules;
|
||||
|
||||
for (const ruleId of Object.keys(this._rules)) {
|
||||
yield [ruleId, this.get(ruleId)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Rules;
|
52
node_modules/eslint/lib/linter/safe-emitter.js
generated
vendored
Normal file
52
node_modules/eslint/lib/linter/safe-emitter.js
generated
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* @fileoverview A variant of EventEmitter which does not give listeners information about each other
|
||||
* @author Teddy Katz
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Typedefs
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* An event emitter
|
||||
* @typedef {Object} SafeEmitter
|
||||
* @property {(eventName: string, listenerFunc: Function) => void} on Adds a listener for a given event name
|
||||
* @property {(eventName: string, arg1?: any, arg2?: any, arg3?: any) => void} emit Emits an event with a given name.
|
||||
* This calls all the listeners that were listening for that name, with `arg1`, `arg2`, and `arg3` as arguments.
|
||||
* @property {function(): string[]} eventNames Gets the list of event names that have registered listeners.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates an object which can listen for and emit events.
|
||||
* This is similar to the EventEmitter API in Node's standard library, but it has a few differences.
|
||||
* The goal is to allow multiple modules to attach arbitrary listeners to the same emitter, without
|
||||
* letting the modules know about each other at all.
|
||||
* 1. It has no special keys like `error` and `newListener`, which would allow modules to detect when
|
||||
* another module throws an error or registers a listener.
|
||||
* 2. It calls listener functions without any `this` value. (`EventEmitter` calls listeners with a
|
||||
* `this` value of the emitter instance, which would give listeners access to other listeners.)
|
||||
* @returns {SafeEmitter} An emitter
|
||||
*/
|
||||
module.exports = () => {
|
||||
const listeners = Object.create(null);
|
||||
|
||||
return Object.freeze({
|
||||
on(eventName, listener) {
|
||||
if (eventName in listeners) {
|
||||
listeners[eventName].push(listener);
|
||||
} else {
|
||||
listeners[eventName] = [listener];
|
||||
}
|
||||
},
|
||||
emit(eventName, ...args) {
|
||||
if (eventName in listeners) {
|
||||
listeners[eventName].forEach(listener => listener(...args));
|
||||
}
|
||||
},
|
||||
eventNames() {
|
||||
return Object.keys(listeners);
|
||||
}
|
||||
});
|
||||
};
|
152
node_modules/eslint/lib/linter/source-code-fixer.js
generated
vendored
Normal file
152
node_modules/eslint/lib/linter/source-code-fixer.js
generated
vendored
Normal file
@ -0,0 +1,152 @@
|
||||
/**
|
||||
* @fileoverview An object that caches and applies source code fixes.
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const debug = require("debug")("eslint:source-code-fixer");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const BOM = "\uFEFF";
|
||||
|
||||
/**
|
||||
* Compares items in a messages array by range.
|
||||
* @param {Message} a The first message.
|
||||
* @param {Message} b The second message.
|
||||
* @returns {int} -1 if a comes before b, 1 if a comes after b, 0 if equal.
|
||||
* @private
|
||||
*/
|
||||
function compareMessagesByFixRange(a, b) {
|
||||
return a.fix.range[0] - b.fix.range[0] || a.fix.range[1] - b.fix.range[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares items in a messages array by line and column.
|
||||
* @param {Message} a The first message.
|
||||
* @param {Message} b The second message.
|
||||
* @returns {int} -1 if a comes before b, 1 if a comes after b, 0 if equal.
|
||||
* @private
|
||||
*/
|
||||
function compareMessagesByLocation(a, b) {
|
||||
return a.line - b.line || a.column - b.column;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Public Interface
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Utility for apply fixes to source code.
|
||||
* @constructor
|
||||
*/
|
||||
function SourceCodeFixer() {
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the fixes specified by the messages to the given text. Tries to be
|
||||
* smart about the fixes and won't apply fixes over the same area in the text.
|
||||
* @param {string} sourceText The text to apply the changes to.
|
||||
* @param {Message[]} messages The array of messages reported by ESLint.
|
||||
* @param {boolean|Function} [shouldFix=true] Determines whether each message should be fixed
|
||||
* @returns {Object} An object containing the fixed text and any unfixed messages.
|
||||
*/
|
||||
SourceCodeFixer.applyFixes = function(sourceText, messages, shouldFix) {
|
||||
debug("Applying fixes");
|
||||
|
||||
if (shouldFix === false) {
|
||||
debug("shouldFix parameter was false, not attempting fixes");
|
||||
return {
|
||||
fixed: false,
|
||||
messages,
|
||||
output: sourceText
|
||||
};
|
||||
}
|
||||
|
||||
// clone the array
|
||||
const remainingMessages = [],
|
||||
fixes = [],
|
||||
bom = sourceText.startsWith(BOM) ? BOM : "",
|
||||
text = bom ? sourceText.slice(1) : sourceText;
|
||||
let lastPos = Number.NEGATIVE_INFINITY,
|
||||
output = bom;
|
||||
|
||||
/**
|
||||
* Try to use the 'fix' from a problem.
|
||||
* @param {Message} problem The message object to apply fixes from
|
||||
* @returns {boolean} Whether fix was successfully applied
|
||||
*/
|
||||
function attemptFix(problem) {
|
||||
const fix = problem.fix;
|
||||
const start = fix.range[0];
|
||||
const end = fix.range[1];
|
||||
|
||||
// Remain it as a problem if it's overlapped or it's a negative range
|
||||
if (lastPos >= start || start > end) {
|
||||
remainingMessages.push(problem);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove BOM.
|
||||
if ((start < 0 && end >= 0) || (start === 0 && fix.text.startsWith(BOM))) {
|
||||
output = "";
|
||||
}
|
||||
|
||||
// Make output to this fix.
|
||||
output += text.slice(Math.max(0, lastPos), Math.max(0, start));
|
||||
output += fix.text;
|
||||
lastPos = end;
|
||||
return true;
|
||||
}
|
||||
|
||||
messages.forEach(problem => {
|
||||
if (Object.hasOwn(problem, "fix")) {
|
||||
fixes.push(problem);
|
||||
} else {
|
||||
remainingMessages.push(problem);
|
||||
}
|
||||
});
|
||||
|
||||
if (fixes.length) {
|
||||
debug("Found fixes to apply");
|
||||
let fixesWereApplied = false;
|
||||
|
||||
for (const problem of fixes.sort(compareMessagesByFixRange)) {
|
||||
if (typeof shouldFix !== "function" || shouldFix(problem)) {
|
||||
attemptFix(problem);
|
||||
|
||||
/*
|
||||
* The only time attemptFix will fail is if a previous fix was
|
||||
* applied which conflicts with it. So we can mark this as true.
|
||||
*/
|
||||
fixesWereApplied = true;
|
||||
} else {
|
||||
remainingMessages.push(problem);
|
||||
}
|
||||
}
|
||||
output += text.slice(Math.max(0, lastPos));
|
||||
|
||||
return {
|
||||
fixed: fixesWereApplied,
|
||||
messages: remainingMessages.sort(compareMessagesByLocation),
|
||||
output
|
||||
};
|
||||
}
|
||||
|
||||
debug("No fixes to apply");
|
||||
return {
|
||||
fixed: false,
|
||||
messages,
|
||||
output: bom + text
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports = SourceCodeFixer;
|
169
node_modules/eslint/lib/linter/timing.js
generated
vendored
Normal file
169
node_modules/eslint/lib/linter/timing.js
generated
vendored
Normal file
@ -0,0 +1,169 @@
|
||||
/**
|
||||
* @fileoverview Tracks performance of individual rules.
|
||||
* @author Brandon Mills
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const { startTime, endTime } = require("../shared/stats");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/* c8 ignore next */
|
||||
/**
|
||||
* Align the string to left
|
||||
* @param {string} str string to evaluate
|
||||
* @param {int} len length of the string
|
||||
* @param {string} ch delimiter character
|
||||
* @returns {string} modified string
|
||||
* @private
|
||||
*/
|
||||
function alignLeft(str, len, ch) {
|
||||
return str + new Array(len - str.length + 1).join(ch || " ");
|
||||
}
|
||||
|
||||
/* c8 ignore next */
|
||||
/**
|
||||
* Align the string to right
|
||||
* @param {string} str string to evaluate
|
||||
* @param {int} len length of the string
|
||||
* @param {string} ch delimiter character
|
||||
* @returns {string} modified string
|
||||
* @private
|
||||
*/
|
||||
function alignRight(str, len, ch) {
|
||||
return new Array(len - str.length + 1).join(ch || " ") + str;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Module definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const enabled = !!process.env.TIMING;
|
||||
|
||||
const HEADERS = ["Rule", "Time (ms)", "Relative"];
|
||||
const ALIGN = [alignLeft, alignRight, alignRight];
|
||||
|
||||
/**
|
||||
* Decide how many rules to show in the output list.
|
||||
* @returns {number} the number of rules to show
|
||||
*/
|
||||
function getListSize() {
|
||||
const MINIMUM_SIZE = 10;
|
||||
|
||||
if (typeof process.env.TIMING !== "string") {
|
||||
return MINIMUM_SIZE;
|
||||
}
|
||||
|
||||
if (process.env.TIMING.toLowerCase() === "all") {
|
||||
return Number.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
const TIMING_ENV_VAR_AS_INTEGER = Number.parseInt(process.env.TIMING, 10);
|
||||
|
||||
return TIMING_ENV_VAR_AS_INTEGER > 10 ? TIMING_ENV_VAR_AS_INTEGER : MINIMUM_SIZE;
|
||||
}
|
||||
|
||||
/* c8 ignore next */
|
||||
/**
|
||||
* display the data
|
||||
* @param {Object} data Data object to be displayed
|
||||
* @returns {void} prints modified string with console.log
|
||||
* @private
|
||||
*/
|
||||
function display(data) {
|
||||
let total = 0;
|
||||
const rows = Object.keys(data)
|
||||
.map(key => {
|
||||
const time = data[key];
|
||||
|
||||
total += time;
|
||||
return [key, time];
|
||||
})
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.slice(0, getListSize());
|
||||
|
||||
rows.forEach(row => {
|
||||
row.push(`${(row[1] * 100 / total).toFixed(1)}%`);
|
||||
row[1] = row[1].toFixed(3);
|
||||
});
|
||||
|
||||
rows.unshift(HEADERS);
|
||||
|
||||
const widths = [];
|
||||
|
||||
rows.forEach(row => {
|
||||
const len = row.length;
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
const n = row[i].length;
|
||||
|
||||
if (!widths[i] || n > widths[i]) {
|
||||
widths[i] = n;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const table = rows.map(row => (
|
||||
row
|
||||
.map((cell, index) => ALIGN[index](cell, widths[index]))
|
||||
.join(" | ")
|
||||
));
|
||||
|
||||
table.splice(1, 0, widths.map((width, index) => {
|
||||
const extraAlignment = index !== 0 && index !== widths.length - 1 ? 2 : 1;
|
||||
|
||||
return ALIGN[index](":", width + extraAlignment, "-");
|
||||
}).join("|"));
|
||||
|
||||
console.log(table.join("\n")); // eslint-disable-line no-console -- Debugging function
|
||||
}
|
||||
|
||||
/* c8 ignore next */
|
||||
module.exports = (function() {
|
||||
|
||||
const data = Object.create(null);
|
||||
|
||||
/**
|
||||
* Time the run
|
||||
* @param {any} key key from the data object
|
||||
* @param {Function} fn function to be called
|
||||
* @param {boolean} stats if 'stats' is true, return the result and the time difference
|
||||
* @returns {Function} function to be executed
|
||||
* @private
|
||||
*/
|
||||
function time(key, fn, stats) {
|
||||
|
||||
return function(...args) {
|
||||
|
||||
const t = startTime();
|
||||
const result = fn(...args);
|
||||
const tdiff = endTime(t);
|
||||
|
||||
if (enabled) {
|
||||
if (typeof data[key] === "undefined") {
|
||||
data[key] = 0;
|
||||
}
|
||||
|
||||
data[key] += tdiff;
|
||||
}
|
||||
|
||||
return stats ? { result, tdiff } : result;
|
||||
};
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
process.on("exit", () => {
|
||||
display(data);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
time,
|
||||
enabled,
|
||||
getListSize
|
||||
};
|
||||
|
||||
}());
|
118
node_modules/eslint/lib/linter/vfile.js
generated
vendored
Normal file
118
node_modules/eslint/lib/linter/vfile.js
generated
vendored
Normal file
@ -0,0 +1,118 @@
|
||||
/**
|
||||
* @fileoverview Virtual file
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Type Definitions
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/** @typedef {import("@eslint/core").File} File */
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Determines if a given value has a byte order mark (BOM).
|
||||
* @param {string|Uint8Array} value The value to check.
|
||||
* @returns {boolean} `true` if the value has a BOM, `false` otherwise.
|
||||
*/
|
||||
function hasUnicodeBOM(value) {
|
||||
return typeof value === "string"
|
||||
? value.charCodeAt(0) === 0xFEFF
|
||||
: value[0] === 0xEF && value[1] === 0xBB && value[2] === 0xBF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips Unicode BOM from the given value.
|
||||
* @param {string|Uint8Array} value The value to remove the BOM from.
|
||||
* @returns {string|Uint8Array} The stripped value.
|
||||
*/
|
||||
function stripUnicodeBOM(value) {
|
||||
|
||||
if (!hasUnicodeBOM(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (typeof value === "string") {
|
||||
|
||||
/*
|
||||
* Check Unicode BOM.
|
||||
* In JavaScript, string data is stored as UTF-16, so BOM is 0xFEFF.
|
||||
* http://www.ecma-international.org/ecma-262/6.0/#sec-unicode-format-control-characters
|
||||
*/
|
||||
return value.slice(1);
|
||||
}
|
||||
|
||||
/*
|
||||
* In a Uint8Array, the BOM is represented by three bytes: 0xEF, 0xBB, and 0xBF,
|
||||
* so we can just remove the first three bytes.
|
||||
*/
|
||||
return value.slice(3);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Exports
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Represents a virtual file inside of ESLint.
|
||||
* @implements {File}
|
||||
*/
|
||||
class VFile {
|
||||
|
||||
/**
|
||||
* The file path including any processor-created virtual path.
|
||||
* @type {string}
|
||||
* @readonly
|
||||
*/
|
||||
path;
|
||||
|
||||
/**
|
||||
* The file path on disk.
|
||||
* @type {string}
|
||||
* @readonly
|
||||
*/
|
||||
physicalPath;
|
||||
|
||||
/**
|
||||
* The file contents.
|
||||
* @type {string|Uint8Array}
|
||||
* @readonly
|
||||
*/
|
||||
body;
|
||||
|
||||
/**
|
||||
* The raw body of the file, including a BOM if present.
|
||||
* @type {string|Uint8Array}
|
||||
* @readonly
|
||||
*/
|
||||
rawBody;
|
||||
|
||||
/**
|
||||
* Indicates whether the file has a byte order mark (BOM).
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
bom;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {string} path The file path.
|
||||
* @param {string|Uint8Array} body The file contents.
|
||||
* @param {Object} [options] Additional options.
|
||||
* @param {string} [options.physicalPath] The file path on disk.
|
||||
*/
|
||||
constructor(path, body, { physicalPath } = {}) {
|
||||
this.path = path;
|
||||
this.physicalPath = physicalPath ?? path;
|
||||
this.bom = hasUnicodeBOM(body);
|
||||
this.body = stripUnicodeBOM(body);
|
||||
this.rawBody = body;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { VFile };
|
442
node_modules/eslint/lib/options.js
generated
vendored
Normal file
442
node_modules/eslint/lib/options.js
generated
vendored
Normal file
@ -0,0 +1,442 @@
|
||||
/**
|
||||
* @fileoverview Options configuration for optionator.
|
||||
* @author George Zahariev
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const optionator = require("optionator");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Typedefs
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* The options object parsed by Optionator.
|
||||
* @typedef {Object} ParsedCLIOptions
|
||||
* @property {boolean} cache Only check changed files
|
||||
* @property {string} cacheFile Path to the cache file. Deprecated: use --cache-location
|
||||
* @property {string} [cacheLocation] Path to the cache file or directory
|
||||
* @property {"metadata" | "content"} cacheStrategy Strategy to use for detecting changed files in the cache
|
||||
* @property {boolean} [color] Force enabling/disabling of color
|
||||
* @property {string} [config] Use this configuration, overriding .eslintrc.* config options if present
|
||||
* @property {boolean} debug Output debugging information
|
||||
* @property {string[]} [env] Specify environments
|
||||
* @property {boolean} envInfo Output execution environment information
|
||||
* @property {boolean} errorOnUnmatchedPattern Prevent errors when pattern is unmatched
|
||||
* @property {boolean} eslintrc Disable use of configuration from .eslintrc.*
|
||||
* @property {string[]} [ext] Specify JavaScript file extensions
|
||||
* @property {string[]} [flag] Feature flags
|
||||
* @property {boolean} fix Automatically fix problems
|
||||
* @property {boolean} fixDryRun Automatically fix problems without saving the changes to the file system
|
||||
* @property {("directive" | "problem" | "suggestion" | "layout")[]} [fixType] Specify the types of fixes to apply (directive, problem, suggestion, layout)
|
||||
* @property {string} format Use a specific output format
|
||||
* @property {string[]} [global] Define global variables
|
||||
* @property {boolean} [help] Show help
|
||||
* @property {boolean} ignore Disable use of ignore files and patterns
|
||||
* @property {string} [ignorePath] Specify path of ignore file
|
||||
* @property {string[]} [ignorePattern] Patterns of files to ignore. In eslintrc mode, these are in addition to `.eslintignore`
|
||||
* @property {boolean} init Run config initialization wizard
|
||||
* @property {boolean} inlineConfig Prevent comments from changing config or rules
|
||||
* @property {number} maxWarnings Number of warnings to trigger nonzero exit code
|
||||
* @property {string} [outputFile] Specify file to write report to
|
||||
* @property {string} [parser] Specify the parser to be used
|
||||
* @property {Object} [parserOptions] Specify parser options
|
||||
* @property {string[]} [plugin] Specify plugins
|
||||
* @property {string} [printConfig] Print the configuration for the given file
|
||||
* @property {boolean | undefined} reportUnusedDisableDirectives Adds reported errors for unused eslint-disable and eslint-enable directives
|
||||
* @property {string | undefined} reportUnusedDisableDirectivesSeverity A severity string indicating if and how unused disable and enable directives should be tracked and reported.
|
||||
* @property {string} [resolvePluginsRelativeTo] A folder where plugins should be resolved from, CWD by default
|
||||
* @property {Object} [rule] Specify rules
|
||||
* @property {string[]} [rulesdir] Load additional rules from this directory. Deprecated: Use rules from plugins
|
||||
* @property {boolean} stdin Lint code provided on <STDIN>
|
||||
* @property {string} [stdinFilename] Specify filename to process STDIN as
|
||||
* @property {boolean} quiet Report errors only
|
||||
* @property {boolean} [version] Output the version number
|
||||
* @property {boolean} warnIgnored Show warnings when the file list includes ignored files
|
||||
* @property {boolean} [passOnNoPatterns=false] When set to true, missing patterns cause
|
||||
* the linting operation to short circuit and not report any failures.
|
||||
* @property {string[]} _ Positional filenames or patterns
|
||||
* @property {boolean} [stats] Report additional statistics
|
||||
*/
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Initialization and Public Interface
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// exports "parse(args)", "generateHelp()", and "generateHelpForOption(optionName)"
|
||||
|
||||
/**
|
||||
* Creates the CLI options for ESLint.
|
||||
* @param {boolean} usingFlatConfig Indicates if flat config is being used.
|
||||
* @returns {Object} The optionator instance.
|
||||
*/
|
||||
module.exports = function(usingFlatConfig) {
|
||||
|
||||
let lookupFlag;
|
||||
|
||||
if (usingFlatConfig) {
|
||||
lookupFlag = {
|
||||
option: "config-lookup",
|
||||
type: "Boolean",
|
||||
default: "true",
|
||||
description: "Disable look up for eslint.config.js"
|
||||
};
|
||||
} else {
|
||||
lookupFlag = {
|
||||
option: "eslintrc",
|
||||
type: "Boolean",
|
||||
default: "true",
|
||||
description: "Disable use of configuration from .eslintrc.*"
|
||||
};
|
||||
}
|
||||
|
||||
let envFlag;
|
||||
|
||||
if (!usingFlatConfig) {
|
||||
envFlag = {
|
||||
option: "env",
|
||||
type: "[String]",
|
||||
description: "Specify environments"
|
||||
};
|
||||
}
|
||||
|
||||
let inspectConfigFlag;
|
||||
|
||||
if (usingFlatConfig) {
|
||||
inspectConfigFlag = {
|
||||
option: "inspect-config",
|
||||
type: "Boolean",
|
||||
description: "Open the config inspector with the current configuration"
|
||||
};
|
||||
}
|
||||
|
||||
let extFlag;
|
||||
|
||||
if (!usingFlatConfig) {
|
||||
extFlag = {
|
||||
option: "ext",
|
||||
type: "[String]",
|
||||
description: "Specify JavaScript file extensions"
|
||||
};
|
||||
}
|
||||
|
||||
let resolvePluginsFlag;
|
||||
|
||||
if (!usingFlatConfig) {
|
||||
resolvePluginsFlag = {
|
||||
option: "resolve-plugins-relative-to",
|
||||
type: "path::String",
|
||||
description: "A folder where plugins should be resolved from, CWD by default"
|
||||
};
|
||||
}
|
||||
|
||||
let rulesDirFlag;
|
||||
|
||||
if (!usingFlatConfig) {
|
||||
rulesDirFlag = {
|
||||
option: "rulesdir",
|
||||
type: "[path::String]",
|
||||
description: "Load additional rules from this directory. Deprecated: Use rules from plugins"
|
||||
};
|
||||
}
|
||||
|
||||
let ignorePathFlag;
|
||||
|
||||
if (!usingFlatConfig) {
|
||||
ignorePathFlag = {
|
||||
option: "ignore-path",
|
||||
type: "path::String",
|
||||
description: "Specify path of ignore file"
|
||||
};
|
||||
}
|
||||
|
||||
let statsFlag;
|
||||
|
||||
if (usingFlatConfig) {
|
||||
statsFlag = {
|
||||
option: "stats",
|
||||
type: "Boolean",
|
||||
default: "false",
|
||||
description: "Add statistics to the lint report"
|
||||
};
|
||||
}
|
||||
|
||||
let warnIgnoredFlag;
|
||||
|
||||
if (usingFlatConfig) {
|
||||
warnIgnoredFlag = {
|
||||
option: "warn-ignored",
|
||||
type: "Boolean",
|
||||
default: "true",
|
||||
description: "Suppress warnings when the file list includes ignored files"
|
||||
};
|
||||
}
|
||||
|
||||
let flagFlag;
|
||||
|
||||
if (usingFlatConfig) {
|
||||
flagFlag = {
|
||||
option: "flag",
|
||||
type: "[String]",
|
||||
description: "Enable a feature flag"
|
||||
};
|
||||
}
|
||||
|
||||
return optionator({
|
||||
prepend: "eslint [options] file.js [file.js] [dir]",
|
||||
defaults: {
|
||||
concatRepeatedArrays: true,
|
||||
mergeRepeatedObjects: true
|
||||
},
|
||||
options: [
|
||||
{
|
||||
heading: "Basic configuration"
|
||||
},
|
||||
lookupFlag,
|
||||
{
|
||||
option: "config",
|
||||
alias: "c",
|
||||
type: "path::String",
|
||||
description: usingFlatConfig
|
||||
? "Use this configuration instead of eslint.config.js, eslint.config.mjs, or eslint.config.cjs"
|
||||
: "Use this configuration, overriding .eslintrc.* config options if present"
|
||||
},
|
||||
inspectConfigFlag,
|
||||
envFlag,
|
||||
extFlag,
|
||||
{
|
||||
option: "global",
|
||||
type: "[String]",
|
||||
description: "Define global variables"
|
||||
},
|
||||
{
|
||||
option: "parser",
|
||||
type: "String",
|
||||
description: "Specify the parser to be used"
|
||||
},
|
||||
{
|
||||
option: "parser-options",
|
||||
type: "Object",
|
||||
description: "Specify parser options"
|
||||
},
|
||||
resolvePluginsFlag,
|
||||
{
|
||||
heading: "Specify Rules and Plugins"
|
||||
},
|
||||
{
|
||||
option: "plugin",
|
||||
type: "[String]",
|
||||
description: "Specify plugins"
|
||||
},
|
||||
{
|
||||
option: "rule",
|
||||
type: "Object",
|
||||
description: "Specify rules"
|
||||
},
|
||||
rulesDirFlag,
|
||||
{
|
||||
heading: "Fix Problems"
|
||||
},
|
||||
{
|
||||
option: "fix",
|
||||
type: "Boolean",
|
||||
default: false,
|
||||
description: "Automatically fix problems"
|
||||
},
|
||||
{
|
||||
option: "fix-dry-run",
|
||||
type: "Boolean",
|
||||
default: false,
|
||||
description: "Automatically fix problems without saving the changes to the file system"
|
||||
},
|
||||
{
|
||||
option: "fix-type",
|
||||
type: "Array",
|
||||
description: "Specify the types of fixes to apply (directive, problem, suggestion, layout)"
|
||||
},
|
||||
{
|
||||
heading: "Ignore Files"
|
||||
},
|
||||
ignorePathFlag,
|
||||
{
|
||||
option: "ignore",
|
||||
type: "Boolean",
|
||||
default: "true",
|
||||
description: "Disable use of ignore files and patterns"
|
||||
},
|
||||
{
|
||||
option: "ignore-pattern",
|
||||
type: "[String]",
|
||||
description: `Patterns of files to ignore${usingFlatConfig ? "" : " (in addition to those in .eslintignore)"}`,
|
||||
concatRepeatedArrays: [true, {
|
||||
oneValuePerFlag: true
|
||||
}]
|
||||
},
|
||||
{
|
||||
heading: "Use stdin"
|
||||
},
|
||||
{
|
||||
option: "stdin",
|
||||
type: "Boolean",
|
||||
default: "false",
|
||||
description: "Lint code provided on <STDIN>"
|
||||
},
|
||||
{
|
||||
option: "stdin-filename",
|
||||
type: "String",
|
||||
description: "Specify filename to process STDIN as"
|
||||
},
|
||||
{
|
||||
heading: "Handle Warnings"
|
||||
},
|
||||
{
|
||||
option: "quiet",
|
||||
type: "Boolean",
|
||||
default: "false",
|
||||
description: "Report errors only"
|
||||
},
|
||||
{
|
||||
option: "max-warnings",
|
||||
type: "Int",
|
||||
default: "-1",
|
||||
description: "Number of warnings to trigger nonzero exit code"
|
||||
},
|
||||
{
|
||||
heading: "Output"
|
||||
},
|
||||
{
|
||||
option: "output-file",
|
||||
alias: "o",
|
||||
type: "path::String",
|
||||
description: "Specify file to write report to"
|
||||
},
|
||||
{
|
||||
option: "format",
|
||||
alias: "f",
|
||||
type: "String",
|
||||
default: "stylish",
|
||||
description: "Use a specific output format"
|
||||
},
|
||||
{
|
||||
option: "color",
|
||||
type: "Boolean",
|
||||
alias: "no-color",
|
||||
description: "Force enabling/disabling of color"
|
||||
},
|
||||
{
|
||||
heading: "Inline configuration comments"
|
||||
},
|
||||
{
|
||||
option: "inline-config",
|
||||
type: "Boolean",
|
||||
default: "true",
|
||||
description: "Prevent comments from changing config or rules"
|
||||
},
|
||||
{
|
||||
option: "report-unused-disable-directives",
|
||||
type: "Boolean",
|
||||
default: void 0,
|
||||
description: "Adds reported errors for unused eslint-disable and eslint-enable directives"
|
||||
},
|
||||
{
|
||||
option: "report-unused-disable-directives-severity",
|
||||
type: "String",
|
||||
default: void 0,
|
||||
description: "Chooses severity level for reporting unused eslint-disable and eslint-enable directives",
|
||||
enum: ["off", "warn", "error", "0", "1", "2"]
|
||||
},
|
||||
{
|
||||
heading: "Caching"
|
||||
},
|
||||
{
|
||||
option: "cache",
|
||||
type: "Boolean",
|
||||
default: "false",
|
||||
description: "Only check changed files"
|
||||
},
|
||||
{
|
||||
option: "cache-file",
|
||||
type: "path::String",
|
||||
default: ".eslintcache",
|
||||
description: "Path to the cache file. Deprecated: use --cache-location"
|
||||
},
|
||||
{
|
||||
option: "cache-location",
|
||||
type: "path::String",
|
||||
description: "Path to the cache file or directory"
|
||||
},
|
||||
{
|
||||
option: "cache-strategy",
|
||||
dependsOn: ["cache"],
|
||||
type: "String",
|
||||
default: "metadata",
|
||||
enum: ["metadata", "content"],
|
||||
description: "Strategy to use for detecting changed files in the cache"
|
||||
},
|
||||
{
|
||||
heading: "Miscellaneous"
|
||||
},
|
||||
{
|
||||
option: "init",
|
||||
type: "Boolean",
|
||||
default: "false",
|
||||
description: "Run config initialization wizard"
|
||||
},
|
||||
{
|
||||
option: "env-info",
|
||||
type: "Boolean",
|
||||
default: "false",
|
||||
description: "Output execution environment information"
|
||||
},
|
||||
{
|
||||
option: "error-on-unmatched-pattern",
|
||||
type: "Boolean",
|
||||
default: "true",
|
||||
description: "Prevent errors when pattern is unmatched"
|
||||
},
|
||||
{
|
||||
option: "exit-on-fatal-error",
|
||||
type: "Boolean",
|
||||
default: "false",
|
||||
description: "Exit with exit code 2 in case of fatal error"
|
||||
},
|
||||
warnIgnoredFlag,
|
||||
{
|
||||
option: "pass-on-no-patterns",
|
||||
type: "Boolean",
|
||||
default: false,
|
||||
description: "Exit with exit code 0 in case no file patterns are passed"
|
||||
},
|
||||
{
|
||||
option: "debug",
|
||||
type: "Boolean",
|
||||
default: false,
|
||||
description: "Output debugging information"
|
||||
},
|
||||
{
|
||||
option: "help",
|
||||
alias: "h",
|
||||
type: "Boolean",
|
||||
description: "Show help"
|
||||
},
|
||||
{
|
||||
option: "version",
|
||||
alias: "v",
|
||||
type: "Boolean",
|
||||
description: "Output the version number"
|
||||
},
|
||||
{
|
||||
option: "print-config",
|
||||
type: "path::String",
|
||||
description: "Print the configuration for the given file"
|
||||
},
|
||||
statsFlag,
|
||||
flagFlag
|
||||
].filter(value => !!value)
|
||||
});
|
||||
};
|
7
node_modules/eslint/lib/rule-tester/index.js
generated
vendored
Normal file
7
node_modules/eslint/lib/rule-tester/index.js
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
const RuleTester = require("./rule-tester");
|
||||
|
||||
module.exports = {
|
||||
RuleTester
|
||||
};
|
1307
node_modules/eslint/lib/rule-tester/rule-tester.js
generated
vendored
Normal file
1307
node_modules/eslint/lib/rule-tester/rule-tester.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
350
node_modules/eslint/lib/rules/accessor-pairs.js
generated
vendored
Normal file
350
node_modules/eslint/lib/rules/accessor-pairs.js
generated
vendored
Normal file
@ -0,0 +1,350 @@
|
||||
/**
|
||||
* @fileoverview Rule to enforce getter and setter pairs in objects and classes.
|
||||
* @author Gyandeep Singh
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("./utils/ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Typedefs
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Property name if it can be computed statically, otherwise the list of the tokens of the key node.
|
||||
* @typedef {string|Token[]} Key
|
||||
*/
|
||||
|
||||
/**
|
||||
* Accessor nodes with the same key.
|
||||
* @typedef {Object} AccessorData
|
||||
* @property {Key} key Accessor's key
|
||||
* @property {ASTNode[]} getters List of getter nodes.
|
||||
* @property {ASTNode[]} setters List of setter nodes.
|
||||
*/
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Checks whether or not the given lists represent the equal tokens in the same order.
|
||||
* Tokens are compared by their properties, not by instance.
|
||||
* @param {Token[]} left First list of tokens.
|
||||
* @param {Token[]} right Second list of tokens.
|
||||
* @returns {boolean} `true` if the lists have same tokens.
|
||||
*/
|
||||
function areEqualTokenLists(left, right) {
|
||||
if (left.length !== right.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < left.length; i++) {
|
||||
const leftToken = left[i],
|
||||
rightToken = right[i];
|
||||
|
||||
if (leftToken.type !== rightToken.type || leftToken.value !== rightToken.value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not the given keys are equal.
|
||||
* @param {Key} left First key.
|
||||
* @param {Key} right Second key.
|
||||
* @returns {boolean} `true` if the keys are equal.
|
||||
*/
|
||||
function areEqualKeys(left, right) {
|
||||
if (typeof left === "string" && typeof right === "string") {
|
||||
|
||||
// Statically computed names.
|
||||
return left === right;
|
||||
}
|
||||
if (Array.isArray(left) && Array.isArray(right)) {
|
||||
|
||||
// Token lists.
|
||||
return areEqualTokenLists(left, right);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not a given node is of an accessor kind ('get' or 'set').
|
||||
* @param {ASTNode} node A node to check.
|
||||
* @returns {boolean} `true` if the node is of an accessor kind.
|
||||
*/
|
||||
function isAccessorKind(node) {
|
||||
return node.kind === "get" || node.kind === "set";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not a given node is an argument of a specified method call.
|
||||
* @param {ASTNode} node A node to check.
|
||||
* @param {number} index An expected index of the node in arguments.
|
||||
* @param {string} object An expected name of the object of the method.
|
||||
* @param {string} property An expected name of the method.
|
||||
* @returns {boolean} `true` if the node is an argument of the specified method call.
|
||||
*/
|
||||
function isArgumentOfMethodCall(node, index, object, property) {
|
||||
const parent = node.parent;
|
||||
|
||||
return (
|
||||
parent.type === "CallExpression" &&
|
||||
astUtils.isSpecificMemberAccess(parent.callee, object, property) &&
|
||||
parent.arguments[index] === node
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not a given node is a property descriptor.
|
||||
* @param {ASTNode} node A node to check.
|
||||
* @returns {boolean} `true` if the node is a property descriptor.
|
||||
*/
|
||||
function isPropertyDescriptor(node) {
|
||||
|
||||
// Object.defineProperty(obj, "foo", {set: ...})
|
||||
if (isArgumentOfMethodCall(node, 2, "Object", "defineProperty") ||
|
||||
isArgumentOfMethodCall(node, 2, "Reflect", "defineProperty")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Object.defineProperties(obj, {foo: {set: ...}})
|
||||
* Object.create(proto, {foo: {set: ...}})
|
||||
*/
|
||||
const grandparent = node.parent.parent;
|
||||
|
||||
return grandparent.type === "ObjectExpression" && (
|
||||
isArgumentOfMethodCall(grandparent, 1, "Object", "create") ||
|
||||
isArgumentOfMethodCall(grandparent, 1, "Object", "defineProperties")
|
||||
);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('../shared/types').Rule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
|
||||
defaultOptions: [{
|
||||
enforceForClassMembers: true,
|
||||
getWithoutSet: false,
|
||||
setWithoutGet: true
|
||||
}],
|
||||
|
||||
docs: {
|
||||
description: "Enforce getter and setter pairs in objects and classes",
|
||||
recommended: false,
|
||||
url: "https://eslint.org/docs/latest/rules/accessor-pairs"
|
||||
},
|
||||
|
||||
schema: [{
|
||||
type: "object",
|
||||
properties: {
|
||||
getWithoutSet: {
|
||||
type: "boolean"
|
||||
},
|
||||
setWithoutGet: {
|
||||
type: "boolean"
|
||||
},
|
||||
enforceForClassMembers: {
|
||||
type: "boolean"
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
}],
|
||||
|
||||
messages: {
|
||||
missingGetterInPropertyDescriptor: "Getter is not present in property descriptor.",
|
||||
missingSetterInPropertyDescriptor: "Setter is not present in property descriptor.",
|
||||
missingGetterInObjectLiteral: "Getter is not present for {{ name }}.",
|
||||
missingSetterInObjectLiteral: "Setter is not present for {{ name }}.",
|
||||
missingGetterInClass: "Getter is not present for class {{ name }}.",
|
||||
missingSetterInClass: "Setter is not present for class {{ name }}."
|
||||
}
|
||||
},
|
||||
create(context) {
|
||||
const [{
|
||||
getWithoutSet: checkGetWithoutSet,
|
||||
setWithoutGet: checkSetWithoutGet,
|
||||
enforceForClassMembers
|
||||
}] = context.options;
|
||||
const sourceCode = context.sourceCode;
|
||||
|
||||
/**
|
||||
* Reports the given node.
|
||||
* @param {ASTNode} node The node to report.
|
||||
* @param {string} messageKind "missingGetter" or "missingSetter".
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function report(node, messageKind) {
|
||||
if (node.type === "Property") {
|
||||
context.report({
|
||||
node,
|
||||
messageId: `${messageKind}InObjectLiteral`,
|
||||
loc: astUtils.getFunctionHeadLoc(node.value, sourceCode),
|
||||
data: { name: astUtils.getFunctionNameWithKind(node.value) }
|
||||
});
|
||||
} else if (node.type === "MethodDefinition") {
|
||||
context.report({
|
||||
node,
|
||||
messageId: `${messageKind}InClass`,
|
||||
loc: astUtils.getFunctionHeadLoc(node.value, sourceCode),
|
||||
data: { name: astUtils.getFunctionNameWithKind(node.value) }
|
||||
});
|
||||
} else {
|
||||
context.report({
|
||||
node,
|
||||
messageId: `${messageKind}InPropertyDescriptor`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports each of the nodes in the given list using the same messageId.
|
||||
* @param {ASTNode[]} nodes Nodes to report.
|
||||
* @param {string} messageKind "missingGetter" or "missingSetter".
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function reportList(nodes, messageKind) {
|
||||
for (const node of nodes) {
|
||||
report(node, messageKind);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks accessor pairs in the given list of nodes.
|
||||
* @param {ASTNode[]} nodes The list to check.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function checkList(nodes) {
|
||||
const accessors = [];
|
||||
let found = false;
|
||||
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const node = nodes[i];
|
||||
|
||||
if (isAccessorKind(node)) {
|
||||
|
||||
// Creates a new `AccessorData` object for the given getter or setter node.
|
||||
const name = astUtils.getStaticPropertyName(node);
|
||||
const key = (name !== null) ? name : sourceCode.getTokens(node.key);
|
||||
|
||||
// Merges the given `AccessorData` object into the given accessors list.
|
||||
for (let j = 0; j < accessors.length; j++) {
|
||||
const accessor = accessors[j];
|
||||
|
||||
if (areEqualKeys(accessor.key, key)) {
|
||||
accessor.getters.push(...node.kind === "get" ? [node] : []);
|
||||
accessor.setters.push(...node.kind === "set" ? [node] : []);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
accessors.push({
|
||||
key,
|
||||
getters: node.kind === "get" ? [node] : [],
|
||||
setters: node.kind === "set" ? [node] : []
|
||||
});
|
||||
}
|
||||
found = false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const { getters, setters } of accessors) {
|
||||
if (checkSetWithoutGet && setters.length && !getters.length) {
|
||||
reportList(setters, "missingGetter");
|
||||
}
|
||||
if (checkGetWithoutSet && getters.length && !setters.length) {
|
||||
reportList(getters, "missingSetter");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks accessor pairs in an object literal.
|
||||
* @param {ASTNode} node `ObjectExpression` node to check.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function checkObjectLiteral(node) {
|
||||
checkList(node.properties.filter(p => p.type === "Property"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks accessor pairs in a property descriptor.
|
||||
* @param {ASTNode} node Property descriptor `ObjectExpression` node to check.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function checkPropertyDescriptor(node) {
|
||||
const namesToCheck = new Set(node.properties
|
||||
.filter(p => p.type === "Property" && p.kind === "init" && !p.computed)
|
||||
.map(({ key }) => key.name));
|
||||
|
||||
const hasGetter = namesToCheck.has("get");
|
||||
const hasSetter = namesToCheck.has("set");
|
||||
|
||||
if (checkSetWithoutGet && hasSetter && !hasGetter) {
|
||||
report(node, "missingGetter");
|
||||
}
|
||||
if (checkGetWithoutSet && hasGetter && !hasSetter) {
|
||||
report(node, "missingSetter");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the given object expression as an object literal and as a possible property descriptor.
|
||||
* @param {ASTNode} node `ObjectExpression` node to check.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function checkObjectExpression(node) {
|
||||
checkObjectLiteral(node);
|
||||
if (isPropertyDescriptor(node)) {
|
||||
checkPropertyDescriptor(node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the given class body.
|
||||
* @param {ASTNode} node `ClassBody` node to check.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function checkClassBody(node) {
|
||||
const methodDefinitions = node.body.filter(m => m.type === "MethodDefinition");
|
||||
|
||||
checkList(methodDefinitions.filter(m => m.static));
|
||||
checkList(methodDefinitions.filter(m => !m.static));
|
||||
}
|
||||
|
||||
const listeners = {};
|
||||
|
||||
if (checkSetWithoutGet || checkGetWithoutSet) {
|
||||
listeners.ObjectExpression = checkObjectExpression;
|
||||
if (enforceForClassMembers) {
|
||||
listeners.ClassBody = checkClassBody;
|
||||
}
|
||||
}
|
||||
|
||||
return listeners;
|
||||
}
|
||||
};
|
261
node_modules/eslint/lib/rules/array-bracket-newline.js
generated
vendored
Normal file
261
node_modules/eslint/lib/rules/array-bracket-newline.js
generated
vendored
Normal file
@ -0,0 +1,261 @@
|
||||
/**
|
||||
* @fileoverview Rule to enforce linebreaks after open and before close array brackets
|
||||
* @author Jan Peer Stöcklmair <https://github.com/JPeer264>
|
||||
* @deprecated in ESLint v8.53.0
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const astUtils = require("./utils/ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('../shared/types').Rule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
deprecated: true,
|
||||
replacedBy: [],
|
||||
type: "layout",
|
||||
|
||||
docs: {
|
||||
description: "Enforce linebreaks after opening and before closing array brackets",
|
||||
recommended: false,
|
||||
url: "https://eslint.org/docs/latest/rules/array-bracket-newline"
|
||||
},
|
||||
|
||||
fixable: "whitespace",
|
||||
|
||||
schema: [
|
||||
{
|
||||
oneOf: [
|
||||
{
|
||||
enum: ["always", "never", "consistent"]
|
||||
},
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
multiline: {
|
||||
type: "boolean"
|
||||
},
|
||||
minItems: {
|
||||
type: ["integer", "null"],
|
||||
minimum: 0
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
messages: {
|
||||
unexpectedOpeningLinebreak: "There should be no linebreak after '['.",
|
||||
unexpectedClosingLinebreak: "There should be no linebreak before ']'.",
|
||||
missingOpeningLinebreak: "A linebreak is required after '['.",
|
||||
missingClosingLinebreak: "A linebreak is required before ']'."
|
||||
}
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const sourceCode = context.sourceCode;
|
||||
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Helpers
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Normalizes a given option value.
|
||||
* @param {string|Object|undefined} option An option value to parse.
|
||||
* @returns {{multiline: boolean, minItems: number}} Normalized option object.
|
||||
*/
|
||||
function normalizeOptionValue(option) {
|
||||
let consistent = false;
|
||||
let multiline = false;
|
||||
let minItems;
|
||||
|
||||
if (option) {
|
||||
if (option === "consistent") {
|
||||
consistent = true;
|
||||
minItems = Number.POSITIVE_INFINITY;
|
||||
} else if (option === "always" || option.minItems === 0) {
|
||||
minItems = 0;
|
||||
} else if (option === "never") {
|
||||
minItems = Number.POSITIVE_INFINITY;
|
||||
} else {
|
||||
multiline = Boolean(option.multiline);
|
||||
minItems = option.minItems || Number.POSITIVE_INFINITY;
|
||||
}
|
||||
} else {
|
||||
consistent = false;
|
||||
multiline = true;
|
||||
minItems = Number.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
return { consistent, multiline, minItems };
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a given option value.
|
||||
* @param {string|Object|undefined} options An option value to parse.
|
||||
* @returns {{ArrayExpression: {multiline: boolean, minItems: number}, ArrayPattern: {multiline: boolean, minItems: number}}} Normalized option object.
|
||||
*/
|
||||
function normalizeOptions(options) {
|
||||
const value = normalizeOptionValue(options);
|
||||
|
||||
return { ArrayExpression: value, ArrayPattern: value };
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports that there shouldn't be a linebreak after the first token
|
||||
* @param {ASTNode} node The node to report in the event of an error.
|
||||
* @param {Token} token The token to use for the report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportNoBeginningLinebreak(node, token) {
|
||||
context.report({
|
||||
node,
|
||||
loc: token.loc,
|
||||
messageId: "unexpectedOpeningLinebreak",
|
||||
fix(fixer) {
|
||||
const nextToken = sourceCode.getTokenAfter(token, { includeComments: true });
|
||||
|
||||
if (astUtils.isCommentToken(nextToken)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fixer.removeRange([token.range[1], nextToken.range[0]]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports that there shouldn't be a linebreak before the last token
|
||||
* @param {ASTNode} node The node to report in the event of an error.
|
||||
* @param {Token} token The token to use for the report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportNoEndingLinebreak(node, token) {
|
||||
context.report({
|
||||
node,
|
||||
loc: token.loc,
|
||||
messageId: "unexpectedClosingLinebreak",
|
||||
fix(fixer) {
|
||||
const previousToken = sourceCode.getTokenBefore(token, { includeComments: true });
|
||||
|
||||
if (astUtils.isCommentToken(previousToken)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fixer.removeRange([previousToken.range[1], token.range[0]]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports that there should be a linebreak after the first token
|
||||
* @param {ASTNode} node The node to report in the event of an error.
|
||||
* @param {Token} token The token to use for the report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportRequiredBeginningLinebreak(node, token) {
|
||||
context.report({
|
||||
node,
|
||||
loc: token.loc,
|
||||
messageId: "missingOpeningLinebreak",
|
||||
fix(fixer) {
|
||||
return fixer.insertTextAfter(token, "\n");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports that there should be a linebreak before the last token
|
||||
* @param {ASTNode} node The node to report in the event of an error.
|
||||
* @param {Token} token The token to use for the report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportRequiredEndingLinebreak(node, token) {
|
||||
context.report({
|
||||
node,
|
||||
loc: token.loc,
|
||||
messageId: "missingClosingLinebreak",
|
||||
fix(fixer) {
|
||||
return fixer.insertTextBefore(token, "\n");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports a given node if it violated this rule.
|
||||
* @param {ASTNode} node A node to check. This is an ArrayExpression node or an ArrayPattern node.
|
||||
* @returns {void}
|
||||
*/
|
||||
function check(node) {
|
||||
const elements = node.elements;
|
||||
const normalizedOptions = normalizeOptions(context.options[0]);
|
||||
const options = normalizedOptions[node.type];
|
||||
const openBracket = sourceCode.getFirstToken(node);
|
||||
const closeBracket = sourceCode.getLastToken(node);
|
||||
const firstIncComment = sourceCode.getTokenAfter(openBracket, { includeComments: true });
|
||||
const lastIncComment = sourceCode.getTokenBefore(closeBracket, { includeComments: true });
|
||||
const first = sourceCode.getTokenAfter(openBracket);
|
||||
const last = sourceCode.getTokenBefore(closeBracket);
|
||||
|
||||
const needsLinebreaks = (
|
||||
elements.length >= options.minItems ||
|
||||
(
|
||||
options.multiline &&
|
||||
elements.length > 0 &&
|
||||
firstIncComment.loc.start.line !== lastIncComment.loc.end.line
|
||||
) ||
|
||||
(
|
||||
elements.length === 0 &&
|
||||
firstIncComment.type === "Block" &&
|
||||
firstIncComment.loc.start.line !== lastIncComment.loc.end.line &&
|
||||
firstIncComment === lastIncComment
|
||||
) ||
|
||||
(
|
||||
options.consistent &&
|
||||
openBracket.loc.end.line !== first.loc.start.line
|
||||
)
|
||||
);
|
||||
|
||||
/*
|
||||
* Use tokens or comments to check multiline or not.
|
||||
* But use only tokens to check whether linebreaks are needed.
|
||||
* This allows:
|
||||
* var arr = [ // eslint-disable-line foo
|
||||
* 'a'
|
||||
* ]
|
||||
*/
|
||||
|
||||
if (needsLinebreaks) {
|
||||
if (astUtils.isTokenOnSameLine(openBracket, first)) {
|
||||
reportRequiredBeginningLinebreak(node, openBracket);
|
||||
}
|
||||
if (astUtils.isTokenOnSameLine(last, closeBracket)) {
|
||||
reportRequiredEndingLinebreak(node, closeBracket);
|
||||
}
|
||||
} else {
|
||||
if (!astUtils.isTokenOnSameLine(openBracket, first)) {
|
||||
reportNoBeginningLinebreak(node, openBracket);
|
||||
}
|
||||
if (!astUtils.isTokenOnSameLine(last, closeBracket)) {
|
||||
reportNoEndingLinebreak(node, closeBracket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Public
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
ArrayPattern: check,
|
||||
ArrayExpression: check
|
||||
};
|
||||
}
|
||||
};
|
244
node_modules/eslint/lib/rules/array-bracket-spacing.js
generated
vendored
Normal file
244
node_modules/eslint/lib/rules/array-bracket-spacing.js
generated
vendored
Normal file
@ -0,0 +1,244 @@
|
||||
/**
|
||||
* @fileoverview Disallows or enforces spaces inside of array brackets.
|
||||
* @author Jamund Ferguson
|
||||
* @deprecated in ESLint v8.53.0
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
const astUtils = require("./utils/ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('../shared/types').Rule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
deprecated: true,
|
||||
replacedBy: [],
|
||||
type: "layout",
|
||||
|
||||
docs: {
|
||||
description: "Enforce consistent spacing inside array brackets",
|
||||
recommended: false,
|
||||
url: "https://eslint.org/docs/latest/rules/array-bracket-spacing"
|
||||
},
|
||||
|
||||
fixable: "whitespace",
|
||||
|
||||
schema: [
|
||||
{
|
||||
enum: ["always", "never"]
|
||||
},
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
singleValue: {
|
||||
type: "boolean"
|
||||
},
|
||||
objectsInArrays: {
|
||||
type: "boolean"
|
||||
},
|
||||
arraysInArrays: {
|
||||
type: "boolean"
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
}
|
||||
],
|
||||
|
||||
messages: {
|
||||
unexpectedSpaceAfter: "There should be no space after '{{tokenValue}}'.",
|
||||
unexpectedSpaceBefore: "There should be no space before '{{tokenValue}}'.",
|
||||
missingSpaceAfter: "A space is required after '{{tokenValue}}'.",
|
||||
missingSpaceBefore: "A space is required before '{{tokenValue}}'."
|
||||
}
|
||||
},
|
||||
create(context) {
|
||||
const spaced = context.options[0] === "always",
|
||||
sourceCode = context.sourceCode;
|
||||
|
||||
/**
|
||||
* Determines whether an option is set, relative to the spacing option.
|
||||
* If spaced is "always", then check whether option is set to false.
|
||||
* If spaced is "never", then check whether option is set to true.
|
||||
* @param {Object} option The option to exclude.
|
||||
* @returns {boolean} Whether or not the property is excluded.
|
||||
*/
|
||||
function isOptionSet(option) {
|
||||
return context.options[1] ? context.options[1][option] === !spaced : false;
|
||||
}
|
||||
|
||||
const options = {
|
||||
spaced,
|
||||
singleElementException: isOptionSet("singleValue"),
|
||||
objectsInArraysException: isOptionSet("objectsInArrays"),
|
||||
arraysInArraysException: isOptionSet("arraysInArrays")
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Reports that there shouldn't be a space after the first token
|
||||
* @param {ASTNode} node The node to report in the event of an error.
|
||||
* @param {Token} token The token to use for the report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportNoBeginningSpace(node, token) {
|
||||
const nextToken = sourceCode.getTokenAfter(token);
|
||||
|
||||
context.report({
|
||||
node,
|
||||
loc: { start: token.loc.end, end: nextToken.loc.start },
|
||||
messageId: "unexpectedSpaceAfter",
|
||||
data: {
|
||||
tokenValue: token.value
|
||||
},
|
||||
fix(fixer) {
|
||||
return fixer.removeRange([token.range[1], nextToken.range[0]]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports that there shouldn't be a space before the last token
|
||||
* @param {ASTNode} node The node to report in the event of an error.
|
||||
* @param {Token} token The token to use for the report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportNoEndingSpace(node, token) {
|
||||
const previousToken = sourceCode.getTokenBefore(token);
|
||||
|
||||
context.report({
|
||||
node,
|
||||
loc: { start: previousToken.loc.end, end: token.loc.start },
|
||||
messageId: "unexpectedSpaceBefore",
|
||||
data: {
|
||||
tokenValue: token.value
|
||||
},
|
||||
fix(fixer) {
|
||||
return fixer.removeRange([previousToken.range[1], token.range[0]]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports that there should be a space after the first token
|
||||
* @param {ASTNode} node The node to report in the event of an error.
|
||||
* @param {Token} token The token to use for the report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportRequiredBeginningSpace(node, token) {
|
||||
context.report({
|
||||
node,
|
||||
loc: token.loc,
|
||||
messageId: "missingSpaceAfter",
|
||||
data: {
|
||||
tokenValue: token.value
|
||||
},
|
||||
fix(fixer) {
|
||||
return fixer.insertTextAfter(token, " ");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports that there should be a space before the last token
|
||||
* @param {ASTNode} node The node to report in the event of an error.
|
||||
* @param {Token} token The token to use for the report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportRequiredEndingSpace(node, token) {
|
||||
context.report({
|
||||
node,
|
||||
loc: token.loc,
|
||||
messageId: "missingSpaceBefore",
|
||||
data: {
|
||||
tokenValue: token.value
|
||||
},
|
||||
fix(fixer) {
|
||||
return fixer.insertTextBefore(token, " ");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a node is an object type
|
||||
* @param {ASTNode} node The node to check.
|
||||
* @returns {boolean} Whether or not the node is an object type.
|
||||
*/
|
||||
function isObjectType(node) {
|
||||
return node && (node.type === "ObjectExpression" || node.type === "ObjectPattern");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a node is an array type
|
||||
* @param {ASTNode} node The node to check.
|
||||
* @returns {boolean} Whether or not the node is an array type.
|
||||
*/
|
||||
function isArrayType(node) {
|
||||
return node && (node.type === "ArrayExpression" || node.type === "ArrayPattern");
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the spacing around array brackets
|
||||
* @param {ASTNode} node The node we're checking for spacing
|
||||
* @returns {void}
|
||||
*/
|
||||
function validateArraySpacing(node) {
|
||||
if (options.spaced && node.elements.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const first = sourceCode.getFirstToken(node),
|
||||
second = sourceCode.getFirstToken(node, 1),
|
||||
last = node.typeAnnotation
|
||||
? sourceCode.getTokenBefore(node.typeAnnotation)
|
||||
: sourceCode.getLastToken(node),
|
||||
penultimate = sourceCode.getTokenBefore(last),
|
||||
firstElement = node.elements[0],
|
||||
lastElement = node.elements.at(-1);
|
||||
|
||||
const openingBracketMustBeSpaced =
|
||||
options.objectsInArraysException && isObjectType(firstElement) ||
|
||||
options.arraysInArraysException && isArrayType(firstElement) ||
|
||||
options.singleElementException && node.elements.length === 1
|
||||
? !options.spaced : options.spaced;
|
||||
|
||||
const closingBracketMustBeSpaced =
|
||||
options.objectsInArraysException && isObjectType(lastElement) ||
|
||||
options.arraysInArraysException && isArrayType(lastElement) ||
|
||||
options.singleElementException && node.elements.length === 1
|
||||
? !options.spaced : options.spaced;
|
||||
|
||||
if (astUtils.isTokenOnSameLine(first, second)) {
|
||||
if (openingBracketMustBeSpaced && !sourceCode.isSpaceBetweenTokens(first, second)) {
|
||||
reportRequiredBeginningSpace(node, first);
|
||||
}
|
||||
if (!openingBracketMustBeSpaced && sourceCode.isSpaceBetweenTokens(first, second)) {
|
||||
reportNoBeginningSpace(node, first);
|
||||
}
|
||||
}
|
||||
|
||||
if (first !== penultimate && astUtils.isTokenOnSameLine(penultimate, last)) {
|
||||
if (closingBracketMustBeSpaced && !sourceCode.isSpaceBetweenTokens(penultimate, last)) {
|
||||
reportRequiredEndingSpace(node, last);
|
||||
}
|
||||
if (!closingBracketMustBeSpaced && sourceCode.isSpaceBetweenTokens(penultimate, last)) {
|
||||
reportNoEndingSpace(node, last);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
ArrayPattern: validateArraySpacing,
|
||||
ArrayExpression: validateArraySpacing
|
||||
};
|
||||
}
|
||||
};
|
448
node_modules/eslint/lib/rules/array-callback-return.js
generated
vendored
Normal file
448
node_modules/eslint/lib/rules/array-callback-return.js
generated
vendored
Normal file
@ -0,0 +1,448 @@
|
||||
/**
|
||||
* @fileoverview Rule to enforce return statements in callbacks of array's methods
|
||||
* @author Toru Nagashima
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("./utils/ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u;
|
||||
const TARGET_METHODS = /^(?:every|filter|find(?:Last)?(?:Index)?|flatMap|forEach|map|reduce(?:Right)?|some|sort|toSorted)$/u;
|
||||
|
||||
/**
|
||||
* Checks a given node is a member access which has the specified name's
|
||||
* property.
|
||||
* @param {ASTNode} node A node to check.
|
||||
* @returns {boolean} `true` if the node is a member access which has
|
||||
* the specified name's property. The node may be a `(Chain|Member)Expression` node.
|
||||
*/
|
||||
function isTargetMethod(node) {
|
||||
return astUtils.isSpecificMemberAccess(node, null, TARGET_METHODS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks all segments in a set and returns true if any are reachable.
|
||||
* @param {Set<CodePathSegment>} segments The segments to check.
|
||||
* @returns {boolean} True if any segment is reachable; false otherwise.
|
||||
*/
|
||||
function isAnySegmentReachable(segments) {
|
||||
|
||||
for (const segment of segments) {
|
||||
if (segment.reachable) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a human-legible description of an array method
|
||||
* @param {string} arrayMethodName A method name to fully qualify
|
||||
* @returns {string} the method name prefixed with `Array.` if it is a class method,
|
||||
* or else `Array.prototype.` if it is an instance method.
|
||||
*/
|
||||
function fullMethodName(arrayMethodName) {
|
||||
if (["from", "of", "isArray"].includes(arrayMethodName)) {
|
||||
return "Array.".concat(arrayMethodName);
|
||||
}
|
||||
return "Array.prototype.".concat(arrayMethodName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not a given node is a function expression which is the
|
||||
* callback of an array method, returning the method name.
|
||||
* @param {ASTNode} node A node to check. This is one of
|
||||
* FunctionExpression or ArrowFunctionExpression.
|
||||
* @returns {string} The method name if the node is a callback method,
|
||||
* null otherwise.
|
||||
*/
|
||||
function getArrayMethodName(node) {
|
||||
let currentNode = node;
|
||||
|
||||
while (currentNode) {
|
||||
const parent = currentNode.parent;
|
||||
|
||||
switch (parent.type) {
|
||||
|
||||
/*
|
||||
* Looks up the destination. e.g.,
|
||||
* foo.every(nativeFoo || function foo() { ... });
|
||||
*/
|
||||
case "LogicalExpression":
|
||||
case "ConditionalExpression":
|
||||
case "ChainExpression":
|
||||
currentNode = parent;
|
||||
break;
|
||||
|
||||
/*
|
||||
* If the upper function is IIFE, checks the destination of the return value.
|
||||
* e.g.
|
||||
* foo.every((function() {
|
||||
* // setup...
|
||||
* return function callback() { ... };
|
||||
* })());
|
||||
*/
|
||||
case "ReturnStatement": {
|
||||
const func = astUtils.getUpperFunction(parent);
|
||||
|
||||
if (func === null || !astUtils.isCallee(func)) {
|
||||
return null;
|
||||
}
|
||||
currentNode = func.parent;
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* e.g.
|
||||
* Array.from([], function() {});
|
||||
* list.every(function() {});
|
||||
*/
|
||||
case "CallExpression":
|
||||
if (astUtils.isArrayFromMethod(parent.callee)) {
|
||||
if (
|
||||
parent.arguments.length >= 2 &&
|
||||
parent.arguments[1] === currentNode
|
||||
) {
|
||||
return "from";
|
||||
}
|
||||
}
|
||||
if (isTargetMethod(parent.callee)) {
|
||||
if (
|
||||
parent.arguments.length >= 1 &&
|
||||
parent.arguments[0] === currentNode
|
||||
) {
|
||||
return astUtils.getStaticPropertyName(parent.callee);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
// Otherwise this node is not target.
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/* c8 ignore next */
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given node is a void expression.
|
||||
* @param {ASTNode} node The node to check.
|
||||
* @returns {boolean} - `true` if the node is a void expression
|
||||
*/
|
||||
function isExpressionVoid(node) {
|
||||
return node.type === "UnaryExpression" && node.operator === "void";
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixes the linting error by prepending "void " to the given node
|
||||
* @param {Object} sourceCode context given by context.sourceCode
|
||||
* @param {ASTNode} node The node to fix.
|
||||
* @param {Object} fixer The fixer object provided by ESLint.
|
||||
* @returns {Array<Object>} - An array of fix objects to apply to the node.
|
||||
*/
|
||||
function voidPrependFixer(sourceCode, node, fixer) {
|
||||
|
||||
const requiresParens =
|
||||
|
||||
// prepending `void ` will fail if the node has a lower precedence than void
|
||||
astUtils.getPrecedence(node) < astUtils.getPrecedence({ type: "UnaryExpression", operator: "void" }) &&
|
||||
|
||||
// check if there are parentheses around the node to avoid redundant parentheses
|
||||
!astUtils.isParenthesised(sourceCode, node);
|
||||
|
||||
// avoid parentheses issues
|
||||
const returnOrArrowToken = sourceCode.getTokenBefore(
|
||||
node,
|
||||
node.parent.type === "ArrowFunctionExpression"
|
||||
? astUtils.isArrowToken
|
||||
|
||||
// isReturnToken
|
||||
: token => token.type === "Keyword" && token.value === "return"
|
||||
);
|
||||
|
||||
const firstToken = sourceCode.getTokenAfter(returnOrArrowToken);
|
||||
|
||||
const prependSpace =
|
||||
|
||||
// is return token, as => allows void to be adjacent
|
||||
returnOrArrowToken.value === "return" &&
|
||||
|
||||
// If two tokens (return and "(") are adjacent
|
||||
returnOrArrowToken.range[1] === firstToken.range[0];
|
||||
|
||||
return [
|
||||
fixer.insertTextBefore(firstToken, `${prependSpace ? " " : ""}void ${requiresParens ? "(" : ""}`),
|
||||
fixer.insertTextAfter(node, requiresParens ? ")" : "")
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixes the linting error by `wrapping {}` around the given node's body.
|
||||
* @param {Object} sourceCode context given by context.sourceCode
|
||||
* @param {ASTNode} node The node to fix.
|
||||
* @param {Object} fixer The fixer object provided by ESLint.
|
||||
* @returns {Array<Object>} - An array of fix objects to apply to the node.
|
||||
*/
|
||||
function curlyWrapFixer(sourceCode, node, fixer) {
|
||||
const arrowToken = sourceCode.getTokenBefore(node.body, astUtils.isArrowToken);
|
||||
const firstToken = sourceCode.getTokenAfter(arrowToken);
|
||||
const lastToken = sourceCode.getLastToken(node);
|
||||
|
||||
return [
|
||||
fixer.insertTextBefore(firstToken, "{"),
|
||||
fixer.insertTextAfter(lastToken, "}")
|
||||
];
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('../shared/types').Rule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: "problem",
|
||||
|
||||
defaultOptions: [{
|
||||
allowImplicit: false,
|
||||
checkForEach: false,
|
||||
allowVoid: false
|
||||
}],
|
||||
|
||||
docs: {
|
||||
description: "Enforce `return` statements in callbacks of array methods",
|
||||
recommended: false,
|
||||
url: "https://eslint.org/docs/latest/rules/array-callback-return"
|
||||
},
|
||||
|
||||
// eslint-disable-next-line eslint-plugin/require-meta-has-suggestions -- false positive
|
||||
hasSuggestions: true,
|
||||
|
||||
schema: [
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
allowImplicit: {
|
||||
type: "boolean"
|
||||
},
|
||||
checkForEach: {
|
||||
type: "boolean"
|
||||
},
|
||||
allowVoid: {
|
||||
type: "boolean"
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
}
|
||||
],
|
||||
|
||||
messages: {
|
||||
expectedAtEnd: "{{arrayMethodName}}() expects a value to be returned at the end of {{name}}.",
|
||||
expectedInside: "{{arrayMethodName}}() expects a return value from {{name}}.",
|
||||
expectedReturnValue: "{{arrayMethodName}}() expects a return value from {{name}}.",
|
||||
expectedNoReturnValue: "{{arrayMethodName}}() expects no useless return value from {{name}}.",
|
||||
wrapBraces: "Wrap the expression in `{}`.",
|
||||
prependVoid: "Prepend `void` to the expression."
|
||||
}
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const [options] = context.options;
|
||||
const sourceCode = context.sourceCode;
|
||||
|
||||
let funcInfo = {
|
||||
arrayMethodName: null,
|
||||
upper: null,
|
||||
codePath: null,
|
||||
hasReturn: false,
|
||||
shouldCheck: false,
|
||||
node: null
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether or not the last code path segment is reachable.
|
||||
* Then reports this function if the segment is reachable.
|
||||
*
|
||||
* If the last code path segment is reachable, there are paths which are not
|
||||
* returned or thrown.
|
||||
* @param {ASTNode} node A node to check.
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkLastSegment(node) {
|
||||
|
||||
if (!funcInfo.shouldCheck) {
|
||||
return;
|
||||
}
|
||||
|
||||
const messageAndSuggestions = { messageId: "", suggest: [] };
|
||||
|
||||
if (funcInfo.arrayMethodName === "forEach") {
|
||||
if (options.checkForEach && node.type === "ArrowFunctionExpression" && node.expression) {
|
||||
|
||||
if (options.allowVoid) {
|
||||
if (isExpressionVoid(node.body)) {
|
||||
return;
|
||||
}
|
||||
|
||||
messageAndSuggestions.messageId = "expectedNoReturnValue";
|
||||
messageAndSuggestions.suggest = [
|
||||
{
|
||||
messageId: "wrapBraces",
|
||||
fix(fixer) {
|
||||
return curlyWrapFixer(sourceCode, node, fixer);
|
||||
}
|
||||
},
|
||||
{
|
||||
messageId: "prependVoid",
|
||||
fix(fixer) {
|
||||
return voidPrependFixer(sourceCode, node.body, fixer);
|
||||
}
|
||||
}
|
||||
];
|
||||
} else {
|
||||
messageAndSuggestions.messageId = "expectedNoReturnValue";
|
||||
messageAndSuggestions.suggest = [{
|
||||
messageId: "wrapBraces",
|
||||
fix(fixer) {
|
||||
return curlyWrapFixer(sourceCode, node, fixer);
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (node.body.type === "BlockStatement" && isAnySegmentReachable(funcInfo.currentSegments)) {
|
||||
messageAndSuggestions.messageId = funcInfo.hasReturn ? "expectedAtEnd" : "expectedInside";
|
||||
}
|
||||
}
|
||||
|
||||
if (messageAndSuggestions.messageId) {
|
||||
const name = astUtils.getFunctionNameWithKind(node);
|
||||
|
||||
context.report({
|
||||
node,
|
||||
loc: astUtils.getFunctionHeadLoc(node, sourceCode),
|
||||
messageId: messageAndSuggestions.messageId,
|
||||
data: { name, arrayMethodName: fullMethodName(funcInfo.arrayMethodName) },
|
||||
suggest: messageAndSuggestions.suggest.length !== 0 ? messageAndSuggestions.suggest : null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
// Stacks this function's information.
|
||||
onCodePathStart(codePath, node) {
|
||||
|
||||
let methodName = null;
|
||||
|
||||
if (TARGET_NODE_TYPE.test(node.type)) {
|
||||
methodName = getArrayMethodName(node);
|
||||
}
|
||||
|
||||
funcInfo = {
|
||||
arrayMethodName: methodName,
|
||||
upper: funcInfo,
|
||||
codePath,
|
||||
hasReturn: false,
|
||||
shouldCheck:
|
||||
methodName &&
|
||||
!node.async &&
|
||||
!node.generator,
|
||||
node,
|
||||
currentSegments: new Set()
|
||||
};
|
||||
},
|
||||
|
||||
// Pops this function's information.
|
||||
onCodePathEnd() {
|
||||
funcInfo = funcInfo.upper;
|
||||
},
|
||||
|
||||
onUnreachableCodePathSegmentStart(segment) {
|
||||
funcInfo.currentSegments.add(segment);
|
||||
},
|
||||
|
||||
onUnreachableCodePathSegmentEnd(segment) {
|
||||
funcInfo.currentSegments.delete(segment);
|
||||
},
|
||||
|
||||
onCodePathSegmentStart(segment) {
|
||||
funcInfo.currentSegments.add(segment);
|
||||
},
|
||||
|
||||
onCodePathSegmentEnd(segment) {
|
||||
funcInfo.currentSegments.delete(segment);
|
||||
},
|
||||
|
||||
|
||||
// Checks the return statement is valid.
|
||||
ReturnStatement(node) {
|
||||
|
||||
if (!funcInfo.shouldCheck) {
|
||||
return;
|
||||
}
|
||||
|
||||
funcInfo.hasReturn = true;
|
||||
|
||||
const messageAndSuggestions = { messageId: "", suggest: [] };
|
||||
|
||||
if (funcInfo.arrayMethodName === "forEach") {
|
||||
|
||||
// if checkForEach: true, returning a value at any path inside a forEach is not allowed
|
||||
if (options.checkForEach && node.argument) {
|
||||
|
||||
if (options.allowVoid) {
|
||||
if (isExpressionVoid(node.argument)) {
|
||||
return;
|
||||
}
|
||||
|
||||
messageAndSuggestions.messageId = "expectedNoReturnValue";
|
||||
messageAndSuggestions.suggest = [{
|
||||
messageId: "prependVoid",
|
||||
fix(fixer) {
|
||||
return voidPrependFixer(sourceCode, node.argument, fixer);
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
messageAndSuggestions.messageId = "expectedNoReturnValue";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
// if allowImplicit: false, should also check node.argument
|
||||
if (!options.allowImplicit && !node.argument) {
|
||||
messageAndSuggestions.messageId = "expectedReturnValue";
|
||||
}
|
||||
}
|
||||
|
||||
if (messageAndSuggestions.messageId) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: messageAndSuggestions.messageId,
|
||||
data: {
|
||||
name: astUtils.getFunctionNameWithKind(funcInfo.node),
|
||||
arrayMethodName: fullMethodName(funcInfo.arrayMethodName)
|
||||
},
|
||||
suggest: messageAndSuggestions.suggest.length !== 0 ? messageAndSuggestions.suggest : null
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Reports a given function if the last path is reachable.
|
||||
"FunctionExpression:exit": checkLastSegment,
|
||||
"ArrowFunctionExpression:exit": checkLastSegment
|
||||
};
|
||||
}
|
||||
};
|
311
node_modules/eslint/lib/rules/array-element-newline.js
generated
vendored
Normal file
311
node_modules/eslint/lib/rules/array-element-newline.js
generated
vendored
Normal file
@ -0,0 +1,311 @@
|
||||
/**
|
||||
* @fileoverview Rule to enforce line breaks after each array element
|
||||
* @author Jan Peer Stöcklmair <https://github.com/JPeer264>
|
||||
* @deprecated in ESLint v8.53.0
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const astUtils = require("./utils/ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('../shared/types').Rule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
deprecated: true,
|
||||
replacedBy: [],
|
||||
type: "layout",
|
||||
|
||||
docs: {
|
||||
description: "Enforce line breaks after each array element",
|
||||
recommended: false,
|
||||
url: "https://eslint.org/docs/latest/rules/array-element-newline"
|
||||
},
|
||||
|
||||
fixable: "whitespace",
|
||||
|
||||
schema: {
|
||||
definitions: {
|
||||
basicConfig: {
|
||||
oneOf: [
|
||||
{
|
||||
enum: ["always", "never", "consistent"]
|
||||
},
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
multiline: {
|
||||
type: "boolean"
|
||||
},
|
||||
minItems: {
|
||||
type: ["integer", "null"],
|
||||
minimum: 0
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
type: "array",
|
||||
items: [
|
||||
{
|
||||
oneOf: [
|
||||
{
|
||||
$ref: "#/definitions/basicConfig"
|
||||
},
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
ArrayExpression: {
|
||||
$ref: "#/definitions/basicConfig"
|
||||
},
|
||||
ArrayPattern: {
|
||||
$ref: "#/definitions/basicConfig"
|
||||
}
|
||||
},
|
||||
additionalProperties: false,
|
||||
minProperties: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
messages: {
|
||||
unexpectedLineBreak: "There should be no linebreak here.",
|
||||
missingLineBreak: "There should be a linebreak after this element."
|
||||
}
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const sourceCode = context.sourceCode;
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Helpers
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Normalizes a given option value.
|
||||
* @param {string|Object|undefined} providedOption An option value to parse.
|
||||
* @returns {{multiline: boolean, minItems: number}} Normalized option object.
|
||||
*/
|
||||
function normalizeOptionValue(providedOption) {
|
||||
let consistent = false;
|
||||
let multiline = false;
|
||||
let minItems;
|
||||
|
||||
const option = providedOption || "always";
|
||||
|
||||
if (!option || option === "always" || option.minItems === 0) {
|
||||
minItems = 0;
|
||||
} else if (option === "never") {
|
||||
minItems = Number.POSITIVE_INFINITY;
|
||||
} else if (option === "consistent") {
|
||||
consistent = true;
|
||||
minItems = Number.POSITIVE_INFINITY;
|
||||
} else {
|
||||
multiline = Boolean(option.multiline);
|
||||
minItems = option.minItems || Number.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
return { consistent, multiline, minItems };
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a given option value.
|
||||
* @param {string|Object|undefined} options An option value to parse.
|
||||
* @returns {{ArrayExpression: {multiline: boolean, minItems: number}, ArrayPattern: {multiline: boolean, minItems: number}}} Normalized option object.
|
||||
*/
|
||||
function normalizeOptions(options) {
|
||||
if (options && (options.ArrayExpression || options.ArrayPattern)) {
|
||||
let expressionOptions, patternOptions;
|
||||
|
||||
if (options.ArrayExpression) {
|
||||
expressionOptions = normalizeOptionValue(options.ArrayExpression);
|
||||
}
|
||||
|
||||
if (options.ArrayPattern) {
|
||||
patternOptions = normalizeOptionValue(options.ArrayPattern);
|
||||
}
|
||||
|
||||
return { ArrayExpression: expressionOptions, ArrayPattern: patternOptions };
|
||||
}
|
||||
|
||||
const value = normalizeOptionValue(options);
|
||||
|
||||
return { ArrayExpression: value, ArrayPattern: value };
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports that there shouldn't be a line break after the first token
|
||||
* @param {Token} token The token to use for the report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportNoLineBreak(token) {
|
||||
const tokenBefore = sourceCode.getTokenBefore(token, { includeComments: true });
|
||||
|
||||
context.report({
|
||||
loc: {
|
||||
start: tokenBefore.loc.end,
|
||||
end: token.loc.start
|
||||
},
|
||||
messageId: "unexpectedLineBreak",
|
||||
fix(fixer) {
|
||||
if (astUtils.isCommentToken(tokenBefore)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!astUtils.isTokenOnSameLine(tokenBefore, token)) {
|
||||
return fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], " ");
|
||||
}
|
||||
|
||||
/*
|
||||
* This will check if the comma is on the same line as the next element
|
||||
* Following array:
|
||||
* [
|
||||
* 1
|
||||
* , 2
|
||||
* , 3
|
||||
* ]
|
||||
*
|
||||
* will be fixed to:
|
||||
* [
|
||||
* 1, 2, 3
|
||||
* ]
|
||||
*/
|
||||
const twoTokensBefore = sourceCode.getTokenBefore(tokenBefore, { includeComments: true });
|
||||
|
||||
if (astUtils.isCommentToken(twoTokensBefore)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fixer.replaceTextRange([twoTokensBefore.range[1], tokenBefore.range[0]], "");
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports that there should be a line break after the first token
|
||||
* @param {Token} token The token to use for the report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportRequiredLineBreak(token) {
|
||||
const tokenBefore = sourceCode.getTokenBefore(token, { includeComments: true });
|
||||
|
||||
context.report({
|
||||
loc: {
|
||||
start: tokenBefore.loc.end,
|
||||
end: token.loc.start
|
||||
},
|
||||
messageId: "missingLineBreak",
|
||||
fix(fixer) {
|
||||
return fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], "\n");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports a given node if it violated this rule.
|
||||
* @param {ASTNode} node A node to check. This is an ObjectExpression node or an ObjectPattern node.
|
||||
* @returns {void}
|
||||
*/
|
||||
function check(node) {
|
||||
const elements = node.elements;
|
||||
const normalizedOptions = normalizeOptions(context.options[0]);
|
||||
const options = normalizedOptions[node.type];
|
||||
|
||||
if (!options) {
|
||||
return;
|
||||
}
|
||||
|
||||
let elementBreak = false;
|
||||
|
||||
/*
|
||||
* MULTILINE: true
|
||||
* loop through every element and check
|
||||
* if at least one element has linebreaks inside
|
||||
* this ensures that following is not valid (due to elements are on the same line):
|
||||
*
|
||||
* [
|
||||
* 1,
|
||||
* 2,
|
||||
* 3
|
||||
* ]
|
||||
*/
|
||||
if (options.multiline) {
|
||||
elementBreak = elements
|
||||
.filter(element => element !== null)
|
||||
.some(element => element.loc.start.line !== element.loc.end.line);
|
||||
}
|
||||
|
||||
let linebreaksCount = 0;
|
||||
|
||||
for (let i = 0; i < node.elements.length; i++) {
|
||||
const element = node.elements[i];
|
||||
|
||||
const previousElement = elements[i - 1];
|
||||
|
||||
if (i === 0 || element === null || previousElement === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const commaToken = sourceCode.getFirstTokenBetween(previousElement, element, astUtils.isCommaToken);
|
||||
const lastTokenOfPreviousElement = sourceCode.getTokenBefore(commaToken);
|
||||
const firstTokenOfCurrentElement = sourceCode.getTokenAfter(commaToken);
|
||||
|
||||
if (!astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement)) {
|
||||
linebreaksCount++;
|
||||
}
|
||||
}
|
||||
|
||||
const needsLinebreaks = (
|
||||
elements.length >= options.minItems ||
|
||||
(
|
||||
options.multiline &&
|
||||
elementBreak
|
||||
) ||
|
||||
(
|
||||
options.consistent &&
|
||||
linebreaksCount > 0 &&
|
||||
linebreaksCount < node.elements.length
|
||||
)
|
||||
);
|
||||
|
||||
elements.forEach((element, i) => {
|
||||
const previousElement = elements[i - 1];
|
||||
|
||||
if (i === 0 || element === null || previousElement === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const commaToken = sourceCode.getFirstTokenBetween(previousElement, element, astUtils.isCommaToken);
|
||||
const lastTokenOfPreviousElement = sourceCode.getTokenBefore(commaToken);
|
||||
const firstTokenOfCurrentElement = sourceCode.getTokenAfter(commaToken);
|
||||
|
||||
if (needsLinebreaks) {
|
||||
if (astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement)) {
|
||||
reportRequiredLineBreak(firstTokenOfCurrentElement);
|
||||
}
|
||||
} else {
|
||||
if (!astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement)) {
|
||||
reportNoLineBreak(firstTokenOfCurrentElement);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Public
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
ArrayPattern: check,
|
||||
ArrayExpression: check
|
||||
};
|
||||
}
|
||||
};
|
298
node_modules/eslint/lib/rules/arrow-body-style.js
generated
vendored
Normal file
298
node_modules/eslint/lib/rules/arrow-body-style.js
generated
vendored
Normal file
@ -0,0 +1,298 @@
|
||||
/**
|
||||
* @fileoverview Rule to require braces in arrow function body.
|
||||
* @author Alberto Rodríguez
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("./utils/ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('../shared/types').Rule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
|
||||
defaultOptions: ["as-needed"],
|
||||
|
||||
docs: {
|
||||
description: "Require braces around arrow function bodies",
|
||||
recommended: false,
|
||||
url: "https://eslint.org/docs/latest/rules/arrow-body-style"
|
||||
},
|
||||
|
||||
schema: {
|
||||
anyOf: [
|
||||
{
|
||||
type: "array",
|
||||
items: [
|
||||
{
|
||||
enum: ["always", "never"]
|
||||
}
|
||||
],
|
||||
minItems: 0,
|
||||
maxItems: 1
|
||||
},
|
||||
{
|
||||
type: "array",
|
||||
items: [
|
||||
{
|
||||
enum: ["as-needed"]
|
||||
},
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
requireReturnForObjectLiteral: { type: "boolean" }
|
||||
},
|
||||
additionalProperties: false
|
||||
}
|
||||
],
|
||||
minItems: 0,
|
||||
maxItems: 2
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
fixable: "code",
|
||||
|
||||
messages: {
|
||||
unexpectedOtherBlock: "Unexpected block statement surrounding arrow body.",
|
||||
unexpectedEmptyBlock: "Unexpected block statement surrounding arrow body; put a value of `undefined` immediately after the `=>`.",
|
||||
unexpectedObjectBlock: "Unexpected block statement surrounding arrow body; parenthesize the returned value and move it immediately after the `=>`.",
|
||||
unexpectedSingleBlock: "Unexpected block statement surrounding arrow body; move the returned value immediately after the `=>`.",
|
||||
expectedBlock: "Expected block statement surrounding arrow body."
|
||||
}
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const options = context.options;
|
||||
const always = options[0] === "always";
|
||||
const asNeeded = options[0] === "as-needed";
|
||||
const never = options[0] === "never";
|
||||
const requireReturnForObjectLiteral = options[1] && options[1].requireReturnForObjectLiteral;
|
||||
const sourceCode = context.sourceCode;
|
||||
let funcInfo = null;
|
||||
|
||||
/**
|
||||
* Checks whether the given node has ASI problem or not.
|
||||
* @param {Token} token The token to check.
|
||||
* @returns {boolean} `true` if it changes semantics if `;` or `}` followed by the token are removed.
|
||||
*/
|
||||
function hasASIProblem(token) {
|
||||
return token && token.type === "Punctuator" && /^[([/`+-]/u.test(token.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the closing parenthesis by the given node.
|
||||
* @param {ASTNode} node first node after an opening parenthesis.
|
||||
* @returns {Token} The found closing parenthesis token.
|
||||
*/
|
||||
function findClosingParen(node) {
|
||||
let nodeToCheck = node;
|
||||
|
||||
while (!astUtils.isParenthesised(sourceCode, nodeToCheck)) {
|
||||
nodeToCheck = nodeToCheck.parent;
|
||||
}
|
||||
return sourceCode.getTokenAfter(nodeToCheck);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the node is inside of a for loop's init
|
||||
* @param {ASTNode} node node is inside for loop
|
||||
* @returns {boolean} `true` if the node is inside of a for loop, else `false`
|
||||
*/
|
||||
function isInsideForLoopInitializer(node) {
|
||||
if (node && node.parent) {
|
||||
if (node.parent.type === "ForStatement" && node.parent.init === node) {
|
||||
return true;
|
||||
}
|
||||
return isInsideForLoopInitializer(node.parent);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a arrow function body needs braces
|
||||
* @param {ASTNode} node The arrow function node.
|
||||
* @returns {void}
|
||||
*/
|
||||
function validate(node) {
|
||||
const arrowBody = node.body;
|
||||
|
||||
if (arrowBody.type === "BlockStatement") {
|
||||
const blockBody = arrowBody.body;
|
||||
|
||||
if (blockBody.length !== 1 && !never) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (asNeeded && requireReturnForObjectLiteral && blockBody[0].type === "ReturnStatement" &&
|
||||
blockBody[0].argument && blockBody[0].argument.type === "ObjectExpression") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (never || asNeeded && blockBody[0].type === "ReturnStatement") {
|
||||
let messageId;
|
||||
|
||||
if (blockBody.length === 0) {
|
||||
messageId = "unexpectedEmptyBlock";
|
||||
} else if (blockBody.length > 1) {
|
||||
messageId = "unexpectedOtherBlock";
|
||||
} else if (blockBody[0].argument === null) {
|
||||
messageId = "unexpectedSingleBlock";
|
||||
} else if (astUtils.isOpeningBraceToken(sourceCode.getFirstToken(blockBody[0], { skip: 1 }))) {
|
||||
messageId = "unexpectedObjectBlock";
|
||||
} else {
|
||||
messageId = "unexpectedSingleBlock";
|
||||
}
|
||||
|
||||
context.report({
|
||||
node,
|
||||
loc: arrowBody.loc,
|
||||
messageId,
|
||||
fix(fixer) {
|
||||
const fixes = [];
|
||||
|
||||
if (blockBody.length !== 1 ||
|
||||
blockBody[0].type !== "ReturnStatement" ||
|
||||
!blockBody[0].argument ||
|
||||
hasASIProblem(sourceCode.getTokenAfter(arrowBody))
|
||||
) {
|
||||
return fixes;
|
||||
}
|
||||
|
||||
const openingBrace = sourceCode.getFirstToken(arrowBody);
|
||||
const closingBrace = sourceCode.getLastToken(arrowBody);
|
||||
const firstValueToken = sourceCode.getFirstToken(blockBody[0], 1);
|
||||
const lastValueToken = sourceCode.getLastToken(blockBody[0]);
|
||||
const commentsExist =
|
||||
sourceCode.commentsExistBetween(openingBrace, firstValueToken) ||
|
||||
sourceCode.commentsExistBetween(lastValueToken, closingBrace);
|
||||
|
||||
/*
|
||||
* Remove tokens around the return value.
|
||||
* If comments don't exist, remove extra spaces as well.
|
||||
*/
|
||||
if (commentsExist) {
|
||||
fixes.push(
|
||||
fixer.remove(openingBrace),
|
||||
fixer.remove(closingBrace),
|
||||
fixer.remove(sourceCode.getTokenAfter(openingBrace)) // return keyword
|
||||
);
|
||||
} else {
|
||||
fixes.push(
|
||||
fixer.removeRange([openingBrace.range[0], firstValueToken.range[0]]),
|
||||
fixer.removeRange([lastValueToken.range[1], closingBrace.range[1]])
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* If the first token of the return value is `{` or the return value is a sequence expression,
|
||||
* enclose the return value by parentheses to avoid syntax error.
|
||||
*/
|
||||
if (astUtils.isOpeningBraceToken(firstValueToken) || blockBody[0].argument.type === "SequenceExpression" || (funcInfo.hasInOperator && isInsideForLoopInitializer(node))) {
|
||||
if (!astUtils.isParenthesised(sourceCode, blockBody[0].argument)) {
|
||||
fixes.push(
|
||||
fixer.insertTextBefore(firstValueToken, "("),
|
||||
fixer.insertTextAfter(lastValueToken, ")")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If the last token of the return statement is semicolon, remove it.
|
||||
* Non-block arrow body is an expression, not a statement.
|
||||
*/
|
||||
if (astUtils.isSemicolonToken(lastValueToken)) {
|
||||
fixes.push(fixer.remove(lastValueToken));
|
||||
}
|
||||
|
||||
return fixes;
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (always || (asNeeded && requireReturnForObjectLiteral && arrowBody.type === "ObjectExpression")) {
|
||||
context.report({
|
||||
node,
|
||||
loc: arrowBody.loc,
|
||||
messageId: "expectedBlock",
|
||||
fix(fixer) {
|
||||
const fixes = [];
|
||||
const arrowToken = sourceCode.getTokenBefore(arrowBody, astUtils.isArrowToken);
|
||||
const [firstTokenAfterArrow, secondTokenAfterArrow] = sourceCode.getTokensAfter(arrowToken, { count: 2 });
|
||||
const lastToken = sourceCode.getLastToken(node);
|
||||
|
||||
let parenthesisedObjectLiteral = null;
|
||||
|
||||
if (
|
||||
astUtils.isOpeningParenToken(firstTokenAfterArrow) &&
|
||||
astUtils.isOpeningBraceToken(secondTokenAfterArrow)
|
||||
) {
|
||||
const braceNode = sourceCode.getNodeByRangeIndex(secondTokenAfterArrow.range[0]);
|
||||
|
||||
if (braceNode.type === "ObjectExpression") {
|
||||
parenthesisedObjectLiteral = braceNode;
|
||||
}
|
||||
}
|
||||
|
||||
// If the value is object literal, remove parentheses which were forced by syntax.
|
||||
if (parenthesisedObjectLiteral) {
|
||||
const openingParenToken = firstTokenAfterArrow;
|
||||
const openingBraceToken = secondTokenAfterArrow;
|
||||
|
||||
if (astUtils.isTokenOnSameLine(openingParenToken, openingBraceToken)) {
|
||||
fixes.push(fixer.replaceText(openingParenToken, "{return "));
|
||||
} else {
|
||||
|
||||
// Avoid ASI
|
||||
fixes.push(
|
||||
fixer.replaceText(openingParenToken, "{"),
|
||||
fixer.insertTextBefore(openingBraceToken, "return ")
|
||||
);
|
||||
}
|
||||
|
||||
// Closing paren for the object doesn't have to be lastToken, e.g.: () => ({}).foo()
|
||||
fixes.push(fixer.remove(findClosingParen(parenthesisedObjectLiteral)));
|
||||
fixes.push(fixer.insertTextAfter(lastToken, "}"));
|
||||
|
||||
} else {
|
||||
fixes.push(fixer.insertTextBefore(firstTokenAfterArrow, "{return "));
|
||||
fixes.push(fixer.insertTextAfter(lastToken, "}"));
|
||||
}
|
||||
|
||||
return fixes;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
"BinaryExpression[operator='in']"() {
|
||||
let info = funcInfo;
|
||||
|
||||
while (info) {
|
||||
info.hasInOperator = true;
|
||||
info = info.upper;
|
||||
}
|
||||
},
|
||||
ArrowFunctionExpression() {
|
||||
funcInfo = {
|
||||
upper: funcInfo,
|
||||
hasInOperator: false
|
||||
};
|
||||
},
|
||||
"ArrowFunctionExpression:exit"(node) {
|
||||
validate(node);
|
||||
funcInfo = funcInfo.upper;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
186
node_modules/eslint/lib/rules/arrow-parens.js
generated
vendored
Normal file
186
node_modules/eslint/lib/rules/arrow-parens.js
generated
vendored
Normal file
@ -0,0 +1,186 @@
|
||||
/**
|
||||
* @fileoverview Rule to require parens in arrow function arguments.
|
||||
* @author Jxck
|
||||
* @deprecated in ESLint v8.53.0
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("./utils/ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Determines if the given arrow function has block body.
|
||||
* @param {ASTNode} node `ArrowFunctionExpression` node.
|
||||
* @returns {boolean} `true` if the function has block body.
|
||||
*/
|
||||
function hasBlockBody(node) {
|
||||
return node.body.type === "BlockStatement";
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('../shared/types').Rule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
deprecated: true,
|
||||
replacedBy: [],
|
||||
type: "layout",
|
||||
|
||||
docs: {
|
||||
description: "Require parentheses around arrow function arguments",
|
||||
recommended: false,
|
||||
url: "https://eslint.org/docs/latest/rules/arrow-parens"
|
||||
},
|
||||
|
||||
fixable: "code",
|
||||
|
||||
schema: [
|
||||
{
|
||||
enum: ["always", "as-needed"]
|
||||
},
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
requireForBlockBody: {
|
||||
type: "boolean",
|
||||
default: false
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
}
|
||||
],
|
||||
|
||||
messages: {
|
||||
unexpectedParens: "Unexpected parentheses around single function argument.",
|
||||
expectedParens: "Expected parentheses around arrow function argument.",
|
||||
|
||||
unexpectedParensInline: "Unexpected parentheses around single function argument having a body with no curly braces.",
|
||||
expectedParensBlock: "Expected parentheses around arrow function argument having a body with curly braces."
|
||||
}
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const asNeeded = context.options[0] === "as-needed";
|
||||
const requireForBlockBody = asNeeded && context.options[1] && context.options[1].requireForBlockBody === true;
|
||||
|
||||
const sourceCode = context.sourceCode;
|
||||
|
||||
/**
|
||||
* Finds opening paren of parameters for the given arrow function, if it exists.
|
||||
* It is assumed that the given arrow function has exactly one parameter.
|
||||
* @param {ASTNode} node `ArrowFunctionExpression` node.
|
||||
* @returns {Token|null} the opening paren, or `null` if the given arrow function doesn't have parens of parameters.
|
||||
*/
|
||||
function findOpeningParenOfParams(node) {
|
||||
const tokenBeforeParams = sourceCode.getTokenBefore(node.params[0]);
|
||||
|
||||
if (
|
||||
tokenBeforeParams &&
|
||||
astUtils.isOpeningParenToken(tokenBeforeParams) &&
|
||||
node.range[0] <= tokenBeforeParams.range[0]
|
||||
) {
|
||||
return tokenBeforeParams;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds closing paren of parameters for the given arrow function.
|
||||
* It is assumed that the given arrow function has parens of parameters and that it has exactly one parameter.
|
||||
* @param {ASTNode} node `ArrowFunctionExpression` node.
|
||||
* @returns {Token} the closing paren of parameters.
|
||||
*/
|
||||
function getClosingParenOfParams(node) {
|
||||
return sourceCode.getTokenAfter(node.params[0], astUtils.isClosingParenToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the given arrow function has comments inside parens of parameters.
|
||||
* It is assumed that the given arrow function has parens of parameters.
|
||||
* @param {ASTNode} node `ArrowFunctionExpression` node.
|
||||
* @param {Token} openingParen Opening paren of parameters.
|
||||
* @returns {boolean} `true` if the function has at least one comment inside of parens of parameters.
|
||||
*/
|
||||
function hasCommentsInParensOfParams(node, openingParen) {
|
||||
return sourceCode.commentsExistBetween(openingParen, getClosingParenOfParams(node));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the given arrow function has unexpected tokens before opening paren of parameters,
|
||||
* in which case it will be assumed that the existing parens of parameters are necessary.
|
||||
* Only tokens within the range of the arrow function (tokens that are part of the arrow function) are taken into account.
|
||||
* Example: <T>(a) => b
|
||||
* @param {ASTNode} node `ArrowFunctionExpression` node.
|
||||
* @param {Token} openingParen Opening paren of parameters.
|
||||
* @returns {boolean} `true` if the function has at least one unexpected token.
|
||||
*/
|
||||
function hasUnexpectedTokensBeforeOpeningParen(node, openingParen) {
|
||||
const expectedCount = node.async ? 1 : 0;
|
||||
|
||||
return sourceCode.getFirstToken(node, { skip: expectedCount }) !== openingParen;
|
||||
}
|
||||
|
||||
return {
|
||||
"ArrowFunctionExpression[params.length=1]"(node) {
|
||||
const shouldHaveParens = !asNeeded || requireForBlockBody && hasBlockBody(node);
|
||||
const openingParen = findOpeningParenOfParams(node);
|
||||
const hasParens = openingParen !== null;
|
||||
const [param] = node.params;
|
||||
|
||||
if (shouldHaveParens && !hasParens) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: requireForBlockBody ? "expectedParensBlock" : "expectedParens",
|
||||
loc: param.loc,
|
||||
*fix(fixer) {
|
||||
yield fixer.insertTextBefore(param, "(");
|
||||
yield fixer.insertTextAfter(param, ")");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
!shouldHaveParens &&
|
||||
hasParens &&
|
||||
param.type === "Identifier" &&
|
||||
!param.typeAnnotation &&
|
||||
!node.returnType &&
|
||||
!hasCommentsInParensOfParams(node, openingParen) &&
|
||||
!hasUnexpectedTokensBeforeOpeningParen(node, openingParen)
|
||||
) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: requireForBlockBody ? "unexpectedParensInline" : "unexpectedParens",
|
||||
loc: param.loc,
|
||||
*fix(fixer) {
|
||||
const tokenBeforeOpeningParen = sourceCode.getTokenBefore(openingParen);
|
||||
const closingParen = getClosingParenOfParams(node);
|
||||
|
||||
if (
|
||||
tokenBeforeOpeningParen &&
|
||||
tokenBeforeOpeningParen.range[1] === openingParen.range[0] &&
|
||||
!astUtils.canTokensBeAdjacent(tokenBeforeOpeningParen, sourceCode.getFirstToken(param))
|
||||
) {
|
||||
yield fixer.insertTextBefore(openingParen, " ");
|
||||
}
|
||||
|
||||
// remove parens, whitespace inside parens, and possible trailing comma
|
||||
yield fixer.removeRange([openingParen.range[0], param.range[0]]);
|
||||
yield fixer.removeRange([param.range[1], closingParen.range[1]]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
164
node_modules/eslint/lib/rules/arrow-spacing.js
generated
vendored
Normal file
164
node_modules/eslint/lib/rules/arrow-spacing.js
generated
vendored
Normal file
@ -0,0 +1,164 @@
|
||||
/**
|
||||
* @fileoverview Rule to define spacing before/after arrow function's arrow.
|
||||
* @author Jxck
|
||||
* @deprecated in ESLint v8.53.0
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("./utils/ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('../shared/types').Rule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
deprecated: true,
|
||||
replacedBy: [],
|
||||
type: "layout",
|
||||
|
||||
docs: {
|
||||
description: "Enforce consistent spacing before and after the arrow in arrow functions",
|
||||
recommended: false,
|
||||
url: "https://eslint.org/docs/latest/rules/arrow-spacing"
|
||||
},
|
||||
|
||||
fixable: "whitespace",
|
||||
|
||||
schema: [
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
before: {
|
||||
type: "boolean",
|
||||
default: true
|
||||
},
|
||||
after: {
|
||||
type: "boolean",
|
||||
default: true
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
}
|
||||
],
|
||||
|
||||
messages: {
|
||||
expectedBefore: "Missing space before =>.",
|
||||
unexpectedBefore: "Unexpected space before =>.",
|
||||
|
||||
expectedAfter: "Missing space after =>.",
|
||||
unexpectedAfter: "Unexpected space after =>."
|
||||
}
|
||||
},
|
||||
|
||||
create(context) {
|
||||
|
||||
// merge rules with default
|
||||
const rule = Object.assign({}, context.options[0]);
|
||||
|
||||
rule.before = rule.before !== false;
|
||||
rule.after = rule.after !== false;
|
||||
|
||||
const sourceCode = context.sourceCode;
|
||||
|
||||
/**
|
||||
* Get tokens of arrow(`=>`) and before/after arrow.
|
||||
* @param {ASTNode} node The arrow function node.
|
||||
* @returns {Object} Tokens of arrow and before/after arrow.
|
||||
*/
|
||||
function getTokens(node) {
|
||||
const arrow = sourceCode.getTokenBefore(node.body, astUtils.isArrowToken);
|
||||
|
||||
return {
|
||||
before: sourceCode.getTokenBefore(arrow),
|
||||
arrow,
|
||||
after: sourceCode.getTokenAfter(arrow)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Count spaces before/after arrow(`=>`) token.
|
||||
* @param {Object} tokens Tokens before/after arrow.
|
||||
* @returns {Object} count of space before/after arrow.
|
||||
*/
|
||||
function countSpaces(tokens) {
|
||||
const before = tokens.arrow.range[0] - tokens.before.range[1];
|
||||
const after = tokens.after.range[0] - tokens.arrow.range[1];
|
||||
|
||||
return { before, after };
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether space(s) before after arrow(`=>`) is satisfy rule.
|
||||
* if before/after value is `true`, there should be space(s).
|
||||
* if before/after value is `false`, there should be no space.
|
||||
* @param {ASTNode} node The arrow function node.
|
||||
* @returns {void}
|
||||
*/
|
||||
function spaces(node) {
|
||||
const tokens = getTokens(node);
|
||||
const countSpace = countSpaces(tokens);
|
||||
|
||||
if (rule.before) {
|
||||
|
||||
// should be space(s) before arrow
|
||||
if (countSpace.before === 0) {
|
||||
context.report({
|
||||
node: tokens.before,
|
||||
messageId: "expectedBefore",
|
||||
fix(fixer) {
|
||||
return fixer.insertTextBefore(tokens.arrow, " ");
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
||||
// should be no space before arrow
|
||||
if (countSpace.before > 0) {
|
||||
context.report({
|
||||
node: tokens.before,
|
||||
messageId: "unexpectedBefore",
|
||||
fix(fixer) {
|
||||
return fixer.removeRange([tokens.before.range[1], tokens.arrow.range[0]]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (rule.after) {
|
||||
|
||||
// should be space(s) after arrow
|
||||
if (countSpace.after === 0) {
|
||||
context.report({
|
||||
node: tokens.after,
|
||||
messageId: "expectedAfter",
|
||||
fix(fixer) {
|
||||
return fixer.insertTextAfter(tokens.arrow, " ");
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
||||
// should be no space after arrow
|
||||
if (countSpace.after > 0) {
|
||||
context.report({
|
||||
node: tokens.after,
|
||||
messageId: "unexpectedAfter",
|
||||
fix(fixer) {
|
||||
return fixer.removeRange([tokens.arrow.range[1], tokens.after.range[0]]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
ArrowFunctionExpression: spaces
|
||||
};
|
||||
}
|
||||
};
|
135
node_modules/eslint/lib/rules/block-scoped-var.js
generated
vendored
Normal file
135
node_modules/eslint/lib/rules/block-scoped-var.js
generated
vendored
Normal file
@ -0,0 +1,135 @@
|
||||
/**
|
||||
* @fileoverview Rule to check for "block scoped" variables by binding context
|
||||
* @author Matt DuVall <http://www.mattduvall.com>
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('../shared/types').Rule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
|
||||
docs: {
|
||||
description: "Enforce the use of variables within the scope they are defined",
|
||||
recommended: false,
|
||||
url: "https://eslint.org/docs/latest/rules/block-scoped-var"
|
||||
},
|
||||
|
||||
schema: [],
|
||||
|
||||
messages: {
|
||||
outOfScope: "'{{name}}' declared on line {{definitionLine}} column {{definitionColumn}} is used outside of binding context."
|
||||
}
|
||||
},
|
||||
|
||||
create(context) {
|
||||
let stack = [];
|
||||
const sourceCode = context.sourceCode;
|
||||
|
||||
/**
|
||||
* Makes a block scope.
|
||||
* @param {ASTNode} node A node of a scope.
|
||||
* @returns {void}
|
||||
*/
|
||||
function enterScope(node) {
|
||||
stack.push(node.range);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pops the last block scope.
|
||||
* @returns {void}
|
||||
*/
|
||||
function exitScope() {
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports a given reference.
|
||||
* @param {eslint-scope.Reference} reference A reference to report.
|
||||
* @param {eslint-scope.Definition} definition A definition for which to report reference.
|
||||
* @returns {void}
|
||||
*/
|
||||
function report(reference, definition) {
|
||||
const identifier = reference.identifier;
|
||||
const definitionPosition = definition.name.loc.start;
|
||||
|
||||
context.report({
|
||||
node: identifier,
|
||||
messageId: "outOfScope",
|
||||
data: {
|
||||
name: identifier.name,
|
||||
definitionLine: definitionPosition.line,
|
||||
definitionColumn: definitionPosition.column + 1
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and reports references which are outside of valid scopes.
|
||||
* @param {ASTNode} node A node to get variables.
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkForVariables(node) {
|
||||
if (node.kind !== "var") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Defines a predicate to check whether or not a given reference is outside of valid scope.
|
||||
const scopeRange = stack.at(-1);
|
||||
|
||||
/**
|
||||
* Check if a reference is out of scope
|
||||
* @param {ASTNode} reference node to examine
|
||||
* @returns {boolean} True is its outside the scope
|
||||
* @private
|
||||
*/
|
||||
function isOutsideOfScope(reference) {
|
||||
const idRange = reference.identifier.range;
|
||||
|
||||
return idRange[0] < scopeRange[0] || idRange[1] > scopeRange[1];
|
||||
}
|
||||
|
||||
// Gets declared variables, and checks its references.
|
||||
const variables = sourceCode.getDeclaredVariables(node);
|
||||
|
||||
for (let i = 0; i < variables.length; ++i) {
|
||||
|
||||
// Reports.
|
||||
variables[i]
|
||||
.references
|
||||
.filter(isOutsideOfScope)
|
||||
.forEach(ref => report(ref, variables[i].defs.find(def => def.parent === node)));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
Program(node) {
|
||||
stack = [node.range];
|
||||
},
|
||||
|
||||
// Manages scopes.
|
||||
BlockStatement: enterScope,
|
||||
"BlockStatement:exit": exitScope,
|
||||
ForStatement: enterScope,
|
||||
"ForStatement:exit": exitScope,
|
||||
ForInStatement: enterScope,
|
||||
"ForInStatement:exit": exitScope,
|
||||
ForOfStatement: enterScope,
|
||||
"ForOfStatement:exit": exitScope,
|
||||
SwitchStatement: enterScope,
|
||||
"SwitchStatement:exit": exitScope,
|
||||
CatchClause: enterScope,
|
||||
"CatchClause:exit": exitScope,
|
||||
StaticBlock: enterScope,
|
||||
"StaticBlock:exit": exitScope,
|
||||
|
||||
// Finds and reports references which are outside of valid scope.
|
||||
VariableDeclaration: checkForVariables
|
||||
};
|
||||
|
||||
}
|
||||
};
|
174
node_modules/eslint/lib/rules/block-spacing.js
generated
vendored
Normal file
174
node_modules/eslint/lib/rules/block-spacing.js
generated
vendored
Normal file
@ -0,0 +1,174 @@
|
||||
/**
|
||||
* @fileoverview A rule to disallow or enforce spaces inside of single line blocks.
|
||||
* @author Toru Nagashima
|
||||
* @deprecated in ESLint v8.53.0
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const util = require("./utils/ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('../shared/types').Rule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
deprecated: true,
|
||||
replacedBy: [],
|
||||
type: "layout",
|
||||
|
||||
docs: {
|
||||
description: "Disallow or enforce spaces inside of blocks after opening block and before closing block",
|
||||
recommended: false,
|
||||
url: "https://eslint.org/docs/latest/rules/block-spacing"
|
||||
},
|
||||
|
||||
fixable: "whitespace",
|
||||
|
||||
schema: [
|
||||
{ enum: ["always", "never"] }
|
||||
],
|
||||
|
||||
messages: {
|
||||
missing: "Requires a space {{location}} '{{token}}'.",
|
||||
extra: "Unexpected space(s) {{location}} '{{token}}'."
|
||||
}
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const always = (context.options[0] !== "never"),
|
||||
messageId = always ? "missing" : "extra",
|
||||
sourceCode = context.sourceCode;
|
||||
|
||||
/**
|
||||
* Gets the open brace token from a given node.
|
||||
* @param {ASTNode} node A BlockStatement/StaticBlock/SwitchStatement node to get.
|
||||
* @returns {Token} The token of the open brace.
|
||||
*/
|
||||
function getOpenBrace(node) {
|
||||
if (node.type === "SwitchStatement") {
|
||||
if (node.cases.length > 0) {
|
||||
return sourceCode.getTokenBefore(node.cases[0]);
|
||||
}
|
||||
return sourceCode.getLastToken(node, 1);
|
||||
}
|
||||
|
||||
if (node.type === "StaticBlock") {
|
||||
return sourceCode.getFirstToken(node, { skip: 1 }); // skip the `static` token
|
||||
}
|
||||
|
||||
// "BlockStatement"
|
||||
return sourceCode.getFirstToken(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not:
|
||||
* - given tokens are on same line.
|
||||
* - there is/isn't a space between given tokens.
|
||||
* @param {Token} left A token to check.
|
||||
* @param {Token} right The token which is next to `left`.
|
||||
* @returns {boolean}
|
||||
* When the option is `"always"`, `true` if there are one or more spaces between given tokens.
|
||||
* When the option is `"never"`, `true` if there are not any spaces between given tokens.
|
||||
* If given tokens are not on same line, it's always `true`.
|
||||
*/
|
||||
function isValid(left, right) {
|
||||
return (
|
||||
!util.isTokenOnSameLine(left, right) ||
|
||||
sourceCode.isSpaceBetweenTokens(left, right) === always
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks and reports invalid spacing style inside braces.
|
||||
* @param {ASTNode} node A BlockStatement/StaticBlock/SwitchStatement node to check.
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkSpacingInsideBraces(node) {
|
||||
|
||||
// Gets braces and the first/last token of content.
|
||||
const openBrace = getOpenBrace(node);
|
||||
const closeBrace = sourceCode.getLastToken(node);
|
||||
const firstToken = sourceCode.getTokenAfter(openBrace, { includeComments: true });
|
||||
const lastToken = sourceCode.getTokenBefore(closeBrace, { includeComments: true });
|
||||
|
||||
// Skip if the node is invalid or empty.
|
||||
if (openBrace.type !== "Punctuator" ||
|
||||
openBrace.value !== "{" ||
|
||||
closeBrace.type !== "Punctuator" ||
|
||||
closeBrace.value !== "}" ||
|
||||
firstToken === closeBrace
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip line comments for option never
|
||||
if (!always && firstToken.type === "Line") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check.
|
||||
if (!isValid(openBrace, firstToken)) {
|
||||
let loc = openBrace.loc;
|
||||
|
||||
if (messageId === "extra") {
|
||||
loc = {
|
||||
start: openBrace.loc.end,
|
||||
end: firstToken.loc.start
|
||||
};
|
||||
}
|
||||
|
||||
context.report({
|
||||
node,
|
||||
loc,
|
||||
messageId,
|
||||
data: {
|
||||
location: "after",
|
||||
token: openBrace.value
|
||||
},
|
||||
fix(fixer) {
|
||||
if (always) {
|
||||
return fixer.insertTextBefore(firstToken, " ");
|
||||
}
|
||||
|
||||
return fixer.removeRange([openBrace.range[1], firstToken.range[0]]);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!isValid(lastToken, closeBrace)) {
|
||||
let loc = closeBrace.loc;
|
||||
|
||||
if (messageId === "extra") {
|
||||
loc = {
|
||||
start: lastToken.loc.end,
|
||||
end: closeBrace.loc.start
|
||||
};
|
||||
}
|
||||
context.report({
|
||||
node,
|
||||
loc,
|
||||
messageId,
|
||||
data: {
|
||||
location: "before",
|
||||
token: closeBrace.value
|
||||
},
|
||||
fix(fixer) {
|
||||
if (always) {
|
||||
return fixer.insertTextAfter(lastToken, " ");
|
||||
}
|
||||
|
||||
return fixer.removeRange([lastToken.range[1], closeBrace.range[0]]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
BlockStatement: checkSpacingInsideBraces,
|
||||
StaticBlock: checkSpacingInsideBraces,
|
||||
SwitchStatement: checkSpacingInsideBraces
|
||||
};
|
||||
}
|
||||
};
|
197
node_modules/eslint/lib/rules/brace-style.js
generated
vendored
Normal file
197
node_modules/eslint/lib/rules/brace-style.js
generated
vendored
Normal file
@ -0,0 +1,197 @@
|
||||
/**
|
||||
* @fileoverview Rule to flag block statements that do not use the one true brace style
|
||||
* @author Ian Christian Myers
|
||||
* @deprecated in ESLint v8.53.0
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const astUtils = require("./utils/ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('../shared/types').Rule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
deprecated: true,
|
||||
replacedBy: [],
|
||||
type: "layout",
|
||||
|
||||
docs: {
|
||||
description: "Enforce consistent brace style for blocks",
|
||||
recommended: false,
|
||||
url: "https://eslint.org/docs/latest/rules/brace-style"
|
||||
},
|
||||
|
||||
schema: [
|
||||
{
|
||||
enum: ["1tbs", "stroustrup", "allman"]
|
||||
},
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
allowSingleLine: {
|
||||
type: "boolean",
|
||||
default: false
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
}
|
||||
],
|
||||
|
||||
fixable: "whitespace",
|
||||
|
||||
messages: {
|
||||
nextLineOpen: "Opening curly brace does not appear on the same line as controlling statement.",
|
||||
sameLineOpen: "Opening curly brace appears on the same line as controlling statement.",
|
||||
blockSameLine: "Statement inside of curly braces should be on next line.",
|
||||
nextLineClose: "Closing curly brace does not appear on the same line as the subsequent block.",
|
||||
singleLineClose: "Closing curly brace should be on the same line as opening curly brace or on the line after the previous block.",
|
||||
sameLineClose: "Closing curly brace appears on the same line as the subsequent block."
|
||||
}
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const style = context.options[0] || "1tbs",
|
||||
params = context.options[1] || {},
|
||||
sourceCode = context.sourceCode;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Fixes a place where a newline unexpectedly appears
|
||||
* @param {Token} firstToken The token before the unexpected newline
|
||||
* @param {Token} secondToken The token after the unexpected newline
|
||||
* @returns {Function} A fixer function to remove the newlines between the tokens
|
||||
*/
|
||||
function removeNewlineBetween(firstToken, secondToken) {
|
||||
const textRange = [firstToken.range[1], secondToken.range[0]];
|
||||
const textBetween = sourceCode.text.slice(textRange[0], textRange[1]);
|
||||
|
||||
// Don't do a fix if there is a comment between the tokens
|
||||
if (textBetween.trim()) {
|
||||
return null;
|
||||
}
|
||||
return fixer => fixer.replaceTextRange(textRange, " ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a pair of curly brackets based on the user's config
|
||||
* @param {Token} openingCurly The opening curly bracket
|
||||
* @param {Token} closingCurly The closing curly bracket
|
||||
* @returns {void}
|
||||
*/
|
||||
function validateCurlyPair(openingCurly, closingCurly) {
|
||||
const tokenBeforeOpeningCurly = sourceCode.getTokenBefore(openingCurly);
|
||||
const tokenAfterOpeningCurly = sourceCode.getTokenAfter(openingCurly);
|
||||
const tokenBeforeClosingCurly = sourceCode.getTokenBefore(closingCurly);
|
||||
const singleLineException = params.allowSingleLine && astUtils.isTokenOnSameLine(openingCurly, closingCurly);
|
||||
|
||||
if (style !== "allman" && !astUtils.isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurly)) {
|
||||
context.report({
|
||||
node: openingCurly,
|
||||
messageId: "nextLineOpen",
|
||||
fix: removeNewlineBetween(tokenBeforeOpeningCurly, openingCurly)
|
||||
});
|
||||
}
|
||||
|
||||
if (style === "allman" && astUtils.isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurly) && !singleLineException) {
|
||||
context.report({
|
||||
node: openingCurly,
|
||||
messageId: "sameLineOpen",
|
||||
fix: fixer => fixer.insertTextBefore(openingCurly, "\n")
|
||||
});
|
||||
}
|
||||
|
||||
if (astUtils.isTokenOnSameLine(openingCurly, tokenAfterOpeningCurly) && tokenAfterOpeningCurly !== closingCurly && !singleLineException) {
|
||||
context.report({
|
||||
node: openingCurly,
|
||||
messageId: "blockSameLine",
|
||||
fix: fixer => fixer.insertTextAfter(openingCurly, "\n")
|
||||
});
|
||||
}
|
||||
|
||||
if (tokenBeforeClosingCurly !== openingCurly && !singleLineException && astUtils.isTokenOnSameLine(tokenBeforeClosingCurly, closingCurly)) {
|
||||
context.report({
|
||||
node: closingCurly,
|
||||
messageId: "singleLineClose",
|
||||
fix: fixer => fixer.insertTextBefore(closingCurly, "\n")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the location of a token that appears before a keyword (e.g. a newline before `else`)
|
||||
* @param {Token} curlyToken The closing curly token. This is assumed to precede a keyword token (such as `else` or `finally`).
|
||||
* @returns {void}
|
||||
*/
|
||||
function validateCurlyBeforeKeyword(curlyToken) {
|
||||
const keywordToken = sourceCode.getTokenAfter(curlyToken);
|
||||
|
||||
if (style === "1tbs" && !astUtils.isTokenOnSameLine(curlyToken, keywordToken)) {
|
||||
context.report({
|
||||
node: curlyToken,
|
||||
messageId: "nextLineClose",
|
||||
fix: removeNewlineBetween(curlyToken, keywordToken)
|
||||
});
|
||||
}
|
||||
|
||||
if (style !== "1tbs" && astUtils.isTokenOnSameLine(curlyToken, keywordToken)) {
|
||||
context.report({
|
||||
node: curlyToken,
|
||||
messageId: "sameLineClose",
|
||||
fix: fixer => fixer.insertTextAfter(curlyToken, "\n")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public API
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
BlockStatement(node) {
|
||||
if (!astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type)) {
|
||||
validateCurlyPair(sourceCode.getFirstToken(node), sourceCode.getLastToken(node));
|
||||
}
|
||||
},
|
||||
StaticBlock(node) {
|
||||
validateCurlyPair(
|
||||
sourceCode.getFirstToken(node, { skip: 1 }), // skip the `static` token
|
||||
sourceCode.getLastToken(node)
|
||||
);
|
||||
},
|
||||
ClassBody(node) {
|
||||
validateCurlyPair(sourceCode.getFirstToken(node), sourceCode.getLastToken(node));
|
||||
},
|
||||
SwitchStatement(node) {
|
||||
const closingCurly = sourceCode.getLastToken(node);
|
||||
const openingCurly = sourceCode.getTokenBefore(node.cases.length ? node.cases[0] : closingCurly);
|
||||
|
||||
validateCurlyPair(openingCurly, closingCurly);
|
||||
},
|
||||
IfStatement(node) {
|
||||
if (node.consequent.type === "BlockStatement" && node.alternate) {
|
||||
|
||||
// Handle the keyword after the `if` block (before `else`)
|
||||
validateCurlyBeforeKeyword(sourceCode.getLastToken(node.consequent));
|
||||
}
|
||||
},
|
||||
TryStatement(node) {
|
||||
|
||||
// Handle the keyword after the `try` block (before `catch` or `finally`)
|
||||
validateCurlyBeforeKeyword(sourceCode.getLastToken(node.block));
|
||||
|
||||
if (node.handler && node.finalizer) {
|
||||
|
||||
// Handle the keyword after the `catch` block (before `finally`)
|
||||
validateCurlyBeforeKeyword(sourceCode.getLastToken(node.handler.body));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
187
node_modules/eslint/lib/rules/callback-return.js
generated
vendored
Normal file
187
node_modules/eslint/lib/rules/callback-return.js
generated
vendored
Normal file
@ -0,0 +1,187 @@
|
||||
/**
|
||||
* @fileoverview Enforce return after a callback.
|
||||
* @author Jamund Ferguson
|
||||
* @deprecated in ESLint v7.0.0
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('../shared/types').Rule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
deprecated: true,
|
||||
|
||||
replacedBy: [],
|
||||
|
||||
type: "suggestion",
|
||||
|
||||
docs: {
|
||||
description: "Require `return` statements after callbacks",
|
||||
recommended: false,
|
||||
url: "https://eslint.org/docs/latest/rules/callback-return"
|
||||
},
|
||||
|
||||
schema: [{
|
||||
type: "array",
|
||||
items: { type: "string" }
|
||||
}],
|
||||
|
||||
messages: {
|
||||
missingReturn: "Expected return with your callback function."
|
||||
}
|
||||
},
|
||||
|
||||
create(context) {
|
||||
|
||||
const callbacks = context.options[0] || ["callback", "cb", "next"],
|
||||
sourceCode = context.sourceCode;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Find the closest parent matching a list of types.
|
||||
* @param {ASTNode} node The node whose parents we are searching
|
||||
* @param {Array} types The node types to match
|
||||
* @returns {ASTNode} The matched node or undefined.
|
||||
*/
|
||||
function findClosestParentOfType(node, types) {
|
||||
if (!node.parent) {
|
||||
return null;
|
||||
}
|
||||
if (!types.includes(node.parent.type)) {
|
||||
return findClosestParentOfType(node.parent, types);
|
||||
}
|
||||
return node.parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if a node contains only identifiers
|
||||
* @param {ASTNode} node The node to check
|
||||
* @returns {boolean} Whether or not the node contains only identifiers
|
||||
*/
|
||||
function containsOnlyIdentifiers(node) {
|
||||
if (node.type === "Identifier") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (node.type === "MemberExpression") {
|
||||
if (node.object.type === "Identifier") {
|
||||
return true;
|
||||
}
|
||||
if (node.object.type === "MemberExpression") {
|
||||
return containsOnlyIdentifiers(node.object);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if a CallExpression is in our callback list.
|
||||
* @param {ASTNode} node The node to check against our callback names list.
|
||||
* @returns {boolean} Whether or not this function matches our callback name.
|
||||
*/
|
||||
function isCallback(node) {
|
||||
return containsOnlyIdentifiers(node.callee) && callbacks.includes(sourceCode.getText(node.callee));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not the callback is part of a callback expression.
|
||||
* @param {ASTNode} node The callback node
|
||||
* @param {ASTNode} parentNode The expression node
|
||||
* @returns {boolean} Whether or not this is part of a callback expression
|
||||
*/
|
||||
function isCallbackExpression(node, parentNode) {
|
||||
|
||||
// ensure the parent node exists and is an expression
|
||||
if (!parentNode || parentNode.type !== "ExpressionStatement") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// cb()
|
||||
if (parentNode.expression === node) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// special case for cb && cb() and similar
|
||||
if (parentNode.expression.type === "BinaryExpression" || parentNode.expression.type === "LogicalExpression") {
|
||||
if (parentNode.expression.right === node) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
CallExpression(node) {
|
||||
|
||||
// if we're not a callback we can return
|
||||
if (!isCallback(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// find the closest block, return or loop
|
||||
const closestBlock = findClosestParentOfType(node, ["BlockStatement", "ReturnStatement", "ArrowFunctionExpression"]) || {};
|
||||
|
||||
// if our parent is a return we know we're ok
|
||||
if (closestBlock.type === "ReturnStatement") {
|
||||
return;
|
||||
}
|
||||
|
||||
// arrow functions don't always have blocks and implicitly return
|
||||
if (closestBlock.type === "ArrowFunctionExpression") {
|
||||
return;
|
||||
}
|
||||
|
||||
// block statements are part of functions and most if statements
|
||||
if (closestBlock.type === "BlockStatement") {
|
||||
|
||||
// find the last item in the block
|
||||
const lastItem = closestBlock.body.at(-1);
|
||||
|
||||
// if the callback is the last thing in a block that might be ok
|
||||
if (isCallbackExpression(node, lastItem)) {
|
||||
|
||||
const parentType = closestBlock.parent.type;
|
||||
|
||||
// but only if the block is part of a function
|
||||
if (parentType === "FunctionExpression" ||
|
||||
parentType === "FunctionDeclaration" ||
|
||||
parentType === "ArrowFunctionExpression"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ending a block with a return is also ok
|
||||
if (lastItem.type === "ReturnStatement") {
|
||||
|
||||
// but only if the callback is immediately before
|
||||
if (isCallbackExpression(node, closestBlock.body.at(-2))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// as long as you're the child of a function at this point you should be asked to return
|
||||
if (findClosestParentOfType(node, ["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"])) {
|
||||
context.report({ node, messageId: "missingReturn" });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
};
|
410
node_modules/eslint/lib/rules/camelcase.js
generated
vendored
Normal file
410
node_modules/eslint/lib/rules/camelcase.js
generated
vendored
Normal file
@ -0,0 +1,410 @@
|
||||
/**
|
||||
* @fileoverview Rule to flag non-camelcased identifiers
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("./utils/ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('../shared/types').Rule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
|
||||
defaultOptions: [{
|
||||
allow: [],
|
||||
ignoreDestructuring: false,
|
||||
ignoreGlobals: false,
|
||||
ignoreImports: false,
|
||||
properties: "always"
|
||||
}],
|
||||
|
||||
docs: {
|
||||
description: "Enforce camelcase naming convention",
|
||||
recommended: false,
|
||||
url: "https://eslint.org/docs/latest/rules/camelcase"
|
||||
},
|
||||
|
||||
schema: [
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
ignoreDestructuring: {
|
||||
type: "boolean"
|
||||
},
|
||||
ignoreImports: {
|
||||
type: "boolean"
|
||||
},
|
||||
ignoreGlobals: {
|
||||
type: "boolean"
|
||||
},
|
||||
properties: {
|
||||
enum: ["always", "never"]
|
||||
},
|
||||
allow: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "string"
|
||||
},
|
||||
minItems: 0,
|
||||
uniqueItems: true
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
}
|
||||
],
|
||||
|
||||
messages: {
|
||||
notCamelCase: "Identifier '{{name}}' is not in camel case.",
|
||||
notCamelCasePrivate: "#{{name}} is not in camel case."
|
||||
}
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const [{
|
||||
allow,
|
||||
ignoreDestructuring,
|
||||
ignoreGlobals,
|
||||
ignoreImports,
|
||||
properties
|
||||
}] = context.options;
|
||||
const sourceCode = context.sourceCode;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
// contains reported nodes to avoid reporting twice on destructuring with shorthand notation
|
||||
const reported = new Set();
|
||||
|
||||
/**
|
||||
* Checks if a string contains an underscore and isn't all upper-case
|
||||
* @param {string} name The string to check.
|
||||
* @returns {boolean} if the string is underscored
|
||||
* @private
|
||||
*/
|
||||
function isUnderscored(name) {
|
||||
const nameBody = name.replace(/^_+|_+$/gu, "");
|
||||
|
||||
// if there's an underscore, it might be A_CONSTANT, which is okay
|
||||
return nameBody.includes("_") && nameBody !== nameBody.toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a string match the ignore list
|
||||
* @param {string} name The string to check.
|
||||
* @returns {boolean} if the string is ignored
|
||||
* @private
|
||||
*/
|
||||
function isAllowed(name) {
|
||||
return allow.some(
|
||||
entry => name === entry || name.match(new RegExp(entry, "u"))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given name is good or not.
|
||||
* @param {string} name The name to check.
|
||||
* @returns {boolean} `true` if the name is good.
|
||||
* @private
|
||||
*/
|
||||
function isGoodName(name) {
|
||||
return !isUnderscored(name) || isAllowed(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given identifier reference or member expression is an assignment
|
||||
* target.
|
||||
* @param {ASTNode} node The node to check.
|
||||
* @returns {boolean} `true` if the node is an assignment target.
|
||||
*/
|
||||
function isAssignmentTarget(node) {
|
||||
const parent = node.parent;
|
||||
|
||||
switch (parent.type) {
|
||||
case "AssignmentExpression":
|
||||
case "AssignmentPattern":
|
||||
return parent.left === node;
|
||||
|
||||
case "Property":
|
||||
return (
|
||||
parent.parent.type === "ObjectPattern" &&
|
||||
parent.value === node
|
||||
);
|
||||
case "ArrayPattern":
|
||||
case "RestElement":
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given binding identifier uses the original name as-is.
|
||||
* - If it's in object destructuring or object expression, the original name is its property name.
|
||||
* - If it's in import declaration, the original name is its exported name.
|
||||
* @param {ASTNode} node The `Identifier` node to check.
|
||||
* @returns {boolean} `true` if the identifier uses the original name as-is.
|
||||
*/
|
||||
function equalsToOriginalName(node) {
|
||||
const localName = node.name;
|
||||
const valueNode = node.parent.type === "AssignmentPattern"
|
||||
? node.parent
|
||||
: node;
|
||||
const parent = valueNode.parent;
|
||||
|
||||
switch (parent.type) {
|
||||
case "Property":
|
||||
return (
|
||||
(parent.parent.type === "ObjectPattern" || parent.parent.type === "ObjectExpression") &&
|
||||
parent.value === valueNode &&
|
||||
!parent.computed &&
|
||||
parent.key.type === "Identifier" &&
|
||||
parent.key.name === localName
|
||||
);
|
||||
|
||||
case "ImportSpecifier":
|
||||
return (
|
||||
parent.local === node &&
|
||||
astUtils.getModuleExportName(parent.imported) === localName
|
||||
);
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports an AST node as a rule violation.
|
||||
* @param {ASTNode} node The node to report.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function report(node) {
|
||||
if (reported.has(node.range[0])) {
|
||||
return;
|
||||
}
|
||||
reported.add(node.range[0]);
|
||||
|
||||
// Report it.
|
||||
context.report({
|
||||
node,
|
||||
messageId: node.type === "PrivateIdentifier"
|
||||
? "notCamelCasePrivate"
|
||||
: "notCamelCase",
|
||||
data: { name: node.name }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports an identifier reference or a binding identifier.
|
||||
* @param {ASTNode} node The `Identifier` node to report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportReferenceId(node) {
|
||||
|
||||
/*
|
||||
* For backward compatibility, if it's in callings then ignore it.
|
||||
* Not sure why it is.
|
||||
*/
|
||||
if (
|
||||
node.parent.type === "CallExpression" ||
|
||||
node.parent.type === "NewExpression"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* For backward compatibility, if it's a default value of
|
||||
* destructuring/parameters then ignore it.
|
||||
* Not sure why it is.
|
||||
*/
|
||||
if (
|
||||
node.parent.type === "AssignmentPattern" &&
|
||||
node.parent.right === node
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* The `ignoreDestructuring` flag skips the identifiers that uses
|
||||
* the property name as-is.
|
||||
*/
|
||||
if (ignoreDestructuring && equalsToOriginalName(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Import attribute keys are always ignored
|
||||
*/
|
||||
if (astUtils.isImportAttributeKey(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
report(node);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
// Report camelcase of global variable references ------------------
|
||||
Program(node) {
|
||||
const scope = sourceCode.getScope(node);
|
||||
|
||||
if (!ignoreGlobals) {
|
||||
|
||||
// Defined globals in config files or directive comments.
|
||||
for (const variable of scope.variables) {
|
||||
if (
|
||||
variable.identifiers.length > 0 ||
|
||||
isGoodName(variable.name)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
for (const reference of variable.references) {
|
||||
|
||||
/*
|
||||
* For backward compatibility, this rule reports read-only
|
||||
* references as well.
|
||||
*/
|
||||
reportReferenceId(reference.identifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Undefined globals.
|
||||
for (const reference of scope.through) {
|
||||
const id = reference.identifier;
|
||||
|
||||
if (isGoodName(id.name) || astUtils.isImportAttributeKey(id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* For backward compatibility, this rule reports read-only
|
||||
* references as well.
|
||||
*/
|
||||
reportReferenceId(id);
|
||||
}
|
||||
},
|
||||
|
||||
// Report camelcase of declared variables --------------------------
|
||||
[[
|
||||
"VariableDeclaration",
|
||||
"FunctionDeclaration",
|
||||
"FunctionExpression",
|
||||
"ArrowFunctionExpression",
|
||||
"ClassDeclaration",
|
||||
"ClassExpression",
|
||||
"CatchClause"
|
||||
]](node) {
|
||||
for (const variable of sourceCode.getDeclaredVariables(node)) {
|
||||
if (isGoodName(variable.name)) {
|
||||
continue;
|
||||
}
|
||||
const id = variable.identifiers[0];
|
||||
|
||||
// Report declaration.
|
||||
if (!(ignoreDestructuring && equalsToOriginalName(id))) {
|
||||
report(id);
|
||||
}
|
||||
|
||||
/*
|
||||
* For backward compatibility, report references as well.
|
||||
* It looks unnecessary because declarations are reported.
|
||||
*/
|
||||
for (const reference of variable.references) {
|
||||
if (reference.init) {
|
||||
continue; // Skip the write references of initializers.
|
||||
}
|
||||
reportReferenceId(reference.identifier);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Report camelcase in properties ----------------------------------
|
||||
[[
|
||||
"ObjectExpression > Property[computed!=true] > Identifier.key",
|
||||
"MethodDefinition[computed!=true] > Identifier.key",
|
||||
"PropertyDefinition[computed!=true] > Identifier.key",
|
||||
"MethodDefinition > PrivateIdentifier.key",
|
||||
"PropertyDefinition > PrivateIdentifier.key"
|
||||
]](node) {
|
||||
if (properties === "never" || astUtils.isImportAttributeKey(node) || isGoodName(node.name)) {
|
||||
return;
|
||||
}
|
||||
report(node);
|
||||
},
|
||||
"MemberExpression[computed!=true] > Identifier.property"(node) {
|
||||
if (
|
||||
properties === "never" ||
|
||||
!isAssignmentTarget(node.parent) || // ← ignore read-only references.
|
||||
isGoodName(node.name)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
report(node);
|
||||
},
|
||||
|
||||
// Report camelcase in import --------------------------------------
|
||||
ImportDeclaration(node) {
|
||||
for (const variable of sourceCode.getDeclaredVariables(node)) {
|
||||
if (isGoodName(variable.name)) {
|
||||
continue;
|
||||
}
|
||||
const id = variable.identifiers[0];
|
||||
|
||||
// Report declaration.
|
||||
if (!(ignoreImports && equalsToOriginalName(id))) {
|
||||
report(id);
|
||||
}
|
||||
|
||||
/*
|
||||
* For backward compatibility, report references as well.
|
||||
* It looks unnecessary because declarations are reported.
|
||||
*/
|
||||
for (const reference of variable.references) {
|
||||
reportReferenceId(reference.identifier);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Report camelcase in re-export -----------------------------------
|
||||
[[
|
||||
"ExportAllDeclaration > Identifier.exported",
|
||||
"ExportSpecifier > Identifier.exported"
|
||||
]](node) {
|
||||
if (isGoodName(node.name)) {
|
||||
return;
|
||||
}
|
||||
report(node);
|
||||
},
|
||||
|
||||
// Report camelcase in labels --------------------------------------
|
||||
[[
|
||||
"LabeledStatement > Identifier.label",
|
||||
|
||||
/*
|
||||
* For backward compatibility, report references as well.
|
||||
* It looks unnecessary because declarations are reported.
|
||||
*/
|
||||
"BreakStatement > Identifier.label",
|
||||
"ContinueStatement > Identifier.label"
|
||||
]](node) {
|
||||
if (isGoodName(node.name)) {
|
||||
return;
|
||||
}
|
||||
report(node);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
303
node_modules/eslint/lib/rules/capitalized-comments.js
generated
vendored
Normal file
303
node_modules/eslint/lib/rules/capitalized-comments.js
generated
vendored
Normal file
@ -0,0 +1,303 @@
|
||||
/**
|
||||
* @fileoverview enforce or disallow capitalization of the first letter of a comment
|
||||
* @author Kevin Partington
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("./utils/ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const DEFAULT_IGNORE_PATTERN = astUtils.COMMENTS_IGNORE_PATTERN,
|
||||
WHITESPACE = /\s/gu,
|
||||
MAYBE_URL = /^\s*[^:/?#\s]+:\/\/[^?#]/u, // TODO: Combine w/ max-len pattern?
|
||||
LETTER_PATTERN = /\p{L}/u;
|
||||
|
||||
/*
|
||||
* Base schema body for defining the basic capitalization rule, ignorePattern,
|
||||
* and ignoreInlineComments values.
|
||||
* This can be used in a few different ways in the actual schema.
|
||||
*/
|
||||
const SCHEMA_BODY = {
|
||||
type: "object",
|
||||
properties: {
|
||||
ignorePattern: {
|
||||
type: "string"
|
||||
},
|
||||
ignoreInlineComments: {
|
||||
type: "boolean"
|
||||
},
|
||||
ignoreConsecutiveComments: {
|
||||
type: "boolean"
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
};
|
||||
const DEFAULTS = {
|
||||
ignorePattern: "",
|
||||
ignoreInlineComments: false,
|
||||
ignoreConsecutiveComments: false
|
||||
};
|
||||
|
||||
/**
|
||||
* Get normalized options for either block or line comments from the given
|
||||
* user-provided options.
|
||||
* - If the user-provided options is just a string, returns a normalized
|
||||
* set of options using default values for all other options.
|
||||
* - If the user-provided options is an object, then a normalized option
|
||||
* set is returned. Options specified in overrides will take priority
|
||||
* over options specified in the main options object, which will in
|
||||
* turn take priority over the rule's defaults.
|
||||
* @param {Object|string} rawOptions The user-provided options.
|
||||
* @param {string} which Either "line" or "block".
|
||||
* @returns {Object} The normalized options.
|
||||
*/
|
||||
function getNormalizedOptions(rawOptions, which) {
|
||||
return Object.assign({}, DEFAULTS, rawOptions[which] || rawOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get normalized options for block and line comments.
|
||||
* @param {Object|string} rawOptions The user-provided options.
|
||||
* @returns {Object} An object with "Line" and "Block" keys and corresponding
|
||||
* normalized options objects.
|
||||
*/
|
||||
function getAllNormalizedOptions(rawOptions = {}) {
|
||||
return {
|
||||
Line: getNormalizedOptions(rawOptions, "line"),
|
||||
Block: getNormalizedOptions(rawOptions, "block")
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a regular expression for each ignorePattern defined in the rule
|
||||
* options.
|
||||
*
|
||||
* This is done in order to avoid invoking the RegExp constructor repeatedly.
|
||||
* @param {Object} normalizedOptions The normalized rule options.
|
||||
* @returns {void}
|
||||
*/
|
||||
function createRegExpForIgnorePatterns(normalizedOptions) {
|
||||
Object.keys(normalizedOptions).forEach(key => {
|
||||
const ignorePatternStr = normalizedOptions[key].ignorePattern;
|
||||
|
||||
if (ignorePatternStr) {
|
||||
const regExp = RegExp(`^\\s*(?:${ignorePatternStr})`, "u");
|
||||
|
||||
normalizedOptions[key].ignorePatternRegExp = regExp;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('../shared/types').Rule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
|
||||
docs: {
|
||||
description: "Enforce or disallow capitalization of the first letter of a comment",
|
||||
recommended: false,
|
||||
url: "https://eslint.org/docs/latest/rules/capitalized-comments"
|
||||
},
|
||||
|
||||
fixable: "code",
|
||||
|
||||
schema: [
|
||||
{ enum: ["always", "never"] },
|
||||
{
|
||||
oneOf: [
|
||||
SCHEMA_BODY,
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
line: SCHEMA_BODY,
|
||||
block: SCHEMA_BODY
|
||||
},
|
||||
additionalProperties: false
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
messages: {
|
||||
unexpectedLowercaseComment: "Comments should not begin with a lowercase character.",
|
||||
unexpectedUppercaseComment: "Comments should not begin with an uppercase character."
|
||||
}
|
||||
},
|
||||
|
||||
create(context) {
|
||||
|
||||
const capitalize = context.options[0] || "always",
|
||||
normalizedOptions = getAllNormalizedOptions(context.options[1]),
|
||||
sourceCode = context.sourceCode;
|
||||
|
||||
createRegExpForIgnorePatterns(normalizedOptions);
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Helpers
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Checks whether a comment is an inline comment.
|
||||
*
|
||||
* For the purpose of this rule, a comment is inline if:
|
||||
* 1. The comment is preceded by a token on the same line; and
|
||||
* 2. The command is followed by a token on the same line.
|
||||
*
|
||||
* Note that the comment itself need not be single-line!
|
||||
*
|
||||
* Also, it follows from this definition that only block comments can
|
||||
* be considered as possibly inline. This is because line comments
|
||||
* would consume any following tokens on the same line as the comment.
|
||||
* @param {ASTNode} comment The comment node to check.
|
||||
* @returns {boolean} True if the comment is an inline comment, false
|
||||
* otherwise.
|
||||
*/
|
||||
function isInlineComment(comment) {
|
||||
const previousToken = sourceCode.getTokenBefore(comment, { includeComments: true }),
|
||||
nextToken = sourceCode.getTokenAfter(comment, { includeComments: true });
|
||||
|
||||
return Boolean(
|
||||
previousToken &&
|
||||
nextToken &&
|
||||
comment.loc.start.line === previousToken.loc.end.line &&
|
||||
comment.loc.end.line === nextToken.loc.start.line
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a comment follows another comment.
|
||||
* @param {ASTNode} comment The comment to check.
|
||||
* @returns {boolean} True if the comment follows a valid comment.
|
||||
*/
|
||||
function isConsecutiveComment(comment) {
|
||||
const previousTokenOrComment = sourceCode.getTokenBefore(comment, { includeComments: true });
|
||||
|
||||
return Boolean(
|
||||
previousTokenOrComment &&
|
||||
["Block", "Line"].includes(previousTokenOrComment.type)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a comment to determine if it is valid for this rule.
|
||||
* @param {ASTNode} comment The comment node to process.
|
||||
* @param {Object} options The options for checking this comment.
|
||||
* @returns {boolean} True if the comment is valid, false otherwise.
|
||||
*/
|
||||
function isCommentValid(comment, options) {
|
||||
|
||||
// 1. Check for default ignore pattern.
|
||||
if (DEFAULT_IGNORE_PATTERN.test(comment.value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 2. Check for custom ignore pattern.
|
||||
const commentWithoutAsterisks = comment.value
|
||||
.replace(/\*/gu, "");
|
||||
|
||||
if (options.ignorePatternRegExp && options.ignorePatternRegExp.test(commentWithoutAsterisks)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3. Check for inline comments.
|
||||
if (options.ignoreInlineComments && isInlineComment(comment)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 4. Is this a consecutive comment (and are we tolerating those)?
|
||||
if (options.ignoreConsecutiveComments && isConsecutiveComment(comment)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 5. Does the comment start with a possible URL?
|
||||
if (MAYBE_URL.test(commentWithoutAsterisks)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 6. Is the initial word character a letter?
|
||||
const commentWordCharsOnly = commentWithoutAsterisks
|
||||
.replace(WHITESPACE, "");
|
||||
|
||||
if (commentWordCharsOnly.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get the first Unicode character (1 or 2 code units).
|
||||
const [firstWordChar] = commentWordCharsOnly;
|
||||
|
||||
if (!LETTER_PATTERN.test(firstWordChar)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 7. Check the case of the initial word character.
|
||||
const isUppercase = firstWordChar !== firstWordChar.toLocaleLowerCase(),
|
||||
isLowercase = firstWordChar !== firstWordChar.toLocaleUpperCase();
|
||||
|
||||
if (capitalize === "always" && isLowercase) {
|
||||
return false;
|
||||
}
|
||||
if (capitalize === "never" && isUppercase) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a comment to determine if it needs to be reported.
|
||||
* @param {ASTNode} comment The comment node to process.
|
||||
* @returns {void}
|
||||
*/
|
||||
function processComment(comment) {
|
||||
const options = normalizedOptions[comment.type],
|
||||
commentValid = isCommentValid(comment, options);
|
||||
|
||||
if (!commentValid) {
|
||||
const messageId = capitalize === "always"
|
||||
? "unexpectedLowercaseComment"
|
||||
: "unexpectedUppercaseComment";
|
||||
|
||||
context.report({
|
||||
node: null, // Intentionally using loc instead
|
||||
loc: comment.loc,
|
||||
messageId,
|
||||
fix(fixer) {
|
||||
const match = comment.value.match(LETTER_PATTERN);
|
||||
const char = match[0];
|
||||
|
||||
// Offset match.index by 2 to account for the first 2 characters that start the comment (// or /*)
|
||||
const charIndex = comment.range[0] + match.index + 2;
|
||||
|
||||
return fixer.replaceTextRange(
|
||||
[charIndex, charIndex + char.length],
|
||||
capitalize === "always" ? char.toLocaleUpperCase() : char.toLocaleLowerCase()
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Public
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
Program() {
|
||||
const comments = sourceCode.getAllComments();
|
||||
|
||||
comments.filter(token => token.type !== "Shebang").forEach(processComment);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
191
node_modules/eslint/lib/rules/class-methods-use-this.js
generated
vendored
Normal file
191
node_modules/eslint/lib/rules/class-methods-use-this.js
generated
vendored
Normal file
@ -0,0 +1,191 @@
|
||||
/**
|
||||
* @fileoverview Rule to enforce that all class methods use 'this'.
|
||||
* @author Patrick Williams
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("./utils/ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('../shared/types').Rule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
|
||||
defaultOptions: [{
|
||||
enforceForClassFields: true,
|
||||
exceptMethods: []
|
||||
}],
|
||||
|
||||
docs: {
|
||||
description: "Enforce that class methods utilize `this`",
|
||||
recommended: false,
|
||||
url: "https://eslint.org/docs/latest/rules/class-methods-use-this"
|
||||
},
|
||||
|
||||
schema: [{
|
||||
type: "object",
|
||||
properties: {
|
||||
exceptMethods: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "string"
|
||||
}
|
||||
},
|
||||
enforceForClassFields: {
|
||||
type: "boolean"
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
}],
|
||||
|
||||
messages: {
|
||||
missingThis: "Expected 'this' to be used by class {{name}}."
|
||||
}
|
||||
},
|
||||
create(context) {
|
||||
const [options] = context.options;
|
||||
const { enforceForClassFields } = options;
|
||||
const exceptMethods = new Set(options.exceptMethods);
|
||||
|
||||
const stack = [];
|
||||
|
||||
/**
|
||||
* Push `this` used flag initialized with `false` onto the stack.
|
||||
* @returns {void}
|
||||
*/
|
||||
function pushContext() {
|
||||
stack.push(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop `this` used flag from the stack.
|
||||
* @returns {boolean | undefined} `this` used flag
|
||||
*/
|
||||
function popContext() {
|
||||
return stack.pop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the current context to false and pushes it onto the stack.
|
||||
* These booleans represent whether 'this' has been used in the context.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function enterFunction() {
|
||||
pushContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the node is an instance method
|
||||
* @param {ASTNode} node node to check
|
||||
* @returns {boolean} True if its an instance method
|
||||
* @private
|
||||
*/
|
||||
function isInstanceMethod(node) {
|
||||
switch (node.type) {
|
||||
case "MethodDefinition":
|
||||
return !node.static && node.kind !== "constructor";
|
||||
case "PropertyDefinition":
|
||||
return !node.static && enforceForClassFields;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the node is an instance method not excluded by config
|
||||
* @param {ASTNode} node node to check
|
||||
* @returns {boolean} True if it is an instance method, and not excluded by config
|
||||
* @private
|
||||
*/
|
||||
function isIncludedInstanceMethod(node) {
|
||||
if (isInstanceMethod(node)) {
|
||||
if (node.computed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const hashIfNeeded = node.key.type === "PrivateIdentifier" ? "#" : "";
|
||||
const name = node.key.type === "Literal"
|
||||
? astUtils.getStaticStringValue(node.key)
|
||||
: (node.key.name || "");
|
||||
|
||||
return !exceptMethods.has(hashIfNeeded + name);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we are leaving a function that is a method, and reports if 'this' has not been used.
|
||||
* Static methods and the constructor are exempt.
|
||||
* Then pops the context off the stack.
|
||||
* @param {ASTNode} node A function node that was entered.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function exitFunction(node) {
|
||||
const methodUsesThis = popContext();
|
||||
|
||||
if (isIncludedInstanceMethod(node.parent) && !methodUsesThis) {
|
||||
context.report({
|
||||
node,
|
||||
loc: astUtils.getFunctionHeadLoc(node, context.sourceCode),
|
||||
messageId: "missingThis",
|
||||
data: {
|
||||
name: astUtils.getFunctionNameWithKind(node)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the current context as having used 'this'.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function markThisUsed() {
|
||||
if (stack.length) {
|
||||
stack[stack.length - 1] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
FunctionDeclaration: enterFunction,
|
||||
"FunctionDeclaration:exit": exitFunction,
|
||||
FunctionExpression: enterFunction,
|
||||
"FunctionExpression:exit": exitFunction,
|
||||
|
||||
/*
|
||||
* Class field value are implicit functions.
|
||||
*/
|
||||
"PropertyDefinition > *.key:exit": pushContext,
|
||||
"PropertyDefinition:exit": popContext,
|
||||
|
||||
/*
|
||||
* Class static blocks are implicit functions. They aren't required to use `this`,
|
||||
* but we have to push context so that it captures any use of `this` in the static block
|
||||
* separately from enclosing contexts, because static blocks have their own `this` and it
|
||||
* shouldn't count as used `this` in enclosing contexts.
|
||||
*/
|
||||
StaticBlock: pushContext,
|
||||
"StaticBlock:exit": popContext,
|
||||
|
||||
ThisExpression: markThisUsed,
|
||||
Super: markThisUsed,
|
||||
...(
|
||||
enforceForClassFields && {
|
||||
"PropertyDefinition > ArrowFunctionExpression.value": enterFunction,
|
||||
"PropertyDefinition > ArrowFunctionExpression.value:exit": exitFunction
|
||||
}
|
||||
)
|
||||
};
|
||||
}
|
||||
};
|
373
node_modules/eslint/lib/rules/comma-dangle.js
generated
vendored
Normal file
373
node_modules/eslint/lib/rules/comma-dangle.js
generated
vendored
Normal file
@ -0,0 +1,373 @@
|
||||
/**
|
||||
* @fileoverview Rule to forbid or enforce dangling commas.
|
||||
* @author Ian Christian Myers
|
||||
* @deprecated in ESLint v8.53.0
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("./utils/ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const DEFAULT_OPTIONS = Object.freeze({
|
||||
arrays: "never",
|
||||
objects: "never",
|
||||
imports: "never",
|
||||
exports: "never",
|
||||
functions: "never"
|
||||
});
|
||||
|
||||
/**
|
||||
* Checks whether or not a trailing comma is allowed in a given node.
|
||||
* If the `lastItem` is `RestElement` or `RestProperty`, it disallows trailing commas.
|
||||
* @param {ASTNode} lastItem The node of the last element in the given node.
|
||||
* @returns {boolean} `true` if a trailing comma is allowed.
|
||||
*/
|
||||
function isTrailingCommaAllowed(lastItem) {
|
||||
return !(
|
||||
lastItem.type === "RestElement" ||
|
||||
lastItem.type === "RestProperty" ||
|
||||
lastItem.type === "ExperimentalRestProperty"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize option value.
|
||||
* @param {string|Object|undefined} optionValue The 1st option value to normalize.
|
||||
* @param {number} ecmaVersion The normalized ECMAScript version.
|
||||
* @returns {Object} The normalized option value.
|
||||
*/
|
||||
function normalizeOptions(optionValue, ecmaVersion) {
|
||||
if (typeof optionValue === "string") {
|
||||
return {
|
||||
arrays: optionValue,
|
||||
objects: optionValue,
|
||||
imports: optionValue,
|
||||
exports: optionValue,
|
||||
functions: ecmaVersion < 2017 ? "ignore" : optionValue
|
||||
};
|
||||
}
|
||||
if (typeof optionValue === "object" && optionValue !== null) {
|
||||
return {
|
||||
arrays: optionValue.arrays || DEFAULT_OPTIONS.arrays,
|
||||
objects: optionValue.objects || DEFAULT_OPTIONS.objects,
|
||||
imports: optionValue.imports || DEFAULT_OPTIONS.imports,
|
||||
exports: optionValue.exports || DEFAULT_OPTIONS.exports,
|
||||
functions: optionValue.functions || DEFAULT_OPTIONS.functions
|
||||
};
|
||||
}
|
||||
|
||||
return DEFAULT_OPTIONS;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('../shared/types').Rule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
deprecated: true,
|
||||
replacedBy: [],
|
||||
type: "layout",
|
||||
|
||||
docs: {
|
||||
description: "Require or disallow trailing commas",
|
||||
recommended: false,
|
||||
url: "https://eslint.org/docs/latest/rules/comma-dangle"
|
||||
},
|
||||
|
||||
fixable: "code",
|
||||
|
||||
schema: {
|
||||
definitions: {
|
||||
value: {
|
||||
enum: [
|
||||
"always-multiline",
|
||||
"always",
|
||||
"never",
|
||||
"only-multiline"
|
||||
]
|
||||
},
|
||||
valueWithIgnore: {
|
||||
enum: [
|
||||
"always-multiline",
|
||||
"always",
|
||||
"ignore",
|
||||
"never",
|
||||
"only-multiline"
|
||||
]
|
||||
}
|
||||
},
|
||||
type: "array",
|
||||
items: [
|
||||
{
|
||||
oneOf: [
|
||||
{
|
||||
$ref: "#/definitions/value"
|
||||
},
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
arrays: { $ref: "#/definitions/valueWithIgnore" },
|
||||
objects: { $ref: "#/definitions/valueWithIgnore" },
|
||||
imports: { $ref: "#/definitions/valueWithIgnore" },
|
||||
exports: { $ref: "#/definitions/valueWithIgnore" },
|
||||
functions: { $ref: "#/definitions/valueWithIgnore" }
|
||||
},
|
||||
additionalProperties: false
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
additionalItems: false
|
||||
},
|
||||
|
||||
messages: {
|
||||
unexpected: "Unexpected trailing comma.",
|
||||
missing: "Missing trailing comma."
|
||||
}
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const options = normalizeOptions(context.options[0], context.languageOptions.ecmaVersion);
|
||||
|
||||
const sourceCode = context.sourceCode;
|
||||
|
||||
/**
|
||||
* Gets the last item of the given node.
|
||||
* @param {ASTNode} node The node to get.
|
||||
* @returns {ASTNode|null} The last node or null.
|
||||
*/
|
||||
function getLastItem(node) {
|
||||
|
||||
/**
|
||||
* Returns the last element of an array
|
||||
* @param {any[]} array The input array
|
||||
* @returns {any} The last element
|
||||
*/
|
||||
function last(array) {
|
||||
return array.at(-1);
|
||||
}
|
||||
|
||||
switch (node.type) {
|
||||
case "ObjectExpression":
|
||||
case "ObjectPattern":
|
||||
return last(node.properties);
|
||||
case "ArrayExpression":
|
||||
case "ArrayPattern":
|
||||
return last(node.elements);
|
||||
case "ImportDeclaration":
|
||||
case "ExportNamedDeclaration":
|
||||
return last(node.specifiers);
|
||||
case "FunctionDeclaration":
|
||||
case "FunctionExpression":
|
||||
case "ArrowFunctionExpression":
|
||||
return last(node.params);
|
||||
case "CallExpression":
|
||||
case "NewExpression":
|
||||
return last(node.arguments);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the trailing comma token of the given node.
|
||||
* If the trailing comma does not exist, this returns the token which is
|
||||
* the insertion point of the trailing comma token.
|
||||
* @param {ASTNode} node The node to get.
|
||||
* @param {ASTNode} lastItem The last item of the node.
|
||||
* @returns {Token} The trailing comma token or the insertion point.
|
||||
*/
|
||||
function getTrailingToken(node, lastItem) {
|
||||
switch (node.type) {
|
||||
case "ObjectExpression":
|
||||
case "ArrayExpression":
|
||||
case "CallExpression":
|
||||
case "NewExpression":
|
||||
return sourceCode.getLastToken(node, 1);
|
||||
default: {
|
||||
const nextToken = sourceCode.getTokenAfter(lastItem);
|
||||
|
||||
if (astUtils.isCommaToken(nextToken)) {
|
||||
return nextToken;
|
||||
}
|
||||
return sourceCode.getLastToken(lastItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not a given node is multiline.
|
||||
* This rule handles a given node as multiline when the closing parenthesis
|
||||
* and the last element are not on the same line.
|
||||
* @param {ASTNode} node A node to check.
|
||||
* @returns {boolean} `true` if the node is multiline.
|
||||
*/
|
||||
function isMultiline(node) {
|
||||
const lastItem = getLastItem(node);
|
||||
|
||||
if (!lastItem) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const penultimateToken = getTrailingToken(node, lastItem);
|
||||
const lastToken = sourceCode.getTokenAfter(penultimateToken);
|
||||
|
||||
return lastToken.loc.end.line !== penultimateToken.loc.end.line;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports a trailing comma if it exists.
|
||||
* @param {ASTNode} node A node to check. Its type is one of
|
||||
* ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
|
||||
* ImportDeclaration, and ExportNamedDeclaration.
|
||||
* @returns {void}
|
||||
*/
|
||||
function forbidTrailingComma(node) {
|
||||
const lastItem = getLastItem(node);
|
||||
|
||||
if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const trailingToken = getTrailingToken(node, lastItem);
|
||||
|
||||
if (astUtils.isCommaToken(trailingToken)) {
|
||||
context.report({
|
||||
node: lastItem,
|
||||
loc: trailingToken.loc,
|
||||
messageId: "unexpected",
|
||||
*fix(fixer) {
|
||||
yield fixer.remove(trailingToken);
|
||||
|
||||
/*
|
||||
* Extend the range of the fix to include surrounding tokens to ensure
|
||||
* that the element after which the comma is removed stays _last_.
|
||||
* This intentionally makes conflicts in fix ranges with rules that may be
|
||||
* adding or removing elements in the same autofix pass.
|
||||
* https://github.com/eslint/eslint/issues/15660
|
||||
*/
|
||||
yield fixer.insertTextBefore(sourceCode.getTokenBefore(trailingToken), "");
|
||||
yield fixer.insertTextAfter(sourceCode.getTokenAfter(trailingToken), "");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports the last element of a given node if it does not have a trailing
|
||||
* comma.
|
||||
*
|
||||
* If a given node is `ArrayPattern` which has `RestElement`, the trailing
|
||||
* comma is disallowed, so report if it exists.
|
||||
* @param {ASTNode} node A node to check. Its type is one of
|
||||
* ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
|
||||
* ImportDeclaration, and ExportNamedDeclaration.
|
||||
* @returns {void}
|
||||
*/
|
||||
function forceTrailingComma(node) {
|
||||
const lastItem = getLastItem(node);
|
||||
|
||||
if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) {
|
||||
return;
|
||||
}
|
||||
if (!isTrailingCommaAllowed(lastItem)) {
|
||||
forbidTrailingComma(node);
|
||||
return;
|
||||
}
|
||||
|
||||
const trailingToken = getTrailingToken(node, lastItem);
|
||||
|
||||
if (trailingToken.value !== ",") {
|
||||
context.report({
|
||||
node: lastItem,
|
||||
loc: {
|
||||
start: trailingToken.loc.end,
|
||||
end: astUtils.getNextLocation(sourceCode, trailingToken.loc.end)
|
||||
},
|
||||
messageId: "missing",
|
||||
*fix(fixer) {
|
||||
yield fixer.insertTextAfter(trailingToken, ",");
|
||||
|
||||
/*
|
||||
* Extend the range of the fix to include surrounding tokens to ensure
|
||||
* that the element after which the comma is inserted stays _last_.
|
||||
* This intentionally makes conflicts in fix ranges with rules that may be
|
||||
* adding or removing elements in the same autofix pass.
|
||||
* https://github.com/eslint/eslint/issues/15660
|
||||
*/
|
||||
yield fixer.insertTextBefore(trailingToken, "");
|
||||
yield fixer.insertTextAfter(sourceCode.getTokenAfter(trailingToken), "");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If a given node is multiline, reports the last element of a given node
|
||||
* when it does not have a trailing comma.
|
||||
* Otherwise, reports a trailing comma if it exists.
|
||||
* @param {ASTNode} node A node to check. Its type is one of
|
||||
* ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
|
||||
* ImportDeclaration, and ExportNamedDeclaration.
|
||||
* @returns {void}
|
||||
*/
|
||||
function forceTrailingCommaIfMultiline(node) {
|
||||
if (isMultiline(node)) {
|
||||
forceTrailingComma(node);
|
||||
} else {
|
||||
forbidTrailingComma(node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Only if a given node is not multiline, reports the last element of a given node
|
||||
* when it does not have a trailing comma.
|
||||
* Otherwise, reports a trailing comma if it exists.
|
||||
* @param {ASTNode} node A node to check. Its type is one of
|
||||
* ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
|
||||
* ImportDeclaration, and ExportNamedDeclaration.
|
||||
* @returns {void}
|
||||
*/
|
||||
function allowTrailingCommaIfMultiline(node) {
|
||||
if (!isMultiline(node)) {
|
||||
forbidTrailingComma(node);
|
||||
}
|
||||
}
|
||||
|
||||
const predicate = {
|
||||
always: forceTrailingComma,
|
||||
"always-multiline": forceTrailingCommaIfMultiline,
|
||||
"only-multiline": allowTrailingCommaIfMultiline,
|
||||
never: forbidTrailingComma,
|
||||
ignore() {}
|
||||
};
|
||||
|
||||
return {
|
||||
ObjectExpression: predicate[options.objects],
|
||||
ObjectPattern: predicate[options.objects],
|
||||
|
||||
ArrayExpression: predicate[options.arrays],
|
||||
ArrayPattern: predicate[options.arrays],
|
||||
|
||||
ImportDeclaration: predicate[options.imports],
|
||||
|
||||
ExportNamedDeclaration: predicate[options.exports],
|
||||
|
||||
FunctionDeclaration: predicate[options.functions],
|
||||
FunctionExpression: predicate[options.functions],
|
||||
ArrowFunctionExpression: predicate[options.functions],
|
||||
CallExpression: predicate[options.functions],
|
||||
NewExpression: predicate[options.functions]
|
||||
};
|
||||
}
|
||||
};
|
192
node_modules/eslint/lib/rules/comma-spacing.js
generated
vendored
Normal file
192
node_modules/eslint/lib/rules/comma-spacing.js
generated
vendored
Normal file
@ -0,0 +1,192 @@
|
||||
/**
|
||||
* @fileoverview Comma spacing - validates spacing before and after comma
|
||||
* @author Vignesh Anand aka vegetableman.
|
||||
* @deprecated in ESLint v8.53.0
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
const astUtils = require("./utils/ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('../shared/types').Rule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
deprecated: true,
|
||||
replacedBy: [],
|
||||
type: "layout",
|
||||
|
||||
docs: {
|
||||
description: "Enforce consistent spacing before and after commas",
|
||||
recommended: false,
|
||||
url: "https://eslint.org/docs/latest/rules/comma-spacing"
|
||||
},
|
||||
|
||||
fixable: "whitespace",
|
||||
|
||||
schema: [
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
before: {
|
||||
type: "boolean",
|
||||
default: false
|
||||
},
|
||||
after: {
|
||||
type: "boolean",
|
||||
default: true
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
}
|
||||
],
|
||||
|
||||
messages: {
|
||||
missing: "A space is required {{loc}} ','.",
|
||||
unexpected: "There should be no space {{loc}} ','."
|
||||
}
|
||||
},
|
||||
|
||||
create(context) {
|
||||
|
||||
const sourceCode = context.sourceCode;
|
||||
const tokensAndComments = sourceCode.tokensAndComments;
|
||||
|
||||
const options = {
|
||||
before: context.options[0] ? context.options[0].before : false,
|
||||
after: context.options[0] ? context.options[0].after : true
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
// list of comma tokens to ignore for the check of leading whitespace
|
||||
const commaTokensToIgnore = [];
|
||||
|
||||
/**
|
||||
* Reports a spacing error with an appropriate message.
|
||||
* @param {ASTNode} node The binary expression node to report.
|
||||
* @param {string} loc Is the error "before" or "after" the comma?
|
||||
* @param {ASTNode} otherNode The node at the left or right of `node`
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function report(node, loc, otherNode) {
|
||||
context.report({
|
||||
node,
|
||||
fix(fixer) {
|
||||
if (options[loc]) {
|
||||
if (loc === "before") {
|
||||
return fixer.insertTextBefore(node, " ");
|
||||
}
|
||||
return fixer.insertTextAfter(node, " ");
|
||||
|
||||
}
|
||||
let start, end;
|
||||
const newText = "";
|
||||
|
||||
if (loc === "before") {
|
||||
start = otherNode.range[1];
|
||||
end = node.range[0];
|
||||
} else {
|
||||
start = node.range[1];
|
||||
end = otherNode.range[0];
|
||||
}
|
||||
|
||||
return fixer.replaceTextRange([start, end], newText);
|
||||
|
||||
},
|
||||
messageId: options[loc] ? "missing" : "unexpected",
|
||||
data: {
|
||||
loc
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds null elements of the given ArrayExpression or ArrayPattern node to the ignore list.
|
||||
* @param {ASTNode} node An ArrayExpression or ArrayPattern node.
|
||||
* @returns {void}
|
||||
*/
|
||||
function addNullElementsToIgnoreList(node) {
|
||||
let previousToken = sourceCode.getFirstToken(node);
|
||||
|
||||
node.elements.forEach(element => {
|
||||
let token;
|
||||
|
||||
if (element === null) {
|
||||
token = sourceCode.getTokenAfter(previousToken);
|
||||
|
||||
if (astUtils.isCommaToken(token)) {
|
||||
commaTokensToIgnore.push(token);
|
||||
}
|
||||
} else {
|
||||
token = sourceCode.getTokenAfter(element);
|
||||
}
|
||||
|
||||
previousToken = token;
|
||||
});
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
"Program:exit"() {
|
||||
tokensAndComments.forEach((token, i) => {
|
||||
|
||||
if (!astUtils.isCommaToken(token)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const previousToken = tokensAndComments[i - 1];
|
||||
const nextToken = tokensAndComments[i + 1];
|
||||
|
||||
if (
|
||||
previousToken &&
|
||||
!astUtils.isCommaToken(previousToken) && // ignore spacing between two commas
|
||||
|
||||
/*
|
||||
* `commaTokensToIgnore` are ending commas of `null` elements (array holes/elisions).
|
||||
* In addition to spacing between two commas, this can also ignore:
|
||||
*
|
||||
* - Spacing after `[` (controlled by array-bracket-spacing)
|
||||
* Example: [ , ]
|
||||
* ^
|
||||
* - Spacing after a comment (for backwards compatibility, this was possibly unintentional)
|
||||
* Example: [a, /* * / ,]
|
||||
* ^
|
||||
*/
|
||||
!commaTokensToIgnore.includes(token) &&
|
||||
|
||||
astUtils.isTokenOnSameLine(previousToken, token) &&
|
||||
options.before !== sourceCode.isSpaceBetweenTokens(previousToken, token)
|
||||
) {
|
||||
report(token, "before", previousToken);
|
||||
}
|
||||
|
||||
if (
|
||||
nextToken &&
|
||||
!astUtils.isCommaToken(nextToken) && // ignore spacing between two commas
|
||||
!astUtils.isClosingParenToken(nextToken) && // controlled by space-in-parens
|
||||
!astUtils.isClosingBracketToken(nextToken) && // controlled by array-bracket-spacing
|
||||
!astUtils.isClosingBraceToken(nextToken) && // controlled by object-curly-spacing
|
||||
!(!options.after && nextToken.type === "Line") && // special case, allow space before line comment
|
||||
astUtils.isTokenOnSameLine(token, nextToken) &&
|
||||
options.after !== sourceCode.isSpaceBetweenTokens(token, nextToken)
|
||||
) {
|
||||
report(token, "after", nextToken);
|
||||
}
|
||||
});
|
||||
},
|
||||
ArrayExpression: addNullElementsToIgnoreList,
|
||||
ArrayPattern: addNullElementsToIgnoreList
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
};
|
314
node_modules/eslint/lib/rules/comma-style.js
generated
vendored
Normal file
314
node_modules/eslint/lib/rules/comma-style.js
generated
vendored
Normal file
@ -0,0 +1,314 @@
|
||||
/**
|
||||
* @fileoverview Comma style - enforces comma styles of two types: last and first
|
||||
* @author Vignesh Anand aka vegetableman
|
||||
* @deprecated in ESLint v8.53.0
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const astUtils = require("./utils/ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('../shared/types').Rule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
deprecated: true,
|
||||
replacedBy: [],
|
||||
type: "layout",
|
||||
|
||||
docs: {
|
||||
description: "Enforce consistent comma style",
|
||||
recommended: false,
|
||||
url: "https://eslint.org/docs/latest/rules/comma-style"
|
||||
},
|
||||
|
||||
fixable: "code",
|
||||
|
||||
schema: [
|
||||
{
|
||||
enum: ["first", "last"]
|
||||
},
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
exceptions: {
|
||||
type: "object",
|
||||
additionalProperties: {
|
||||
type: "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
}
|
||||
],
|
||||
|
||||
messages: {
|
||||
unexpectedLineBeforeAndAfterComma: "Bad line breaking before and after ','.",
|
||||
expectedCommaFirst: "',' should be placed first.",
|
||||
expectedCommaLast: "',' should be placed last."
|
||||
}
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const style = context.options[0] || "last",
|
||||
sourceCode = context.sourceCode;
|
||||
const exceptions = {
|
||||
ArrayPattern: true,
|
||||
ArrowFunctionExpression: true,
|
||||
CallExpression: true,
|
||||
FunctionDeclaration: true,
|
||||
FunctionExpression: true,
|
||||
ImportDeclaration: true,
|
||||
ObjectPattern: true,
|
||||
NewExpression: true
|
||||
};
|
||||
|
||||
if (context.options.length === 2 && Object.hasOwn(context.options[1], "exceptions")) {
|
||||
const keys = Object.keys(context.options[1].exceptions);
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
exceptions[keys[i]] = context.options[1].exceptions[keys[i]];
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Modified text based on the style
|
||||
* @param {string} styleType Style type
|
||||
* @param {string} text Source code text
|
||||
* @returns {string} modified text
|
||||
* @private
|
||||
*/
|
||||
function getReplacedText(styleType, text) {
|
||||
switch (styleType) {
|
||||
case "between":
|
||||
return `,${text.replace(astUtils.LINEBREAK_MATCHER, "")}`;
|
||||
|
||||
case "first":
|
||||
return `${text},`;
|
||||
|
||||
case "last":
|
||||
return `,${text}`;
|
||||
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the fixer function for a given style.
|
||||
* @param {string} styleType comma style
|
||||
* @param {ASTNode} previousItemToken The token to check.
|
||||
* @param {ASTNode} commaToken The token to check.
|
||||
* @param {ASTNode} currentItemToken The token to check.
|
||||
* @returns {Function} Fixer function
|
||||
* @private
|
||||
*/
|
||||
function getFixerFunction(styleType, previousItemToken, commaToken, currentItemToken) {
|
||||
const text =
|
||||
sourceCode.text.slice(previousItemToken.range[1], commaToken.range[0]) +
|
||||
sourceCode.text.slice(commaToken.range[1], currentItemToken.range[0]);
|
||||
const range = [previousItemToken.range[1], currentItemToken.range[0]];
|
||||
|
||||
return function(fixer) {
|
||||
return fixer.replaceTextRange(range, getReplacedText(styleType, text));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the spacing around single items in lists.
|
||||
* @param {Token} previousItemToken The last token from the previous item.
|
||||
* @param {Token} commaToken The token representing the comma.
|
||||
* @param {Token} currentItemToken The first token of the current item.
|
||||
* @param {Token} reportItem The item to use when reporting an error.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function validateCommaItemSpacing(previousItemToken, commaToken, currentItemToken, reportItem) {
|
||||
|
||||
// if single line
|
||||
if (astUtils.isTokenOnSameLine(commaToken, currentItemToken) &&
|
||||
astUtils.isTokenOnSameLine(previousItemToken, commaToken)) {
|
||||
|
||||
// do nothing.
|
||||
|
||||
} else if (!astUtils.isTokenOnSameLine(commaToken, currentItemToken) &&
|
||||
!astUtils.isTokenOnSameLine(previousItemToken, commaToken)) {
|
||||
|
||||
const comment = sourceCode.getCommentsAfter(commaToken)[0];
|
||||
const styleType = comment && comment.type === "Block" && astUtils.isTokenOnSameLine(commaToken, comment)
|
||||
? style
|
||||
: "between";
|
||||
|
||||
// lone comma
|
||||
context.report({
|
||||
node: reportItem,
|
||||
loc: commaToken.loc,
|
||||
messageId: "unexpectedLineBeforeAndAfterComma",
|
||||
fix: getFixerFunction(styleType, previousItemToken, commaToken, currentItemToken)
|
||||
});
|
||||
|
||||
} else if (style === "first" && !astUtils.isTokenOnSameLine(commaToken, currentItemToken)) {
|
||||
|
||||
context.report({
|
||||
node: reportItem,
|
||||
loc: commaToken.loc,
|
||||
messageId: "expectedCommaFirst",
|
||||
fix: getFixerFunction(style, previousItemToken, commaToken, currentItemToken)
|
||||
});
|
||||
|
||||
} else if (style === "last" && astUtils.isTokenOnSameLine(commaToken, currentItemToken)) {
|
||||
|
||||
context.report({
|
||||
node: reportItem,
|
||||
loc: commaToken.loc,
|
||||
messageId: "expectedCommaLast",
|
||||
fix: getFixerFunction(style, previousItemToken, commaToken, currentItemToken)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the comma placement with regards to a declaration/property/element
|
||||
* @param {ASTNode} node The binary expression node to check
|
||||
* @param {string} property The property of the node containing child nodes.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function validateComma(node, property) {
|
||||
const items = node[property],
|
||||
arrayLiteral = (node.type === "ArrayExpression" || node.type === "ArrayPattern");
|
||||
|
||||
if (items.length > 1 || arrayLiteral) {
|
||||
|
||||
// seed as opening [
|
||||
let previousItemToken = sourceCode.getFirstToken(node);
|
||||
|
||||
items.forEach(item => {
|
||||
const commaToken = item ? sourceCode.getTokenBefore(item) : previousItemToken,
|
||||
currentItemToken = item ? sourceCode.getFirstToken(item) : sourceCode.getTokenAfter(commaToken),
|
||||
reportItem = item || currentItemToken;
|
||||
|
||||
/*
|
||||
* This works by comparing three token locations:
|
||||
* - previousItemToken is the last token of the previous item
|
||||
* - commaToken is the location of the comma before the current item
|
||||
* - currentItemToken is the first token of the current item
|
||||
*
|
||||
* These values get switched around if item is undefined.
|
||||
* previousItemToken will refer to the last token not belonging
|
||||
* to the current item, which could be a comma or an opening
|
||||
* square bracket. currentItemToken could be a comma.
|
||||
*
|
||||
* All comparisons are done based on these tokens directly, so
|
||||
* they are always valid regardless of an undefined item.
|
||||
*/
|
||||
if (astUtils.isCommaToken(commaToken)) {
|
||||
validateCommaItemSpacing(previousItemToken, commaToken, currentItemToken, reportItem);
|
||||
}
|
||||
|
||||
if (item) {
|
||||
const tokenAfterItem = sourceCode.getTokenAfter(item, astUtils.isNotClosingParenToken);
|
||||
|
||||
previousItemToken = tokenAfterItem
|
||||
? sourceCode.getTokenBefore(tokenAfterItem)
|
||||
: sourceCode.ast.tokens.at(-1);
|
||||
} else {
|
||||
previousItemToken = currentItemToken;
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Special case for array literals that have empty last items, such
|
||||
* as [ 1, 2, ]. These arrays only have two items show up in the
|
||||
* AST, so we need to look at the token to verify that there's no
|
||||
* dangling comma.
|
||||
*/
|
||||
if (arrayLiteral) {
|
||||
|
||||
const lastToken = sourceCode.getLastToken(node),
|
||||
nextToLastToken = sourceCode.getTokenBefore(lastToken);
|
||||
|
||||
if (astUtils.isCommaToken(nextToLastToken)) {
|
||||
validateCommaItemSpacing(
|
||||
sourceCode.getTokenBefore(nextToLastToken),
|
||||
nextToLastToken,
|
||||
lastToken,
|
||||
lastToken
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
const nodes = {};
|
||||
|
||||
if (!exceptions.VariableDeclaration) {
|
||||
nodes.VariableDeclaration = function(node) {
|
||||
validateComma(node, "declarations");
|
||||
};
|
||||
}
|
||||
if (!exceptions.ObjectExpression) {
|
||||
nodes.ObjectExpression = function(node) {
|
||||
validateComma(node, "properties");
|
||||
};
|
||||
}
|
||||
if (!exceptions.ObjectPattern) {
|
||||
nodes.ObjectPattern = function(node) {
|
||||
validateComma(node, "properties");
|
||||
};
|
||||
}
|
||||
if (!exceptions.ArrayExpression) {
|
||||
nodes.ArrayExpression = function(node) {
|
||||
validateComma(node, "elements");
|
||||
};
|
||||
}
|
||||
if (!exceptions.ArrayPattern) {
|
||||
nodes.ArrayPattern = function(node) {
|
||||
validateComma(node, "elements");
|
||||
};
|
||||
}
|
||||
if (!exceptions.FunctionDeclaration) {
|
||||
nodes.FunctionDeclaration = function(node) {
|
||||
validateComma(node, "params");
|
||||
};
|
||||
}
|
||||
if (!exceptions.FunctionExpression) {
|
||||
nodes.FunctionExpression = function(node) {
|
||||
validateComma(node, "params");
|
||||
};
|
||||
}
|
||||
if (!exceptions.ArrowFunctionExpression) {
|
||||
nodes.ArrowFunctionExpression = function(node) {
|
||||
validateComma(node, "params");
|
||||
};
|
||||
}
|
||||
if (!exceptions.CallExpression) {
|
||||
nodes.CallExpression = function(node) {
|
||||
validateComma(node, "arguments");
|
||||
};
|
||||
}
|
||||
if (!exceptions.ImportDeclaration) {
|
||||
nodes.ImportDeclaration = function(node) {
|
||||
validateComma(node, "specifiers");
|
||||
};
|
||||
}
|
||||
if (!exceptions.NewExpression) {
|
||||
nodes.NewExpression = function(node) {
|
||||
validateComma(node, "arguments");
|
||||
};
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
};
|
192
node_modules/eslint/lib/rules/complexity.js
generated
vendored
Normal file
192
node_modules/eslint/lib/rules/complexity.js
generated
vendored
Normal file
@ -0,0 +1,192 @@
|
||||
/**
|
||||
* @fileoverview Counts the cyclomatic complexity of each function of the script. See http://en.wikipedia.org/wiki/Cyclomatic_complexity.
|
||||
* Counts the number of if, conditional, for, while, try, switch/case,
|
||||
* @author Patrick Brosset
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("./utils/ast-utils");
|
||||
const { upperCaseFirst } = require("../shared/string-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const THRESHOLD_DEFAULT = 20;
|
||||
|
||||
/** @type {import('../shared/types').Rule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
|
||||
defaultOptions: [THRESHOLD_DEFAULT],
|
||||
|
||||
docs: {
|
||||
description: "Enforce a maximum cyclomatic complexity allowed in a program",
|
||||
recommended: false,
|
||||
url: "https://eslint.org/docs/latest/rules/complexity"
|
||||
},
|
||||
|
||||
schema: [
|
||||
{
|
||||
oneOf: [
|
||||
{
|
||||
type: "integer",
|
||||
minimum: 0
|
||||
},
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
maximum: {
|
||||
type: "integer",
|
||||
minimum: 0
|
||||
},
|
||||
max: {
|
||||
type: "integer",
|
||||
minimum: 0
|
||||
},
|
||||
variant: {
|
||||
enum: ["classic", "modified"]
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
messages: {
|
||||
complex: "{{name}} has a complexity of {{complexity}}. Maximum allowed is {{max}}."
|
||||
}
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const option = context.options[0];
|
||||
let threshold = THRESHOLD_DEFAULT;
|
||||
let VARIANT = "classic";
|
||||
|
||||
if (typeof option === "object") {
|
||||
if (Object.hasOwn(option, "maximum") || Object.hasOwn(option, "max")) {
|
||||
threshold = option.maximum || option.max;
|
||||
}
|
||||
|
||||
if (Object.hasOwn(option, "variant")) {
|
||||
VARIANT = option.variant;
|
||||
}
|
||||
} else if (typeof option === "number") {
|
||||
threshold = option;
|
||||
}
|
||||
|
||||
const IS_MODIFIED_COMPLEXITY = VARIANT === "modified";
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
// Using a stack to store complexity per code path
|
||||
const complexities = [];
|
||||
|
||||
/**
|
||||
* Increase the complexity of the code path in context
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function increaseComplexity() {
|
||||
complexities[complexities.length - 1]++;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public API
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
|
||||
onCodePathStart() {
|
||||
|
||||
// The initial complexity is 1, representing one execution path in the CodePath
|
||||
complexities.push(1);
|
||||
},
|
||||
|
||||
// Each branching in the code adds 1 to the complexity
|
||||
CatchClause: increaseComplexity,
|
||||
ConditionalExpression: increaseComplexity,
|
||||
LogicalExpression: increaseComplexity,
|
||||
ForStatement: increaseComplexity,
|
||||
ForInStatement: increaseComplexity,
|
||||
ForOfStatement: increaseComplexity,
|
||||
IfStatement: increaseComplexity,
|
||||
WhileStatement: increaseComplexity,
|
||||
DoWhileStatement: increaseComplexity,
|
||||
AssignmentPattern: increaseComplexity,
|
||||
|
||||
// Avoid `default`
|
||||
"SwitchCase[test]": () => IS_MODIFIED_COMPLEXITY || increaseComplexity(),
|
||||
SwitchStatement: () => IS_MODIFIED_COMPLEXITY && increaseComplexity(),
|
||||
|
||||
// Logical assignment operators have short-circuiting behavior
|
||||
AssignmentExpression(node) {
|
||||
if (astUtils.isLogicalAssignmentOperator(node.operator)) {
|
||||
increaseComplexity();
|
||||
}
|
||||
},
|
||||
|
||||
MemberExpression(node) {
|
||||
if (node.optional === true) {
|
||||
increaseComplexity();
|
||||
}
|
||||
},
|
||||
|
||||
CallExpression(node) {
|
||||
if (node.optional === true) {
|
||||
increaseComplexity();
|
||||
}
|
||||
},
|
||||
|
||||
onCodePathEnd(codePath, node) {
|
||||
const complexity = complexities.pop();
|
||||
|
||||
/*
|
||||
* This rule only evaluates complexity of functions, so "program" is excluded.
|
||||
* Class field initializers and class static blocks are implicit functions. Therefore,
|
||||
* they shouldn't contribute to the enclosing function's complexity, but their
|
||||
* own complexity should be evaluated.
|
||||
*/
|
||||
if (
|
||||
codePath.origin !== "function" &&
|
||||
codePath.origin !== "class-field-initializer" &&
|
||||
codePath.origin !== "class-static-block"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (complexity > threshold) {
|
||||
let name;
|
||||
|
||||
if (codePath.origin === "class-field-initializer") {
|
||||
name = "class field initializer";
|
||||
} else if (codePath.origin === "class-static-block") {
|
||||
name = "class static block";
|
||||
} else {
|
||||
name = astUtils.getFunctionNameWithKind(node);
|
||||
}
|
||||
|
||||
context.report({
|
||||
node,
|
||||
messageId: "complex",
|
||||
data: {
|
||||
name: upperCaseFirst(name),
|
||||
complexity,
|
||||
max: threshold
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
};
|
208
node_modules/eslint/lib/rules/computed-property-spacing.js
generated
vendored
Normal file
208
node_modules/eslint/lib/rules/computed-property-spacing.js
generated
vendored
Normal file
@ -0,0 +1,208 @@
|
||||
/**
|
||||
* @fileoverview Disallows or enforces spaces inside computed properties.
|
||||
* @author Jamund Ferguson
|
||||
* @deprecated in ESLint v8.53.0
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
const astUtils = require("./utils/ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('../shared/types').Rule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
deprecated: true,
|
||||
replacedBy: [],
|
||||
type: "layout",
|
||||
|
||||
docs: {
|
||||
description: "Enforce consistent spacing inside computed property brackets",
|
||||
recommended: false,
|
||||
url: "https://eslint.org/docs/latest/rules/computed-property-spacing"
|
||||
},
|
||||
|
||||
fixable: "whitespace",
|
||||
|
||||
schema: [
|
||||
{
|
||||
enum: ["always", "never"]
|
||||
},
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
enforceForClassMembers: {
|
||||
type: "boolean",
|
||||
default: true
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
}
|
||||
],
|
||||
|
||||
messages: {
|
||||
unexpectedSpaceBefore: "There should be no space before '{{tokenValue}}'.",
|
||||
unexpectedSpaceAfter: "There should be no space after '{{tokenValue}}'.",
|
||||
|
||||
missingSpaceBefore: "A space is required before '{{tokenValue}}'.",
|
||||
missingSpaceAfter: "A space is required after '{{tokenValue}}'."
|
||||
}
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const sourceCode = context.sourceCode;
|
||||
const propertyNameMustBeSpaced = context.options[0] === "always"; // default is "never"
|
||||
const enforceForClassMembers = !context.options[1] || context.options[1].enforceForClassMembers;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Reports that there shouldn't be a space after the first token
|
||||
* @param {ASTNode} node The node to report in the event of an error.
|
||||
* @param {Token} token The token to use for the report.
|
||||
* @param {Token} tokenAfter The token after `token`.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportNoBeginningSpace(node, token, tokenAfter) {
|
||||
context.report({
|
||||
node,
|
||||
loc: { start: token.loc.end, end: tokenAfter.loc.start },
|
||||
messageId: "unexpectedSpaceAfter",
|
||||
data: {
|
||||
tokenValue: token.value
|
||||
},
|
||||
fix(fixer) {
|
||||
return fixer.removeRange([token.range[1], tokenAfter.range[0]]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports that there shouldn't be a space before the last token
|
||||
* @param {ASTNode} node The node to report in the event of an error.
|
||||
* @param {Token} token The token to use for the report.
|
||||
* @param {Token} tokenBefore The token before `token`.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportNoEndingSpace(node, token, tokenBefore) {
|
||||
context.report({
|
||||
node,
|
||||
loc: { start: tokenBefore.loc.end, end: token.loc.start },
|
||||
messageId: "unexpectedSpaceBefore",
|
||||
data: {
|
||||
tokenValue: token.value
|
||||
},
|
||||
fix(fixer) {
|
||||
return fixer.removeRange([tokenBefore.range[1], token.range[0]]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports that there should be a space after the first token
|
||||
* @param {ASTNode} node The node to report in the event of an error.
|
||||
* @param {Token} token The token to use for the report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportRequiredBeginningSpace(node, token) {
|
||||
context.report({
|
||||
node,
|
||||
loc: token.loc,
|
||||
messageId: "missingSpaceAfter",
|
||||
data: {
|
||||
tokenValue: token.value
|
||||
},
|
||||
fix(fixer) {
|
||||
return fixer.insertTextAfter(token, " ");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports that there should be a space before the last token
|
||||
* @param {ASTNode} node The node to report in the event of an error.
|
||||
* @param {Token} token The token to use for the report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportRequiredEndingSpace(node, token) {
|
||||
context.report({
|
||||
node,
|
||||
loc: token.loc,
|
||||
messageId: "missingSpaceBefore",
|
||||
data: {
|
||||
tokenValue: token.value
|
||||
},
|
||||
fix(fixer) {
|
||||
return fixer.insertTextBefore(token, " ");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a function that checks the spacing of a node on the property name
|
||||
* that was passed in.
|
||||
* @param {string} propertyName The property on the node to check for spacing
|
||||
* @returns {Function} A function that will check spacing on a node
|
||||
*/
|
||||
function checkSpacing(propertyName) {
|
||||
return function(node) {
|
||||
if (!node.computed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const property = node[propertyName];
|
||||
|
||||
const before = sourceCode.getTokenBefore(property, astUtils.isOpeningBracketToken),
|
||||
first = sourceCode.getTokenAfter(before, { includeComments: true }),
|
||||
after = sourceCode.getTokenAfter(property, astUtils.isClosingBracketToken),
|
||||
last = sourceCode.getTokenBefore(after, { includeComments: true });
|
||||
|
||||
if (astUtils.isTokenOnSameLine(before, first)) {
|
||||
if (propertyNameMustBeSpaced) {
|
||||
if (!sourceCode.isSpaceBetweenTokens(before, first) && astUtils.isTokenOnSameLine(before, first)) {
|
||||
reportRequiredBeginningSpace(node, before);
|
||||
}
|
||||
} else {
|
||||
if (sourceCode.isSpaceBetweenTokens(before, first)) {
|
||||
reportNoBeginningSpace(node, before, first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (astUtils.isTokenOnSameLine(last, after)) {
|
||||
if (propertyNameMustBeSpaced) {
|
||||
if (!sourceCode.isSpaceBetweenTokens(last, after) && astUtils.isTokenOnSameLine(last, after)) {
|
||||
reportRequiredEndingSpace(node, after);
|
||||
}
|
||||
} else {
|
||||
if (sourceCode.isSpaceBetweenTokens(last, after)) {
|
||||
reportNoEndingSpace(node, after, last);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
const listeners = {
|
||||
Property: checkSpacing("key"),
|
||||
MemberExpression: checkSpacing("property")
|
||||
};
|
||||
|
||||
if (enforceForClassMembers) {
|
||||
listeners.MethodDefinition =
|
||||
listeners.PropertyDefinition = listeners.Property;
|
||||
}
|
||||
|
||||
return listeners;
|
||||
|
||||
}
|
||||
};
|
210
node_modules/eslint/lib/rules/consistent-return.js
generated
vendored
Normal file
210
node_modules/eslint/lib/rules/consistent-return.js
generated
vendored
Normal file
@ -0,0 +1,210 @@
|
||||
/**
|
||||
* @fileoverview Rule to flag consistent return values
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("./utils/ast-utils");
|
||||
const { upperCaseFirst } = require("../shared/string-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Checks all segments in a set and returns true if all are unreachable.
|
||||
* @param {Set<CodePathSegment>} segments The segments to check.
|
||||
* @returns {boolean} True if all segments are unreachable; false otherwise.
|
||||
*/
|
||||
function areAllSegmentsUnreachable(segments) {
|
||||
|
||||
for (const segment of segments) {
|
||||
if (segment.reachable) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given node is a `constructor` method in an ES6 class
|
||||
* @param {ASTNode} node A node to check
|
||||
* @returns {boolean} `true` if the node is a `constructor` method
|
||||
*/
|
||||
function isClassConstructor(node) {
|
||||
return node.type === "FunctionExpression" &&
|
||||
node.parent &&
|
||||
node.parent.type === "MethodDefinition" &&
|
||||
node.parent.kind === "constructor";
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('../shared/types').Rule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
|
||||
docs: {
|
||||
description: "Require `return` statements to either always or never specify values",
|
||||
recommended: false,
|
||||
url: "https://eslint.org/docs/latest/rules/consistent-return"
|
||||
},
|
||||
|
||||
schema: [{
|
||||
type: "object",
|
||||
properties: {
|
||||
treatUndefinedAsUnspecified: {
|
||||
type: "boolean"
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
}],
|
||||
|
||||
defaultOptions: [{ treatUndefinedAsUnspecified: false }],
|
||||
|
||||
messages: {
|
||||
missingReturn: "Expected to return a value at the end of {{name}}.",
|
||||
missingReturnValue: "{{name}} expected a return value.",
|
||||
unexpectedReturnValue: "{{name}} expected no return value."
|
||||
}
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const [{ treatUndefinedAsUnspecified }] = context.options;
|
||||
let funcInfo = null;
|
||||
|
||||
/**
|
||||
* Checks whether of not the implicit returning is consistent if the last
|
||||
* code path segment is reachable.
|
||||
* @param {ASTNode} node A program/function node to check.
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkLastSegment(node) {
|
||||
let loc, name;
|
||||
|
||||
/*
|
||||
* Skip if it expected no return value or unreachable.
|
||||
* When unreachable, all paths are returned or thrown.
|
||||
*/
|
||||
if (!funcInfo.hasReturnValue ||
|
||||
areAllSegmentsUnreachable(funcInfo.currentSegments) ||
|
||||
astUtils.isES5Constructor(node) ||
|
||||
isClassConstructor(node)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Adjust a location and a message.
|
||||
if (node.type === "Program") {
|
||||
|
||||
// The head of program.
|
||||
loc = { line: 1, column: 0 };
|
||||
name = "program";
|
||||
} else if (node.type === "ArrowFunctionExpression") {
|
||||
|
||||
// `=>` token
|
||||
loc = context.sourceCode.getTokenBefore(node.body, astUtils.isArrowToken).loc;
|
||||
} else if (
|
||||
node.parent.type === "MethodDefinition" ||
|
||||
(node.parent.type === "Property" && node.parent.method)
|
||||
) {
|
||||
|
||||
// Method name.
|
||||
loc = node.parent.key.loc;
|
||||
} else {
|
||||
|
||||
// Function name or `function` keyword.
|
||||
loc = (node.id || context.sourceCode.getFirstToken(node)).loc;
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
name = astUtils.getFunctionNameWithKind(node);
|
||||
}
|
||||
|
||||
// Reports.
|
||||
context.report({
|
||||
node,
|
||||
loc,
|
||||
messageId: "missingReturn",
|
||||
data: { name }
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
// Initializes/Disposes state of each code path.
|
||||
onCodePathStart(codePath, node) {
|
||||
funcInfo = {
|
||||
upper: funcInfo,
|
||||
codePath,
|
||||
hasReturn: false,
|
||||
hasReturnValue: false,
|
||||
messageId: "",
|
||||
node,
|
||||
currentSegments: new Set()
|
||||
};
|
||||
},
|
||||
onCodePathEnd() {
|
||||
funcInfo = funcInfo.upper;
|
||||
},
|
||||
|
||||
onUnreachableCodePathSegmentStart(segment) {
|
||||
funcInfo.currentSegments.add(segment);
|
||||
},
|
||||
|
||||
onUnreachableCodePathSegmentEnd(segment) {
|
||||
funcInfo.currentSegments.delete(segment);
|
||||
},
|
||||
|
||||
onCodePathSegmentStart(segment) {
|
||||
funcInfo.currentSegments.add(segment);
|
||||
},
|
||||
|
||||
onCodePathSegmentEnd(segment) {
|
||||
funcInfo.currentSegments.delete(segment);
|
||||
},
|
||||
|
||||
|
||||
// Reports a given return statement if it's inconsistent.
|
||||
ReturnStatement(node) {
|
||||
const argument = node.argument;
|
||||
let hasReturnValue = Boolean(argument);
|
||||
|
||||
if (treatUndefinedAsUnspecified && hasReturnValue) {
|
||||
hasReturnValue = !astUtils.isSpecificId(argument, "undefined") && argument.operator !== "void";
|
||||
}
|
||||
|
||||
if (!funcInfo.hasReturn) {
|
||||
funcInfo.hasReturn = true;
|
||||
funcInfo.hasReturnValue = hasReturnValue;
|
||||
funcInfo.messageId = hasReturnValue ? "missingReturnValue" : "unexpectedReturnValue";
|
||||
funcInfo.data = {
|
||||
name: funcInfo.node.type === "Program"
|
||||
? "Program"
|
||||
: upperCaseFirst(astUtils.getFunctionNameWithKind(funcInfo.node))
|
||||
};
|
||||
} else if (funcInfo.hasReturnValue !== hasReturnValue) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: funcInfo.messageId,
|
||||
data: funcInfo.data
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Reports a given program/function if the implicit returning is not consistent.
|
||||
"Program:exit": checkLastSegment,
|
||||
"FunctionDeclaration:exit": checkLastSegment,
|
||||
"FunctionExpression:exit": checkLastSegment,
|
||||
"ArrowFunctionExpression:exit": checkLastSegment
|
||||
};
|
||||
}
|
||||
};
|
149
node_modules/eslint/lib/rules/consistent-this.js
generated
vendored
Normal file
149
node_modules/eslint/lib/rules/consistent-this.js
generated
vendored
Normal file
@ -0,0 +1,149 @@
|
||||
/**
|
||||
* @fileoverview Rule to enforce consistent naming of "this" context variables
|
||||
* @author Raphael Pigulla
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('../shared/types').Rule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
|
||||
docs: {
|
||||
description: "Enforce consistent naming when capturing the current execution context",
|
||||
recommended: false,
|
||||
url: "https://eslint.org/docs/latest/rules/consistent-this"
|
||||
},
|
||||
|
||||
schema: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "string",
|
||||
minLength: 1
|
||||
},
|
||||
uniqueItems: true
|
||||
},
|
||||
|
||||
defaultOptions: ["that"],
|
||||
|
||||
messages: {
|
||||
aliasNotAssignedToThis: "Designated alias '{{name}}' is not assigned to 'this'.",
|
||||
unexpectedAlias: "Unexpected alias '{{name}}' for 'this'."
|
||||
}
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const aliases = context.options;
|
||||
const sourceCode = context.sourceCode;
|
||||
|
||||
/**
|
||||
* Reports that a variable declarator or assignment expression is assigning
|
||||
* a non-'this' value to the specified alias.
|
||||
* @param {ASTNode} node The assigning node.
|
||||
* @param {string} name the name of the alias that was incorrectly used.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportBadAssignment(node, name) {
|
||||
context.report({ node, messageId: "aliasNotAssignedToThis", data: { name } });
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that an assignment to an identifier only assigns 'this' to the
|
||||
* appropriate alias, and the alias is only assigned to 'this'.
|
||||
* @param {ASTNode} node The assigning node.
|
||||
* @param {Identifier} name The name of the variable assigned to.
|
||||
* @param {Expression} value The value of the assignment.
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkAssignment(node, name, value) {
|
||||
const isThis = value.type === "ThisExpression";
|
||||
|
||||
if (aliases.includes(name)) {
|
||||
if (!isThis || node.operator && node.operator !== "=") {
|
||||
reportBadAssignment(node, name);
|
||||
}
|
||||
} else if (isThis) {
|
||||
context.report({ node, messageId: "unexpectedAlias", data: { name } });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that a variable declaration of the alias in a program or function
|
||||
* is assigned to the correct value.
|
||||
* @param {string} alias alias the check the assignment of.
|
||||
* @param {Object} scope scope of the current code we are checking.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkWasAssigned(alias, scope) {
|
||||
const variable = scope.set.get(alias);
|
||||
|
||||
if (!variable) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (variable.defs.some(def => def.node.type === "VariableDeclarator" &&
|
||||
def.node.init !== null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* The alias has been declared and not assigned: check it was
|
||||
* assigned later in the same scope.
|
||||
*/
|
||||
if (!variable.references.some(reference => {
|
||||
const write = reference.writeExpr;
|
||||
|
||||
return (
|
||||
reference.from === scope &&
|
||||
write && write.type === "ThisExpression" &&
|
||||
write.parent.operator === "="
|
||||
);
|
||||
})) {
|
||||
variable.defs.map(def => def.node).forEach(node => {
|
||||
reportBadAssignment(node, alias);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check each alias to ensure that is was assigned to the correct value.
|
||||
* @param {ASTNode} node The node that represents the scope to check.
|
||||
* @returns {void}
|
||||
*/
|
||||
function ensureWasAssigned(node) {
|
||||
const scope = sourceCode.getScope(node);
|
||||
|
||||
aliases.forEach(alias => {
|
||||
checkWasAssigned(alias, scope);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
"Program:exit": ensureWasAssigned,
|
||||
"FunctionExpression:exit": ensureWasAssigned,
|
||||
"FunctionDeclaration:exit": ensureWasAssigned,
|
||||
|
||||
VariableDeclarator(node) {
|
||||
const id = node.id;
|
||||
const isDestructuring =
|
||||
id.type === "ArrayPattern" || id.type === "ObjectPattern";
|
||||
|
||||
if (node.init !== null && !isDestructuring) {
|
||||
checkAssignment(node, id.name, node.init);
|
||||
}
|
||||
},
|
||||
|
||||
AssignmentExpression(node) {
|
||||
if (node.left.type === "Identifier") {
|
||||
checkAssignment(node, node.left.name, node.right);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
};
|
445
node_modules/eslint/lib/rules/constructor-super.js
generated
vendored
Normal file
445
node_modules/eslint/lib/rules/constructor-super.js
generated
vendored
Normal file
@ -0,0 +1,445 @@
|
||||
/**
|
||||
* @fileoverview A rule to verify `super()` callings in constructor.
|
||||
* @author Toru Nagashima
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Checks whether or not a given node is a constructor.
|
||||
* @param {ASTNode} node A node to check. This node type is one of
|
||||
* `Program`, `FunctionDeclaration`, `FunctionExpression`, and
|
||||
* `ArrowFunctionExpression`.
|
||||
* @returns {boolean} `true` if the node is a constructor.
|
||||
*/
|
||||
function isConstructorFunction(node) {
|
||||
return (
|
||||
node.type === "FunctionExpression" &&
|
||||
node.parent.type === "MethodDefinition" &&
|
||||
node.parent.kind === "constructor"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given node can be a constructor or not.
|
||||
* @param {ASTNode} node A node to check.
|
||||
* @returns {boolean} `true` if the node can be a constructor.
|
||||
*/
|
||||
function isPossibleConstructor(node) {
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (node.type) {
|
||||
case "ClassExpression":
|
||||
case "FunctionExpression":
|
||||
case "ThisExpression":
|
||||
case "MemberExpression":
|
||||
case "CallExpression":
|
||||
case "NewExpression":
|
||||
case "ChainExpression":
|
||||
case "YieldExpression":
|
||||
case "TaggedTemplateExpression":
|
||||
case "MetaProperty":
|
||||
return true;
|
||||
|
||||
case "Identifier":
|
||||
return node.name !== "undefined";
|
||||
|
||||
case "AssignmentExpression":
|
||||
if (["=", "&&="].includes(node.operator)) {
|
||||
return isPossibleConstructor(node.right);
|
||||
}
|
||||
|
||||
if (["||=", "??="].includes(node.operator)) {
|
||||
return (
|
||||
isPossibleConstructor(node.left) ||
|
||||
isPossibleConstructor(node.right)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* All other assignment operators are mathematical assignment operators (arithmetic or bitwise).
|
||||
* An assignment expression with a mathematical operator can either evaluate to a primitive value,
|
||||
* or throw, depending on the operands. Thus, it cannot evaluate to a constructor function.
|
||||
*/
|
||||
return false;
|
||||
|
||||
case "LogicalExpression":
|
||||
|
||||
/*
|
||||
* If the && operator short-circuits, the left side was falsy and therefore not a constructor, and if
|
||||
* it doesn't short-circuit, it takes the value from the right side, so the right side must always be a
|
||||
* possible constructor. A future improvement could verify that the left side could be truthy by
|
||||
* excluding falsy literals.
|
||||
*/
|
||||
if (node.operator === "&&") {
|
||||
return isPossibleConstructor(node.right);
|
||||
}
|
||||
|
||||
return (
|
||||
isPossibleConstructor(node.left) ||
|
||||
isPossibleConstructor(node.right)
|
||||
);
|
||||
|
||||
case "ConditionalExpression":
|
||||
return (
|
||||
isPossibleConstructor(node.alternate) ||
|
||||
isPossibleConstructor(node.consequent)
|
||||
);
|
||||
|
||||
case "SequenceExpression": {
|
||||
const lastExpression = node.expressions.at(-1);
|
||||
|
||||
return isPossibleConstructor(lastExpression);
|
||||
}
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A class to store information about a code path segment.
|
||||
*/
|
||||
class SegmentInfo {
|
||||
|
||||
/**
|
||||
* Indicates if super() is called in all code paths.
|
||||
* @type {boolean}
|
||||
*/
|
||||
calledInEveryPaths = false;
|
||||
|
||||
/**
|
||||
* Indicates if super() is called in any code paths.
|
||||
* @type {boolean}
|
||||
*/
|
||||
calledInSomePaths = false;
|
||||
|
||||
/**
|
||||
* The nodes which have been validated and don't need to be reconsidered.
|
||||
* @type {ASTNode[]}
|
||||
*/
|
||||
validNodes = [];
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('../shared/types').Rule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: "problem",
|
||||
|
||||
docs: {
|
||||
description: "Require `super()` calls in constructors",
|
||||
recommended: true,
|
||||
url: "https://eslint.org/docs/latest/rules/constructor-super"
|
||||
},
|
||||
|
||||
schema: [],
|
||||
|
||||
messages: {
|
||||
missingSome: "Lacked a call of 'super()' in some code paths.",
|
||||
missingAll: "Expected to call 'super()'.",
|
||||
|
||||
duplicate: "Unexpected duplicate 'super()'.",
|
||||
badSuper: "Unexpected 'super()' because 'super' is not a constructor."
|
||||
}
|
||||
},
|
||||
|
||||
create(context) {
|
||||
|
||||
/*
|
||||
* {{hasExtends: boolean, scope: Scope, codePath: CodePath}[]}
|
||||
* Information for each constructor.
|
||||
* - upper: Information of the upper constructor.
|
||||
* - hasExtends: A flag which shows whether own class has a valid `extends`
|
||||
* part.
|
||||
* - scope: The scope of own class.
|
||||
* - codePath: The code path object of the constructor.
|
||||
*/
|
||||
let funcInfo = null;
|
||||
|
||||
/**
|
||||
* @type {Record<string, SegmentInfo>}
|
||||
*/
|
||||
const segInfoMap = Object.create(null);
|
||||
|
||||
/**
|
||||
* Gets the flag which shows `super()` is called in some paths.
|
||||
* @param {CodePathSegment} segment A code path segment to get.
|
||||
* @returns {boolean} The flag which shows `super()` is called in some paths
|
||||
*/
|
||||
function isCalledInSomePath(segment) {
|
||||
return segment.reachable && segInfoMap[segment.id].calledInSomePaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a segment has been seen in the traversal.
|
||||
* @param {CodePathSegment} segment A code path segment to check.
|
||||
* @returns {boolean} `true` if the segment has been seen.
|
||||
*/
|
||||
function hasSegmentBeenSeen(segment) {
|
||||
return !!segInfoMap[segment.id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the flag which shows `super()` is called in all paths.
|
||||
* @param {CodePathSegment} segment A code path segment to get.
|
||||
* @returns {boolean} The flag which shows `super()` is called in all paths.
|
||||
*/
|
||||
function isCalledInEveryPath(segment) {
|
||||
return segment.reachable && segInfoMap[segment.id].calledInEveryPaths;
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
/**
|
||||
* Stacks a constructor information.
|
||||
* @param {CodePath} codePath A code path which was started.
|
||||
* @param {ASTNode} node The current node.
|
||||
* @returns {void}
|
||||
*/
|
||||
onCodePathStart(codePath, node) {
|
||||
if (isConstructorFunction(node)) {
|
||||
|
||||
// Class > ClassBody > MethodDefinition > FunctionExpression
|
||||
const classNode = node.parent.parent.parent;
|
||||
const superClass = classNode.superClass;
|
||||
|
||||
funcInfo = {
|
||||
upper: funcInfo,
|
||||
isConstructor: true,
|
||||
hasExtends: Boolean(superClass),
|
||||
superIsConstructor: isPossibleConstructor(superClass),
|
||||
codePath,
|
||||
currentSegments: new Set()
|
||||
};
|
||||
} else {
|
||||
funcInfo = {
|
||||
upper: funcInfo,
|
||||
isConstructor: false,
|
||||
hasExtends: false,
|
||||
superIsConstructor: false,
|
||||
codePath,
|
||||
currentSegments: new Set()
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Pops a constructor information.
|
||||
* And reports if `super()` lacked.
|
||||
* @param {CodePath} codePath A code path which was ended.
|
||||
* @param {ASTNode} node The current node.
|
||||
* @returns {void}
|
||||
*/
|
||||
onCodePathEnd(codePath, node) {
|
||||
const hasExtends = funcInfo.hasExtends;
|
||||
|
||||
// Pop.
|
||||
funcInfo = funcInfo.upper;
|
||||
|
||||
if (!hasExtends) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reports if `super()` lacked.
|
||||
const returnedSegments = codePath.returnedSegments;
|
||||
const calledInEveryPaths = returnedSegments.every(isCalledInEveryPath);
|
||||
const calledInSomePaths = returnedSegments.some(isCalledInSomePath);
|
||||
|
||||
if (!calledInEveryPaths) {
|
||||
context.report({
|
||||
messageId: calledInSomePaths
|
||||
? "missingSome"
|
||||
: "missingAll",
|
||||
node: node.parent
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize information of a given code path segment.
|
||||
* @param {CodePathSegment} segment A code path segment to initialize.
|
||||
* @param {CodePathSegment} node Node that starts the segment.
|
||||
* @returns {void}
|
||||
*/
|
||||
onCodePathSegmentStart(segment, node) {
|
||||
|
||||
funcInfo.currentSegments.add(segment);
|
||||
|
||||
if (!(funcInfo.isConstructor && funcInfo.hasExtends)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize info.
|
||||
const info = segInfoMap[segment.id] = new SegmentInfo();
|
||||
|
||||
const seenPrevSegments = segment.prevSegments.filter(hasSegmentBeenSeen);
|
||||
|
||||
// When there are previous segments, aggregates these.
|
||||
if (seenPrevSegments.length > 0) {
|
||||
info.calledInSomePaths = seenPrevSegments.some(isCalledInSomePath);
|
||||
info.calledInEveryPaths = seenPrevSegments.every(isCalledInEveryPath);
|
||||
}
|
||||
|
||||
/*
|
||||
* ForStatement > *.update segments are a special case as they are created in advance,
|
||||
* without seen previous segments. Since they logically don't affect `calledInEveryPaths`
|
||||
* calculations, and they can never be a lone previous segment of another one, we'll set
|
||||
* their `calledInEveryPaths` to `true` to effectively ignore them in those calculations.
|
||||
* .
|
||||
*/
|
||||
if (node.parent && node.parent.type === "ForStatement" && node.parent.update === node) {
|
||||
info.calledInEveryPaths = true;
|
||||
}
|
||||
},
|
||||
|
||||
onUnreachableCodePathSegmentStart(segment) {
|
||||
funcInfo.currentSegments.add(segment);
|
||||
},
|
||||
|
||||
onUnreachableCodePathSegmentEnd(segment) {
|
||||
funcInfo.currentSegments.delete(segment);
|
||||
},
|
||||
|
||||
onCodePathSegmentEnd(segment) {
|
||||
funcInfo.currentSegments.delete(segment);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Update information of the code path segment when a code path was
|
||||
* looped.
|
||||
* @param {CodePathSegment} fromSegment The code path segment of the
|
||||
* end of a loop.
|
||||
* @param {CodePathSegment} toSegment A code path segment of the head
|
||||
* of a loop.
|
||||
* @returns {void}
|
||||
*/
|
||||
onCodePathSegmentLoop(fromSegment, toSegment) {
|
||||
if (!(funcInfo.isConstructor && funcInfo.hasExtends)) {
|
||||
return;
|
||||
}
|
||||
|
||||
funcInfo.codePath.traverseSegments(
|
||||
{ first: toSegment, last: fromSegment },
|
||||
(segment, controller) => {
|
||||
const info = segInfoMap[segment.id];
|
||||
|
||||
// skip segments after the loop
|
||||
if (!info) {
|
||||
controller.skip();
|
||||
return;
|
||||
}
|
||||
|
||||
const seenPrevSegments = segment.prevSegments.filter(hasSegmentBeenSeen);
|
||||
const calledInSomePreviousPaths = seenPrevSegments.some(isCalledInSomePath);
|
||||
const calledInEveryPreviousPaths = seenPrevSegments.every(isCalledInEveryPath);
|
||||
|
||||
info.calledInSomePaths ||= calledInSomePreviousPaths;
|
||||
info.calledInEveryPaths ||= calledInEveryPreviousPaths;
|
||||
|
||||
// If flags become true anew, reports the valid nodes.
|
||||
if (calledInSomePreviousPaths) {
|
||||
const nodes = info.validNodes;
|
||||
|
||||
info.validNodes = [];
|
||||
|
||||
for (let i = 0; i < nodes.length; ++i) {
|
||||
const node = nodes[i];
|
||||
|
||||
context.report({
|
||||
messageId: "duplicate",
|
||||
node
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks for a call of `super()`.
|
||||
* @param {ASTNode} node A CallExpression node to check.
|
||||
* @returns {void}
|
||||
*/
|
||||
"CallExpression:exit"(node) {
|
||||
if (!(funcInfo.isConstructor && funcInfo.hasExtends)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skips except `super()`.
|
||||
if (node.callee.type !== "Super") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reports if needed.
|
||||
const segments = funcInfo.currentSegments;
|
||||
let duplicate = false;
|
||||
let info = null;
|
||||
|
||||
for (const segment of segments) {
|
||||
|
||||
if (segment.reachable) {
|
||||
info = segInfoMap[segment.id];
|
||||
|
||||
duplicate = duplicate || info.calledInSomePaths;
|
||||
info.calledInSomePaths = info.calledInEveryPaths = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (info) {
|
||||
if (duplicate) {
|
||||
context.report({
|
||||
messageId: "duplicate",
|
||||
node
|
||||
});
|
||||
} else if (!funcInfo.superIsConstructor) {
|
||||
context.report({
|
||||
messageId: "badSuper",
|
||||
node
|
||||
});
|
||||
} else {
|
||||
info.validNodes.push(node);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the mark to the returned path as `super()` was called.
|
||||
* @param {ASTNode} node A ReturnStatement node to check.
|
||||
* @returns {void}
|
||||
*/
|
||||
ReturnStatement(node) {
|
||||
if (!(funcInfo.isConstructor && funcInfo.hasExtends)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skips if no argument.
|
||||
if (!node.argument) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Returning argument is a substitute of 'super()'.
|
||||
const segments = funcInfo.currentSegments;
|
||||
|
||||
for (const segment of segments) {
|
||||
|
||||
if (segment.reachable) {
|
||||
const info = segInfoMap[segment.id];
|
||||
|
||||
info.calledInSomePaths = info.calledInEveryPaths = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
349
node_modules/eslint/lib/rules/curly.js
generated
vendored
Normal file
349
node_modules/eslint/lib/rules/curly.js
generated
vendored
Normal file
@ -0,0 +1,349 @@
|
||||
/**
|
||||
* @fileoverview Rule to flag statements without curly braces
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("./utils/ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('../shared/types').Rule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
|
||||
docs: {
|
||||
description: "Enforce consistent brace style for all control statements",
|
||||
recommended: false,
|
||||
url: "https://eslint.org/docs/latest/rules/curly"
|
||||
},
|
||||
|
||||
schema: {
|
||||
anyOf: [
|
||||
{
|
||||
type: "array",
|
||||
items: [
|
||||
{
|
||||
enum: ["all"]
|
||||
}
|
||||
],
|
||||
minItems: 0,
|
||||
maxItems: 1
|
||||
},
|
||||
{
|
||||
type: "array",
|
||||
items: [
|
||||
{
|
||||
enum: ["multi", "multi-line", "multi-or-nest"]
|
||||
},
|
||||
{
|
||||
enum: ["consistent"]
|
||||
}
|
||||
],
|
||||
minItems: 0,
|
||||
maxItems: 2
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
defaultOptions: ["all"],
|
||||
|
||||
fixable: "code",
|
||||
|
||||
messages: {
|
||||
missingCurlyAfter: "Expected { after '{{name}}'.",
|
||||
missingCurlyAfterCondition: "Expected { after '{{name}}' condition.",
|
||||
unexpectedCurlyAfter: "Unnecessary { after '{{name}}'.",
|
||||
unexpectedCurlyAfterCondition: "Unnecessary { after '{{name}}' condition."
|
||||
}
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const multiOnly = (context.options[0] === "multi");
|
||||
const multiLine = (context.options[0] === "multi-line");
|
||||
const multiOrNest = (context.options[0] === "multi-or-nest");
|
||||
const consistent = (context.options[1] === "consistent");
|
||||
|
||||
const sourceCode = context.sourceCode;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Determines if a given node is a one-liner that's on the same line as it's preceding code.
|
||||
* @param {ASTNode} node The node to check.
|
||||
* @returns {boolean} True if the node is a one-liner that's on the same line as it's preceding code.
|
||||
* @private
|
||||
*/
|
||||
function isCollapsedOneLiner(node) {
|
||||
const before = sourceCode.getTokenBefore(node);
|
||||
const last = sourceCode.getLastToken(node);
|
||||
const lastExcludingSemicolon = astUtils.isSemicolonToken(last) ? sourceCode.getTokenBefore(last) : last;
|
||||
|
||||
return before.loc.start.line === lastExcludingSemicolon.loc.end.line;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a given node is a one-liner.
|
||||
* @param {ASTNode} node The node to check.
|
||||
* @returns {boolean} True if the node is a one-liner.
|
||||
* @private
|
||||
*/
|
||||
function isOneLiner(node) {
|
||||
if (node.type === "EmptyStatement") {
|
||||
return true;
|
||||
}
|
||||
|
||||
const first = sourceCode.getFirstToken(node);
|
||||
const last = sourceCode.getLastToken(node);
|
||||
const lastExcludingSemicolon = astUtils.isSemicolonToken(last) ? sourceCode.getTokenBefore(last) : last;
|
||||
|
||||
return first.loc.start.line === lastExcludingSemicolon.loc.end.line;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a semicolon needs to be inserted after removing a set of curly brackets, in order to avoid a SyntaxError.
|
||||
* @param {Token} closingBracket The } token
|
||||
* @returns {boolean} `true` if a semicolon needs to be inserted after the last statement in the block.
|
||||
*/
|
||||
function needsSemicolon(closingBracket) {
|
||||
const tokenBefore = sourceCode.getTokenBefore(closingBracket);
|
||||
const tokenAfter = sourceCode.getTokenAfter(closingBracket);
|
||||
const lastBlockNode = sourceCode.getNodeByRangeIndex(tokenBefore.range[0]);
|
||||
|
||||
if (astUtils.isSemicolonToken(tokenBefore)) {
|
||||
|
||||
// If the last statement already has a semicolon, don't add another one.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!tokenAfter) {
|
||||
|
||||
// If there are no statements after this block, there is no need to add a semicolon.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (lastBlockNode.type === "BlockStatement" && lastBlockNode.parent.type !== "FunctionExpression" && lastBlockNode.parent.type !== "ArrowFunctionExpression") {
|
||||
|
||||
/*
|
||||
* If the last node surrounded by curly brackets is a BlockStatement (other than a FunctionExpression or an ArrowFunctionExpression),
|
||||
* don't insert a semicolon. Otherwise, the semicolon would be parsed as a separate statement, which would cause
|
||||
* a SyntaxError if it was followed by `else`.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tokenBefore.loc.end.line === tokenAfter.loc.start.line) {
|
||||
|
||||
// If the next token is on the same line, insert a semicolon.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (/^[([/`+-]/u.test(tokenAfter.value)) {
|
||||
|
||||
// If the next token starts with a character that would disrupt ASI, insert a semicolon.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (tokenBefore.type === "Punctuator" && (tokenBefore.value === "++" || tokenBefore.value === "--")) {
|
||||
|
||||
// If the last token is ++ or --, insert a semicolon to avoid disrupting ASI.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise, do not insert a semicolon.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares to check the body of a node to see if it's a block statement.
|
||||
* @param {ASTNode} node The node to report if there's a problem.
|
||||
* @param {ASTNode} body The body node to check for blocks.
|
||||
* @param {string} name The name to report if there's a problem.
|
||||
* @param {{ condition: boolean }} opts Options to pass to the report functions
|
||||
* @returns {Object} a prepared check object, with "actual", "expected", "check" properties.
|
||||
* "actual" will be `true` or `false` whether the body is already a block statement.
|
||||
* "expected" will be `true` or `false` if the body should be a block statement or not, or
|
||||
* `null` if it doesn't matter, depending on the rule options. It can be modified to change
|
||||
* the final behavior of "check".
|
||||
* "check" will be a function reporting appropriate problems depending on the other
|
||||
* properties.
|
||||
*/
|
||||
function prepareCheck(node, body, name, opts) {
|
||||
const hasBlock = (body.type === "BlockStatement");
|
||||
let expected = null;
|
||||
|
||||
if (hasBlock && (body.body.length !== 1 || astUtils.areBracesNecessary(body, sourceCode))) {
|
||||
expected = true;
|
||||
} else if (multiOnly) {
|
||||
expected = false;
|
||||
} else if (multiLine) {
|
||||
if (!isCollapsedOneLiner(body)) {
|
||||
expected = true;
|
||||
}
|
||||
|
||||
// otherwise, the body is allowed to have braces or not to have braces
|
||||
|
||||
} else if (multiOrNest) {
|
||||
if (hasBlock) {
|
||||
const statement = body.body[0];
|
||||
const leadingCommentsInBlock = sourceCode.getCommentsBefore(statement);
|
||||
|
||||
expected = !isOneLiner(statement) || leadingCommentsInBlock.length > 0;
|
||||
} else {
|
||||
expected = !isOneLiner(body);
|
||||
}
|
||||
} else {
|
||||
|
||||
// default "all"
|
||||
expected = true;
|
||||
}
|
||||
|
||||
return {
|
||||
actual: hasBlock,
|
||||
expected,
|
||||
check() {
|
||||
if (this.expected !== null && this.expected !== this.actual) {
|
||||
if (this.expected) {
|
||||
context.report({
|
||||
node,
|
||||
loc: body.loc,
|
||||
messageId: opts && opts.condition ? "missingCurlyAfterCondition" : "missingCurlyAfter",
|
||||
data: {
|
||||
name
|
||||
},
|
||||
fix: fixer => fixer.replaceText(body, `{${sourceCode.getText(body)}}`)
|
||||
});
|
||||
} else {
|
||||
context.report({
|
||||
node,
|
||||
loc: body.loc,
|
||||
messageId: opts && opts.condition ? "unexpectedCurlyAfterCondition" : "unexpectedCurlyAfter",
|
||||
data: {
|
||||
name
|
||||
},
|
||||
fix(fixer) {
|
||||
|
||||
/*
|
||||
* `do while` expressions sometimes need a space to be inserted after `do`.
|
||||
* e.g. `do{foo()} while (bar)` should be corrected to `do foo() while (bar)`
|
||||
*/
|
||||
const needsPrecedingSpace = node.type === "DoWhileStatement" &&
|
||||
sourceCode.getTokenBefore(body).range[1] === body.range[0] &&
|
||||
!astUtils.canTokensBeAdjacent("do", sourceCode.getFirstToken(body, { skip: 1 }));
|
||||
|
||||
const openingBracket = sourceCode.getFirstToken(body);
|
||||
const closingBracket = sourceCode.getLastToken(body);
|
||||
const lastTokenInBlock = sourceCode.getTokenBefore(closingBracket);
|
||||
|
||||
if (needsSemicolon(closingBracket)) {
|
||||
|
||||
/*
|
||||
* If removing braces would cause a SyntaxError due to multiple statements on the same line (or
|
||||
* change the semantics of the code due to ASI), don't perform a fix.
|
||||
*/
|
||||
return null;
|
||||
}
|
||||
|
||||
const resultingBodyText = sourceCode.getText().slice(openingBracket.range[1], lastTokenInBlock.range[0]) +
|
||||
sourceCode.getText(lastTokenInBlock) +
|
||||
sourceCode.getText().slice(lastTokenInBlock.range[1], closingBracket.range[0]);
|
||||
|
||||
return fixer.replaceText(body, (needsPrecedingSpace ? " " : "") + resultingBodyText);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares to check the bodies of a "if", "else if" and "else" chain.
|
||||
* @param {ASTNode} node The first IfStatement node of the chain.
|
||||
* @returns {Object[]} prepared checks for each body of the chain. See `prepareCheck` for more
|
||||
* information.
|
||||
*/
|
||||
function prepareIfChecks(node) {
|
||||
const preparedChecks = [];
|
||||
|
||||
for (let currentNode = node; currentNode; currentNode = currentNode.alternate) {
|
||||
preparedChecks.push(prepareCheck(currentNode, currentNode.consequent, "if", { condition: true }));
|
||||
if (currentNode.alternate && currentNode.alternate.type !== "IfStatement") {
|
||||
preparedChecks.push(prepareCheck(currentNode, currentNode.alternate, "else"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (consistent) {
|
||||
|
||||
/*
|
||||
* If any node should have or already have braces, make sure they
|
||||
* all have braces.
|
||||
* If all nodes shouldn't have braces, make sure they don't.
|
||||
*/
|
||||
const expected = preparedChecks.some(preparedCheck => {
|
||||
if (preparedCheck.expected !== null) {
|
||||
return preparedCheck.expected;
|
||||
}
|
||||
return preparedCheck.actual;
|
||||
});
|
||||
|
||||
preparedChecks.forEach(preparedCheck => {
|
||||
preparedCheck.expected = expected;
|
||||
});
|
||||
}
|
||||
|
||||
return preparedChecks;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
IfStatement(node) {
|
||||
const parent = node.parent;
|
||||
const isElseIf = parent.type === "IfStatement" && parent.alternate === node;
|
||||
|
||||
if (!isElseIf) {
|
||||
|
||||
// This is a top `if`, check the whole `if-else-if` chain
|
||||
prepareIfChecks(node).forEach(preparedCheck => {
|
||||
preparedCheck.check();
|
||||
});
|
||||
}
|
||||
|
||||
// Skip `else if`, it's already checked (when the top `if` was visited)
|
||||
},
|
||||
|
||||
WhileStatement(node) {
|
||||
prepareCheck(node, node.body, "while", { condition: true }).check();
|
||||
},
|
||||
|
||||
DoWhileStatement(node) {
|
||||
prepareCheck(node, node.body, "do").check();
|
||||
},
|
||||
|
||||
ForStatement(node) {
|
||||
prepareCheck(node, node.body, "for", { condition: true }).check();
|
||||
},
|
||||
|
||||
ForInStatement(node) {
|
||||
prepareCheck(node, node.body, "for-in").check();
|
||||
},
|
||||
|
||||
ForOfStatement(node) {
|
||||
prepareCheck(node, node.body, "for-of").check();
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
44
node_modules/eslint/lib/rules/default-case-last.js
generated
vendored
Normal file
44
node_modules/eslint/lib/rules/default-case-last.js
generated
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* @fileoverview Rule to enforce default clauses in switch statements to be last
|
||||
* @author Milos Djermanovic
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('../shared/types').Rule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
|
||||
docs: {
|
||||
description: "Enforce default clauses in switch statements to be last",
|
||||
recommended: false,
|
||||
url: "https://eslint.org/docs/latest/rules/default-case-last"
|
||||
},
|
||||
|
||||
schema: [],
|
||||
|
||||
messages: {
|
||||
notLast: "Default clause should be the last clause."
|
||||
}
|
||||
},
|
||||
|
||||
create(context) {
|
||||
return {
|
||||
SwitchStatement(node) {
|
||||
const cases = node.cases,
|
||||
indexOfDefault = cases.findIndex(c => c.test === null);
|
||||
|
||||
if (indexOfDefault !== -1 && indexOfDefault !== cases.length - 1) {
|
||||
const defaultClause = cases[indexOfDefault];
|
||||
|
||||
context.report({ node: defaultClause, messageId: "notLast" });
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
99
node_modules/eslint/lib/rules/default-case.js
generated
vendored
Normal file
99
node_modules/eslint/lib/rules/default-case.js
generated
vendored
Normal file
@ -0,0 +1,99 @@
|
||||
/**
|
||||
* @fileoverview require default case in switch statements
|
||||
* @author Aliaksei Shytkin
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
const DEFAULT_COMMENT_PATTERN = /^no default$/iu;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('../shared/types').Rule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
|
||||
defaultOptions: [{}],
|
||||
|
||||
docs: {
|
||||
description: "Require `default` cases in `switch` statements",
|
||||
recommended: false,
|
||||
url: "https://eslint.org/docs/latest/rules/default-case"
|
||||
},
|
||||
|
||||
schema: [{
|
||||
type: "object",
|
||||
properties: {
|
||||
commentPattern: {
|
||||
type: "string"
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
}],
|
||||
|
||||
messages: {
|
||||
missingDefaultCase: "Expected a default case."
|
||||
}
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const [options] = context.options;
|
||||
const commentPattern = options.commentPattern
|
||||
? new RegExp(options.commentPattern, "u")
|
||||
: DEFAULT_COMMENT_PATTERN;
|
||||
|
||||
const sourceCode = context.sourceCode;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Shortcut to get last element of array
|
||||
* @param {*[]} collection Array
|
||||
* @returns {any} Last element
|
||||
*/
|
||||
function last(collection) {
|
||||
return collection.at(-1);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
|
||||
SwitchStatement(node) {
|
||||
|
||||
if (!node.cases.length) {
|
||||
|
||||
/*
|
||||
* skip check of empty switch because there is no easy way
|
||||
* to extract comments inside it now
|
||||
*/
|
||||
return;
|
||||
}
|
||||
|
||||
const hasDefault = node.cases.some(v => v.test === null);
|
||||
|
||||
if (!hasDefault) {
|
||||
|
||||
let comment;
|
||||
|
||||
const lastCase = last(node.cases);
|
||||
const comments = sourceCode.getCommentsAfter(lastCase);
|
||||
|
||||
if (comments.length) {
|
||||
comment = last(comments);
|
||||
}
|
||||
|
||||
if (!comment || !commentPattern.test(comment.value.trim())) {
|
||||
context.report({ node, messageId: "missingDefaultCase" });
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
62
node_modules/eslint/lib/rules/default-param-last.js
generated
vendored
Normal file
62
node_modules/eslint/lib/rules/default-param-last.js
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
/**
|
||||
* @fileoverview enforce default parameters to be last
|
||||
* @author Chiawen Chen
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/** @type {import('../shared/types').Rule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
|
||||
docs: {
|
||||
description: "Enforce default parameters to be last",
|
||||
recommended: false,
|
||||
url: "https://eslint.org/docs/latest/rules/default-param-last"
|
||||
},
|
||||
|
||||
schema: [],
|
||||
|
||||
messages: {
|
||||
shouldBeLast: "Default parameters should be last."
|
||||
}
|
||||
},
|
||||
|
||||
create(context) {
|
||||
|
||||
/**
|
||||
* Handler for function contexts.
|
||||
* @param {ASTNode} node function node
|
||||
* @returns {void}
|
||||
*/
|
||||
function handleFunction(node) {
|
||||
let hasSeenPlainParam = false;
|
||||
|
||||
for (let i = node.params.length - 1; i >= 0; i -= 1) {
|
||||
const param = node.params[i];
|
||||
|
||||
if (
|
||||
param.type !== "AssignmentPattern" &&
|
||||
param.type !== "RestElement"
|
||||
) {
|
||||
hasSeenPlainParam = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hasSeenPlainParam && param.type === "AssignmentPattern") {
|
||||
context.report({
|
||||
node: param,
|
||||
messageId: "shouldBeLast"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
FunctionDeclaration: handleFunction,
|
||||
FunctionExpression: handleFunction,
|
||||
ArrowFunctionExpression: handleFunction
|
||||
};
|
||||
}
|
||||
};
|
108
node_modules/eslint/lib/rules/dot-location.js
generated
vendored
Normal file
108
node_modules/eslint/lib/rules/dot-location.js
generated
vendored
Normal file
@ -0,0 +1,108 @@
|
||||
/**
|
||||
* @fileoverview Validates newlines before and after dots
|
||||
* @author Greg Cochard
|
||||
* @deprecated in ESLint v8.53.0
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const astUtils = require("./utils/ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('../shared/types').Rule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
deprecated: true,
|
||||
replacedBy: [],
|
||||
type: "layout",
|
||||
|
||||
docs: {
|
||||
description: "Enforce consistent newlines before and after dots",
|
||||
recommended: false,
|
||||
url: "https://eslint.org/docs/latest/rules/dot-location"
|
||||
},
|
||||
|
||||
schema: [
|
||||
{
|
||||
enum: ["object", "property"]
|
||||
}
|
||||
],
|
||||
|
||||
fixable: "code",
|
||||
|
||||
messages: {
|
||||
expectedDotAfterObject: "Expected dot to be on same line as object.",
|
||||
expectedDotBeforeProperty: "Expected dot to be on same line as property."
|
||||
}
|
||||
},
|
||||
|
||||
create(context) {
|
||||
|
||||
const config = context.options[0];
|
||||
|
||||
// default to onObject if no preference is passed
|
||||
const onObject = config === "object" || !config;
|
||||
|
||||
const sourceCode = context.sourceCode;
|
||||
|
||||
/**
|
||||
* Reports if the dot between object and property is on the correct location.
|
||||
* @param {ASTNode} node The `MemberExpression` node.
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkDotLocation(node) {
|
||||
const property = node.property;
|
||||
const dotToken = sourceCode.getTokenBefore(property);
|
||||
|
||||
if (onObject) {
|
||||
|
||||
// `obj` expression can be parenthesized, but those paren tokens are not a part of the `obj` node.
|
||||
const tokenBeforeDot = sourceCode.getTokenBefore(dotToken);
|
||||
|
||||
if (!astUtils.isTokenOnSameLine(tokenBeforeDot, dotToken)) {
|
||||
context.report({
|
||||
node,
|
||||
loc: dotToken.loc,
|
||||
messageId: "expectedDotAfterObject",
|
||||
*fix(fixer) {
|
||||
if (dotToken.value.startsWith(".") && astUtils.isDecimalIntegerNumericToken(tokenBeforeDot)) {
|
||||
yield fixer.insertTextAfter(tokenBeforeDot, ` ${dotToken.value}`);
|
||||
} else {
|
||||
yield fixer.insertTextAfter(tokenBeforeDot, dotToken.value);
|
||||
}
|
||||
yield fixer.remove(dotToken);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (!astUtils.isTokenOnSameLine(dotToken, property)) {
|
||||
context.report({
|
||||
node,
|
||||
loc: dotToken.loc,
|
||||
messageId: "expectedDotBeforeProperty",
|
||||
*fix(fixer) {
|
||||
yield fixer.remove(dotToken);
|
||||
yield fixer.insertTextBefore(property, dotToken.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the spacing of the dot within a member expression.
|
||||
* @param {ASTNode} node The node to check.
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkNode(node) {
|
||||
if (!node.computed) {
|
||||
checkDotLocation(node);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
MemberExpression: checkNode
|
||||
};
|
||||
}
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user