diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 16c9172d055c..baa3546b0e08 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -15,11 +15,11 @@ body: description: | Please tell us about how you're running ESLint (Run `npx eslint --env-info`.) value: | - Node version: - npm version: - Local ESLint version: - Global ESLint version: - Operating System: + Node version: + npm version: + Local ESLint version: + Global ESLint version: + Operating System: validations: required: true - type: dropdown @@ -87,6 +87,9 @@ body: options: - label: I am willing to submit a pull request for this issue. required: false +- type: markdown + attributes: + value: Please **do not** open a pull request until this issue has been accepted by the team. - type: textarea id: comments attributes: diff --git a/.github/ISSUE_TEMPLATE/change.yml b/.github/ISSUE_TEMPLATE/change.yml index 1a1f1c023aef..b019c11123bc 100644 --- a/.github/ISSUE_TEMPLATE/change.yml +++ b/.github/ISSUE_TEMPLATE/change.yml @@ -41,6 +41,9 @@ body: options: - label: I am willing to submit a pull request for this change. required: false +- type: markdown + attributes: + value: Please **do not** open a pull request until this issue has been accepted by the team. - type: textarea attributes: label: Additional comments diff --git a/.github/ISSUE_TEMPLATE/docs.yml b/.github/ISSUE_TEMPLATE/docs.yml index ab0803984808..4790122ef11a 100644 --- a/.github/ISSUE_TEMPLATE/docs.yml +++ b/.github/ISSUE_TEMPLATE/docs.yml @@ -40,6 +40,9 @@ body: options: - label: I am willing to submit a pull request for this change. required: false +- type: markdown + attributes: + value: Please **do not** open a pull request until this issue has been accepted by the team. - type: textarea attributes: label: Additional comments diff --git a/.github/ISSUE_TEMPLATE/new-rule.yml b/.github/ISSUE_TEMPLATE/new-rule.yml index 60b0c42200b1..d5431b320f18 100644 --- a/.github/ISSUE_TEMPLATE/new-rule.yml +++ b/.github/ISSUE_TEMPLATE/new-rule.yml @@ -47,6 +47,9 @@ body: options: - label: I am willing to submit a pull request to implement this rule. required: false +- type: markdown + attributes: + value: Please **do not** open a pull request until this issue has been accepted by the team. - type: textarea attributes: label: Additional comments diff --git a/.github/ISSUE_TEMPLATE/new-syntax.yml b/.github/ISSUE_TEMPLATE/new-syntax.yml index 85bdd2db6795..bc20f2deea2e 100644 --- a/.github/ISSUE_TEMPLATE/new-syntax.yml +++ b/.github/ISSUE_TEMPLATE/new-syntax.yml @@ -54,6 +54,9 @@ body: options: - label: I am willing to submit a pull request for this change. required: false +- type: markdown + attributes: + value: Please **do not** open a pull request until this issue has been accepted by the team. - type: textarea attributes: label: Additional comments diff --git a/.github/ISSUE_TEMPLATE/rule-change.yml b/.github/ISSUE_TEMPLATE/rule-change.yml index 503f2a5eeb90..e91d16a488fb 100644 --- a/.github/ISSUE_TEMPLATE/rule-change.yml +++ b/.github/ISSUE_TEMPLATE/rule-change.yml @@ -55,6 +55,9 @@ body: options: - label: I am willing to submit a pull request to implement this change. required: false +- type: markdown + attributes: + value: Please **do not** open a pull request until this issue has been accepted by the team. - type: textarea attributes: label: Additional comments diff --git a/CHANGELOG.md b/CHANGELOG.md index 75c2d986eb89..2ef9915870cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,54 @@ +v9.11.0 - September 20, 2024 + +* [`ca21a64`](https://github.com/eslint/eslint/commit/ca21a64ed0f59adb9dadcef2fc8f7248879edbd3) chore: upgrade @eslint/js@9.11.0 (#18927) (Milos Djermanovic) +* [`a10f90a`](https://github.com/eslint/eslint/commit/a10f90af35aea9ac555b1f33106fbba1027d774e) chore: package.json update for @eslint/js release (Jenkins) +* [`5e5f39b`](https://github.com/eslint/eslint/commit/5e5f39b82535f59780ce4be56d01fd1466029c25) fix: add missing types for `no-restricted-exports` rule (#18914) (Kristóf Poduszló) +* [`e4e5709`](https://github.com/eslint/eslint/commit/e4e570952249d1c4fde59c79a0f49a38490b72c9) docs: correct `prefer-object-has-own` type definition comment (#18924) (Nitin Kumar) +* [`8f630eb`](https://github.com/eslint/eslint/commit/8f630eb5794ef9fe38e0b8f034287650def634bd) fix: add missing types for `no-param-reassign` options (#18906) (Kristóf Poduszló) +* [`d715781`](https://github.com/eslint/eslint/commit/d71578124f14d6da3fa5ab5cc391bb6c9ac3ffcf) fix: add missing types for `no-extra-boolean-cast` options (#18902) (Kristóf Poduszló) +* [`e4e02cc`](https://github.com/eslint/eslint/commit/e4e02cc6938f38ad5028bb8ad82f52460a18dea5) refactor: Extract processor logic into ProcessorService (#18818) (Nicholas C. Zakas) +* [`ec30c73`](https://github.com/eslint/eslint/commit/ec30c7349e0bc2c37465a036e8c7ea3318ac2328) feat: add "eslint/universal" to export `Linter` (#18883) (唯然) +* [`c591da6`](https://github.com/eslint/eslint/commit/c591da68d4a96aa28df68f4eff7641f42af82b15) feat: Add language to types (#18917) (Nicholas C. Zakas) +* [`91cbd18`](https://github.com/eslint/eslint/commit/91cbd18c70dee2ef73de8d8e43f2c744fd173934) docs: add unicode abbreviations in no-irregular-whitespace rule (#18894) (Alix Royere) +* [`959d360`](https://github.com/eslint/eslint/commit/959d360be597d3112b10590018cd52f1d98712d6) build: Support updates to previous major versions (#18871) (Milos Djermanovic) +* [`6d4484d`](https://github.com/eslint/eslint/commit/6d4484d9c19e4132f3dee948174a543dbbb5d30f) chore: updates for v8.57.1 release (Jenkins) +* [`492eb8f`](https://github.com/eslint/eslint/commit/492eb8f34ebbc5c9d1dbfcf4dd06b8dde8d1df74) feat: limit the name given to `ImportSpecifier` in `id-length` (#18861) (Tanuj Kanti) +* [`2de5742`](https://github.com/eslint/eslint/commit/2de5742682ec45e24dca9ca7faaa45330497fca9) fix: add missing types for `no-misleading-character-class` options (#18905) (Kristóf Poduszló) +* [`c153084`](https://github.com/eslint/eslint/commit/c153084250673b31bed46e3fe6af7a65b4ce8d6f) fix: add missing types for `no-implicit-coercion` options (#18903) (Kristóf Poduszló) +* [`19c6856`](https://github.com/eslint/eslint/commit/19c685608d134d9120a129cc80c0ba7f8f016aa3) feat: Add `no-useless-constructor` suggestion (#18799) (Jordan Thomson) +* [`fa11b2e`](https://github.com/eslint/eslint/commit/fa11b2ede6e5dc1f55dfe4b9b65d9760828900e8) fix: add missing types for `no-empty-function` options (#18901) (Kristóf Poduszló) +* [`a0deed1`](https://github.com/eslint/eslint/commit/a0deed122a9676fab07b903c8d16fbf60b92eadf) fix: add missing types for `camelcase` options (#18897) (Kristóf Poduszló) +* [`71f37c5`](https://github.com/eslint/eslint/commit/71f37c5bf04afb704232d312cc6c72c957d1c14e) refactor: use optional chaining when validating config rules (#18893) (lucasrmendonca) +* [`2c2805f`](https://github.com/eslint/eslint/commit/2c2805f8ee0fb1f27f3e442de248f45e5a98a067) chore: Add PR note to all templates (#18892) (Nicholas C. Zakas) +* [`7b852ce`](https://github.com/eslint/eslint/commit/7b852ce59e6ed56931c080aa46ab548fa57feffc) refactor: use `Directive` class from `@eslint/plugin-kit` (#18884) (Milos Djermanovic) +* [`a48f8c2`](https://github.com/eslint/eslint/commit/a48f8c29b58c27d87dbf202d55a5770d678d37d6) feat: add type `FormatterFunction`, update `LoadedFormatter` (#18872) (Francesco Trotta) +* [`d594ddd`](https://github.com/eslint/eslint/commit/d594ddd2cc9b0c251291ea12fbd14ccd2ee32ac7) chore: update dependency @eslint/core to ^0.6.0 (#18863) (renovate[bot]) +* [`59cfc0f`](https://github.com/eslint/eslint/commit/59cfc0f1b3bbb62260602579f79bd1c36ab5a00f) docs: clarify `resultsMeta` in `LoadedFormatter` type (#18881) (Milos Djermanovic) +* [`78b2421`](https://github.com/eslint/eslint/commit/78b2421e28f29206fe120ae1b03804b1b79e6324) chore: Update change.yml (#18882) (Nicholas C. Zakas) +* [`a416f0a`](https://github.com/eslint/eslint/commit/a416f0a270e922c86e8571e94a30fc87d72fa873) chore: enable `$ExpectType` comments in .ts files (#18869) (Francesco Trotta) +* [`adcc50d`](https://github.com/eslint/eslint/commit/adcc50dbf1fb98c0884f841e2a627796a4490373) docs: Update README (GitHub Actions Bot) +* [`4edac1a`](https://github.com/eslint/eslint/commit/4edac1a325a832804f76602736a86217b40f69ac) docs: Update README (GitHub Actions Bot) + +v8.57.1 - September 16, 2024 + +* [`140ec45`](https://github.com/eslint/eslint/commit/140ec4569fda5a974b6964242b0b2991828a5567) chore: upgrade @eslint/js@8.57.1 (#18913) (Milos Djermanovic) +* [`bcdfc04`](https://github.com/eslint/eslint/commit/bcdfc04a69c53dbf1fc3d38603fe0a796bf2274d) chore: package.json update for @eslint/js release (Jenkins) +* [`3f6ce8d`](https://github.com/eslint/eslint/commit/3f6ce8d6b74aba0d645448e898f271825eeb9630) chore: pin vite-plugin-commonjs@0.10.1 (#18910) (Milos Djermanovic) +* [`a19072f`](https://github.com/eslint/eslint/commit/a19072f9f17ea8266bc66193e5f8a4bf1368835d) fix: add logic to handle fixTypes in the lintText() method (#18900) (Francesco Trotta) +* [`04c7188`](https://github.com/eslint/eslint/commit/04c718865b75a95ebfc4d429b8c9fad773228624) fix: Don't lint same file multiple times (#18899) (Francesco Trotta) +* [`87ec3c4`](https://github.com/eslint/eslint/commit/87ec3c49dd23ab8892bc19aae711292d03a73483) fix: do not throw when defining a global named `__defineSetter__` (#18898) (Francesco Trotta) +* [`60a1267`](https://github.com/eslint/eslint/commit/60a12676878c3fe0623c3b93e7565f003daac5f0) fix: Provide helpful error message for nullish configs (#18889) (Milos Djermanovic) +* [`35d366a`](https://github.com/eslint/eslint/commit/35d366aed6e8ab0cfa8f9c9bac4656e3784c11f6) build: Support updates to previous major versions (#18870) (Milos Djermanovic) +* [`a0dea8e`](https://github.com/eslint/eslint/commit/a0dea8ee01cc4c1b65927562afd3a46418573a02) fix: allow `name` in global ignores, fix `--no-ignore` for non-global (#18875) (Milos Djermanovic) +* [`3836bb4`](https://github.com/eslint/eslint/commit/3836bb48d3f12058ec36c2edf2ca1b50eb1c923b) fix: do not crash on error in `fs.walk` filter (#18886) (Milos Djermanovic) +* [`2dec349`](https://github.com/eslint/eslint/commit/2dec349199df4cba1554172ad38163cc09ad0a52) fix: skip processor code blocks that match only universal patterns (#18880) (Milos Djermanovic) +* [`6a5add4`](https://github.com/eslint/eslint/commit/6a5add41e80941c7253b56b02815ac316e583006) docs: v8.x Add EOL banner (#18744) (Amaresh S M) +* [`b034575`](https://github.com/eslint/eslint/commit/b034575978e3bb57e2edca0d2dc547c7a3abc928) docs: v8.x add version support page to the dropdown (#18731) (Amaresh S M) +* [`760ef7d`](https://github.com/eslint/eslint/commit/760ef7d9dbd7b615ccbdc20f02cbc05dbabbada8) docs: v8.x add version support page in the side navbar (#18740) (Amaresh S M) +* [`428b7ea`](https://github.com/eslint/eslint/commit/428b7ea0a9c086b7d8afa0adb629b09d7347d41d) docs: Add Powered by Algolia label to the search (#18658) (Amaresh S M) +* [`9f07549`](https://github.com/eslint/eslint/commit/9f0754979527d05cd0abb2ea7ab1c3563fb4a361) chore: ignore `/docs/v8.x` in link checker (#18660) (Milos Djermanovic) +* [`c68c07f`](https://github.com/eslint/eslint/commit/c68c07ff44c180952e93c6f2c860079db6291b29) docs: version selectors synchronization (#18265) (Milos Djermanovic) + v9.10.0 - September 6, 2024 * [`24c3ff7`](https://github.com/eslint/eslint/commit/24c3ff7d0c0bd8b98994e04f0870cbec94c5801d) chore: upgrade to @eslint/js@9.10.0 (#18866) (Francesco Trotta) diff --git a/Makefile.js b/Makefile.js index 571da418da26..16a8912e1404 100644 --- a/Makefile.js +++ b/Makefile.js @@ -50,6 +50,8 @@ const OPEN_SOURCE_LICENSES = [ /Public Domain/u, /LGPL/u, /Python/u, /BlueOak/u ]; +const MAIN_GIT_BRANCH = "main"; + //------------------------------------------------------------------------------ // Data //------------------------------------------------------------------------------ @@ -74,6 +76,8 @@ const NODE = "node ", // intentional extra space TEST_FILES = "\"tests/{bin,conf,lib,tools}/**/*.js\"", PERF_ESLINTRC = path.join(PERF_TMP_DIR, "eslint.config.js"), PERF_MULTIFILES_TARGET_DIR = path.join(PERF_TMP_DIR, "eslint"), + CHANGELOG_FILE = "./CHANGELOG.md", + VERSIONS_FILE = "./docs/src/_data/versions.json", /* * glob arguments with Windows separator `\` don't work: @@ -97,6 +101,14 @@ function execSilent(cmd) { return exec(cmd, { silent: true }).stdout; } +/** + * Gets name of the currently checked out Git branch. + * @returns {string} Name of the currently checked out Git branch. + */ +function getCurrentGitBranch() { + return execSilent("git branch --show-current").trim(); +} + /** * Generates a release blog post for eslint.org * @param {Object} releaseInfo The release metadata. @@ -313,14 +325,18 @@ function updateVersions(oldVersion, newVersion) { /** * Updates the changelog, bumps the version number in package.json, creates a local git commit and tag, * and generates the site in an adjacent `website` folder. - * @param {string} [prereleaseId] The prerelease identifier (alpha, beta, etc.). If `undefined`, this is + * @param {Object} options Release options. + * @param {string} [options.prereleaseId] The prerelease identifier (alpha, beta, etc.). If `undefined`, this is * a regular release. + * @param {string} options.packageTag Tag that should be added to the package submitted to the npm registry. * @returns {void} */ -function generateRelease(prereleaseId) { +function generateRelease({ prereleaseId, packageTag }) { + echo(`Current Git branch: ${getCurrentGitBranch()}`); + const oldVersion = require("./package.json").version; - ReleaseOps.generateRelease(prereleaseId); + ReleaseOps.generateRelease(prereleaseId, packageTag); const releaseInfo = JSON.parse(cat(".eslint-release-info.json")); echo("Generating site"); @@ -335,7 +351,9 @@ function generateRelease(prereleaseId) { docsPackage.version = releaseInfo.version; fs.writeFileSync(docsPackagePath, `${JSON.stringify(docsPackage, null, 4)}\n`); - updateVersions(oldVersion, releaseInfo.version); + if (getCurrentGitBranch() === MAIN_GIT_BRANCH) { + updateVersions(oldVersion, releaseInfo.version); + } echo("Updating commit with docs data"); exec("git add docs/ && git commit --amend --no-edit"); @@ -351,17 +369,32 @@ function publishRelease() { ReleaseOps.publishRelease(); const releaseInfo = JSON.parse(cat(".eslint-release-info.json")); - /* - * for a pre-release, push to the "next" branch to trigger docs deploy - * for a release, push to the "latest" branch to trigger docs deploy - */ - if (isPreRelease(releaseInfo.version)) { - exec("git push origin HEAD:next -f"); - } else { - exec("git push origin HEAD:latest -f"); - } + const docsSiteBranch = releaseInfo.packageTag === "maintenance" + ? `v${semver.major(releaseInfo.version)}.x` + : releaseInfo.packageTag; // "latest" or "next" + + echo(`Updating docs site branch: ${docsSiteBranch}`); + exec(`git push origin HEAD:${docsSiteBranch} -f`); publishSite(); + + // Update changelog and list of versions on the main branch + if (getCurrentGitBranch() !== MAIN_GIT_BRANCH) { + echo(`Updating changelog and versions on branch: ${MAIN_GIT_BRANCH}`); + + exec(`git checkout ${MAIN_GIT_BRANCH} --force`); + + fs.writeFileSync(CHANGELOG_FILE, `${releaseInfo.markdownChangelog}${cat(CHANGELOG_FILE)}`); + + const versions = JSON.parse(cat(VERSIONS_FILE)); + + versions.items.find(({ branch }) => branch === docsSiteBranch).version = releaseInfo.version; + fs.writeFileSync(VERSIONS_FILE, `${JSON.stringify(versions, null, 4)}\n`); + + exec(`git add ${CHANGELOG_FILE} ${VERSIONS_FILE}`); + exec(`git commit -m "chore: updates for v${releaseInfo.version} release"`); + exec("git push origin HEAD"); + } } /** @@ -1022,6 +1055,6 @@ target.perf = function() { }); }; -target.generateRelease = () => generateRelease(); -target.generatePrerelease = ([prereleaseType]) => generateRelease(prereleaseType); +target.generateRelease = ([packageTag]) => generateRelease({ packageTag }); +target.generatePrerelease = ([prereleaseId]) => generateRelease({ prereleaseId, packageTag: "next" }); target.publishRelease = publishRelease; diff --git a/README.md b/README.md index 45519dece380..940114717573 100644 --- a/README.md +++ b/README.md @@ -297,9 +297,9 @@ The following companies, organizations, and individuals support ESLint's ongoing

Platinum Sponsors

Automattic Airbnb

Gold Sponsors

-

trunk.io Siemens

Silver Sponsors

+

trunk.io

Silver Sponsors

JetBrains Liftoff American Express Workleap

Bronze Sponsors

-

Anagram Solver Icons8 Discord Ignition Nx HeroCoders Nextbase Starter Kit

+

Anagram Solver Icons8 Discord Nx HeroCoders Nextbase Starter Kit

diff --git a/docs/package.json b/docs/package.json index 7b13033177d6..15ee677981c2 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,7 @@ { "name": "docs-eslint", "private": true, - "version": "9.10.0", + "version": "9.11.0", "description": "", "main": "index.js", "keywords": [], diff --git a/docs/src/_data/rules.json b/docs/src/_data/rules.json index 71011e5f710a..6af55f30ddcf 100644 --- a/docs/src/_data/rules.json +++ b/docs/src/_data/rules.json @@ -1135,7 +1135,7 @@ "description": "Disallow unnecessary constructors", "recommended": false, "fixable": false, - "hasSuggestions": false + "hasSuggestions": true }, { "name": "no-useless-escape", diff --git a/docs/src/_data/rules_meta.json b/docs/src/_data/rules_meta.json index 712469b9ed76..ed3ee59c8715 100644 --- a/docs/src/_data/rules_meta.json +++ b/docs/src/_data/rules_meta.json @@ -2023,7 +2023,8 @@ "description": "Disallow unnecessary constructors", "recommended": false, "url": "https://eslint.org/docs/latest/rules/no-useless-constructor" - } + }, + "hasSuggestions": true }, "no-useless-escape": { "type": "suggestion", diff --git a/docs/src/_data/versions.json b/docs/src/_data/versions.json index 62b32936983d..80be7753fe57 100644 --- a/docs/src/_data/versions.json +++ b/docs/src/_data/versions.json @@ -6,12 +6,12 @@ "path": "/docs/head/" }, { - "version": "9.10.0", + "version": "9.11.0", "branch": "latest", "path": "/docs/latest/" }, { - "version": "8.57.0", + "version": "8.57.1", "branch": "v8.x", "path": "/docs/v8.x/" } diff --git a/docs/src/integrate/nodejs-api.md b/docs/src/integrate/nodejs-api.md index 43e4a5f32d40..c8628a3ad248 100644 --- a/docs/src/integrate/nodejs-api.md +++ b/docs/src/integrate/nodejs-api.md @@ -466,8 +466,8 @@ This edit information means replacing the range of the `range` property by the ` The `LoadedFormatter` value is the object to convert the [LintResult] objects to text. The [eslint.loadFormatter()][eslint-loadformatter] method returns it. It has the following method: -* `format` (`(results: LintResult[], resultsMeta: ResultsMeta) => string | Promise`)
- The method to convert the [LintResult] objects to text. `resultsMeta` is an object that will contain a `maxWarningsExceeded` object if `--max-warnings` was set and the number of warnings exceeded the limit. The `maxWarningsExceeded` object will contain two properties: `maxWarnings`, the value of the `--max-warnings` option, and `foundWarnings`, the number of lint warnings. +* `format` (`(results: LintResult[], resultsMeta?: ResultsMeta) => string | Promise`)
+ The method to convert the [LintResult] objects to text. `resultsMeta` is an optional parameter that is primarily intended for use by the ESLint CLI and can contain only a `maxWarningsExceeded` property that would be passed through the [`context`](../extend/custom-formatters#the-context-argument) object when this method calls the underlying formatter function. Note that ESLint automatically generates `cwd` and `rulesMeta` properties of the `context` object, so you typically don't need to pass in the second argument when calling this method. --- diff --git a/docs/src/maintain/manage-releases.md b/docs/src/maintain/manage-releases.md index b8ae546c6026..b1befbcca1de 100644 --- a/docs/src/maintain/manage-releases.md +++ b/docs/src/maintain/manage-releases.md @@ -49,6 +49,47 @@ In rare cases, a second patch release might be necessary if the release is known After the patch release has been published (or no patch release is necessary), close the release issue and inform the team that they can start merging in semver-minor changes again. +### Release Parameters + +The following tables show examples of the option to select as `RELEASE_TYPE` when starting `eslint-js Release` (the `@eslint/js` package release) and `eslint Release` (the `eslint` package release) jobs on Jenkins to release a new version with the latest features. In both jobs, `main` should be selected as `RELEASE_BRANCH`. + +| **HEAD Version** | **Desired Next Version** | **`eslint-js Release`
`RELEASE_TYPE`** | +| :---: | :---: | :---: | +| `9.25.0` | `9.25.1` | `patch` | +| `9.25.0` | `9.26.0` | `minor` | +| `9.25.0` | `10.0.0-alpha.0` | `alpha.0` | +| `10.0.0-alpha.0` | `10.0.0-alpha.1` | `alpha` | +| `10.0.0-alpha.1` | `10.0.0-beta.0` | `beta` | +| `10.0.0-beta.0` | `10.0.0-beta.1` | `beta` | +| `10.0.0-beta.1` | `10.0.0-rc.0` | `rc` | +| `10.0.0-rc.0` | `10.0.0-rc.1` | `rc` | +| `10.0.0-rc.1` | `10.0.0` | `major` | + +| **HEAD Version** | **Desired Next Version** | **`eslint Release`
`RELEASE_TYPE`** | +| :---: | :---: | :---: | +| `9.25.0` | `9.25.1` or `9.26.0` |`latest` | +| `9.25.0` | `10.0.0-alpha.0` | `alpha` | +| `10.0.0-alpha.0` | `10.0.0-alpha.1` | `alpha` | +| `10.0.0-alpha.1` | `10.0.0-beta.0` | `beta` | +| `10.0.0-beta.0` | `10.0.0-beta.1` | `beta` | +| `10.0.0-beta.1` | `10.0.0-rc.0` | `rc` | +| `10.0.0-rc.0` | `10.0.0-rc.1` | `rc` | +| `10.0.0-rc.1` | `10.0.0` | `latest` | + +When releasing a new version of the previous major line, the option to select as `RELEASE_TYPE` depends on whether the HEAD version is a prerelease or not. In both jobs, the corresponding development branch (for example, `v9.x-dev`) should be selected as `RELEASE_BRANCH`. + +| **HEAD Version** | **Previous Major Line Version** | **Desired Next Version** | **`eslint-js Release`
`RELEASE_TYPE`** | +| :---: | :---: | :---: | :---: | +| `10.0.0-alpha.0` | `9.25.0` | `9.25.1` | `patch` | +| `10.0.0-alpha.0` | `9.25.0` | `9.26.0` | `minor` | +| `10.0.0` | `9.25.0` | `9.25.1` | `maintenance.patch` | +| `10.0.0` | `9.25.0` | `9.26.0` | `maintenance.minor` | + +| **HEAD Version** | **Previous Major Line Version** | **Desired Next Version** | **`eslint Release`
`RELEASE_TYPE`** | +| :---: | :---: | :---: | :---: | +| `10.0.0-alpha.0` | `9.25.0` | `9.25.1` or `9.26.0` | `latest` | +| `10.0.0` | `9.25.0` | `9.25.1` or `9.26.0` | `maintenance` | + ## Emergency Releases An emergency release is unplanned and isn't the regularly scheduled release or the anticipated patch release. diff --git a/docs/src/rules/no-irregular-whitespace.md b/docs/src/rules/no-irregular-whitespace.md index d7cd83402fc0..eb0086f57ab4 100644 --- a/docs/src/rules/no-irregular-whitespace.md +++ b/docs/src/rules/no-irregular-whitespace.md @@ -39,27 +39,27 @@ This rule disallows the following characters except where the options allow: \u000B - Line Tabulation (\v) - \u000C - Form Feed (\f) - \u00A0 - No-Break Space - -\u0085 - Next Line -\u1680 - Ogham Space Mark +\u0085 - Next Line - +\u1680 - Ogham Space Mark - \u180E - Mongolian Vowel Separator - \ufeff - Zero Width No-Break Space - -\u2000 - En Quad -\u2001 - Em Quad +\u2000 - En Quad - +\u2001 - Em Quad - \u2002 - En Space - \u2003 - Em Space - -\u2004 - Three-Per-Em -\u2005 - Four-Per-Em -\u2006 - Six-Per-Em -\u2007 - Figure Space +\u2004 - Three-Per-Em - - <3/MSP> +\u2005 - Four-Per-Em - - <4/MSP> +\u2006 - Six-Per-Em - - <6/MSP> +\u2007 - Figure Space - \u2008 - Punctuation Space - -\u2009 - Thin Space -\u200A - Hair Space +\u2009 - Thin Space - +\u200A - Hair Space - \u200B - Zero Width Space - -\u2028 - Line Separator -\u2029 - Paragraph Separator -\u202F - Narrow No-Break Space -\u205f - Medium Mathematical Space -\u3000 - Ideographic Space +\u2028 - Line Separator - - +\u2029 - Paragraph Separator - - +\u202F - Narrow No-Break Space - +\u205f - Medium Mathematical Space - +\u3000 - Ideographic Space - ``` ## Options diff --git a/docs/src/use/formatters/html-formatter-example.html b/docs/src/use/formatters/html-formatter-example.html index 445e6ab3f2cd..1d6891ae3088 100644 --- a/docs/src/use/formatters/html-formatter-example.html +++ b/docs/src/use/formatters/html-formatter-example.html @@ -118,7 +118,7 @@

ESLint Report

- 8 problems (4 errors, 4 warnings) - Generated on Fri Sep 06 2024 20:24:45 GMT+0000 (Coordinated Universal Time) + 8 problems (4 errors, 4 warnings) - Generated on Fri Sep 20 2024 15:43:28 GMT+0000 (Coordinated Universal Time)
diff --git a/eslint.config.js b/eslint.config.js index cb37750ea7d8..eaa740ea4a85 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -18,6 +18,8 @@ const eslintConfigESLintCJS = require("eslint-config-eslint/cjs"); const eslintConfigESLintFormatting = require("eslint-config-eslint/formatting"); const eslintPluginYml = require("eslint-plugin-yml"); const json = require("@eslint/json").default; +const expectType = require("eslint-plugin-expect-type"); +const tsParser = require("@typescript-eslint/parser"); //----------------------------------------------------------------------------- // Helpers @@ -87,14 +89,14 @@ module.exports = [ "docs/!(src|tools)/", "docs/src/!(_data)", "jsdoc/**", + "lib/types/**/*.ts", "templates/**", "tests/bench/**", "tests/fixtures/**", "tests/performance/**", "tmp/**", "**/test.js", - ".vscode", - "**/*.ts" + ".vscode" ] }, { @@ -198,7 +200,7 @@ module.exports = [ { name: "eslint/lib", files: ["lib/*.js"], - ignores: ["lib/unsupported-api.js"], + ignores: ["lib/unsupported-api.js", "lib/universal.js"], rules: { "n/no-restricted-require": ["error", [ ...createInternalFilesPatterns() @@ -279,5 +281,21 @@ module.exports = [ ...eslintPluginYml.configs["flat/recommended"].map(config => ({ ...config, files: [ALL_YAML_FILES] - })) + })), + { + name: "eslint/ts-rules", + files: ["tests/lib/types/*.ts"], + languageOptions: { + parser: tsParser, + parserOptions: { + project: "tests/lib/types/tsconfig.json" + } + }, + plugins: { + "expect-type": expectType + }, + rules: { + "expect-type/expect": "error" + } + } ]; diff --git a/lib/config/flat-config-helpers.js b/lib/config/flat-config-helpers.js index 0280255932c1..a904a0dd7c08 100644 --- a/lib/config/flat-config-helpers.js +++ b/lib/config/flat-config-helpers.js @@ -65,13 +65,9 @@ function parseRuleId(ruleId) { * or undefined if not. */ function getRuleFromConfig(ruleId, config) { - const { pluginName, ruleName } = parseRuleId(ruleId); - const plugin = config.plugins && config.plugins[pluginName]; - const rule = plugin && plugin.rules && plugin.rules[ruleName]; - - return rule; + return config.plugins?.[pluginName]?.rules?.[ruleName]; } /** diff --git a/lib/languages/js/source-code/source-code.js b/lib/languages/js/source-code/source-code.js index 1e3ee0d719f9..9c073fa2ee3e 100644 --- a/lib/languages/js/source-code/source-code.js +++ b/lib/languages/js/source-code/source-code.js @@ -20,7 +20,7 @@ const CodePathAnalyzer = require("../../../linter/code-path-analysis/code-path-analyzer"), createEmitter = require("../../../linter/safe-emitter"), - { ConfigCommentParser, VisitNodeStep, CallMethodStep } = require("@eslint/plugin-kit"), + { ConfigCommentParser, VisitNodeStep, CallMethodStep, Directive } = require("@eslint/plugin-kit"), eslintScope = require("eslint-scope"); @@ -316,57 +316,6 @@ function markExportedVariables(globalScope, variables) { } -/** - * A class to represent a directive comment. - * @implements {IDirective} - */ -class Directive { - - /** - * The type of directive. - * @type {"disable"|"enable"|"disable-next-line"|"disable-line"} - * @readonly - */ - type; - - /** - * The node representing the directive. - * @type {ASTNode|Comment} - * @readonly - */ - node; - - /** - * Everything after the "eslint-disable" portion of the directive, - * but before the "--" that indicates the justification. - * @type {string} - * @readonly - */ - value; - - /** - * The justification for the directive. - * @type {string} - * @readonly - */ - justification; - - /** - * Creates a new instance. - * @param {Object} options The options for the directive. - * @param {"disable"|"enable"|"disable-next-line"|"disable-line"} options.type The type of directive. - * @param {ASTNode|Comment} options.node The node representing the directive. - * @param {string} options.value The value of the directive. - * @param {string} options.justification The justification for the directive. - */ - constructor({ type, node, value, justification }) { - this.type = type; - this.node = node; - this.value = value; - this.justification = justification; - } -} - //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ diff --git a/lib/linter/linter.js b/lib/linter/linter.js index e4a537c0c9a6..c717634778de 100644 --- a/lib/linter/linter.js +++ b/lib/linter/linter.js @@ -54,6 +54,7 @@ const { LATEST_ECMA_VERSION } = require("../../conf/ecma-version"); const { VFile } = require("./vfile"); const { ParserService } = require("../services/parser-service"); const { FileContext } = require("./file-context"); +const { ProcessorService } = require("../services/processor-service"); const STEP_KIND_VISIT = 1; const STEP_KIND_CALL = 2; @@ -1292,27 +1293,18 @@ class Linter { } /** - * Same as linter.verify, except without support for processors. - * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object. + * Lint using eslintrc and without processors. + * @param {VFile} file The file to lint. * @param {ConfigData} providedConfig An ESLintConfig instance to configure everything. * @param {VerifyOptions} [providedOptions] The optional filename of the file being checked. * @throws {Error} If during rule execution. * @returns {(LintMessage|SuppressedLintMessage)[]} The results as an array of messages or an empty array if no messages. */ - _verifyWithoutProcessors(textOrSourceCode, providedConfig, providedOptions) { + #eslintrcVerifyWithoutProcessors(file, providedConfig, providedOptions) { + const slots = internalSlotsMap.get(this); const config = providedConfig || {}; const options = normalizeVerifyOptions(providedOptions, config); - let text; - - // evaluate arguments - if (typeof textOrSourceCode === "string") { - slots.lastSourceCode = null; - text = textOrSourceCode; - } else { - slots.lastSourceCode = textOrSourceCode; - text = textOrSourceCode.text; - } // Resolve parser. let parserName = DEFAULT_PARSER_NAME; @@ -1339,7 +1331,7 @@ class Linter { // search and apply "eslint-env *". const envInFile = options.allowInlineConfig && !options.warnInlineConfig - ? findEslintEnv(text) + ? findEslintEnv(file.body) : {}; const resolvedEnvConfig = Object.assign({ builtin: true }, config.env, envInFile); const enabledEnvs = Object.keys(resolvedEnvConfig) @@ -1355,9 +1347,6 @@ class Linter { parser, parserOptions }); - const file = new VFile(options.filename, text, { - physicalPath: providedOptions.physicalFilename - }); if (!slots.lastSourceCode) { let t; @@ -1468,6 +1457,36 @@ class Linter { .sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column), reportUnusedDisableDirectives: options.reportUnusedDisableDirectives }); + + } + + /** + * Same as linter.verify, except without support for processors. + * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object. + * @param {ConfigData} providedConfig An ESLintConfig instance to configure everything. + * @param {VerifyOptions} [providedOptions] The optional filename of the file being checked. + * @throws {Error} If during rule execution. + * @returns {(LintMessage|SuppressedLintMessage)[]} The results as an array of messages or an empty array if no messages. + */ + _verifyWithoutProcessors(textOrSourceCode, providedConfig, providedOptions) { + const slots = internalSlotsMap.get(this); + const filename = normalizeFilename(providedOptions.filename || ""); + let text; + + // evaluate arguments + if (typeof textOrSourceCode === "string") { + slots.lastSourceCode = null; + text = textOrSourceCode; + } else { + slots.lastSourceCode = textOrSourceCode; + text = textOrSourceCode.text; + } + + const file = new VFile(filename, text, { + physicalPath: providedOptions.physicalFilename + }); + + return this.#eslintrcVerifyWithoutProcessors(file, providedConfig, providedOptions); } /** @@ -1537,102 +1556,91 @@ class Linter { * @returns {(LintMessage|SuppressedLintMessage)[]} The found problems. */ _verifyWithFlatConfigArrayAndProcessor(textOrSourceCode, config, options, configForRecursive) { + const slots = internalSlotsMap.get(this); const filename = options.filename || ""; const filenameToExpose = normalizeFilename(filename); const physicalFilename = options.physicalFilename || filenameToExpose; const text = ensureText(textOrSourceCode); + const file = new VFile(filenameToExpose, text, { + physicalPath: physicalFilename + }); + const preprocess = options.preprocess || (rawText => [rawText]); const postprocess = options.postprocess || (messagesList => messagesList.flat()); + + const processorService = new ProcessorService(); + const preprocessResult = processorService.preprocessSync(file, { + processor: { + preprocess, + postprocess + } + }); + + if (!preprocessResult.ok) { + return preprocessResult.errors; + } + const filterCodeBlock = options.filterCodeBlock || (blockFilename => blockFilename.endsWith(".js")); const originalExtname = path.extname(filename); + const { files } = preprocessResult; - let blocks; - - try { - blocks = preprocess(text, filenameToExpose); - } catch (ex) { - - // If the message includes a leading line number, strip it: - const message = `Preprocessing error: ${ex.message.replace(/^line \d+:/iu, "").trim()}`; - - debug("%s\n%s", message, ex.stack); - - return [ - { - ruleId: null, - fatal: true, - severity: 2, - message, - line: ex.lineNumber, - column: ex.column, - nodeType: null - } - ]; - } - - const messageLists = blocks.map((block, i) => { - debug("A code block was found: %o", block.filename || "(unnamed)"); + const messageLists = files.map(block => { + debug("A code block was found: %o", block.path || "(unnamed)"); // Keep the legacy behavior. if (typeof block === "string") { return this._verifyWithFlatConfigArrayAndWithoutProcessors(block, config, options); } - const blockText = block.text; - const blockName = path.join(filename, `${i}_${block.filename}`); - // Skip this block if filtered. - if (!filterCodeBlock(blockName, blockText)) { + if (!filterCodeBlock(block.path, block.body)) { debug("This code block was skipped."); return []; } // Resolve configuration again if the file content or extension was changed. - if (configForRecursive && (text !== blockText || path.extname(blockName) !== originalExtname)) { + if (configForRecursive && (text !== block.rawBody || path.extname(block.path) !== originalExtname)) { debug("Resolving configuration again because the file content or extension was changed."); return this._verifyWithFlatConfigArray( - blockText, + block.rawBody, configForRecursive, - { ...options, filename: blockName, physicalFilename } + { ...options, filename: block.path, physicalFilename: block.physicalPath } ); } + slots.lastSourceCode = null; + // Does lint. - return this._verifyWithFlatConfigArrayAndWithoutProcessors( - blockText, + return this.#flatVerifyWithoutProcessors( + block, config, - { ...options, filename: blockName, physicalFilename } + { ...options, filename: block.path, physicalFilename: block.physicalPath } ); }); - return postprocess(messageLists, filenameToExpose); + return processorService.postprocessSync(file, messageLists, { + processor: { + preprocess, + postprocess + } + }); } /** - * Same as linter.verify, except without support for processors. - * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object. + * Verify using flat config and without any processors. + * @param {VFile} file The file to lint. * @param {FlatConfig} providedConfig An ESLintConfig instance to configure everything. * @param {VerifyOptions} [providedOptions] The optional filename of the file being checked. * @throws {Error} If during rule execution. * @returns {(LintMessage|SuppressedLintMessage)[]} The results as an array of messages or an empty array if no messages. */ - _verifyWithFlatConfigArrayAndWithoutProcessors(textOrSourceCode, providedConfig, providedOptions) { + #flatVerifyWithoutProcessors(file, providedConfig, providedOptions) { + const slots = internalSlotsMap.get(this); const config = providedConfig || {}; const options = normalizeVerifyOptions(providedOptions, config); - let text; - - // evaluate arguments - if (typeof textOrSourceCode === "string") { - slots.lastSourceCode = null; - text = textOrSourceCode; - } else { - slots.lastSourceCode = textOrSourceCode; - text = textOrSourceCode.text; - } - const languageOptions = config.languageOptions; languageOptions.ecmaVersion = normalizeEcmaVersionForLanguageOptions( @@ -1663,9 +1671,6 @@ class Linter { } const settings = config.settings || {}; - const file = new VFile(options.filename, text, { - physicalPath: providedOptions.physicalFilename - }); if (!slots.lastSourceCode) { let t; @@ -1957,6 +1962,37 @@ class Linter { ruleFilter: options.ruleFilter, configuredRules }); + + + } + + /** + * Same as linter.verify, except without support for processors. + * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object. + * @param {FlatConfig} providedConfig An ESLintConfig instance to configure everything. + * @param {VerifyOptions} [providedOptions] The optional filename of the file being checked. + * @throws {Error} If during rule execution. + * @returns {(LintMessage|SuppressedLintMessage)[]} The results as an array of messages or an empty array if no messages. + */ + _verifyWithFlatConfigArrayAndWithoutProcessors(textOrSourceCode, providedConfig, providedOptions) { + const slots = internalSlotsMap.get(this); + const filename = normalizeFilename(providedOptions.filename || ""); + let text; + + // evaluate arguments + if (typeof textOrSourceCode === "string") { + slots.lastSourceCode = null; + text = textOrSourceCode; + } else { + slots.lastSourceCode = textOrSourceCode; + text = textOrSourceCode.text; + } + + const file = new VFile(filename, text, { + physicalPath: providedOptions.physicalFilename + }); + + return this.#flatVerifyWithoutProcessors(file, providedConfig, providedOptions); } /** @@ -2057,77 +2093,78 @@ class Linter { * @returns {(LintMessage|SuppressedLintMessage)[]} The found problems. */ _verifyWithProcessor(textOrSourceCode, config, options, configForRecursive) { + const slots = internalSlotsMap.get(this); const filename = options.filename || ""; const filenameToExpose = normalizeFilename(filename); const physicalFilename = options.physicalFilename || filenameToExpose; const text = ensureText(textOrSourceCode); + const file = new VFile(filenameToExpose, text, { + physicalPath: physicalFilename + }); + const preprocess = options.preprocess || (rawText => [rawText]); const postprocess = options.postprocess || (messagesList => messagesList.flat()); - const filterCodeBlock = - options.filterCodeBlock || - (blockFilename => blockFilename.endsWith(".js")); - const originalExtname = path.extname(filename); - - let blocks; - try { - blocks = preprocess(text, filenameToExpose); - } catch (ex) { + const processorService = new ProcessorService(); + const preprocessResult = processorService.preprocessSync(file, { + processor: { + preprocess, + postprocess + } + }); - // If the message includes a leading line number, strip it: - const message = `Preprocessing error: ${ex.message.replace(/^line \d+:/iu, "").trim()}`; + if (!preprocessResult.ok) { + return preprocessResult.errors; + } - debug("%s\n%s", message, ex.stack); + const filterCodeBlock = + options.filterCodeBlock || + (blockFilePath => blockFilePath.endsWith(".js")); + const originalExtname = path.extname(filename); - return [ - { - ruleId: null, - fatal: true, - severity: 2, - message, - line: ex.lineNumber, - column: ex.column, - nodeType: null - } - ]; - } + const { files } = preprocessResult; - const messageLists = blocks.map((block, i) => { - debug("A code block was found: %o", block.filename || "(unnamed)"); + const messageLists = files.map(block => { + debug("A code block was found: %o", block.path ?? "(unnamed)"); // Keep the legacy behavior. if (typeof block === "string") { return this._verifyWithoutProcessors(block, config, options); } - const blockText = block.text; - const blockName = path.join(filename, `${i}_${block.filename}`); - // Skip this block if filtered. - if (!filterCodeBlock(blockName, blockText)) { + if (!filterCodeBlock(block.path, block.body)) { debug("This code block was skipped."); return []; } // Resolve configuration again if the file content or extension was changed. - if (configForRecursive && (text !== blockText || path.extname(blockName) !== originalExtname)) { + if (configForRecursive && (text !== block.rawBody || path.extname(block.path) !== originalExtname)) { debug("Resolving configuration again because the file content or extension was changed."); return this._verifyWithConfigArray( - blockText, + block.rawBody, configForRecursive, - { ...options, filename: blockName, physicalFilename } + { ...options, filename: block.path, physicalFilename: block.physicalPath } ); } + slots.lastSourceCode = null; + // Does lint. - return this._verifyWithoutProcessors( - blockText, + return this.#eslintrcVerifyWithoutProcessors( + block, config, - { ...options, filename: blockName, physicalFilename } + { ...options, filename: block.path, physicalFilename: block.physicalPath } ); }); - return postprocess(messageLists, filenameToExpose); + return processorService.postprocessSync(file, messageLists, { + processor: { + preprocess, + postprocess + } + }); + } /** diff --git a/lib/linter/vfile.js b/lib/linter/vfile.js index 8528a5197b05..bb2da0a7795d 100644 --- a/lib/linter/vfile.js +++ b/lib/linter/vfile.js @@ -85,6 +85,13 @@ class VFile { */ 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} @@ -104,8 +111,8 @@ class VFile { this.physicalPath = physicalPath ?? path; this.bom = hasUnicodeBOM(body); this.body = stripUnicodeBOM(body); + this.rawBody = body; } - } module.exports = { VFile }; diff --git a/lib/rules/id-length.js b/lib/rules/id-length.js index 5b32c60b4558..d719c5346747 100644 --- a/lib/rules/id-length.js +++ b/lib/rules/id-length.js @@ -11,6 +11,7 @@ //------------------------------------------------------------------------------ const { getGraphemeCount } = require("../shared/string-utils"); +const { getModuleExportName } = require("./utils/ast-utils"); //------------------------------------------------------------------------------ // Rule Definition @@ -116,6 +117,12 @@ module.exports = { } return properties && !parent.computed && parent.key.name === node.name; }, + ImportSpecifier(parent, node) { + return ( + parent.local === node && + getModuleExportName(parent.imported) !== getModuleExportName(parent.local) + ); + }, ImportDefaultSpecifier: true, ImportNamespaceSpecifier: true, RestElement: true, diff --git a/lib/rules/no-useless-constructor.js b/lib/rules/no-useless-constructor.js index 2b9c18e51cbd..2c063caa26b3 100644 --- a/lib/rules/no-useless-constructor.js +++ b/lib/rules/no-useless-constructor.js @@ -4,6 +4,8 @@ */ "use strict"; +const astUtils = require("./utils/ast-utils"); + //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ @@ -143,10 +145,13 @@ module.exports = { url: "https://eslint.org/docs/latest/rules/no-useless-constructor" }, + hasSuggestions: true, + schema: [], messages: { - noUselessConstructor: "Useless constructor." + noUselessConstructor: "Useless constructor.", + removeConstructor: "Remove the constructor." } }, @@ -177,7 +182,18 @@ module.exports = { if (superClass ? isRedundantSuperCall(body, ctorParams) : (body.length === 0)) { context.report({ node, - messageId: "noUselessConstructor" + messageId: "noUselessConstructor", + suggest: [ + { + messageId: "removeConstructor", + *fix(fixer) { + const nextToken = context.sourceCode.getTokenAfter(node); + const addSemiColon = nextToken.type === "Punctuator" && nextToken.value === "[" && astUtils.needsPrecedingSemicolon(context.sourceCode, node); + + yield fixer.replaceText(node, addSemiColon ? ";" : ""); + } + } + ] }); } } diff --git a/lib/services/processor-service.js b/lib/services/processor-service.js new file mode 100644 index 000000000000..403b97c1a484 --- /dev/null +++ b/lib/services/processor-service.js @@ -0,0 +1,109 @@ +/** + * @fileoverview ESLint Processor Service + * @author Nicholas C. Zakas + */ +/* eslint class-methods-use-this: off -- Anticipate future constructor arguments. */ + +"use strict"; + +//----------------------------------------------------------------------------- +// Requirements +//----------------------------------------------------------------------------- + +const path = require("node:path"); +const { VFile } = require("../linter/vfile.js"); + +//----------------------------------------------------------------------------- +// Types +//----------------------------------------------------------------------------- + +/** @typedef {import("../shared/types.js").LintMessage} LintMessage */ +/** @typedef {import("../linter/vfile.js").VFile} VFile */ +/** @typedef {import("@eslint/core").Language} Language */ +/** @typedef {import("@eslint/core").LanguageOptions} LanguageOptions */ +/** @typedef {import("eslint").Linter.Processor} Processor */ + +//----------------------------------------------------------------------------- +// Exports +//----------------------------------------------------------------------------- + +/** + * The service that applies processors to files. + */ +class ProcessorService { + + /** + * Preprocesses the given file synchronously. + * @param {VFile} file The file to preprocess. + * @param {{processor:Processor}} config The configuration to use. + * @returns {{ok:boolean, files?: Array, errors?: Array}} An array of preprocessed files or errors. + * @throws {Error} If the preprocessor returns a promise. + */ + preprocessSync(file, config) { + + const { processor } = config; + let blocks; + + try { + blocks = processor.preprocess(file.rawBody, file.path); + } catch (ex) { + + // If the message includes a leading line number, strip it: + const message = `Preprocessing error: ${ex.message.replace(/^line \d+:/iu, "").trim()}`; + + return { + ok: false, + errors: [ + { + ruleId: null, + fatal: true, + severity: 2, + message, + line: ex.lineNumber, + column: ex.column, + nodeType: null + } + ] + }; + } + + if (typeof blocks.then === "function") { + throw new Error("Unsupported: Preprocessor returned a promise."); + } + + return { + ok: true, + files: blocks.map((block, i) => { + + // Legacy behavior: return the block as a string + if (typeof block === "string") { + return block; + } + + const filePath = path.join(file.path, `${i}_${block.filename}`); + + return new VFile(filePath, block.text, { + physicalPath: file.physicalPath + }); + }) + }; + + } + + /** + * Postprocesses the given messages synchronously. + * @param {VFile} file The file to postprocess. + * @param {LintMessage[][]} messages The messages to postprocess. + * @param {{processor:Processor}} config The configuration to use. + * @returns {LintMessage[]} The postprocessed messages. + */ + postprocessSync(file, messages, config) { + + const { processor } = config; + + return processor.postprocess(messages, file.path); + } + +} + +module.exports = { ProcessorService }; diff --git a/lib/shared/types.js b/lib/shared/types.js index 94a91afc8139..5ee7b01e2836 100644 --- a/lib/shared/types.js +++ b/lib/shared/types.js @@ -245,6 +245,6 @@ module.exports = {}; * A formatter function. * @callback FormatterFunction * @param {LintResult[]} results The list of linting results. - * @param {{cwd: string, maxWarningsExceeded?: MaxWarningsExceeded, rulesMeta: Record}} [context] A context object. + * @param {{cwd: string, maxWarningsExceeded?: MaxWarningsExceeded, rulesMeta: Record}} context A context object. * @returns {string | Promise} Formatted text. */ diff --git a/lib/types/index.d.ts b/lib/types/index.d.ts index 6cefe3470355..027465109c2b 100644 --- a/lib/types/index.d.ts +++ b/lib/types/index.d.ts @@ -26,6 +26,7 @@ */ import * as ESTree from "estree"; +import { Language } from "@eslint/core"; import { JSONSchema4 } from "json-schema"; import { LegacyESLint } from "./use-at-your-own-risk.js"; @@ -1265,6 +1266,13 @@ export namespace Linter { */ ignores?: string[]; + /** + * The name of the language used for linting. This is used to determine the + * parser and other language-specific settings. + * @since 9.7.0 + */ + language?: string; + /** * An object containing settings related to how JavaScript is configured for * linting. @@ -1411,7 +1419,7 @@ export class ESLint { isPathIgnored(filePath: string): Promise; - loadFormatter(nameOrPath?: string): Promise; + loadFormatter(nameOrPath?: string): Promise; } export namespace ESLint { @@ -1441,6 +1449,7 @@ export namespace ESLint { interface Plugin extends ObjectMetaProperties { configs?: Record | undefined; environments?: Record | undefined; + languages?: Record | undefined; processors?: Record | undefined; rules?: Record | undefined; } @@ -1555,14 +1564,38 @@ export namespace ESLint { replacedBy: string[]; } - interface Formatter { - format(results: LintResult[], data?: LintResultData): string | Promise; + interface ResultsMeta { + maxWarningsExceeded?: MaxWarningsExceeded | undefined; } + /** The type of an object resolved by {@link ESLint.loadFormatter}. */ + interface LoadedFormatter { + + /** + * Used to call the underlying formatter. + * @param results An array of lint results to format. + * @param resultsMeta An object with an optional `maxWarningsExceeded` property that will be + * passed to the underlying formatter function along with other properties set by ESLint. + * This argument can be omitted if `maxWarningsExceeded` is not needed. + * @return The formatter output. + */ + format(results: LintResult[], resultsMeta?: ResultsMeta): string | Promise; + } + + // The documented type name is `LoadedFormatter`, but `Formatter` has been historically more used. + type Formatter = LoadedFormatter; + + /** + * The expected signature of a custom formatter. + * @param results An array of lint results to format. + * @param context Additional information for the formatter. + * @return The formatter output. + */ + type FormatterFunction = + (results: LintResult[], context: LintResultData) => string | Promise; + // Docs reference the types by those name type EditInfo = Rule.Fix; - type LoadedFormatter = Formatter; - type ResultsMeta = LintResultData; } // #endregion diff --git a/lib/types/rules/best-practices.d.ts b/lib/types/rules/best-practices.d.ts index b49ce9ad838b..2d8e4fbad5cd 100644 --- a/lib/types/rules/best-practices.d.ts +++ b/lib/types/rules/best-practices.d.ts @@ -324,6 +324,8 @@ export interface BestPractices extends Linter.RulesRecord { | "getters" | "setters" | "constructors" + | "asyncFunctions" + | "asyncMethods" >; }>, ] @@ -472,7 +474,7 @@ export interface BestPractices extends Linter.RulesRecord { /** * @default [] */ - allow: Array<"~" | "!!" | "+" | "*">; + allow: Array<"~" | "!!" | "+" | "- -" | "-" | "*">; }>, ] >; @@ -683,16 +685,22 @@ export interface BestPractices extends Linter.RulesRecord { */ "no-param-reassign": Linter.RuleEntry< [ - Partial<{ - /** - * @default false - */ - props: boolean; - /** - * @default [] - */ - ignorePropertyModificationsFor: string[]; - }>, + | { + props?: false; + } + | ({ + props: true; + } & Partial<{ + /** + * @default [] + */ + ignorePropertyModificationsFor: string[]; + /** + * @since 6.6.0 + * @default [] + */ + ignorePropertyModificationsForRegex: string[]; + }>), ] >; @@ -966,7 +974,7 @@ export interface BestPractices extends Linter.RulesRecord { /** * Disallow use of `Object.prototype.hasOwnProperty.call()` and prefer use of `Object.hasOwn()`. * - * @since 3.5.0 + * @since 8.5.0 * @see https://eslint.org/docs/rules/prefer-object-has-own */ "prefer-object-has-own": Linter.RuleEntry<[]>; diff --git a/lib/types/rules/ecmascript-6.d.ts b/lib/types/rules/ecmascript-6.d.ts index feeff98f4c56..5fcc560c1bed 100644 --- a/lib/types/rules/ecmascript-6.d.ts +++ b/lib/types/rules/ecmascript-6.d.ts @@ -231,6 +231,52 @@ export interface ECMAScript6 extends Linter.RulesRecord { */ "no-new-symbol": Linter.RuleEntry<[]>; + /** + * Rule to disallow specified names in exports. + * + * @since 7.0.0-alpha.0 + * @see https://eslint.org/docs/rules/no-restricted-exports + */ + "no-restricted-exports": Linter.RuleEntry< + [ + Partial<{ + /** + * @default [] + */ + restrictedNamedExports: string[]; + /** + * @since 9.3.0 + */ + restrictedNamedExportsPattern: string; + /** + * @since 8.33.0 + */ + restrictDefaultExports: Partial<{ + /** + * @default false + */ + direct: boolean; + /** + * @default false + */ + named: boolean; + /** + * @default false + */ + defaultFrom: boolean; + /** + * @default false + */ + namedFrom: boolean; + /** + * @default false + */ + namespaceFrom: boolean; + }>; + }>, + ] + >; + /** * Rule to disallow specified modules when loaded by `import`. * diff --git a/lib/types/rules/possible-errors.d.ts b/lib/types/rules/possible-errors.d.ts index f81320021f54..19cbaec8cc62 100644 --- a/lib/types/rules/possible-errors.d.ts +++ b/lib/types/rules/possible-errors.d.ts @@ -251,7 +251,30 @@ export interface PossibleErrors extends Linter.RulesRecord { * @since 0.4.0 * @see https://eslint.org/docs/rules/no-extra-boolean-cast */ - "no-extra-boolean-cast": Linter.RuleEntry<[]>; + "no-extra-boolean-cast": Linter.RuleEntry< + [ + | Partial<{ + /** + * @since 9.3.0 + * @default false + */ + enforceForInnerExpressions: boolean; + /** + * @deprecated + */ + enforceForLogicalOperands: never; + }> + | Partial<{ + /** + * @deprecated + * @since 7.0.0-alpha.2 + * @default false + */ + enforceForLogicalOperands: boolean; + enforceForInnerExpressions: never; + }>, + ] + >; /** * Rule to disallow unnecessary parentheses. @@ -391,7 +414,17 @@ export interface PossibleErrors extends Linter.RulesRecord { * @since 5.3.0 * @see https://eslint.org/docs/rules/no-misleading-character-class */ - "no-misleading-character-class": Linter.RuleEntry<[]>; + "no-misleading-character-class": Linter.RuleEntry< + [ + Partial<{ + /** + * @since 9.3.0 + * @default false + */ + allowEscape: boolean; + }>, + ] + >; /** * Rule to disallow calling global object properties as functions. diff --git a/lib/types/rules/stylistic-issues.d.ts b/lib/types/rules/stylistic-issues.d.ts index bfc9d74715c4..a79b84a3919c 100644 --- a/lib/types/rules/stylistic-issues.d.ts +++ b/lib/types/rules/stylistic-issues.d.ts @@ -165,6 +165,16 @@ export interface StylisticIssues extends Linter.RulesRecord { * @default false */ ignoreDestructuring: boolean; + /** + * @since 6.7.0 + * @default false + */ + ignoreImports: boolean; + /** + * @since 7.4.0 + * @default false + */ + ignoreGlobals: boolean; /** * @remarks * Also accept for regular expression patterns diff --git a/lib/types/universal.d.ts b/lib/types/universal.d.ts new file mode 100644 index 000000000000..df4fdecda75d --- /dev/null +++ b/lib/types/universal.d.ts @@ -0,0 +1,6 @@ +/** + * @fileoverview typings for "eslint/universal" module + * @author 唯然 + */ + +export { Linter } from "./index"; diff --git a/lib/universal.js b/lib/universal.js new file mode 100644 index 000000000000..51bac58453c9 --- /dev/null +++ b/lib/universal.js @@ -0,0 +1,10 @@ +/** + * @fileoverview exports for browsers + * @author 唯然 + */ + +"use strict"; + +const { Linter } = require("./linter/linter"); + +module.exports = { Linter }; diff --git a/package.json b/package.json index 2bbff6add2f6..ff6f45206c6e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint", - "version": "9.10.0", + "version": "9.11.0", "author": "Nicholas C. Zakas ", "description": "An AST-based pattern checker for JavaScript.", "bin": { @@ -20,6 +20,10 @@ }, "./rules": { "types": "./lib/types/rules/index.d.ts" + }, + "./universal": { + "types": "./lib/types/universal.d.ts", + "default": "./lib/universal.js" } }, "scripts": { @@ -36,7 +40,8 @@ "lint:fix:docs:js": "trunk check -y --ignore=** --ignore=!docs/**/*.js -a --flter=eslint && trunk check -y --ignore=** --ignore=!docs/**/*.js", "release:generate:alpha": "node Makefile.js generatePrerelease -- alpha", "release:generate:beta": "node Makefile.js generatePrerelease -- beta", - "release:generate:latest": "node Makefile.js generateRelease", + "release:generate:latest": "node Makefile.js generateRelease -- latest", + "release:generate:maintenance": "node Makefile.js generateRelease -- maintenance", "release:generate:rc": "node Makefile.js generatePrerelease -- rc", "release:publish": "node Makefile.js publishRelease", "test": "node Makefile.js test", @@ -81,8 +86,8 @@ "@eslint-community/regexpp": "^4.11.0", "@eslint/config-array": "^0.18.0", "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.10.0", - "@eslint/plugin-kit": "^0.1.0", + "@eslint/js": "9.11.0", + "@eslint/plugin-kit": "^0.2.0", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.3.0", "@nodelib/fs.walk": "^1.2.8", @@ -115,12 +120,13 @@ "devDependencies": { "@babel/core": "^7.4.3", "@babel/preset-env": "^7.4.3", - "@eslint/core": "^0.5.0", + "@eslint/core": "^0.6.0", "@eslint/json": "^0.4.0", "@trunkio/launcher": "^1.3.0", "@types/estree": "^1.0.5", "@types/json-schema": "^7.0.15", "@types/node": "^20.11.5", + "@typescript-eslint/parser": "^8.4.0", "@wdio/browser-runner": "^9.0.5", "@wdio/cli": "^9.0.5", "@wdio/concise-reporter": "^9.0.4", @@ -135,8 +141,9 @@ "eslint": "file:.", "eslint-config-eslint": "file:packages/eslint-config-eslint", "eslint-plugin-eslint-plugin": "^6.0.0", + "eslint-plugin-expect-type": "^0.4.0", "eslint-plugin-yml": "^1.14.0", - "eslint-release": "^3.2.2", + "eslint-release": "^3.3.0", "eslint-rule-composer": "^0.3.0", "eslump": "^3.0.0", "esprima": "^4.0.1", diff --git a/packages/js/package.json b/packages/js/package.json index 9889d7a96712..c1d7b3408d5d 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -1,6 +1,6 @@ { "name": "@eslint/js", - "version": "9.10.0", + "version": "9.11.0", "description": "ESLint JavaScript language implementation", "main": "./src/index.js", "scripts": {}, diff --git a/tests/lib/eslint/eslint.js b/tests/lib/eslint/eslint.js index d53b069403c8..af8809139211 100644 --- a/tests/lib/eslint/eslint.js +++ b/tests/lib/eslint/eslint.js @@ -1246,6 +1246,36 @@ describe("ESLint", () => { }); }); + + it("should pass BOM through processors", async () => { + eslint = new ESLint({ + overrideConfigFile: true, + overrideConfig: [ + { + files: ["**/*.myjs"], + processor: { + preprocess(text, filename) { + return [{ text, filename }]; + }, + postprocess(messages) { + return messages.flat(); + }, + supportsAutofix: true + }, + rules: { + "unicode-bom": ["error", "never"] + } + } + ], + cwd: path.join(fixtureDir) + }); + const results = await eslint.lintText("\uFEFFvar foo = 'bar';", { filePath: "test.myjs" }); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].messages[0].ruleId, "unicode-bom"); + }); }); describe("lintFiles()", () => { diff --git a/tests/lib/linter/linter.js b/tests/lib/linter/linter.js index 4d8366bba85f..2673ef396061 100644 --- a/tests/lib/linter/linter.js +++ b/tests/lib/linter/linter.js @@ -16071,6 +16071,47 @@ var a = "test2"; assert.strictEqual(logs.length, 1, "preprocess() should only be called once."); }); + it("should pass the BOM to preprocess", () => { + const logs = []; + const code = "\uFEFFfoo"; + const config = { + files: ["**/*.myjs"], + processor: { + preprocess(text, filenameForText) { + logs.push({ + text, + filename: filenameForText + }); + + return [{ text, filename: filenameForText }]; + }, + postprocess(messages) { + return messages.flat(); + } + }, + rules: { + "unicode-bom": ["error", "never"] + } + }; + + const results = linter.verify(code, config, { + filename: "a.myjs", + filterCodeBlock() { + return true; + } + }); + + assert.deepStrictEqual(logs, [ + { + text: code, + filename: "a.myjs" + } + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].ruleId, "unicode-bom"); + }); + it("should apply a preprocessor to the code, and lint each code sample separately", () => { const code = "foo bar baz"; const configs = createFlatConfigArray([ diff --git a/tests/lib/linter/vfile.js b/tests/lib/linter/vfile.js index 2de2e69bbca7..10f13b4815db 100644 --- a/tests/lib/linter/vfile.js +++ b/tests/lib/linter/vfile.js @@ -26,6 +26,7 @@ describe("VFile", () => { assert.strictEqual(vfile.path, "foo.js"); assert.strictEqual(vfile.physicalPath, "foo.js"); assert.strictEqual(vfile.body, "var foo = bar;"); + assert.strictEqual(vfile.rawBody, "var foo = bar;"); assert.isFalse(vfile.bom); }); @@ -35,6 +36,7 @@ describe("VFile", () => { assert.strictEqual(vfile.path, "foo.js"); assert.strictEqual(vfile.physicalPath, "foo.js"); assert.strictEqual(vfile.body, "var foo = bar;"); + assert.strictEqual(vfile.rawBody, "\uFEFFvar foo = bar;"); assert.isTrue(vfile.bom); }); @@ -44,6 +46,7 @@ describe("VFile", () => { assert.strictEqual(vfile.path, "foo.js"); assert.strictEqual(vfile.physicalPath, "foo/bar"); assert.strictEqual(vfile.body, "var foo = bar;"); + assert.strictEqual(vfile.rawBody, "var foo = bar;"); assert.isFalse(vfile.bom); }); @@ -55,6 +58,7 @@ describe("VFile", () => { assert.strictEqual(vfile.path, "foo.js"); assert.strictEqual(vfile.physicalPath, "foo.js"); assert.deepStrictEqual(vfile.body, body); + assert.deepStrictEqual(vfile.rawBody, body); assert.isFalse(vfile.bom); }); @@ -66,6 +70,7 @@ describe("VFile", () => { assert.strictEqual(vfile.path, "foo.js"); assert.strictEqual(vfile.physicalPath, "foo.js"); assert.deepStrictEqual(vfile.body, body.slice(3)); + assert.deepStrictEqual(vfile.rawBody, body); assert.isTrue(vfile.bom); }); diff --git a/tests/lib/rules/id-length.js b/tests/lib/rules/id-length.js index febd821df9f8..21fea387d006 100644 --- a/tests/lib/rules/id-length.js +++ b/tests/lib/rules/id-length.js @@ -66,7 +66,11 @@ ruleTester.run("id-length", rule, { { code: "export var num = 0;", languageOptions: { ecmaVersion: 6, sourceType: "module" } }, { code: "import * as something from 'y';", languageOptions: { ecmaVersion: 6, sourceType: "module" } }, { code: "import { x } from 'y';", languageOptions: { ecmaVersion: 6, sourceType: "module" } }, + { code: "import { x as x } from 'y';", languageOptions: { ecmaVersion: 6, sourceType: "module" } }, + { code: "import { 'x' as x } from 'y';", languageOptions: { ecmaVersion: 2022, sourceType: "module" } }, + { code: "import { x as foo } from 'y';", languageOptions: { ecmaVersion: 6, sourceType: "module" } }, { code: "import { longName } from 'y';", options: [{ max: 5 }], languageOptions: { ecmaVersion: 6, sourceType: "module" } }, + { code: "import { x as bar } from 'y';", options: [{ max: 5 }], languageOptions: { ecmaVersion: 6, sourceType: "module" } }, { code: "({ prop: obj.x.y.something } = {});", languageOptions: { ecmaVersion: 6 } }, { code: "({ prop: obj.longName } = {});", languageOptions: { ecmaVersion: 6 } }, { code: "var obj = { a: 1, bc: 2 };", options: [{ properties: "never" }] }, @@ -250,6 +254,9 @@ ruleTester.run("id-length", rule, { { code: "var [,i,a] = arr;", languageOptions: { ecmaVersion: 6 }, errors: [tooShortError, tooShortError] }, { code: "function foo([a]) {}", languageOptions: { ecmaVersion: 6 }, errors: [tooShortError] }, { code: "import x from 'module';", languageOptions: { ecmaVersion: 6 }, errors: [tooShortError] }, + { code: "import { x as z } from 'module';", languageOptions: { ecmaVersion: 6 }, errors: [{ ...tooShortError, column: 15 }] }, + { code: "import { foo as z } from 'module';", languageOptions: { ecmaVersion: 6 }, errors: [{ ...tooShortError, column: 17 }] }, + { code: "import { 'foo' as z } from 'module';", languageOptions: { ecmaVersion: 2022 }, errors: [{ ...tooShortError, column: 19 }] }, { code: "import * as x from 'module';", languageOptions: { ecmaVersion: 6 }, errors: [tooShortError] }, { code: "import longName from 'module';", @@ -263,6 +270,12 @@ ruleTester.run("id-length", rule, { languageOptions: { ecmaVersion: 6 }, errors: [tooLongError] }, + { + code: "import { foo as longName } from 'module';", + options: [{ max: 5 }], + languageOptions: { ecmaVersion: 6 }, + errors: [{ ...tooLongError, column: 17 }] + }, { code: "var _$xt_$ = Foo(42)", options: [{ min: 2, max: 4 }], diff --git a/tests/lib/rules/no-useless-constructor.js b/tests/lib/rules/no-useless-constructor.js index 7b396cececda..17aee4e95211 100644 --- a/tests/lib/rules/no-useless-constructor.js +++ b/tests/lib/rules/no-useless-constructor.js @@ -11,6 +11,7 @@ const rule = require("../../../lib/rules/no-useless-constructor"); const RuleTester = require("../../../lib/rule-tester/rule-tester"); +const { unIndent } = require("../../_utils"); //------------------------------------------------------------------------------ // Tests @@ -47,39 +48,114 @@ ruleTester.run("no-useless-constructor", rule, { invalid: [ { code: "class A { constructor(){} }", - errors: [error] + errors: [{ + ...error, + suggestions: [{ + messageId: "removeConstructor", + output: "class A { }" + }] + }] }, { code: "class A { 'constructor'(){} }", - errors: [error] + errors: [{ + ...error, + suggestions: [{ + messageId: "removeConstructor", + output: "class A { }" + }] + }] }, { code: "class A extends B { constructor() { super(); } }", - errors: [error] + errors: [{ + ...error, + suggestions: [{ + messageId: "removeConstructor", + output: "class A extends B { }" + }] + }] }, { code: "class A extends B { constructor(foo){ super(foo); } }", - errors: [error] + errors: [{ + ...error, + suggestions: [{ + messageId: "removeConstructor", + output: "class A extends B { }" + }] + }] }, { code: "class A extends B { constructor(foo, bar){ super(foo, bar); } }", - errors: [error] + errors: [{ + ...error, + suggestions: [{ + messageId: "removeConstructor", + output: "class A extends B { }" + }] + }] }, { code: "class A extends B { constructor(...args){ super(...args); } }", - errors: [error] + errors: [{ + ...error, + suggestions: [{ + messageId: "removeConstructor", + output: "class A extends B { }" + }] + }] }, { code: "class A extends B.C { constructor() { super(...arguments); } }", - errors: [error] + errors: [{ + ...error, + suggestions: [{ + messageId: "removeConstructor", + output: "class A extends B.C { }" + }] + }] }, { code: "class A extends B { constructor(a, b, ...c) { super(...arguments); } }", - errors: [error] + errors: [{ + ...error, + suggestions: [{ + messageId: "removeConstructor", + output: "class A extends B { }" + }] + }] }, { code: "class A extends B { constructor(a, b, ...c) { super(a, b, ...c); } }", - errors: [error] + errors: [{ + ...error, + suggestions: [{ + messageId: "removeConstructor", + output: "class A extends B { }" + }] + }] + }, + { + code: unIndent` + class A { + foo = 'bar' + constructor() { } + [0]() { } + }`, + languageOptions: { ecmaVersion: 2022 }, + errors: [{ + ...error, + suggestions: [{ + messageId: "removeConstructor", + output: unIndent` + class A { + foo = 'bar' + ; + [0]() { } + }` + }] + }] } ] }); diff --git a/tests/lib/types/types.test.ts b/tests/lib/types/types.test.ts index 29c2e7d93c0f..5ece3df0a5a4 100644 --- a/tests/lib/types/types.test.ts +++ b/tests/lib/types/types.test.ts @@ -27,6 +27,7 @@ import { AST, ESLint, Linter, loadESLint, Rule, RuleTester, Scope, SourceCode } from "eslint"; import { ESLintRules } from "eslint/rules"; +import { Linter as ESLinter } from "eslint/universal"; import { builtinRules, FileEnumerator, @@ -35,6 +36,7 @@ import { shouldUseFlatConfig, } from "eslint/use-at-your-own-risk"; import { Comment, PrivateIdentifier, PropertyDefinition, StaticBlock, WhileStatement } from "estree"; +import { Language } from "@eslint/core"; const SOURCE = `var foo = bar;`; @@ -105,9 +107,9 @@ loc.column; // $ExpectType number sourceCode.getIndexFromLoc({ line: 0, column: 0 }); sourceCode.getTokenByRangeStart(0); // $ExpectType Token | null -sourceCode.getTokenByRangeStart(0, { includeComments: true }); // $ExpectType Comment | Token | null || Token | Comment | null +sourceCode.getTokenByRangeStart(0, { includeComments: true }); // $ExpectType Comment | Token | null sourceCode.getTokenByRangeStart(0, { includeComments: false }); // $ExpectType Token | null -sourceCode.getTokenByRangeStart(0, { includeComments: false as boolean }); // $ExpectType Comment | Token | null || Token | Comment | null +sourceCode.getTokenByRangeStart(0, { includeComments: false as boolean }); // $ExpectType Comment | Token | null sourceCode.getFirstToken(AST); // $ExpectType Token | null sourceCode.getFirstToken(AST, 0); @@ -115,7 +117,7 @@ sourceCode.getFirstToken(AST, { skip: 0 }); sourceCode.getFirstToken(AST, (t): t is AST.Token & { type: "Identifier" } => t.type === "Identifier"); // $ExpectType (Token & { type: "Identifier"; }) | null sourceCode.getFirstToken(AST, { filter: (t): t is AST.Token & { type: "Identifier" } => t.type === "Identifier" }); // $ExpectType (Token & { type: "Identifier"; }) | null sourceCode.getFirstToken(AST, { skip: 0, filter: t => t.type === "Identifier" }); -sourceCode.getFirstToken(AST, { includeComments: true }); // $ExpectType Comment | Token | null || Token | Comment | null +sourceCode.getFirstToken(AST, { includeComments: true }); // $ExpectType Comment | Token | null sourceCode.getFirstToken(AST, { includeComments: true, skip: 0 }); // prettier-ignore sourceCode.getFirstToken(AST, { // $ExpectType (Token & { type: "Identifier"; }) | null @@ -223,7 +225,7 @@ sourceCode.getFirstTokenBetween(AST, AST, { skip: 0, filter: (t): t is AST.Token & { type: "Identifier" } => t.type === "Identifier", }); -sourceCode.getFirstTokenBetween(AST, AST, { includeComments: true }); // $ExpectType Comment | Token | null || Token | Comment | null +sourceCode.getFirstTokenBetween(AST, AST, { includeComments: true }); // $ExpectType Comment | Token | null sourceCode.getFirstTokenBetween(AST, AST, { includeComments: true, skip: 0 }); // prettier-ignore sourceCode.getFirstTokenBetween(AST, AST, { // $ExpectType (Token & { type: "Identifier"; }) | null @@ -241,7 +243,7 @@ sourceCode.getFirstTokensBetween(AST, AST, { // $ExpectType (Token & { type: "Id filter: (t): t is AST.Token & { type: "Identifier" } => t.type === "Identifier", }); sourceCode.getFirstTokensBetween(AST, AST, { count: 0, filter: t => t.type === "Identifier" }); -sourceCode.getFirstTokensBetween(AST, AST, { includeComments: true }); // $ExpectType (Comment | Token)[] || (Token | Comment)[] +sourceCode.getFirstTokensBetween(AST, AST, { includeComments: true }); // $ExpectType (Comment | Token)[] sourceCode.getFirstTokensBetween(AST, AST, { includeComments: true, count: 0 }); // prettier-ignore sourceCode.getFirstTokensBetween(AST, AST, { // $ExpectType (Token & { type: "Identifier"; })[] @@ -278,7 +280,7 @@ sourceCode.getTokens(AST, 0); sourceCode.getTokens(AST, 0, 0); sourceCode.getTokens(AST, (t): t is AST.Token & { type: "Identifier" } => t.type === "Identifier"); // $ExpectType (Token & { type: "Identifier"; })[] sourceCode.getTokens(AST, { filter: (t): t is AST.Token & { type: "Identifier" } => t.type === "Identifier" }); // $ExpectType (Token & { type: "Identifier"; })[] -sourceCode.getTokens(AST, { includeComments: true }); // $ExpectType (Comment | Token)[] || (Token | Comment)[] +sourceCode.getTokens(AST, { includeComments: true }); // $ExpectType (Comment | Token)[] // prettier-ignore sourceCode.getTokens(AST, { // $ExpectType (Token & { type: "Identifier"; })[] includeComments: true, @@ -578,6 +580,7 @@ rule = { // #region Linter const linter = new Linter(); +const eslinter = new ESLinter(); linter.version; @@ -1003,7 +1006,7 @@ linterWithEslintrcConfig.getRules(); const customFormatter1: ESLint.Formatter = { format: () => "ok" }; const customFormatter2: ESLint.Formatter = { format: () => Promise.resolve("ok") }; - let data: ESLint.LintResultData; + let resultsMeta: ESLint.ResultsMeta; const meta: Rule.RuleMetaData = { type: "suggestion", docs: { @@ -1019,7 +1022,7 @@ linterWithEslintrcConfig.getRules(); }, }; - data = { cwd: "/foo/bar", rulesMeta: { "no-extra-semi": meta } }; + resultsMeta = { maxWarningsExceeded: { maxWarnings: 42, foundWarnings: 43 } }; const version: string = ESLint.version; @@ -1027,7 +1030,7 @@ linterWithEslintrcConfig.getRules(); const results: ESLint.LintResult[] = await resultsPromise; const formatter = await formatterPromise; - const output: string = await formatter.format(results, data); + const output: string = await formatter.format(results, resultsMeta); eslint.getRulesMetaForResults(results); @@ -1131,7 +1134,7 @@ linterWithEslintrcConfig.getRules(); const customFormatter1: ESLint.Formatter = { format: () => "ok" }; const customFormatter2: ESLint.Formatter = { format: () => Promise.resolve("ok") }; - let data: ESLint.LintResultData; + let resultsMeta: ESLint.ResultsMeta; const meta: Rule.RuleMetaData = { type: "suggestion", docs: { @@ -1147,7 +1150,7 @@ linterWithEslintrcConfig.getRules(); }, }; - data = { cwd: "/foo/bar", rulesMeta: { "no-extra-semi": meta } }; + resultsMeta = { maxWarningsExceeded: { maxWarnings: 42, foundWarnings: 43 } }; const version: string = LegacyESLint.version; @@ -1155,7 +1158,7 @@ linterWithEslintrcConfig.getRules(); const results: ESLint.LintResult[] = await resultsPromise; const formatter = await formatterPromise; - const output: string = await formatter.format(results, data); + const output: string = await formatter.format(results, resultsMeta); eslint.getRulesMetaForResults(results); @@ -1169,6 +1172,20 @@ linterWithEslintrcConfig.getRules(); // #endregion +// #region ESLint.Formatter + +function jsonFormatter(results: ESLint.LintResult[]) { + return JSON.stringify(results, null, 2); +}; + +const customFormatter: ESLint.FormatterFunction = jsonFormatter; + +function wrapperFormatter(results: ESLint.LintResult[], { cwd, maxWarningsExceeded, rulesMeta }: ESLint.LintResultData) { + customFormatter(results, { cwd, maxWarningsExceeded, rulesMeta }); +} + +// #endregion ESLint.Formatter + // #region ESLint.LintResult let results!: ESLint.LintResult[]; @@ -1216,7 +1233,7 @@ for (const result of results) { } } -// #region ESLint.LintResult +// #endregion ESLint.LintResult // #region ESLintRules @@ -1340,6 +1357,15 @@ ruleTester.run("simple-valid-test", rule, { // #region Config +((): Linter.Config => ({ + language: "js/js" +})); + +((): Linter.Config => ({ + // @ts-expect-error + language: null +})); + ((): Linter.Config => ({ languageOptions: { parser: { @@ -1482,6 +1508,16 @@ flatConfig = config; // #endregion Config +// #region Plugins + +((): ESLint.Plugin => ({ + languages: { + "js": {} as Language + } +})); + +// #endregion Plugins + (async (useFlatConfig?: boolean) => { await loadESLint(); // $ExpectType typeof ESLint | typeof LegacyESLint await loadESLint({}); // $ExpectType typeof ESLint | typeof LegacyESLint diff --git a/tests/lib/universal.js b/tests/lib/universal.js new file mode 100644 index 000000000000..52145f1ab27f --- /dev/null +++ b/tests/lib/universal.js @@ -0,0 +1,21 @@ +/** + * @fileoverview Tests for "eslint/universal". + * @author 唯然 + */ + +"use strict"; +const assert = require("node:assert/strict"); + +describe("universal", () => { + it("should have Linter exported", () => { + const { Linter } = require("eslint/universal"); + + assert.ok(Linter); + assert.ok(typeof Linter === "function"); + + + const linter = new Linter(); + + assert.ok(linter); + }); +});