From 9281c2e954dea0d75bc76b6bfd133300fe591852 Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Sat, 26 Oct 2024 05:25:01 -0500 Subject: [PATCH 1/7] Add type definitions --- .github/workflows/ci.yml | 18 ++++++++++++++++++ configs.js | 16 +++++++++------- index.js | 11 +++++++---- package.json | 26 +++++++++++++++++++++++--- tsconfig.json | 21 +++++++++++++++++++++ types/configs.d.ts | 11 +++++++++++ types/index.d.ts | 7 +++++++ 7 files changed, 96 insertions(+), 14 deletions(-) create mode 100644 tsconfig.json create mode 100644 types/configs.d.ts create mode 100644 types/index.d.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f9e4ee..7604bb8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -97,6 +97,24 @@ jobs: - name: ⬆️ Upload coverage report uses: codecov/codecov-action@v4 + are-the-types-wrong: + name: 🤔 Are the types wrong? + runs-on: ubuntu-latest + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@v4 + + - name: ⎔ Setup Node + uses: actions/setup-node@v4 + with: + node-version: 18 + + - name: 📥 Install dependencies + run: npm install --legacy-peer-deps + + - name: ▶️ Run test:types script + run: npm run test:types -- --format=table + release: name: 🚀 Release needs: [ lint, test ] diff --git a/configs.js b/configs.js index 7e0bd30..dd6fa39 100644 --- a/configs.js +++ b/configs.js @@ -9,14 +9,16 @@ const plugin = { rules, } +const recommended = { + name: "@eslint-community/eslint-comments/recommended", + plugins: { + "@eslint-community/eslint-comments": plugin, + }, + rules: rulesRecommended, +} + module.exports = { - recommended: { - name: '@eslint-community/eslint-comments/recommended', - plugins: { - "@eslint-community/eslint-comments": plugin, - }, - rules: rulesRecommended, - }, + recommended, } module.exports.default = module.exports diff --git a/index.js b/index.js index 5ace35b..78db10c 100644 --- a/index.js +++ b/index.js @@ -1,8 +1,11 @@ -/** DON'T EDIT THIS FILE WHICH WAS CREATED BY 'scripts/generate-index.js'. */ "use strict" +const rules = require("./lib/rules") +const utils = require("./lib/utils") +const configs = require("./lib/configs") + module.exports = { - configs: require("./lib/configs"), - rules: require("./lib/rules"), - utils: require("./lib/utils"), + configs, + rules, + utils, } diff --git a/package.json b/package.json index e0e4d78..71e0209 100644 --- a/package.json +++ b/package.json @@ -6,14 +6,30 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "main": "index.js", + "types": "./types/index.d.ts", "type": "commonjs", "files": [ "configs.js", - "lib" + "lib", + "types" ], "exports": { - "./configs": "./configs.js", - ".": "./index.js" + "./package.json": "./package.json", + "./configs": { + "types": "./types/configs.d.ts", + "default": "./configs.js" + }, + ".": { + "types": "./types/index.d.ts", + "default": "./index.js" + } + }, + "typesVersions": { + "*": { + "configs": [ + "./types/configs.d.ts" + ] + } }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" @@ -23,11 +39,13 @@ "ignore": "^5.2.4" }, "devDependencies": { + "@arethetypeswrong/cli": "^0.18.1", "@babel/core": "^7.22.9", "@babel/eslint-parser": "^7.22.9", "@eslint-community/eslint-plugin-mysticatea": "^15.5.1", "@eslint/core": "^0.13.0", "@eslint/css": "^0.6.0", + "@types/eslint": "^8", "@types/node": "^14.18.54", "@vuepress/plugin-pwa": "^1.9.9", "cross-spawn": "^7.0.3", @@ -40,6 +58,7 @@ "opener": "^1.5.2", "rimraf": "^3.0.2", "semver": "^7.5.4", + "typescript": "^5.8.3", "vite-plugin-eslint4b": "^0.2.1", "vitepress": "^1.0.0-rc.15" }, @@ -53,6 +72,7 @@ "lint": "eslint lib scripts tests", "test": "nyc npm run debug", "debug": "mocha \"tests/lib/**/*.js\" --reporter dot --timeout 8000", + "test:types": "attw --pack", "coverage": "nyc report --reporter lcov && opener coverage/lcov-report/index.html", "watch": "npm run -s test -- --watch --growl" }, diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..fff2a40 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": false, + "esModuleInterop": false, + "exactOptionalPropertyTypes": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "lib": ["ESNext"], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "noEmit": true, + "resolveJsonModule": true, + "skipLibCheck": false, + "strict": true, + "target": "ESNext", + "useDefineForClassFields": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true + }, + "include": ["."] +} diff --git a/types/configs.d.ts b/types/configs.d.ts new file mode 100644 index 0000000..011b635 --- /dev/null +++ b/types/configs.d.ts @@ -0,0 +1,11 @@ +import type { Linter } from "eslint" + +declare namespace Configs { + import defaultExports = Configs + + export const recommended: Linter.FlatConfig + + export { defaultExports as default } +} + +export = Configs diff --git a/types/index.d.ts b/types/index.d.ts new file mode 100644 index 0000000..765fd2c --- /dev/null +++ b/types/index.d.ts @@ -0,0 +1,7 @@ +import type { ESLint, Linter } from "eslint" + +export declare const configs: { recommended: Linter.FlatConfig } + +export declare const rules: NonNullable + +export declare const utils: { patch: (ruleId?: string) => void } From 1309a6b277fa1d58e52baa09de6e16e0d71c4b2d Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Tue, 19 Nov 2024 23:48:44 -0600 Subject: [PATCH 2/7] Add basic type tests --- .github/workflows/ci.yml | 5 ++++- package.json | 4 +++- tests/types/configs.test-d.cts | 18 ++++++++++++++++++ tests/types/configs.test-d.mts | 15 +++++++++++++++ tsconfig.json | 3 +-- 5 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 tests/types/configs.test-d.cts create mode 100644 tests/types/configs.test-d.mts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7604bb8..a85ef51 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -112,8 +112,11 @@ jobs: - name: 📥 Install dependencies run: npm install --legacy-peer-deps + - name: ▶️ Run check-exports script + run: npm run check-exports -- --format=table + - name: ▶️ Run test:types script - run: npm run test:types -- --format=table + run: npm run test:types release: name: 🚀 Release diff --git a/package.json b/package.json index 71e0209..23977e0 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "cross-spawn": "^7.0.3", "esbuild": "^0.19.3", "eslint": "^8.46.0", + "expect-type": "^1.2.1", "fs-extra": "^10.1.0", "mocha": "^10.4.0", "monaco-editor": "^0.47.0", @@ -72,7 +73,8 @@ "lint": "eslint lib scripts tests", "test": "nyc npm run debug", "debug": "mocha \"tests/lib/**/*.js\" --reporter dot --timeout 8000", - "test:types": "attw --pack", + "test:types": "tsc -p tsconfig.json", + "check-exports": "attw --pack", "coverage": "nyc report --reporter lcov && opener coverage/lcov-report/index.html", "watch": "npm run -s test -- --watch --growl" }, diff --git a/tests/types/configs.test-d.cts b/tests/types/configs.test-d.cts new file mode 100644 index 0000000..540bd48 --- /dev/null +++ b/tests/types/configs.test-d.cts @@ -0,0 +1,18 @@ +import configs = require("@eslint-community/eslint-plugin-eslint-comments/configs") +import expectTypeModule = require("expect-type") + +import type { Linter } from "eslint" + +import expectTypeOf = expectTypeModule.expectTypeOf + +expectTypeOf(configs) + .toHaveProperty("recommended") + .toMatchTypeOf() + +expectTypeOf([configs.recommended]).toMatchTypeOf() + +expectTypeOf(configs.recommended).toMatchTypeOf() + +expectTypeOf(configs) + .toHaveProperty("recommended") + .toMatchTypeOf() diff --git a/tests/types/configs.test-d.mts b/tests/types/configs.test-d.mts new file mode 100644 index 0000000..9e9ff73 --- /dev/null +++ b/tests/types/configs.test-d.mts @@ -0,0 +1,15 @@ +import configs from "@eslint-community/eslint-plugin-eslint-comments/configs" +import type { Linter } from "eslint" +import { expectTypeOf } from "expect-type" + +expectTypeOf(configs) + .toHaveProperty("recommended") + .toMatchTypeOf() + +expectTypeOf([configs.recommended]).toMatchTypeOf() + +expectTypeOf(configs.recommended).toMatchTypeOf() + +expectTypeOf(configs) + .toHaveProperty("recommended") + .toMatchTypeOf() diff --git a/tsconfig.json b/tsconfig.json index fff2a40..99d54ef 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,6 @@ "compilerOptions": { "allowSyntheticDefaultImports": false, "esModuleInterop": false, - "exactOptionalPropertyTypes": true, "forceConsistentCasingInFileNames": true, "isolatedModules": true, "lib": ["ESNext"], @@ -10,7 +9,7 @@ "moduleResolution": "NodeNext", "noEmit": true, "resolveJsonModule": true, - "skipLibCheck": false, + "skipLibCheck": true, "strict": true, "target": "ESNext", "useDefineForClassFields": true, From 0f4bdc9315791a1bb316804cdb29ee7d1eac0da9 Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Wed, 20 Nov 2024 04:47:49 -0600 Subject: [PATCH 3/7] Run type tests against different versions of ESLint and TS during CI --- .github/workflows/ci.yml | 51 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a85ef51..a109102 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,8 +43,7 @@ jobs: run: npm run lint test: - name: - 🧪 Test (Node@${{ matrix.node }} - ESLint@${{ matrix.eslint }} - ${{ + name: 🧪 Test (Node@${{ matrix.node }} - ESLint@${{ matrix.eslint }} - ${{ matrix.os }}) strategy: fail-fast: false @@ -118,12 +117,54 @@ jobs: - name: ▶️ Run test:types script run: npm run test:types + type-tests: + name: 🧪 Type tests with ESLint ${{ matrix.eslint }} and TypeScript ${{ matrix.ts }} + runs-on: ubuntu-latest + + strategy: + fail-fast: false + + matrix: + eslint: [8, 9] + ts: ["5.0", "5.1", "5.2", "5.3", "5.4", "5.5", "5.6"] + + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@v4 + + - name: ⎔ Setup Node + uses: actions/setup-node@v4 + with: + node-version: 18 + + - name: 📥 Install dependencies + run: npm install + + - name: Pack the package + id: pack + run: npm install "$(npm pack | tail -n1)" + + - name: 📥 Uninstall @types/eslint + if: matrix.eslint != 8 + run: npm uninstall @types/eslint + + - name: 📥 Install ESLint version ${{ matrix.eslint }} + run: npm install --save-dev eslint@${{ matrix.eslint }} + + - name: 📥 Install TypeScript version ${{ matrix.ts }} + run: npm install --save-dev typescript@${{ matrix.ts }} -f + + - name: ▶️ Run test:types script + run: npm run test:types + + - name: 📝 List version of ESLint + run: npm why eslint @types/eslint + release: name: 🚀 Release - needs: [ lint, test ] + needs: [lint, test] runs-on: ubuntu-latest - if: - github.repository == 'eslint-community/eslint-plugin-eslint-comments' && + if: github.repository == 'eslint-community/eslint-plugin-eslint-comments' && contains('refs/heads/main,refs/heads/next,refs/heads/beta,refs/heads/alpha', github.ref) && github.event_name == 'push' steps: From a326c3adc31e3a00ae50ef1e74307e4f0154918d Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Fri, 28 Feb 2025 08:48:44 -0600 Subject: [PATCH 4/7] Convert files to TypeScript --- configs.js => configs.ts | 0 docs/.vitepress/public/{service-worker.js => service-worker.ts} | 0 index.js => index.ts | 0 lib/{configs.js => configs.ts} | 0 lib/configs/{recommended.js => recommended.ts} | 0 lib/internal/{disabled-area.js => disabled-area.ts} | 0 lib/internal/{get-linters.js => get-linters.ts} | 0 lib/internal/{utils.js => utils.ts} | 0 lib/{rules.js => rules.ts} | 0 lib/rules/{disable-enable-pair.js => disable-enable-pair.ts} | 0 lib/rules/{no-aggregating-enable.js => no-aggregating-enable.ts} | 0 lib/rules/{no-duplicate-disable.js => no-duplicate-disable.ts} | 0 lib/rules/{no-restricted-disable.js => no-restricted-disable.ts} | 0 lib/rules/{no-unlimited-disable.js => no-unlimited-disable.ts} | 0 lib/rules/{no-unused-disable.js => no-unused-disable.ts} | 0 lib/rules/{no-unused-enable.js => no-unused-enable.ts} | 0 lib/rules/{no-use.js => no-use.ts} | 0 lib/rules/{require-description.js => require-description.ts} | 0 lib/{utils.js => utils.ts} | 0 lib/utils/{patch.js => patch.ts} | 0 scripts/lib/{rules.js => rules.ts} | 0 scripts/lib/{utils.js => utils.ts} | 0 scripts/{update-docs-headers.js => update-docs-headers.ts} | 0 scripts/{update-docs-index.js => update-docs-index.ts} | 0 .../{update-recommended-rules.js => update-recommended-rules.ts} | 0 scripts/{update.js => update.ts} | 0 ...egal-eslint-disable-line.js => illegal-eslint-disable-line.ts} | 0 .../lib/rules/{disable-enable-pair.js => disable-enable-pair.ts} | 0 .../rules/{no-aggregating-enable.js => no-aggregating-enable.ts} | 0 .../rules/{no-duplicate-disable.js => no-duplicate-disable.ts} | 0 .../rules/{no-restricted-disable.js => no-restricted-disable.ts} | 0 .../rules/{no-unlimited-disable.js => no-unlimited-disable.ts} | 0 tests/lib/rules/{no-unused-disable.js => no-unused-disable.ts} | 0 tests/lib/rules/{no-unused-enable.js => no-unused-enable.ts} | 0 tests/lib/rules/{no-use.js => no-use.ts} | 0 .../lib/rules/{require-description.js => require-description.ts} | 0 36 files changed, 0 insertions(+), 0 deletions(-) rename configs.js => configs.ts (100%) rename docs/.vitepress/public/{service-worker.js => service-worker.ts} (100%) rename index.js => index.ts (100%) rename lib/{configs.js => configs.ts} (100%) rename lib/configs/{recommended.js => recommended.ts} (100%) rename lib/internal/{disabled-area.js => disabled-area.ts} (100%) rename lib/internal/{get-linters.js => get-linters.ts} (100%) rename lib/internal/{utils.js => utils.ts} (100%) rename lib/{rules.js => rules.ts} (100%) rename lib/rules/{disable-enable-pair.js => disable-enable-pair.ts} (100%) rename lib/rules/{no-aggregating-enable.js => no-aggregating-enable.ts} (100%) rename lib/rules/{no-duplicate-disable.js => no-duplicate-disable.ts} (100%) rename lib/rules/{no-restricted-disable.js => no-restricted-disable.ts} (100%) rename lib/rules/{no-unlimited-disable.js => no-unlimited-disable.ts} (100%) rename lib/rules/{no-unused-disable.js => no-unused-disable.ts} (100%) rename lib/rules/{no-unused-enable.js => no-unused-enable.ts} (100%) rename lib/rules/{no-use.js => no-use.ts} (100%) rename lib/rules/{require-description.js => require-description.ts} (100%) rename lib/{utils.js => utils.ts} (100%) rename lib/utils/{patch.js => patch.ts} (100%) rename scripts/lib/{rules.js => rules.ts} (100%) rename scripts/lib/{utils.js => utils.ts} (100%) rename scripts/{update-docs-headers.js => update-docs-headers.ts} (100%) rename scripts/{update-docs-index.js => update-docs-index.ts} (100%) rename scripts/{update-recommended-rules.js => update-recommended-rules.ts} (100%) rename scripts/{update.js => update.ts} (100%) rename tests/lib/{illegal-eslint-disable-line.js => illegal-eslint-disable-line.ts} (100%) rename tests/lib/rules/{disable-enable-pair.js => disable-enable-pair.ts} (100%) rename tests/lib/rules/{no-aggregating-enable.js => no-aggregating-enable.ts} (100%) rename tests/lib/rules/{no-duplicate-disable.js => no-duplicate-disable.ts} (100%) rename tests/lib/rules/{no-restricted-disable.js => no-restricted-disable.ts} (100%) rename tests/lib/rules/{no-unlimited-disable.js => no-unlimited-disable.ts} (100%) rename tests/lib/rules/{no-unused-disable.js => no-unused-disable.ts} (100%) rename tests/lib/rules/{no-unused-enable.js => no-unused-enable.ts} (100%) rename tests/lib/rules/{no-use.js => no-use.ts} (100%) rename tests/lib/rules/{require-description.js => require-description.ts} (100%) diff --git a/configs.js b/configs.ts similarity index 100% rename from configs.js rename to configs.ts diff --git a/docs/.vitepress/public/service-worker.js b/docs/.vitepress/public/service-worker.ts similarity index 100% rename from docs/.vitepress/public/service-worker.js rename to docs/.vitepress/public/service-worker.ts diff --git a/index.js b/index.ts similarity index 100% rename from index.js rename to index.ts diff --git a/lib/configs.js b/lib/configs.ts similarity index 100% rename from lib/configs.js rename to lib/configs.ts diff --git a/lib/configs/recommended.js b/lib/configs/recommended.ts similarity index 100% rename from lib/configs/recommended.js rename to lib/configs/recommended.ts diff --git a/lib/internal/disabled-area.js b/lib/internal/disabled-area.ts similarity index 100% rename from lib/internal/disabled-area.js rename to lib/internal/disabled-area.ts diff --git a/lib/internal/get-linters.js b/lib/internal/get-linters.ts similarity index 100% rename from lib/internal/get-linters.js rename to lib/internal/get-linters.ts diff --git a/lib/internal/utils.js b/lib/internal/utils.ts similarity index 100% rename from lib/internal/utils.js rename to lib/internal/utils.ts diff --git a/lib/rules.js b/lib/rules.ts similarity index 100% rename from lib/rules.js rename to lib/rules.ts diff --git a/lib/rules/disable-enable-pair.js b/lib/rules/disable-enable-pair.ts similarity index 100% rename from lib/rules/disable-enable-pair.js rename to lib/rules/disable-enable-pair.ts diff --git a/lib/rules/no-aggregating-enable.js b/lib/rules/no-aggregating-enable.ts similarity index 100% rename from lib/rules/no-aggregating-enable.js rename to lib/rules/no-aggregating-enable.ts diff --git a/lib/rules/no-duplicate-disable.js b/lib/rules/no-duplicate-disable.ts similarity index 100% rename from lib/rules/no-duplicate-disable.js rename to lib/rules/no-duplicate-disable.ts diff --git a/lib/rules/no-restricted-disable.js b/lib/rules/no-restricted-disable.ts similarity index 100% rename from lib/rules/no-restricted-disable.js rename to lib/rules/no-restricted-disable.ts diff --git a/lib/rules/no-unlimited-disable.js b/lib/rules/no-unlimited-disable.ts similarity index 100% rename from lib/rules/no-unlimited-disable.js rename to lib/rules/no-unlimited-disable.ts diff --git a/lib/rules/no-unused-disable.js b/lib/rules/no-unused-disable.ts similarity index 100% rename from lib/rules/no-unused-disable.js rename to lib/rules/no-unused-disable.ts diff --git a/lib/rules/no-unused-enable.js b/lib/rules/no-unused-enable.ts similarity index 100% rename from lib/rules/no-unused-enable.js rename to lib/rules/no-unused-enable.ts diff --git a/lib/rules/no-use.js b/lib/rules/no-use.ts similarity index 100% rename from lib/rules/no-use.js rename to lib/rules/no-use.ts diff --git a/lib/rules/require-description.js b/lib/rules/require-description.ts similarity index 100% rename from lib/rules/require-description.js rename to lib/rules/require-description.ts diff --git a/lib/utils.js b/lib/utils.ts similarity index 100% rename from lib/utils.js rename to lib/utils.ts diff --git a/lib/utils/patch.js b/lib/utils/patch.ts similarity index 100% rename from lib/utils/patch.js rename to lib/utils/patch.ts diff --git a/scripts/lib/rules.js b/scripts/lib/rules.ts similarity index 100% rename from scripts/lib/rules.js rename to scripts/lib/rules.ts diff --git a/scripts/lib/utils.js b/scripts/lib/utils.ts similarity index 100% rename from scripts/lib/utils.js rename to scripts/lib/utils.ts diff --git a/scripts/update-docs-headers.js b/scripts/update-docs-headers.ts similarity index 100% rename from scripts/update-docs-headers.js rename to scripts/update-docs-headers.ts diff --git a/scripts/update-docs-index.js b/scripts/update-docs-index.ts similarity index 100% rename from scripts/update-docs-index.js rename to scripts/update-docs-index.ts diff --git a/scripts/update-recommended-rules.js b/scripts/update-recommended-rules.ts similarity index 100% rename from scripts/update-recommended-rules.js rename to scripts/update-recommended-rules.ts diff --git a/scripts/update.js b/scripts/update.ts similarity index 100% rename from scripts/update.js rename to scripts/update.ts diff --git a/tests/lib/illegal-eslint-disable-line.js b/tests/lib/illegal-eslint-disable-line.ts similarity index 100% rename from tests/lib/illegal-eslint-disable-line.js rename to tests/lib/illegal-eslint-disable-line.ts diff --git a/tests/lib/rules/disable-enable-pair.js b/tests/lib/rules/disable-enable-pair.ts similarity index 100% rename from tests/lib/rules/disable-enable-pair.js rename to tests/lib/rules/disable-enable-pair.ts diff --git a/tests/lib/rules/no-aggregating-enable.js b/tests/lib/rules/no-aggregating-enable.ts similarity index 100% rename from tests/lib/rules/no-aggregating-enable.js rename to tests/lib/rules/no-aggregating-enable.ts diff --git a/tests/lib/rules/no-duplicate-disable.js b/tests/lib/rules/no-duplicate-disable.ts similarity index 100% rename from tests/lib/rules/no-duplicate-disable.js rename to tests/lib/rules/no-duplicate-disable.ts diff --git a/tests/lib/rules/no-restricted-disable.js b/tests/lib/rules/no-restricted-disable.ts similarity index 100% rename from tests/lib/rules/no-restricted-disable.js rename to tests/lib/rules/no-restricted-disable.ts diff --git a/tests/lib/rules/no-unlimited-disable.js b/tests/lib/rules/no-unlimited-disable.ts similarity index 100% rename from tests/lib/rules/no-unlimited-disable.js rename to tests/lib/rules/no-unlimited-disable.ts diff --git a/tests/lib/rules/no-unused-disable.js b/tests/lib/rules/no-unused-disable.ts similarity index 100% rename from tests/lib/rules/no-unused-disable.js rename to tests/lib/rules/no-unused-disable.ts diff --git a/tests/lib/rules/no-unused-enable.js b/tests/lib/rules/no-unused-enable.ts similarity index 100% rename from tests/lib/rules/no-unused-enable.js rename to tests/lib/rules/no-unused-enable.ts diff --git a/tests/lib/rules/no-use.js b/tests/lib/rules/no-use.ts similarity index 100% rename from tests/lib/rules/no-use.js rename to tests/lib/rules/no-use.ts diff --git a/tests/lib/rules/require-description.js b/tests/lib/rules/require-description.ts similarity index 100% rename from tests/lib/rules/require-description.js rename to tests/lib/rules/require-description.ts From b931460ad152d868d9e61480e4ad13d388f6d40b Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Sun, 2 Mar 2025 07:16:35 -0600 Subject: [PATCH 5/7] Migrate to TypeScript --- .eslintrc.yml | 46 ++- .github/workflows/ci.yml | 70 ++-- .gitignore | 1 + configs.ts | 77 +++- index.ts | 14 +- lib/configs.ts | 7 +- lib/configs/recommended.ts | 31 +- lib/internal/disabled-area.ts | 145 +++++--- ...ments.js => get-all-directive-comments.ts} | 141 ++++--- lib/internal/get-linters.ts | 19 +- lib/internal/utils.ts | 250 +++++++------ lib/rules.ts | 52 ++- lib/rules/disable-enable-pair.ts | 34 +- lib/rules/no-aggregating-enable.ts | 24 +- lib/rules/no-duplicate-disable.ts | 22 +- lib/rules/no-restricted-disable.ts | 24 +- lib/rules/no-unlimited-disable.ts | 22 +- lib/rules/no-unused-disable.ts | 12 +- lib/rules/no-unused-enable.ts | 22 +- lib/rules/no-use.ts | 22 +- lib/rules/require-description.ts | 22 +- lib/utils.ts | 7 +- lib/utils/patch.ts | 77 ++-- package.json | 68 ++-- scripts/lib/rules.ts | 27 +- scripts/lib/utils.ts | 17 +- scripts/update-docs-headers.ts | 22 +- scripts/update-docs-index.ts | 40 +- scripts/update-recommended-rules.ts | 19 +- scripts/update.ts | 21 +- ...ts => illegal-eslint-disable-line.test.ts} | 47 +-- ...le-pair.ts => disable-enable-pair.test.ts} | 16 +- ...nable.ts => no-aggregating-enable.test.ts} | 16 +- ...isable.ts => no-duplicate-disable.test.ts} | 16 +- ...sable.ts => no-restricted-disable.test.ts} | 23 +- ...isable.ts => no-unlimited-disable.test.ts} | 16 +- ...d-disable.ts => no-unused-disable.test.ts} | 349 +++++++++--------- ...sed-enable.ts => no-unused-enable.test.ts} | 8 +- tests/lib/rules/{no-use.ts => no-use.test.ts} | 16 +- tests/lib/rules/require-description.test.ts | 209 +++++++++++ tests/lib/rules/require-description.ts | 241 ------------ tests/types/configs.test-d.cts | 22 +- tests/types/configs.test-d.mts | 29 +- tsconfig.build.json | 10 + tsconfig.json | 26 +- tsup.config.mts | 55 +++ types/configs.d.ts | 11 - types/index.d.ts | 7 - 48 files changed, 1410 insertions(+), 1062 deletions(-) rename lib/internal/{get-all-directive-comments.js => get-all-directive-comments.ts} (54%) rename tests/lib/{illegal-eslint-disable-line.ts => illegal-eslint-disable-line.test.ts} (73%) rename tests/lib/rules/{disable-enable-pair.ts => disable-enable-pair.test.ts} (95%) rename tests/lib/rules/{no-aggregating-enable.ts => no-aggregating-enable.test.ts} (91%) rename tests/lib/rules/{no-duplicate-disable.ts => no-duplicate-disable.test.ts} (93%) rename tests/lib/rules/{no-restricted-disable.ts => no-restricted-disable.test.ts} (93%) rename tests/lib/rules/{no-unlimited-disable.ts => no-unlimited-disable.test.ts} (93%) rename tests/lib/rules/{no-unused-disable.ts => no-unused-disable.test.ts} (81%) rename tests/lib/rules/{no-unused-enable.ts => no-unused-enable.test.ts} (94%) rename tests/lib/rules/{no-use.ts => no-use.test.ts} (92%) create mode 100644 tests/lib/rules/require-description.test.ts delete mode 100644 tests/lib/rules/require-description.ts create mode 100644 tsconfig.build.json create mode 100644 tsup.config.mts delete mode 100644 types/configs.d.ts delete mode 100644 types/index.d.ts diff --git a/.eslintrc.yml b/.eslintrc.yml index c72b072..9f8befb 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -2,22 +2,62 @@ extends: - plugin:@eslint-community/mysticatea/es2015 - plugin:@eslint-community/mysticatea/+eslint-plugin +rules: + "@eslint-community/mysticatea/node/file-extension-in-import": + - error + - always + - ".ts": "always" + "@eslint-community/mysticatea/ts/naming-convention": + - off + "@eslint-community/mysticatea/ts/prefer-readonly-parameter-types": + - off + "@eslint-community/mysticatea/ts/no-unsafe-member-access": + - off + "@eslint-community/mysticatea/ts/no-var-requires": + - off + "@eslint-community/mysticatea/ts/no-require-imports": + - off + "@eslint-community/mysticatea/ts/no-unsafe-assignment": + - off + "@eslint-community/mysticatea/ts/no-unsafe-call": + - off + "@eslint-community/mysticatea/ts/no-unsafe-argument": + - off + "@eslint-community/mysticatea/ts/no-unsafe-return": + - off + "@eslint-community/mysticatea/ts/ban-ts-comment": + - off + "@eslint-community/mysticatea/ts/no-non-null-asserted-optional-chain": + - off + "@eslint-community/mysticatea/ts/prefer-optional-chain": + - off + "@eslint-community/mysticatea/ts/prefer-nullish-coalescing": + - off + "@eslint-community/mysticatea/ts/no-floating-promises": + - off + "@eslint-community/mysticatea/ts/no-unnecessary-type-assertion": + - off + "no-shadow": + - off + "@eslint-community/mysticatea/node/no-missing-import": off + "@eslint-community/mysticatea/node/no-unpublished-import": off + overrides: - files: "docs/.vuepress/components/*.vue" parserOptions: parser: "@babel/eslint-parser" - - files: "lib/rules/*.js" + - files: "lib/rules/*.ts" rules: "@eslint-community/mysticatea/eslint-plugin/require-meta-docs-url": - error - pattern: "https://eslint-community.github.io/eslint-plugin-eslint-comments/rules/{{name}}.html" - - files: ["lib/configs.js", "lib/rules.js", "lib/utils.js"] + - files: ["lib/configs.ts", "lib/rules.ts", "lib/utils.ts"] rules: "@eslint-community/mysticatea/node/global-require": off - - files: ["tests/**/*.js", "scripts/**/*.js"] + - files: ["tests/**/*.ts", "scripts/**/*.ts"] rules: "@eslint-community/mysticatea/node/global-require": off "@eslint-community/mysticatea/node/no-sync": off diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a109102..94ce7dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,17 +1,5 @@ name: CI -on: - push: - branches: - # default semantic-release branches - - +([0-9])?(.{+([0-9]),x}).x - - main - - next - - next-major - - beta - - alpha - pull_request: - schedule: - - cron: 0 0 * * 0 +on: [push, pull_request, workflow_dispatch] concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -34,7 +22,7 @@ jobs: - name: ⎔ Setup Node uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 22 - name: 📥 Install dependencies run: npm install @@ -49,31 +37,31 @@ jobs: fail-fast: false matrix: eslint: [8] - node: [12.22.0, 12, 14.17.0, 14, 16.0.0, 16, 18.0.0, 18, 20, 22, 24] + node: [22, 24] os: [ubuntu-latest] include: # On other platforms - os: windows-latest eslint: 8 - node: 18 + node: 22 - os: macos-latest eslint: 8 - node: 18 + node: 22 # On ESLint 9 - eslint: 9 - node: 18 + node: 22 os: ubuntu-latest # On old ESLint versions - - eslint: 7 - node: 18 - os: ubuntu-latest - - eslint: 6 - node: 18 - os: ubuntu-latest + # - eslint: 7 + # node: 22 + # os: ubuntu-latest + # - eslint: 6 + # node: 22 + # os: ubuntu-latest # On the minimum supported ESLint/Node.js version - - eslint: 6.0.0 - node: 12.22.0 - os: ubuntu-latest + # - eslint: 6.0.0 + # node: 12.22.0 + # os: ubuntu-latest runs-on: ${{ matrix.os }} steps: - name: ⬇️ Checkout repo @@ -83,14 +71,20 @@ jobs: uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} + check-latest: true - name: 📥 Install dependencies run: npm install --legacy-peer-deps + - name: 📥 Build + run: npm run build + - name: 📥 Install ESLint v${{ matrix.eslint }} run: npm install --save-dev eslint@${{ matrix.eslint }} - name: ▶️ Run test script + env: + NODE_OPTIONS: --experimental-transform-types run: npm run test - name: ⬆️ Upload coverage report @@ -106,16 +100,16 @@ jobs: - name: ⎔ Setup Node uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 22 - name: 📥 Install dependencies run: npm install --legacy-peer-deps - name: ▶️ Run check-exports script - run: npm run check-exports -- --format=table + run: npm run check-exports - - name: ▶️ Run test:types script - run: npm run test:types + - name: ▶️ Run typecheck script + run: npm run typecheck type-tests: name: 🧪 Type tests with ESLint ${{ matrix.eslint }} and TypeScript ${{ matrix.ts }} @@ -126,7 +120,7 @@ jobs: matrix: eslint: [8, 9] - ts: ["5.0", "5.1", "5.2", "5.3", "5.4", "5.5", "5.6"] + ts: ["5.7", "5.8"] steps: - name: ⬇️ Checkout repo @@ -135,7 +129,7 @@ jobs: - name: ⎔ Setup Node uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 22 - name: 📥 Install dependencies run: npm install @@ -154,8 +148,8 @@ jobs: - name: 📥 Install TypeScript version ${{ matrix.ts }} run: npm install --save-dev typescript@${{ matrix.ts }} -f - - name: ▶️ Run test:types script - run: npm run test:types + - name: ▶️ Run typecheck script + run: npm run typecheck - name: 📝 List version of ESLint run: npm why eslint @types/eslint @@ -174,11 +168,7 @@ jobs: - name: ⎔ Setup node uses: actions/setup-node@v4 with: - node-version: lts/* - - # npm 11.5.1 or later is required so update to latest to be sure - - name: Update npm - run: npm install -g npm@latest + node-version: 22 - name: 📥 Install dependencies run: npm install --legacy-peer-deps diff --git a/.gitignore b/.gitignore index 5303a54..5556b2c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ /node_modules /npm-debug.log /test.js +dist/ diff --git a/configs.ts b/configs.ts index dd6fa39..8a4046f 100644 --- a/configs.ts +++ b/configs.ts @@ -1,24 +1,63 @@ -"use strict" +import type { ESLint, Linter, Rule } from "eslint" +import { rulesRecommended } from "./lib/configs/recommended.ts" +import { rules } from "./lib/rules.ts" +import packageJson from "./package.json" with { type: "json" }; -const { rules: rulesRecommended } = require("./lib/configs/recommended") -const rules = require("./lib/rules") -const { name, version } = require("./package.json") +const { name, version } = packageJson -const plugin = { +const plugin: { + meta: { + name: string + version: string + } + rules: { + "disable-enable-pair": Rule.RuleModule + "no-aggregating-enable": Rule.RuleModule + "no-duplicate-disable": Rule.RuleModule + "no-restricted-disable": Rule.RuleModule + "no-unlimited": Rule.RuleModule + "no-unused-disable": Rule.RuleModule + "no-unused-enable": Rule.RuleModule + "no-use": Rule.RuleModule + "require-description": Rule.RuleModule + } +} = { meta: { name, version }, rules, -} +} as const satisfies ESLint.Plugin -const recommended = { - name: "@eslint-community/eslint-comments/recommended", - plugins: { - "@eslint-community/eslint-comments": plugin, - }, - rules: rulesRecommended, -} - -module.exports = { - recommended, -} - -module.exports.default = module.exports +export const recommended: { + name: "@eslint-community/eslint-comments/recommended" + plugins: { + "@eslint-community/eslint-comments": { + meta: { + name: string + version: string + } + rules: { + "disable-enable-pair": Rule.RuleModule + "no-aggregating-enable": Rule.RuleModule + "no-duplicate-disable": Rule.RuleModule + "no-restricted-disable": Rule.RuleModule + "no-unlimited": Rule.RuleModule + "no-unused-disable": Rule.RuleModule + "no-unused-enable": Rule.RuleModule + "no-use": Rule.RuleModule + "require-description": Rule.RuleModule + } + } + } + rules: { + "@eslint-community/eslint-comments/disable-enable-pair": "error" + "@eslint-community/eslint-comments/no-aggregating-enable": "error" + "@eslint-community/eslint-comments/no-duplicate-disable": "error" + "@eslint-community/eslint-comments/no-unlimited-disable": "error" + "@eslint-community/eslint-comments/no-unused-enable": "error" + } +} = { + name: "@eslint-community/eslint-comments/recommended", + plugins: { + "@eslint-community/eslint-comments": plugin, + }, + rules: rulesRecommended, +} as const satisfies Linter.FlatConfig diff --git a/index.ts b/index.ts index 78db10c..9fe428c 100644 --- a/index.ts +++ b/index.ts @@ -1,11 +1,3 @@ -"use strict" - -const rules = require("./lib/rules") -const utils = require("./lib/utils") -const configs = require("./lib/configs") - -module.exports = { - configs, - rules, - utils, -} +export * as configs from "./lib/configs.ts" +export { rules } from "./lib/rules.ts" +export * as utils from "./lib/utils.ts" diff --git a/lib/configs.ts b/lib/configs.ts index 4a330b4..3e512d1 100644 --- a/lib/configs.ts +++ b/lib/configs.ts @@ -1,6 +1 @@ -/** DON'T EDIT THIS FILE; was created by scripts. */ -"use strict" - -module.exports = { - recommended: require("./configs/recommended"), -} +export * as recommended from "./configs/recommended.ts" diff --git a/lib/configs/recommended.ts b/lib/configs/recommended.ts index 49bad50..aee96fa 100644 --- a/lib/configs/recommended.ts +++ b/lib/configs/recommended.ts @@ -1,13 +1,20 @@ -/** DON'T EDIT THIS FILE; was created by scripts. */ -"use strict" +import type { BaseConfig, RulesConfig } from "@eslint/core" +import type { Linter } from "eslint" -module.exports = { - plugins: ["@eslint-community/eslint-comments"], - rules: { - "@eslint-community/eslint-comments/disable-enable-pair": "error", - "@eslint-community/eslint-comments/no-aggregating-enable": "error", - "@eslint-community/eslint-comments/no-duplicate-disable": "error", - "@eslint-community/eslint-comments/no-unlimited-disable": "error", - "@eslint-community/eslint-comments/no-unused-enable": "error", - }, -} +export const plugins: string[] = [ + "@eslint-community/eslint-comments", +] as const satisfies BaseConfig["plugins"] + +export const rulesRecommended: { + "@eslint-community/eslint-comments/disable-enable-pair": "error" + "@eslint-community/eslint-comments/no-aggregating-enable": "error" + "@eslint-community/eslint-comments/no-duplicate-disable": "error" + "@eslint-community/eslint-comments/no-unlimited-disable": "error" + "@eslint-community/eslint-comments/no-unused-enable": "error" +} = { + "@eslint-community/eslint-comments/disable-enable-pair": "error", + "@eslint-community/eslint-comments/no-aggregating-enable": "error", + "@eslint-community/eslint-comments/no-duplicate-disable": "error", + "@eslint-community/eslint-comments/no-unlimited-disable": "error", + "@eslint-community/eslint-comments/no-unused-enable": "error", +} as const satisfies Linter.RulesRecord satisfies RulesConfig diff --git a/lib/internal/disabled-area.ts b/lib/internal/disabled-area.ts index f7dca6a..6a44b3d 100644 --- a/lib/internal/disabled-area.ts +++ b/lib/internal/disabled-area.ts @@ -2,17 +2,38 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import type { AST, Linter, Rule, SourceCode } from "eslint" +import * as utils from "./utils.ts" -const utils = require("./utils") const DELIMITER = /[\s,]+/gu -const pool = new WeakMap() +const pool = new WeakMap< + AST.Program, + DisabledAreaForLanguagePlugin | DisabledAreaForLegacy +>() class DisabledArea { + public areas: (AST.SourceLocation & + NonNullable> & { + comment: AST.Program["comments"][number] + kind: string + })[] + public duplicateDisableDirectives: { + comment: AST.Program["comments"][number] + ruleId: string | null + }[] + public unusedEnableDirectives: { + comment: AST.Program["comments"][number] + ruleId: string | null + }[] + public numberOfRelatedDisableDirectives: Map< + AST.Program["comments"][number], + number + > + /** * Constructor. */ - constructor() { + public constructor() { this.areas = [] this.duplicateDisableDirectives = [] this.unusedEnableDirectives = [] @@ -29,7 +50,12 @@ class DisabledArea { * @returns {void} * @protected */ - _disable(comment, location, ruleIds, kind) { + protected _disable( + comment: AST.Program["comments"][number], + location: AST.SourceLocation["start"], + ruleIds: string[] | null, + kind: Lowercase, + ): void { if (ruleIds) { for (const ruleId of ruleIds) { if (this._getArea(ruleId, location) != null) { @@ -41,7 +67,7 @@ class DisabledArea { ruleId, kind, start: location, - end: null, + end: null as any, }) } } else { @@ -54,7 +80,7 @@ class DisabledArea { ruleId: null, kind, start: location, - end: null, + end: null as any, }) } } @@ -69,7 +95,12 @@ class DisabledArea { * @returns {void} * @protected */ - _enable(comment, location, ruleIds, kind) { + protected _enable( + comment: AST.Program["comments"][number], + location: AST.SourceLocation["start"], + ruleIds: string[] | null, + kind: Lowercase, + ): void { const relatedDisableDirectives = new Set() if (ruleIds) { @@ -114,7 +145,7 @@ class DisabledArea { this.numberOfRelatedDisableDirectives.set( comment, - relatedDisableDirectives.size + relatedDisableDirectives.size, ) } @@ -126,7 +157,16 @@ class DisabledArea { * @returns {object|null} The area of the given ruleId and location. * @private */ - _getArea(ruleId, location) { + private _getArea( + ruleId: string | null, + location: AST.SourceLocation["start"], + ): + | (AST.SourceLocation & + Pick & { + comment: AST.Program["comments"][number] + kind: string + }) + | null { for (let i = this.areas.length - 1; i >= 0; --i) { const area = this.areas[i] @@ -150,9 +190,9 @@ class DisabledAreaForLanguagePlugin extends DisabledArea { * @param {import('@eslint/core').TextSourceCode} sourceCode - The source code to scan. * @returns {void} */ - _scan(sourceCode) { - const disableDirectives = sourceCode.getDisableDirectives() - for (const directive of disableDirectives.directives) { + public _scan(sourceCode: SourceCode): void { + const disableDirectives = (sourceCode as any).getDisableDirectives?.()! + for (const directive of disableDirectives!.directives) { if ( ![ "disable", @@ -167,25 +207,32 @@ class DisabledAreaForLanguagePlugin extends DisabledArea { ? directive.value.split(DELIMITER) : null - const loc = sourceCode.getLoc(directive.node) + const loc: AST.SourceLocation = (sourceCode as any).getLoc( + directive.node, + ) if (directive.type === "disable") { - this._disable(directive.node, loc.start, ruleIds, "block") + this._disable( + directive.node as any, + loc.start, + ruleIds, + "block", + ) } else if (directive.type === "enable") { - this._enable(directive.node, loc.start, ruleIds, "block") + this._enable(directive.node as any, loc.start, ruleIds, "block") } else if (directive.type === "disable-line") { - const line = loc.start.line + const { line } = loc.start const start = { line, column: 0 } const end = { line: line + 1, column: -1 } - this._disable(directive.node, start, ruleIds, "line") - this._enable(directive.node, end, ruleIds, "line") + this._disable(directive.node as any, start, ruleIds, "line") + this._enable(directive.node as any, end, ruleIds, "line") } else if (directive.type === "disable-next-line") { - const line = loc.start.line + const { line } = loc.start const start = { line: line + 1, column: 0 } const end = { line: line + 2, column: -1 } - this._disable(directive.node, start, ruleIds, "line") - this._enable(directive.node, end, ruleIds, "line") + this._disable(directive.node as any, start, ruleIds, "line") + this._enable(directive.node as any, end, ruleIds, "line") } } } @@ -198,14 +245,16 @@ class DisabledAreaForLegacy extends DisabledArea { * @param {eslint.SourceCode} sourceCode - The source code to scan. * @returns {void} */ - _scan(sourceCode) { - for (const comment of sourceCode.getAllComments()) { + public _scan(sourceCode: SourceCode): void { + for (const comment of ( + sourceCode as any + ).getAllComments() as AST.Program["comments"]) { const directiveComment = utils.parseDirectiveComment(comment) if (directiveComment == null) { continue } - const kind = directiveComment.kind + const { kind } = directiveComment if ( ![ "eslint-disable", @@ -221,18 +270,18 @@ class DisabledAreaForLegacy extends DisabledArea { : null if (kind === "eslint-disable") { - this._disable(comment, comment.loc.start, ruleIds, "block") + this._disable(comment, comment.loc!.start, ruleIds, "block") } else if (kind === "eslint-enable") { - this._enable(comment, comment.loc.start, ruleIds, "block") + this._enable(comment, comment.loc!.start, ruleIds, "block") } else if (kind === "eslint-disable-line") { - const line = comment.loc.start.line + const { line } = comment.loc!.start const start = { line, column: 0 } const end = { line: line + 1, column: -1 } this._disable(comment, start, ruleIds, "line") this._enable(comment, end, ruleIds, "line") } else if (kind === "eslint-disable-next-line") { - const line = comment.loc.start.line + const { line } = comment.loc!.start const start = { line: line + 1, column: 0 } const end = { line: line + 2, column: -1 } @@ -243,26 +292,24 @@ class DisabledAreaForLegacy extends DisabledArea { } } -module.exports = { - /** - * Get singleton instance for the given rule context. - * - * @param {import("@eslint/core").RuleContext} context - The rule context code to get. - * @returns {DisabledArea} The singleton object for the rule context. - */ - getDisabledArea(context) { - const sourceCode = context.sourceCode || context.getSourceCode() - let retv = pool.get(sourceCode.ast) +/** + * Get singleton instance for the given rule context. + * + * @param {import("@eslint/core").RuleContext} context - The rule context code to get. + * @returns {DisabledArea} The singleton object for the rule context. + */ +export function getDisabledArea(context: Rule.RuleContext): DisabledArea { + const sourceCode = context.sourceCode || context.getSourceCode() + let retv = pool.get(sourceCode.ast) - if (retv == null) { - retv = - typeof sourceCode.getDisableDirectives === "function" - ? new DisabledAreaForLanguagePlugin() - : new DisabledAreaForLegacy() - retv._scan(sourceCode) - pool.set(sourceCode.ast, retv) - } + if (retv == null) { + retv = + typeof (sourceCode as any).getDisableDirectives === "function" + ? new DisabledAreaForLanguagePlugin() + : new DisabledAreaForLegacy() + retv._scan(sourceCode) + pool.set(sourceCode.ast, retv) + } - return retv - }, + return retv } diff --git a/lib/internal/get-all-directive-comments.js b/lib/internal/get-all-directive-comments.ts similarity index 54% rename from lib/internal/get-all-directive-comments.js rename to lib/internal/get-all-directive-comments.ts index f471c0a..a2d0556 100644 --- a/lib/internal/get-all-directive-comments.js +++ b/lib/internal/get-all-directive-comments.ts @@ -1,16 +1,40 @@ -"use strict" +import type { + RuleContext, + RuleContextTypeOptions, + SourceCodeBaseTypeOptions, + SourceLocation, + SourceRange, + TextSourceCode, +} from "@eslint/core" +import type { AST, SourceCode } from "eslint" +import * as utils from "./utils.ts" -const utils = require("./utils") - -/** - * @typedef {object} DirectiveComment - * @property {string} kind The kind of directive comment. - * @property {string} [value] The directive value if it is `eslint-` comment. - * @property {string} description The description of the directive comment. - * @property {object} node The node of the directive comment. - * @property {import("@eslint/core").SourceRange} range The range of the directive comment. - * @property {import("@eslint/core").SourceLocation} loc The location of the directive comment. - */ +interface DirectiveComment { + /** + * The kind of directive comment. + */ + kind: string + /** + * The directive value if it is `eslint-` comment. + */ + value?: string + /** + * The description of the directive comment. + */ + description: string + /** + * The node of the directive comment. + */ + node: object + /** + * The range of the directive comment. + */ + range: SourceRange + /** + * The location of the directive comment. + */ + loc: SourceLocation +} const pool = new WeakMap() @@ -18,7 +42,9 @@ const pool = new WeakMap() * @param {import('eslint').SourceCode} sourceCode - The source code to scan. * @returns {DirectiveComment[]} The directive comments. */ -function getAllDirectiveCommentsFromAllComments(sourceCode) { +function getAllDirectiveCommentsFromAllComments( + sourceCode: SourceCode, +): DirectiveComment[] { return sourceCode .getAllComments() .map((comment) => ({ @@ -28,14 +54,14 @@ function getAllDirectiveCommentsFromAllComments(sourceCode) { .filter(({ directiveComment }) => Boolean(directiveComment)) .map( ({ comment, directiveComment }) => - /** @type {DirectiveComment} */ ({ - kind: directiveComment.kind, - value: directiveComment.value, - description: directiveComment.description, + ({ + kind: directiveComment!.kind, + value: directiveComment!.value, + description: directiveComment!.description, node: comment, range: comment.range, loc: comment.loc, - }) + } as DirectiveComment), ) } @@ -43,10 +69,12 @@ function getAllDirectiveCommentsFromAllComments(sourceCode) { * @param {import('@eslint/core').TextSourceCode} sourceCode - The source code to scan. * @returns {DirectiveComment[]} The directive comments. */ -function getAllDirectiveCommentsFromInlineConfigNodes(sourceCode) { - const result = sourceCode.getDisableDirectives().directives.map( +function getAllDirectiveCommentsFromInlineConfigNodes( + sourceCode: TextSourceCode, +) { + const result = sourceCode.getDisableDirectives!()!.directives.map( (directive) => - /** @type {DirectiveComment} */ ({ + ({ kind: `eslint-${directive.type}`, value: directive.value, description: directive.justification, @@ -55,12 +83,11 @@ function getAllDirectiveCommentsFromInlineConfigNodes(sourceCode) { get loc() { return sourceCode.getLoc(directive.node) }, - }) + } as DirectiveComment), ) return result.concat( - sourceCode - .getInlineConfigNodes() + sourceCode.getInlineConfigNodes!()! .map((node) => ({ node, range: sourceCode.getRange(node), @@ -72,8 +99,8 @@ function getAllDirectiveCommentsFromInlineConfigNodes(sourceCode) { !result.some( (comment) => comment.range[0] <= range[1] && - range[0] <= comment.range[1] - ) + range[0] <= comment.range[1], + ), ) .map(({ node, range }) => { const nodeText = sourceCode.text.slice(range[0], range[1]) @@ -94,25 +121,25 @@ function getAllDirectiveCommentsFromInlineConfigNodes(sourceCode) { "eslint-disable-line", "eslint-disable-next-line", "eslint-enable", - ].includes(directiveComment.kind) + ].includes(directiveComment.kind), ) .map( ({ directiveComment, node, range }) => - /** @type {DirectiveComment} */ ({ - kind: directiveComment.kind, - value: directiveComment.value, - description: directiveComment.description, + ({ + kind: directiveComment!.kind, + value: directiveComment!.value, + description: directiveComment!.description, node, range, get loc() { return sourceCode.getLoc(node) }, - }) - ) + } as DirectiveComment), + ), ) } -function extractCommentContent(text) { +function extractCommentContent(text: string): string { // Extract comment content from the comment text. // The comment format was based on the language comment definition in vscode-eslint. // See https://github.com/microsoft/vscode-eslint/blob/c0e753713ea9935667e849d91e549adbff213e7e/server/src/languageDefaults.ts#L14 @@ -129,26 +156,32 @@ function extractCommentContent(text) { : text } -module.exports = { - /** - * Get all directive comments for the given rule context. - * - * @param {import("@eslint/core").RuleContext} context - The rule context to get. - * @returns {DirectiveComment[]} The all directive comments object for the rule context. - */ - getAllDirectiveComments(context) { - const sourceCode = context.sourceCode || context.getSourceCode() - let result = pool.get(sourceCode.ast) - - if (result == null) { - result = - typeof sourceCode.getInlineConfigNodes === "function" && - typeof sourceCode.getDisableDirectives === "function" - ? getAllDirectiveCommentsFromInlineConfigNodes(sourceCode) - : getAllDirectiveCommentsFromAllComments(sourceCode) - pool.set(sourceCode.ast, result) +/** + * Get all directive comments for the given rule context. + * + * @param {import("@eslint/core").RuleContext} context - The rule context to get. + * @returns {DirectiveComment[]} The all directive comments object for the rule context. + */ +export function getAllDirectiveComments( + context: RuleContext< + RuleContextTypeOptions & { + Code: TextSourceCode< + SourceCodeBaseTypeOptions & { RootNode: AST.Program } + > } + >, +): DirectiveComment[] { + const sourceCode = context.sourceCode || (context as any).getSourceCode() + let result = pool.get(sourceCode.ast) + + if (result == null) { + result = + typeof sourceCode.getInlineConfigNodes === "function" && + typeof sourceCode.getDisableDirectives === "function" + ? getAllDirectiveCommentsFromInlineConfigNodes(sourceCode) + : getAllDirectiveCommentsFromAllComments(sourceCode as any) + pool.set(sourceCode.ast, result) + } - return result - }, + return result } diff --git a/lib/internal/get-linters.ts b/lib/internal/get-linters.ts index c85ecf3..8d11af3 100644 --- a/lib/internal/get-linters.ts +++ b/lib/internal/get-linters.ts @@ -2,29 +2,30 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" - -const path = require("path") +import type { Linter } from "eslint" +import { createRequire } from "node:module" +import * as path from "node:path" const needle = `${path.sep}node_modules${path.sep}eslint${path.sep}` -module.exports = () => { +const require = createRequire(__filename) + +const getLinters = (): (typeof Linter)[] => { const eslintPaths = new Set( Object.keys(require.cache) .filter((id) => id.includes(needle)) - .map((id) => id.slice(0, id.indexOf(needle) + needle.length)) + .map((id) => id.slice(0, id.indexOf(needle) + needle.length)), ) const linters = [] for (const eslintPath of eslintPaths) { try { - // eslint-disable-next-line @eslint-community/mysticatea/node/global-require - const linter = require(eslintPath).Linter + const linter: typeof Linter = require(eslintPath).Linter if (linter) { linters.push(linter) } } catch (error) { - if (error.code !== "MODULE_NOT_FOUND") { + if ((error as any).code !== "MODULE_NOT_FOUND") { throw error } } @@ -32,3 +33,5 @@ module.exports = () => { return linters } + +export default getLinters diff --git a/lib/internal/utils.ts b/lib/internal/utils.ts index bf88709..2e10749 100644 --- a/lib/internal/utils.ts +++ b/lib/internal/utils.ts @@ -2,137 +2,143 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import escapeStringRegexp from "escape-string-regexp" +import type { AST, Linter, Rule } from "eslint" -const escapeStringRegexp = require("escape-string-regexp") const LINE_PATTERN = /[^\r\n\u2028\u2029]*(?:\r\n|[\r\n\u2028\u2029]|$)/gu const DIRECTIVE_PATTERN = /^(eslint(?:-env|-enable|-disable(?:(?:-next)?-line)?)?|exported|globals?)(?:\s|$)/u const LINE_COMMENT_PATTERN = /^eslint-disable-(next-)?line$/u -module.exports = { - /** - * Make the location ignoring `eslint-disable` comments. - * - * @param {object} location - The location to convert. - * @returns {object} Converted location. - */ - toForceLocation(location) { - return { - start: { - line: location.start.line, - column: -1, - }, - end: location.end, - } - }, - - /** - * Calculate the location of the given rule in the given comment token. - * - * @param {Partial} context - The rule context code. - * @param {Token} comment - The comment token to calculate. - * @param {string|null} ruleId - The rule name to calculate. - * @returns {object} The location of the given information. - */ - toRuleIdLocation(context, comment, ruleId) { - const commentLoc = getLoc(context, comment) - if (ruleId == null) { - return module.exports.toForceLocation(commentLoc) - } +/** + * Make the location ignoring `eslint-disable` comments. + * + * @param {object} location - The location to convert. + * @returns {object} Converted location. + */ +export function toForceLocation( + location: AST.SourceLocation, +): AST.SourceLocation { + return { + start: { + line: location.start.line, + column: -1, + }, + end: location.end, + } +} + +/** + * Calculate the location of the given rule in the given comment token. + * + * @param {Partial} context - The rule context code. + * @param {Token} comment - The comment token to calculate. + * @param {string|null} ruleId - The rule name to calculate. + * @returns {object} The location of the given information. + */ +export function toRuleIdLocation( + context: Rule.RuleContext, + comment: AST.Program["comments"][number], + ruleId: string | null | undefined, +): AST.SourceLocation | null | undefined { + const commentLoc = getLoc(context, comment) + if (ruleId == null) { + return toForceLocation(commentLoc) + } - const lines = comment.value.match(LINE_PATTERN) - //eslint-disable-next-line require-unicode-regexp - const ruleIdPattern = new RegExp( - `([\\s,]|^)${escapeStringRegexp(ruleId)}(?:[\\s,]|$)` - ) - - { - const m = ruleIdPattern.exec(lines[0]) - if (m != null) { - const start = commentLoc.start - return { - start: { - line: start.line, - column: 2 + start.column + m.index + m[1].length, - }, - end: { - line: start.line, - column: - 2 + - start.column + - m.index + - m[1].length + - ruleId.length, - }, - } + const lines = comment.value.match(LINE_PATTERN)! + //eslint-disable-next-line require-unicode-regexp + const ruleIdPattern = new RegExp( + `([\\s,]|^)${escapeStringRegexp(ruleId)}(?:[\\s,]|$)`, + ) + + { + const m = ruleIdPattern.exec(lines[0]) + if (m != null) { + const { start } = commentLoc + return { + start: { + line: start.line, + column: 2 + start.column + m.index + m[1].length, + }, + end: { + line: start.line, + column: + 2 + + start.column + + m.index + + m[1].length + + ruleId.length, + }, } } + } - for (let i = 1; i < lines.length; ++i) { - const m = ruleIdPattern.exec(lines[i]) - if (m != null) { - const start = commentLoc.start - return { - start: { - line: start.line + i, - column: m.index + m[1].length, - }, - end: { - line: start.line + i, - column: m.index + m[1].length + ruleId.length, - }, - } + for (let i = 1; i < lines.length; ++i) { + const m = ruleIdPattern.exec(lines[i]) + if (m != null) { + const { start } = commentLoc + return { + start: { + line: start.line + i, + column: m.index + m[1].length, + }, + end: { + line: start.line + i, + column: m.index + m[1].length + ruleId.length, + }, } } + } - /*istanbul ignore next : foolproof */ - return commentLoc - }, - - /** - * Checks `a` is less than `b` or `a` equals `b`. - * - * @param {{line: number, column: number}} a - A location to compare. - * @param {{line: number, column: number}} b - Another location to compare. - * @returns {boolean} `true` if `a` is less than `b` or `a` equals `b`. - */ - lte(a, b) { - return a.line < b.line || (a.line === b.line && a.column <= b.column) - }, - - /** - * Parse the given comment token as a directive comment. - * - * @param {Token} comment - The comment token to parse. - * @returns {{kind: string, value: string, description: string | null}|null} The parsed data of the given comment. If `null`, it is not a directive comment. - */ - parseDirectiveComment(comment) { - const parsed = parseDirectiveText(comment.value) - if (!parsed) { - return null - } + /*istanbul ignore next : foolproof */ + return commentLoc +} - const lineCommentSupported = LINE_COMMENT_PATTERN.test(parsed.kind) +/** + * Checks `a` is less than `b` or `a` equals `b`. + * + * @param {{line: number, column: number}} a - A location to compare. + * @param {{line: number, column: number}} b - Another location to compare. + * @returns {boolean} `true` if `a` is less than `b` or `a` equals `b`. + */ +export function lte( + a: Pick, + b: Pick, +): boolean { + return a.line < b.line || (a.line === b.line && a.column <= b.column) +} - if (comment.type === "Line" && !lineCommentSupported) { - return null - } +/** + * Parse the given comment token as a directive comment. + * + * @param {Token} comment - The comment token to parse. + * @returns {{kind: string, value: string, description: string | null}|null} The parsed data of the given comment. If `null`, it is not a directive comment. + */ +export function parseDirectiveComment( + comment: AST.Program["comments"][number], +): { kind: string; value: string; description: string | null } | null { + const parsed = parseDirectiveText(comment.value) + if (!parsed) { + return null + } - if ( - parsed.kind === "eslint-disable-line" && - comment.loc.start.line !== comment.loc.end.line - ) { - // disable-line comment should not span multiple lines. - return null - } + const lineCommentSupported = LINE_COMMENT_PATTERN.test(parsed.kind) + + if (comment.type === "Line" && !lineCommentSupported) { + return null + } - return parsed - }, - parseDirectiveText, + if ( + parsed.kind === "eslint-disable-line" && + comment.loc!.start.line !== comment.loc!.end.line + ) { + // disable-line comment should not span multiple lines. + return null + } - getLoc, + return parsed } /** @@ -141,7 +147,9 @@ module.exports = { * @param {string} textToParse - The text to parse. * @returns {{kind: string, value: string, description: string | null}|null} The parsed data of the given comment. If `null`, it is not a directive comment. */ -function parseDirectiveText(textToParse) { +export function parseDirectiveText( + textToParse: string, +): { kind: string; value: string; description: string | null } | null { const { text, description } = divideDirectiveComment(textToParse) const match = DIRECTIVE_PATTERN.exec(text) @@ -164,7 +172,10 @@ function parseDirectiveText(textToParse) { * @param {string} value The comment text to strip. * @returns {{text: string, description: string | null}} The stripped text. */ -function divideDirectiveComment(value) { +function divideDirectiveComment(value: string): { + text: string + description: string | null +} { const divided = value.split(/\s-{2,}\s/u) const text = divided[0].trim() return { @@ -180,10 +191,13 @@ function divideDirectiveComment(value) { * @param {unknown} nodeOrToken - The node or token to get. * @returns {object} The source code location. */ -function getLoc(context, nodeOrToken) { +export function getLoc( + context: Rule.RuleContext, + nodeOrToken: { loc?: AST.SourceLocation | null | undefined }, +): AST.SourceLocation { const sourceCode = context.sourceCode || (context.getSourceCode && context.getSourceCode()) - return sourceCode && typeof sourceCode.getLoc === "function" - ? sourceCode.getLoc(nodeOrToken) - : nodeOrToken.loc + return sourceCode && typeof (sourceCode as any).getLoc === "function" + ? (sourceCode as any).getLoc(nodeOrToken) + : nodeOrToken.loc! } diff --git a/lib/rules.ts b/lib/rules.ts index c91b9f5..1791b2e 100644 --- a/lib/rules.ts +++ b/lib/rules.ts @@ -1,14 +1,42 @@ -/** DON'T EDIT THIS FILE; was created by scripts. */ -"use strict" +import type { Rule } from "eslint" +import disableEnablePair from "./rules/disable-enable-pair.ts" +import noAggregatingEnable from "./rules/no-aggregating-enable.ts" +import noDuplicateDisable from "./rules/no-duplicate-disable.ts" +import noRestrictedDisable from "./rules/no-restricted-disable.ts" +import noUnlimitedDisable from "./rules/no-unlimited-disable.ts" +import noUnusedDisable from "./rules/no-unused-disable.ts" +import noUnusedEnable from "./rules/no-unused-enable.ts" +import noUse from "./rules/no-use.ts" +import requireDescription from "./rules/require-description.ts" -module.exports = { - "disable-enable-pair": require("./rules/disable-enable-pair"), - "no-aggregating-enable": require("./rules/no-aggregating-enable"), - "no-duplicate-disable": require("./rules/no-duplicate-disable"), - "no-restricted-disable": require("./rules/no-restricted-disable"), - "no-unlimited-disable": require("./rules/no-unlimited-disable"), - "no-unused-disable": require("./rules/no-unused-disable"), - "no-unused-enable": require("./rules/no-unused-enable"), - "no-use": require("./rules/no-use"), - "require-description": require("./rules/require-description"), +export const rules = { + "disable-enable-pair": disableEnablePair, + "no-aggregating-enable": noAggregatingEnable, + "no-duplicate-disable": noDuplicateDisable, + "no-restricted-disable": noRestrictedDisable, + "no-unlimited": noUnlimitedDisable, + "no-unused-disable": noUnusedDisable, + "no-unused-enable": noUnusedEnable, + "no-use": noUse, + "require-description": requireDescription, +} as const satisfies Record satisfies { + "disable-enable-pair": Rule.RuleModule + "no-aggregating-enable": Rule.RuleModule + "no-duplicate-disable": Rule.RuleModule + "no-restricted-disable": Rule.RuleModule + "no-unlimited": Rule.RuleModule + "no-unused-disable": Rule.RuleModule + "no-unused-enable": Rule.RuleModule + "no-use": Rule.RuleModule + "require-description": Rule.RuleModule +} as { + "disable-enable-pair": Rule.RuleModule + "no-aggregating-enable": Rule.RuleModule + "no-duplicate-disable": Rule.RuleModule + "no-restricted-disable": Rule.RuleModule + "no-unlimited": Rule.RuleModule + "no-unused-disable": Rule.RuleModule + "no-unused-enable": Rule.RuleModule + "no-use": Rule.RuleModule + "require-description": Rule.RuleModule } diff --git a/lib/rules/disable-enable-pair.ts b/lib/rules/disable-enable-pair.ts index ce0330e..4b5a480 100644 --- a/lib/rules/disable-enable-pair.ts +++ b/lib/rules/disable-enable-pair.ts @@ -2,12 +2,11 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import type { Rule } from "eslint" +import { getDisabledArea } from "../internal/disabled-area.ts" +import * as utils from "../internal/utils.ts" -const { getDisabledArea } = require("../internal/disabled-area") -const utils = require("../internal/utils") - -module.exports = { +const disableEnablePair: Rule.RuleModule = { meta: { docs: { description: @@ -16,7 +15,7 @@ module.exports = { recommended: true, url: "https://eslint-community.github.io/eslint-plugin-eslint-comments/rules/disable-enable-pair.html", }, - fixable: null, + fixable: null as any, messages: { missingPair: "Requires 'eslint-enable' directive.", missingRulePair: @@ -37,11 +36,10 @@ module.exports = { }, create(context) { - const allowWholeFile = + const allowWholeFile: boolean = context.options[0] && context.options[0].allowWholeFile - const disabledArea = getDisabledArea(context) + const disabledArea = getDisabledArea(context as any) - /** @type {import('@eslint/core').TextSourceCode} */ const sourceCode = context.sourceCode || context.getSourceCode() const firstToken = @@ -57,17 +55,27 @@ module.exports = { } if ( allowWholeFile && - utils.lte(area.start, utils.getLoc(context, firstToken).start) + utils.lte( + area.start, + utils.getLoc(context as any, firstToken).start, + ) ) { continue } context.report({ - loc: utils.toRuleIdLocation(context, area.comment, area.ruleId), + loc: utils.toRuleIdLocation( + context as any, + area.comment, + area.ruleId, + )!, messageId: area.ruleId ? "missingRulePair" : "missingPair", - data: area, + data: area as Record, }) } + return {} }, -} +} as const satisfies Rule.RuleModule + +export default disableEnablePair diff --git a/lib/rules/no-aggregating-enable.ts b/lib/rules/no-aggregating-enable.ts index d7c1f94..40a35b5 100644 --- a/lib/rules/no-aggregating-enable.ts +++ b/lib/rules/no-aggregating-enable.ts @@ -2,12 +2,17 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import type { + RuleDefinition, + RuleDefinitionTypeOptions, + SourceCode, + SourceCodeBaseTypeOptions, +} from "@eslint/core" +import type { AST, Rule } from "eslint" +import { getDisabledArea } from "../internal/disabled-area.ts" +import * as utils from "../internal/utils.ts" -const { getDisabledArea } = require("../internal/disabled-area") -const utils = require("../internal/utils") - -module.exports = { +const rule: Rule.RuleModule = { meta: { docs: { description: @@ -16,7 +21,7 @@ module.exports = { recommended: true, url: "https://eslint-community.github.io/eslint-plugin-eslint-comments/rules/no-aggregating-enable.html", }, - fixable: null, + fixable: null as any, messages: { aggregatingEnable: "This `eslint-enable` comment affects {{count}} `eslint-disable` comments. An `eslint-enable` comment should be for an `eslint-disable` comment.", @@ -34,12 +39,15 @@ module.exports = { if (count >= 2) { context.report({ - loc: utils.toForceLocation(utils.getLoc(context, comment)), + loc: utils.toForceLocation(comment.loc!), messageId: "aggregatingEnable", - data: { count }, + data: { count } as Record, }) } } + return {} }, } + +export default rule diff --git a/lib/rules/no-duplicate-disable.ts b/lib/rules/no-duplicate-disable.ts index 78a7aaa..d6863c3 100644 --- a/lib/rules/no-duplicate-disable.ts +++ b/lib/rules/no-duplicate-disable.ts @@ -2,12 +2,11 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import type { Rule } from "eslint" +import { getDisabledArea } from "../internal/disabled-area.ts" +import * as utils from "../internal/utils.ts" -const { getDisabledArea } = require("../internal/disabled-area") -const utils = require("../internal/utils") - -module.exports = { +const rule: Rule.RuleModule = { meta: { docs: { description: "disallow duplicate `eslint-disable` comments", @@ -15,7 +14,7 @@ module.exports = { recommended: true, url: "https://eslint-community.github.io/eslint-plugin-eslint-comments/rules/no-duplicate-disable.html", }, - fixable: null, + fixable: null as any, messages: { duplicate: "ESLint rules have been disabled already.", duplicateRule: "'{{ruleId}}' rule has been disabled already.", @@ -29,11 +28,18 @@ module.exports = { for (const item of disabledArea.duplicateDisableDirectives) { context.report({ - loc: utils.toRuleIdLocation(context, item.comment, item.ruleId), + loc: utils.toRuleIdLocation( + context as any, + item.comment, + item.ruleId, + )!, messageId: item.ruleId ? "duplicateRule" : "duplicate", - data: item, + data: item as Record, }) } + return {} }, } + +export default rule diff --git a/lib/rules/no-restricted-disable.ts b/lib/rules/no-restricted-disable.ts index b96ab9d..e009e65 100644 --- a/lib/rules/no-restricted-disable.ts +++ b/lib/rules/no-restricted-disable.ts @@ -2,13 +2,12 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import type { Rule } from "eslint" +import ignore from "ignore" +import { getDisabledArea } from "../internal/disabled-area.ts" +import * as utils from "../internal/utils.ts" -const ignore = require("ignore") -const { getDisabledArea } = require("../internal/disabled-area") -const utils = require("../internal/utils") - -module.exports = { +const rule: Rule.RuleModule = { meta: { docs: { description: @@ -17,7 +16,7 @@ module.exports = { recommended: false, url: "https://eslint-community.github.io/eslint-plugin-eslint-comments/rules/no-restricted-disable.html", }, - fixable: null, + fixable: null as any, messages: { disallow: "Disabling '{{ruleId}}' is not allowed.", }, @@ -36,7 +35,7 @@ module.exports = { return {} } - const ig = ignore() + const ig = (ignore as any)() for (const pattern of context.options) { ig.add(pattern) } @@ -45,10 +44,10 @@ module.exports = { if (area.ruleId == null || ig.ignores(area.ruleId)) { context.report({ loc: utils.toRuleIdLocation( - context, + context as any, area.comment, - area.ruleId - ), + area.ruleId, + )!, messageId: "disallow", data: { ruleId: area.ruleId || String(context.options), @@ -56,6 +55,9 @@ module.exports = { }) } } + return {} }, } + +export default rule diff --git a/lib/rules/no-unlimited-disable.ts b/lib/rules/no-unlimited-disable.ts index 87ae0f0..35dc610 100644 --- a/lib/rules/no-unlimited-disable.ts +++ b/lib/rules/no-unlimited-disable.ts @@ -2,14 +2,11 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import type { Rule } from "eslint" +import { getAllDirectiveComments } from "../internal/get-all-directive-comments.ts" +import * as utils from "../internal/utils.ts" -const { - getAllDirectiveComments, -} = require("../internal/get-all-directive-comments") -const utils = require("../internal/utils") - -module.exports = { +const rule: Rule.RuleModule = { meta: { docs: { description: @@ -18,7 +15,7 @@ module.exports = { recommended: true, url: "https://eslint-community.github.io/eslint-plugin-eslint-comments/rules/no-unlimited-disable.html", }, - fixable: null, + fixable: null as any, messages: { unexpected: "Unexpected unlimited '{{kind}}' comment. Specify some rule names to disable.", @@ -28,8 +25,10 @@ module.exports = { }, create(context) { - for (const directiveComment of getAllDirectiveComments(context)) { - const kind = directiveComment.kind + for (const directiveComment of getAllDirectiveComments( + context as any, + )) { + const { kind } = directiveComment if ( kind !== "eslint-disable" && kind !== "eslint-disable-line" && @@ -45,6 +44,9 @@ module.exports = { }) } } + return {} }, } + +export default rule diff --git a/lib/rules/no-unused-disable.ts b/lib/rules/no-unused-disable.ts index 64d1e40..1034a3e 100644 --- a/lib/rules/no-unused-disable.ts +++ b/lib/rules/no-unused-disable.ts @@ -2,12 +2,12 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" - +import type { Rule } from "eslint" // Patch `Linter#verify` to work. -require("../utils/patch")() +import patch from "../utils/patch.ts" +patch() -module.exports = { +const rule: Rule.RuleModule = { meta: { docs: { description: "disallow unused `eslint-disable` comments", @@ -15,7 +15,7 @@ module.exports = { recommended: false, url: "https://eslint-community.github.io/eslint-plugin-eslint-comments/rules/no-unused-disable.html", }, - fixable: null, + fixable: null as any, // eslint-disable-next-line @eslint-community/mysticatea/eslint-plugin/prefer-message-ids messages: {}, schema: [], @@ -33,3 +33,5 @@ module.exports = { return {} }, } + +export default rule diff --git a/lib/rules/no-unused-enable.ts b/lib/rules/no-unused-enable.ts index c4e4fba..05aa9b6 100644 --- a/lib/rules/no-unused-enable.ts +++ b/lib/rules/no-unused-enable.ts @@ -2,12 +2,11 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import type { Rule } from "eslint" +import { getDisabledArea } from "../internal/disabled-area.ts" +import * as utils from "../internal/utils.ts" -const { getDisabledArea } = require("../internal/disabled-area") -const utils = require("../internal/utils") - -module.exports = { +const rule: Rule.RuleModule = { meta: { docs: { description: "disallow unused `eslint-enable` comments", @@ -15,7 +14,7 @@ module.exports = { recommended: true, url: "https://eslint-community.github.io/eslint-plugin-eslint-comments/rules/no-unused-enable.html", }, - fixable: null, + fixable: null as any, messages: { unused: "ESLint rules are re-enabled but those have not been disabled.", unusedRule: @@ -30,11 +29,18 @@ module.exports = { for (const item of disabledArea.unusedEnableDirectives) { context.report({ - loc: utils.toRuleIdLocation(context, item.comment, item.ruleId), + loc: utils.toRuleIdLocation( + context as any, + item.comment, + item.ruleId, + )!, messageId: item.ruleId ? "unusedRule" : "unused", - data: item, + data: item as Record, }) } + return {} }, } + +export default rule diff --git a/lib/rules/no-use.ts b/lib/rules/no-use.ts index 5b1afc0..3aa20bc 100644 --- a/lib/rules/no-use.ts +++ b/lib/rules/no-use.ts @@ -2,14 +2,11 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import type { Rule } from "eslint" +import { getAllDirectiveComments } from "../internal/get-all-directive-comments.ts" +import * as utils from "../internal/utils.ts" -const { - getAllDirectiveComments, -} = require("../internal/get-all-directive-comments") -const utils = require("../internal/utils") - -module.exports = { +const rule: Rule.RuleModule = { meta: { docs: { description: "disallow ESLint directive-comments", @@ -17,7 +14,7 @@ module.exports = { recommended: false, url: "https://eslint-community.github.io/eslint-plugin-eslint-comments/rules/no-use.html", }, - fixable: null, + fixable: null as any, messages: { disallow: "Unexpected ESLint directive comment.", }, @@ -52,10 +49,12 @@ module.exports = { create(context) { const allowed = new Set( - (context.options[0] && context.options[0].allow) || [] + (context.options[0] && context.options[0].allow) || [], ) - for (const directiveComment of getAllDirectiveComments(context)) { + for (const directiveComment of getAllDirectiveComments( + context as any, + )) { if (!allowed.has(directiveComment.kind)) { context.report({ loc: utils.toForceLocation(directiveComment.loc), @@ -63,6 +62,9 @@ module.exports = { }) } } + return {} }, } + +export default rule diff --git a/lib/rules/require-description.ts b/lib/rules/require-description.ts index 149840f..c504aba 100644 --- a/lib/rules/require-description.ts +++ b/lib/rules/require-description.ts @@ -2,14 +2,11 @@ * @author Yosuke Ota * See LICENSE file in root directory for full license. */ -"use strict" +import type { Rule } from "eslint" +import { getAllDirectiveComments } from "../internal/get-all-directive-comments.ts" +import * as utils from "../internal/utils.ts" -const { - getAllDirectiveComments, -} = require("../internal/get-all-directive-comments") -const utils = require("../internal/utils") - -module.exports = { +const rule: Rule.RuleModule = { meta: { docs: { description: @@ -18,7 +15,7 @@ module.exports = { recommended: false, url: "https://eslint-community.github.io/eslint-plugin-eslint-comments/rules/require-description.html", }, - fixable: null, + fixable: null as any, messages: { missingDescription: "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", @@ -54,10 +51,12 @@ module.exports = { create(context) { const ignores = new Set( - (context.options[0] && context.options[0].ignore) || [] + (context.options[0] && context.options[0].ignore) || [], ) - for (const directiveComment of getAllDirectiveComments(context)) { + for (const directiveComment of getAllDirectiveComments( + context as any, + )) { if (ignores.has(directiveComment.kind)) { continue } @@ -68,6 +67,9 @@ module.exports = { }) } } + return {} }, } + +export default rule diff --git a/lib/utils.ts b/lib/utils.ts index ec0f993..aa0b279 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,6 +1 @@ -/** DON'T EDIT THIS FILE; was created by scripts. */ -"use strict" - -module.exports = { - patch: require("./utils/patch"), -} +export { default as patch } from "./utils/patch.ts" diff --git a/lib/utils/patch.ts b/lib/utils/patch.ts index 404b44c..17a5a30 100644 --- a/lib/utils/patch.ts +++ b/lib/utils/patch.ts @@ -2,10 +2,9 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" - -const getLinters = require("../internal/get-linters") -const { toRuleIdLocation } = require("../internal/utils") +import type { AST, Linter, SourceCode } from "eslint" +import getLinters from "../internal/get-linters.ts" +import { toRuleIdLocation } from "../internal/utils.ts" const quotedName = /'(.+?)'/u /** @@ -14,7 +13,10 @@ const quotedName = /'(.+?)'/u * @param {string} ruleId The rule ID to check. * @returns {number} The severity of the rule. */ -function getSeverity(config, ruleId) { +function getSeverity( + config: Linter.FlatConfig, + ruleId: string, +): Linter.Severity { const rules = config && config.rules const ruleOptions = rules && rules[ruleId] const severity = Array.isArray(ruleOptions) ? ruleOptions[0] : ruleOptions @@ -39,7 +41,10 @@ function getSeverity(config, ruleId) { * @param {SourceCode|undefined} sourceCode The source code object to get. * @returns {Comment|undefined} The gotten comment. */ -function getCommentAt(message, sourceCode) { +function getCommentAt( + message: Linter.LintMessage, + sourceCode: SourceCode | undefined, +): AST.Program["comments"][number] | undefined { if (sourceCode != null) { const loc = { line: message.line, column: message.column - 1 } const index = sourceCode.getIndexFromLoc(loc) @@ -60,7 +65,7 @@ function getCommentAt(message, sourceCode) { * @param {Message} message The message. * @returns {boolean} `true` if the message is a `reportUnusedDisableDirectives` error. */ -function isUnusedDisableDirectiveError(message) { +function isUnusedDisableDirectiveError(message: Linter.LintMessage): boolean { return ( !message.fatal && !message.ruleId && @@ -76,13 +81,18 @@ function isUnusedDisableDirectiveError(message) { * @param {Comment|undefined} comment The directive comment. * @returns {Message} The created error. */ -function createNoUnusedDisableError(ruleId, severity, message, comment) { +function createNoUnusedDisableError( + ruleId: string, + severity: Linter.Severity, + message: Linter.LintMessage, + comment: AST.Program["comments"][number] | undefined, +): Linter.LintMessage { const clone = Object.assign({}, message) const match = quotedName.exec(message.message) const targetRuleId = match && match[1] clone.ruleId = ruleId - clone.severity = severity + clone.severity = severity as Linter.LintMessage["severity"] clone.message = targetRuleId ? `'${targetRuleId}' rule is disabled but never reported.` : "ESLint rules are disabled but never reported." @@ -90,14 +100,14 @@ function createNoUnusedDisableError(ruleId, severity, message, comment) { if (comment != null) { if (targetRuleId) { - const loc = toRuleIdLocation({}, comment, targetRuleId) - clone.line = loc.start.line - clone.column = loc.start.column + 1 - clone.endLine = loc.end.line - clone.endColumn = loc.end.column + 1 + const loc = toRuleIdLocation({} as any, comment, targetRuleId) + clone.line = loc?.start.line! + clone.column = loc?.start.column! + 1 + clone.endLine = loc?.end.line + clone.endColumn = loc?.end.column! + 1 } else { - clone.endLine = comment.loc.end.line - clone.endColumn = comment.loc.end.column + 1 + clone.endLine = comment.loc?.end.line + clone.endColumn = comment.loc?.end.column! + 1 } // Remove the whole node if it is the only rule, otherwise // don't try to fix because it is quite complicated. @@ -108,7 +118,7 @@ function createNoUnusedDisableError(ruleId, severity, message, comment) { { desc: "Remove `eslint-disable` comment.", fix: { - range: comment.range, + range: comment.range!, text: comment.value.includes("\n") ? "\n" : "", }, }, @@ -128,7 +138,13 @@ function createNoUnusedDisableError(ruleId, severity, message, comment) { * @param {boolean} keepAsIs The flag to keep original errors as is. * @returns {Message[]} The converted messages. */ -function convert(messages, sourceCode, ruleId, severity, keepAsIs) { +function convert( + messages: Linter.LintMessage[], + sourceCode: SourceCode | undefined, + ruleId: string, + severity: Linter.Severity, + keepAsIs: boolean, +): Linter.LintMessage[] { for (let i = messages.length - 1; i >= 0; --i) { const message = messages[i] if (!isUnusedDisableDirectiveError(message)) { @@ -139,7 +155,7 @@ function convert(messages, sourceCode, ruleId, severity, keepAsIs) { ruleId, severity, message, - getCommentAt(message, sourceCode) + getCommentAt(message, sourceCode), ) if (keepAsIs) { @@ -152,16 +168,17 @@ function convert(messages, sourceCode, ruleId, severity, keepAsIs) { return messages } -module.exports = ( - ruleId = "@eslint-community/eslint-comments/no-unused-disable" -) => { +const patch = ( + ruleId = "@eslint-community/eslint-comments/no-unused-disable", +): void => { for (const Linter of getLinters()) { + // @ts-expect-error const verify0 = Linter.prototype._verifyWithoutProcessors Object.defineProperty(Linter.prototype, "_verifyWithoutProcessors", { value: function _verifyWithoutProcessors( - textOrSourceCode, - config, - filenameOrOptions + textOrSourceCode: SourceCode | string, + config: Linter.FlatConfig, + filenameOrOptions: Record | string, ) { const severity = getSeverity(config, ruleId) if (severity === 0) { @@ -169,7 +186,7 @@ module.exports = ( this, textOrSourceCode, config, - filenameOrOptions + filenameOrOptions, ) } @@ -178,7 +195,7 @@ module.exports = ( ? { filename: filenameOrOptions } : filenameOrOptions || {} const reportUnusedDisableDirectives = Boolean( - options.reportUnusedDisableDirectives + options.reportUnusedDisableDirectives, ) const messages = verify0.call( this, @@ -186,14 +203,14 @@ module.exports = ( config, Object.assign({}, options, { reportUnusedDisableDirectives: true, - }) + }), ) return convert( messages, this.getSourceCode(), ruleId, severity, - reportUnusedDisableDirectives + reportUnusedDisableDirectives, ) }, configurable: true, @@ -201,3 +218,5 @@ module.exports = ( }) } } + +export default patch diff --git a/package.json b/package.json index 23977e0..478f4ec 100644 --- a/package.json +++ b/package.json @@ -5,29 +5,41 @@ "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "main": "index.js", - "types": "./types/index.d.ts", - "type": "commonjs", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "type": "module", "files": [ "configs.js", "lib", - "types" + "dist" ], "exports": { - "./package.json": "./package.json", "./configs": { - "types": "./types/configs.d.ts", - "default": "./configs.js" + "import": { + "types": "./dist/configs.d.ts", + "default": "./dist/configs.js" + }, + "default": { + "types": "./dist/configs.d.cts", + "default": "./dist/configs.cjs" + } }, ".": { - "types": "./types/index.d.ts", - "default": "./index.js" - } + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "default": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + }, + "./package.json": "./package.json" }, "typesVersions": { "*": { "configs": [ - "./types/configs.d.ts" + "./dist/configs.d.ts" ] } }, @@ -39,41 +51,49 @@ "ignore": "^5.2.4" }, "devDependencies": { - "@arethetypeswrong/cli": "^0.18.1", + "@arethetypeswrong/cli": "^0.18.2", "@babel/core": "^7.22.9", "@babel/eslint-parser": "^7.22.9", - "@eslint-community/eslint-plugin-mysticatea": "^15.5.1", - "@eslint/core": "^0.13.0", + "@eslint-community/eslint-plugin-mysticatea": "^15.8.0", + "@eslint/core": "^1.0.0", "@eslint/css": "^0.6.0", + "@types/cross-spawn": "^6.0.6", "@types/eslint": "^8", - "@types/node": "^14.18.54", + "@types/mocha": "^10.0.10", + "@types/node": "^24.10.1", + "@types/rimraf": "^3.0.2", + "@types/semver": "^7.7.1", "@vuepress/plugin-pwa": "^1.9.9", "cross-spawn": "^7.0.3", "esbuild": "^0.19.3", - "eslint": "^8.46.0", - "expect-type": "^1.2.1", + "eslint": "^8.57.1", + "eslint-v9": "npm:eslint@^9.39.1", + "expect-type": "^1.2.2", "fs-extra": "^10.1.0", "mocha": "^10.4.0", "monaco-editor": "^0.47.0", "nyc": "^15.1.0", "opener": "^1.5.2", "rimraf": "^3.0.2", - "semver": "^7.5.4", - "typescript": "^5.8.3", - "vite-plugin-eslint4b": "^0.2.1", + "semver": "^7.7.3", + "tsup": "^8.5.1", + "typescript": "^5.9.3", + "vite-plugin-eslint4b": "^0.6.0", "vitepress": "^1.0.0-rc.15" }, "scripts": { + "build": "rimraf dist/ && tsup --config=tsup.config.mts", + "prepack": "npm run clean && npm run build", "preversion": "npm test", "version": "node scripts/update && git add .", "postversion": "git push && git push --tags", - "clean": "rimraf .nyc_output coverage docs/.vitepress/cache", + "clean": "rimraf .nyc_output/ coverage/ docs/.vitepress/cache/ dist/", "docs:build": "vitepress build docs", "docs:watch": "vitepress dev docs", "lint": "eslint lib scripts tests", - "test": "nyc npm run debug", - "debug": "mocha \"tests/lib/**/*.js\" --reporter dot --timeout 8000", - "test:types": "tsc -p tsconfig.json", + "test": "set NODE_OPTIONS=--experimental-transform-types && npm run debug", + "debug": "mocha \"tests/lib/**/*.test.ts\" --reporter dot --timeout 8000", + "typecheck": "tsc -p tsconfig.json", "check-exports": "attw --pack", "coverage": "nyc report --reporter lcov && opener coverage/lcov-report/index.html", "watch": "npm run -s test -- --watch --growl" diff --git a/scripts/lib/rules.ts b/scripts/lib/rules.ts index f9cc745..fce82f5 100644 --- a/scripts/lib/rules.ts +++ b/scripts/lib/rules.ts @@ -2,17 +2,24 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" - -const fs = require("fs") -const path = require("path") +import * as fs from "node:fs" +import * as path from "node:path" /** * @type {{id:string,name:string,category:string,description:string,recommended:boolean,fixable:boolean,deprecated:boolean,replacedBy:(string[]|null)}[]} */ -const rules = fs +const rules: { + id: string + name: string + category: string + description: string + recommended: boolean + fixable: boolean + deprecated: boolean + replacedBy: string[] | null +}[] = fs .readdirSync(path.resolve(__dirname, "../../lib/rules")) - .map((fileName) => path.basename(fileName, ".js")) + .map((fileName) => path.basename(fileName, ".ts")) .map((name) => { const meta = require(`../../lib/rules/${name}`).meta return { @@ -27,12 +34,12 @@ const rules = fs } }) -module.exports = rules -module.exports.withCategories = ["Best Practices", "Stylistic Issues"].map( +export default rules +export const withCategories = ["Best Practices", "Stylistic Issues"].map( (category) => ({ category, rules: rules.filter( - (rule) => rule.category === category && !rule.deprecated + (rule) => rule.category === category && !rule.deprecated, ), - }) + }), ) diff --git a/scripts/lib/utils.ts b/scripts/lib/utils.ts index a3e0f9a..37b5983 100644 --- a/scripts/lib/utils.ts +++ b/scripts/lib/utils.ts @@ -2,11 +2,10 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import { ESLint } from "eslint" +import * as fs from "node:fs" +import * as path from "node:path" -const fs = require("fs") -const path = require("path") -const { ESLint } = require("eslint") const linter = new ESLint({ fix: true }) /** @@ -14,7 +13,7 @@ const linter = new ESLint({ fix: true }) * @param {string} text The text to format. * @returns {Promise} The formatted text. */ -function format(text) { +function format(text: string): Promise { return linter.lintText(text).then(([{ output }]) => output || text) } @@ -23,10 +22,9 @@ function format(text) { * @param {string} dirPath The path to the directory to create index. * @returns {Promise} The index file content. */ -function createIndex(dirPath) { +function createIndex(dirPath: string): Promise { const dirName = path.basename(dirPath) return format(`/** DON'T EDIT THIS FILE; was created by scripts. */ - "use strict" module.exports = { ${fs @@ -38,7 +36,4 @@ function createIndex(dirPath) { `) } -module.exports = { - createIndex, - format, -} +export { createIndex, format } diff --git a/scripts/update-docs-headers.ts b/scripts/update-docs-headers.ts index c73170d..eef960c 100644 --- a/scripts/update-docs-headers.ts +++ b/scripts/update-docs-headers.ts @@ -2,11 +2,13 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import * as fs from "node:fs" +import * as path from "node:path" +import { fileURLToPath } from "node:url" +import rules from "./lib/rules.ts" + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) -const fs = require("fs") -const path = require("path") -const rules = require("./lib/rules") const PLACE_HOLDER = /^#[^\n]*\n+> .+\n+(?:- .+\n)*\n*/u for (const rule of rules) { @@ -18,18 +20,18 @@ for (const rule of rules) { } if (rule.deprecated) { headerLines.push( - `- ⚠️ This rule was **deprecated** and replaced by ${rule.replacedBy - .map((id) => `[${id}](${id}.md) rule`) - .join(", ")}.` + `- ⚠️ This rule was **deprecated** and replaced by ${rule + .replacedBy!.map((id) => `[${id}](${id}.md) rule`) + .join(", ")}.`, ) } else if (rule.recommended) { headerLines.push( - '- 🌟 The `"extends": "plugin:@eslint-community/eslint-comments/recommended"` property in a configuration file enables this rule.' + '- 🌟 The `"extends": "plugin:@eslint-community/eslint-comments/recommended"` property in a configuration file enables this rule.', ) } if (rule.fixable) { headerLines.push( - "- ✒️ The `--fix` option on the [command line](http://eslint.org/docs/user-guide/command-line-interface#fix) can automatically fix some of the problems reported by this rule." + "- ✒️ The `--fix` option on the [command line](http://eslint.org/docs/user-guide/command-line-interface#fix) can automatically fix some of the problems reported by this rule.", ) } headerLines.push("", "") @@ -38,6 +40,6 @@ for (const rule of rules) { filePath, fs .readFileSync(filePath, "utf8") - .replace(PLACE_HOLDER, headerLines.join("\n")) + .replace(PLACE_HOLDER, headerLines.join("\n")), ) } diff --git a/scripts/update-docs-index.ts b/scripts/update-docs-index.ts index f45fee5..d125b57 100644 --- a/scripts/update-docs-index.ts +++ b/scripts/update-docs-index.ts @@ -2,18 +2,27 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" - -const fs = require("fs") -const path = require("path") -const { withCategories } = require("./lib/rules") +import * as fs from "node:fs" +import * as path from "node:path" +import { fileURLToPath } from "node:url" +import { withCategories } from "./lib/rules.ts" +const __dirname = path.dirname(fileURLToPath(import.meta.url)) /** * Convert a given rule to a table row. * @param {{id:string,name:string,category:string,description:string,recommended:boolean,fixable:boolean,deprecated:boolean,replacedBy:string[]}} rule The rule object. * @returns {string} The table row of the rule. */ -function toTableRow(rule) { +function toTableRow(rule: { + id: string + name: string + category: string + description: string + recommended: boolean + fixable: boolean + deprecated: boolean + replacedBy: string[] | null +}): string { const mark = `${rule.recommended ? "🌟" : ""}${rule.fixable ? "✒️" : ""}` const link = `[@eslint-community/eslint-comments/${rule.name}](./${rule.name}.md)` const description = rule.description || "(no description)" @@ -25,7 +34,22 @@ function toTableRow(rule) { * @param {{category:string,rules:{id:string,name:string,category:string,description:string,recommended:boolean,fixable:boolean,deprecated:boolean,replacedBy:string[]}[]}} categoryInfo The category information to convert. * @returns {string} The section of the category. */ -function toCategorySection({ category, rules }) { +function toCategorySection({ + category, + rules, +}: { + category: string + rules: { + id: string + name: string + category: string + description: string + recommended: boolean + fixable: boolean + deprecated: boolean + replacedBy: string[] | null + }[] +}): string { return `## ${category} | Rule ID | Description | | @@ -42,5 +66,5 @@ fs.writeFileSync( - ✒️ mark: the rule which is fixable by \`eslint --fix\` command. ${withCategories.map(toCategorySection).join("\n")} -` +`, ) diff --git a/scripts/update-recommended-rules.ts b/scripts/update-recommended-rules.ts index a421132..248acb9 100644 --- a/scripts/update-recommended-rules.ts +++ b/scripts/update-recommended-rules.ts @@ -2,12 +2,13 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import * as fs from "node:fs" +import * as path from "node:path" +import { fileURLToPath } from "node:url" +import rules from "./lib/rules.ts" +import { format } from "./lib/utils.ts" -const fs = require("fs") -const path = require("path") -const rules = require("./lib/rules") -const { format } = require("./lib/utils") +const __dirname = path.dirname(fileURLToPath(import.meta.url)) // recommended.js format(`/** DON'T EDIT THIS FILE; was created by scripts. */ @@ -22,9 +23,9 @@ module.exports = { .join("\n ")} }, } -`).then((content) => +`).then((content) => { fs.writeFileSync( - path.resolve(__dirname, "../lib/configs/recommended.js"), - content + path.resolve(__dirname, "../lib/configs/recommended.ts"), + content, ) -) +}) diff --git a/scripts/update.ts b/scripts/update.ts index 7e45f8c..2c903e8 100644 --- a/scripts/update.ts +++ b/scripts/update.ts @@ -2,18 +2,19 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import * as fs from "node:fs" +import * as path from "node:path" +import { fileURLToPath } from "node:url" +import { createIndex } from "./lib/utils.ts" -const fs = require("fs") -const path = require("path") -const { createIndex } = require("./lib/utils") +const __dirname = path.dirname(fileURLToPath(import.meta.url)) // docs. -require("./update-docs-headers") -require("./update-docs-index") +import "./update-docs-headers.ts" +import "./update-docs-index.ts" // recommended rules. -require("./update-recommended-rules") +import "./update-recommended-rules.ts" // indices. for (const dirPath of [ @@ -21,7 +22,7 @@ for (const dirPath of [ path.resolve(__dirname, "../lib/rules"), path.resolve(__dirname, "../lib/utils"), ]) { - createIndex(dirPath).then((content) => - fs.writeFileSync(`${dirPath}.js`, content) - ) + createIndex(dirPath).then((content) => { + fs.writeFileSync(`${dirPath}.ts`, content) + }) } diff --git a/tests/lib/illegal-eslint-disable-line.ts b/tests/lib/illegal-eslint-disable-line.test.ts similarity index 73% rename from tests/lib/illegal-eslint-disable-line.ts rename to tests/lib/illegal-eslint-disable-line.test.ts index 495d68c..9e2b14d 100644 --- a/tests/lib/illegal-eslint-disable-line.ts +++ b/tests/lib/illegal-eslint-disable-line.test.ts @@ -1,20 +1,20 @@ /** * Test that multi-line eslint-disable-line comments are not false positives. */ -"use strict" -const assert = require("assert") -const fs = require("fs") -const path = require("path") -const spawn = require("cross-spawn") -const rimraf = require("rimraf") +import spawn from "cross-spawn" +import type { Linter } from "eslint" +import * as assert from "node:assert" +import * as fs from "node:fs" +import * as path from "node:path" +import rimraf from "rimraf" /** * Run eslint CLI command with a given source code. * @param {string} code The source code to lint. * @returns {Promise} The result message. */ -function runESLint(code) { +function runESLint(code: string): Promise { return new Promise((resolve, reject) => { const cp = spawn( "eslint", @@ -32,16 +32,16 @@ function runESLint(code) { stdio: ["pipe", "pipe", "inherit"], // eslint-disable-next-line no-process-env, @eslint-community/mysticatea/node/no-process-env env: { ...process.env, ESLINT_USE_FLAT_CONFIG: "false" }, - } + }, ) - const chunks = [] + const chunks: any[] = [] let totalLength = 0 - cp.stdout.on("data", (chunk) => { + cp.stdout?.on("data", (chunk) => { chunks.push(chunk) totalLength += chunk.length }) - cp.stdout.on("end", () => { + cp.stdout?.on("end", () => { try { const resultsStr = String(Buffer.concat(chunks, totalLength)) const results = JSON.parse(resultsStr) @@ -52,19 +52,18 @@ function runESLint(code) { }) cp.on("error", reject) - cp.stdin.end(code) + cp.stdin?.end(code) }) } describe("multi-line eslint-disable-line comments", () => { + // Register this plugin. + const selfPath = path.resolve(process.cwd()) + const pluginPath = path.resolve( + process.cwd(), + "node_modules/@eslint-community/eslint-plugin-eslint-comments", + ) before(() => { - // Register this plugin. - const selfPath = path.resolve(__dirname, "../../") - const pluginPath = path.resolve( - __dirname, - "../../node_modules/@eslint-community/eslint-plugin-eslint-comments" - ) - fs.mkdirSync(path.dirname(pluginPath), { recursive: true }) if (fs.existsSync(pluginPath)) { rimraf.sync(pluginPath) @@ -73,6 +72,10 @@ describe("multi-line eslint-disable-line comments", () => { fs.symlinkSync(selfPath, pluginPath, "junction") }) + after(() => { + rimraf.sync(pluginPath) + }) + describe("`@eslint-community/eslint-comments/*` rules are valid", () => { for (const code of [ `/* eslint @eslint-community/eslint-comments/no-use:[error, {allow: ['eslint']}] */ @@ -88,10 +91,10 @@ no-undef*/ runESLint(code).then((messages) => { assert.strictEqual(messages.length > 0, true) const normalMessages = messages.filter( - (message) => message.ruleId != null + (message) => message.ruleId != null, ) - assert.deepStrictEqual(normalMessages, []) - }) + assert.strictEqual(normalMessages.length, 0) + }), ) } }) diff --git a/tests/lib/rules/disable-enable-pair.ts b/tests/lib/rules/disable-enable-pair.test.ts similarity index 95% rename from tests/lib/rules/disable-enable-pair.ts rename to tests/lib/rules/disable-enable-pair.test.ts index b1e3197..e58801e 100644 --- a/tests/lib/rules/disable-enable-pair.ts +++ b/tests/lib/rules/disable-enable-pair.test.ts @@ -2,11 +2,11 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import cssPlugin from "@eslint/css" +import { Linter, RuleTester } from "eslint" +import * as semver from "semver" +import rule from "../../../lib/rules/disable-enable-pair.ts" -const semver = require("semver") -const { Linter, RuleTester } = require("eslint") -const rule = require("../../../lib/rules/disable-enable-pair") const tester = new RuleTester() tester.run("disable-enable-pair", rule, { @@ -102,10 +102,10 @@ var foo = 1 a {} `, plugins: { - css: require("@eslint/css").default, + css: cssPlugin, }, language: "css/css", - }, + } as any, ] : []), ], @@ -254,7 +254,7 @@ console.log(); { code: "/* eslint-disable no-unused-vars */ a {}", plugins: { - css: require("@eslint/css").default, + css: cssPlugin, }, language: "css/css", errors: [ @@ -267,7 +267,7 @@ console.log(); endColumn: 33, }, ], - }, + } as any, ] : []), ], diff --git a/tests/lib/rules/no-aggregating-enable.ts b/tests/lib/rules/no-aggregating-enable.test.ts similarity index 91% rename from tests/lib/rules/no-aggregating-enable.ts rename to tests/lib/rules/no-aggregating-enable.test.ts index 979ddce..4447dec 100644 --- a/tests/lib/rules/no-aggregating-enable.ts +++ b/tests/lib/rules/no-aggregating-enable.test.ts @@ -2,11 +2,11 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import cssPlugin from "@eslint/css" +import { Linter, RuleTester } from "eslint" +import * as semver from "semver" +import rule from "../../../lib/rules/no-aggregating-enable.ts" -const semver = require("semver") -const { Linter, RuleTester } = require("eslint") -const rule = require("../../../lib/rules/no-aggregating-enable") const tester = new RuleTester() tester.run("no-aggregating-enable", rule, { @@ -42,10 +42,10 @@ tester.run("no-aggregating-enable", rule, { /*eslint-enable no-shadow*/ a {}`, plugins: { - css: require("@eslint/css").default, + css: cssPlugin, }, language: "css/css", - }, + } as any, ] : []), ], @@ -106,13 +106,13 @@ tester.run("no-aggregating-enable", rule, { /*eslint-enable*/ a {}`, plugins: { - css: require("@eslint/css").default, + css: cssPlugin, }, language: "css/css", errors: [ "This `eslint-enable` comment affects 2 `eslint-disable` comments. An `eslint-enable` comment should be for an `eslint-disable` comment.", ], - }, + } as any, ] : []), ], diff --git a/tests/lib/rules/no-duplicate-disable.ts b/tests/lib/rules/no-duplicate-disable.test.ts similarity index 93% rename from tests/lib/rules/no-duplicate-disable.ts rename to tests/lib/rules/no-duplicate-disable.test.ts index 3ba9652..9fb8104 100644 --- a/tests/lib/rules/no-duplicate-disable.ts +++ b/tests/lib/rules/no-duplicate-disable.test.ts @@ -2,11 +2,11 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import cssPlugin from "@eslint/css" +import { Linter, RuleTester } from "eslint" +import * as semver from "semver" +import rule from "../../../lib/rules/no-duplicate-disable.ts" -const semver = require("semver") -const { Linter, RuleTester } = require("eslint") -const rule = require("../../../lib/rules/no-duplicate-disable") const tester = new RuleTester() tester.run("no-duplicate-disable", rule, { @@ -40,10 +40,10 @@ tester.run("no-duplicate-disable", rule, { /*eslint-disable eqeqeq*/ a {}`, plugins: { - css: require("@eslint/css").default, + css: cssPlugin, }, language: "css/css", - }, + } as any, ] : []), ], @@ -168,7 +168,7 @@ a {}`, /* eslint-disable-line no-undef */ a {}`, plugins: { - css: require("@eslint/css").default, + css: cssPlugin, }, language: "css/css", errors: [ @@ -181,7 +181,7 @@ a {}`, endColumn: 32, }, ], - }, + } as any, ] : []), ], diff --git a/tests/lib/rules/no-restricted-disable.ts b/tests/lib/rules/no-restricted-disable.test.ts similarity index 93% rename from tests/lib/rules/no-restricted-disable.ts rename to tests/lib/rules/no-restricted-disable.test.ts index e515838..abb9699 100644 --- a/tests/lib/rules/no-restricted-disable.ts +++ b/tests/lib/rules/no-restricted-disable.test.ts @@ -2,18 +2,21 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import cssPlugin from "@eslint/css" +import { Linter, RuleTester } from "eslint" +import * as semver from "semver" +import rule from "../../../lib/rules/no-restricted-disable.ts" -const semver = require("semver") -const { Linter, RuleTester } = require("eslint") -const rule = require("../../../lib/rules/no-restricted-disable") const coreRules = new Linter({ configType: "eslintrc" }).getRules() let tester = null +// @ts-expect-error if (typeof RuleTester.prototype.defineRule === "function") { // ESLint < 9 tester = new RuleTester() + // @ts-expect-error tester.defineRule("foo/no-undef", coreRules.get("no-undef")) + // @ts-expect-error tester.defineRule("foo/no-redeclare", coreRules.get("no-redeclare")) } else { // ESLint 9 @@ -21,8 +24,8 @@ if (typeof RuleTester.prototype.defineRule === "function") { plugins: { foo: { rules: { - "no-undef": coreRules.get("no-undef"), - "no-redeclare": coreRules.get("no-redeclare"), + "no-undef": coreRules.get("no-undef")!, + "no-redeclare": coreRules.get("no-redeclare")!, }, }, }, @@ -55,10 +58,10 @@ tester.run("no-restricted-disable", rule, { code: "/*eslint-disable eqeqeq*/ a {}", options: ["*", "!eqeqeq"], plugins: { - css: require("@eslint/css").default, + css: cssPlugin, }, language: "css/css", - }, + } as any, ] : []), ], @@ -199,11 +202,11 @@ tester.run("no-restricted-disable", rule, { code: "/*eslint-disable eqeqeq*/ a {}", options: ["eqeqeq"], plugins: { - css: require("@eslint/css").default, + css: cssPlugin, }, language: "css/css", errors: ["Disabling 'eqeqeq' is not allowed."], - }, + } as any, ] : []), ], diff --git a/tests/lib/rules/no-unlimited-disable.ts b/tests/lib/rules/no-unlimited-disable.test.ts similarity index 93% rename from tests/lib/rules/no-unlimited-disable.ts rename to tests/lib/rules/no-unlimited-disable.test.ts index 34f7427..704e807 100644 --- a/tests/lib/rules/no-unlimited-disable.ts +++ b/tests/lib/rules/no-unlimited-disable.test.ts @@ -2,11 +2,11 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import cssPlugin from "@eslint/css" +import { Linter, RuleTester } from "eslint" +import * as semver from "semver" +import rule from "../../../lib/rules/no-unlimited-disable.ts" -const semver = require("semver") -const { Linter, RuleTester } = require("eslint") -const rule = require("../../../lib/rules/no-unlimited-disable") const tester = new RuleTester() tester.run("no-unlimited-disable", rule, { @@ -25,10 +25,10 @@ tester.run("no-unlimited-disable", rule, { { code: "/*eslint-disable-line eqeqeq*/ a {}", plugins: { - css: require("@eslint/css").default, + css: cssPlugin, }, language: "css/css", - }, + } as any, ] : []), ], @@ -122,13 +122,13 @@ tester.run("no-unlimited-disable", rule, { { code: "/* eslint-disable */ a {}", plugins: { - css: require("@eslint/css").default, + css: cssPlugin, }, language: "css/css", errors: [ "Unexpected unlimited 'eslint-disable' comment. Specify some rule names to disable.", ], - }, + } as any, ] : []), ], diff --git a/tests/lib/rules/no-unused-disable.ts b/tests/lib/rules/no-unused-disable.test.ts similarity index 81% rename from tests/lib/rules/no-unused-disable.ts rename to tests/lib/rules/no-unused-disable.test.ts index ebd0d1e..df96685 100644 --- a/tests/lib/rules/no-unused-disable.ts +++ b/tests/lib/rules/no-unused-disable.test.ts @@ -12,15 +12,13 @@ * So it cannot test with `eslint.RuleTester`. * This test confirmes that this rule works file in eslint CLI command. */ -"use strict" - -const assert = require("assert") -const fs = require("fs") -const path = require("path") -const spawn = require("cross-spawn") -const rimraf = require("rimraf") -const semver = require("semver") -const { Linter } = require("eslint") +import spawn from "cross-spawn" +import { Linter } from "eslint" +import * as assert from "node:assert" +import * as fs from "node:fs" +import * as path from "node:path" +import rimraf from "rimraf" +import * as semver from "semver" /** * Run eslint CLI command with a given source code. @@ -28,7 +26,10 @@ const { Linter } = require("eslint") * @param {boolean} [reportUnusedDisableDirectives] The flag to enable `--report-unused-disable-directives` option. * @returns {Promise} The result message. */ -function runESLint(code, reportUnusedDisableDirectives = false) { +function runESLint( + code: string, + reportUnusedDisableDirectives = false, +): Promise[]> { return new Promise((resolve, reject) => { const cp = spawn( "eslint", @@ -51,39 +52,39 @@ function runESLint(code, reportUnusedDisableDirectives = false) { stdio: ["pipe", "pipe", "inherit"], // eslint-disable-next-line no-process-env, @eslint-community/mysticatea/node/no-process-env env: { ...process.env, ESLINT_USE_FLAT_CONFIG: "false" }, - } + }, ) - const chunks = [] + const chunks: any[] = [] let totalLength = 0 - cp.stdout.on("data", (chunk) => { + cp.stdout?.on("data", (chunk) => { chunks.push(chunk) totalLength += chunk.length }) - cp.stdout.on("end", () => { + cp.stdout?.on("end", () => { try { const resultsStr = String(Buffer.concat(chunks, totalLength)) const results = JSON.parse(resultsStr) resolve(results[0].messages) } catch (error) { + console.error(error) reject(error) } }) cp.on("error", reject) - cp.stdin.end(code) + cp.stdin?.end(code) }) } describe("no-unused-disable", () => { + // Register this plugin. + const selfPath = path.resolve(process.cwd()) + const pluginPath = path.resolve( + process.cwd(), + "node_modules/@eslint-community/eslint-plugin-eslint-comments", + ) before(() => { - // Register this plugin. - const selfPath = path.resolve(__dirname, "../../../") - const pluginPath = path.resolve( - __dirname, - "../../../node_modules/@eslint-community/eslint-plugin-eslint-comments" - ) - fs.mkdirSync(path.dirname(pluginPath), { recursive: true }) if (fs.existsSync(pluginPath)) { rimraf.sync(pluginPath) @@ -92,6 +93,10 @@ describe("no-unused-disable", () => { fs.symlinkSync(selfPath, pluginPath, "junction") }) + after(() => { + rimraf.sync(pluginPath) + }) + describe("valid", () => { for (const code of [ `/*eslint no-undef:error*/ @@ -177,7 +182,7 @@ var a = b //eslint-disable-line -- description`, it(code, () => runESLint(code).then((messages) => { assert.strictEqual(messages.length, 0) - }) + }), ) } }) @@ -209,34 +214,34 @@ var a = b //eslint-disable-line`, }, { code: `/*eslint no-undef:off*/ -var a = b /*eslint-disable-line*/`, + var a = b /*eslint-disable-line*/`, errors: [ { message: "ESLint rules are disabled but never reported.", line: 2, - column: 11, + column: 17, endLine: 2, - endColumn: 34, + endColumn: 40, }, ], }, { code: `/*eslint no-undef:off*/ -var a = b //eslint-disable-line no-undef`, + var a = b //eslint-disable-line no-undef`, errors: [ { message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 33, + column: 39, endLine: 2, - endColumn: 41, + endColumn: 47, suggestions: [ { desc: "Remove `eslint-disable` comment.", fix: { - range: [34, 64], + range: [40, 70], text: "", }, }, @@ -246,30 +251,30 @@ var a = b //eslint-disable-line no-undef`, }, { code: `/*eslint no-undef:off*/ -var a = b /*eslint-disable-line no-undef*/`, + var a = b /*eslint-disable-line no-undef*/`, errors: [ { message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 33, + column: 39, endLine: 2, - endColumn: 41, + endColumn: 47, }, ], }, { code: `/*eslint no-undef:off, no-unused-vars:off*/ -var a = b //eslint-disable-line no-undef,no-unused-vars`, + var a = b //eslint-disable-line no-undef,no-unused-vars`, errors: semver.satisfies(Linter.version, ">=8.0.0") ? [ { message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 33, + column: 39, endLine: 2, - endColumn: 41, + endColumn: 47, suggestions: [], }, ] @@ -278,9 +283,9 @@ var a = b //eslint-disable-line no-undef,no-unused-vars`, message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 33, + column: 39, endLine: 2, - endColumn: 41, + endColumn: 47, suggestions: [], }, { @@ -296,16 +301,16 @@ var a = b //eslint-disable-line no-undef,no-unused-vars`, }, { code: `/*eslint no-undef:off, no-unused-vars:off*/ -var a = b /*eslint-disable-line no-undef,no-unused-vars*/`, + var a = b /*eslint-disable-line no-undef,no-unused-vars*/`, errors: semver.satisfies(Linter.version, ">=8.0.0") ? [ { message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 33, + column: 39, endLine: 2, - endColumn: 41, + endColumn: 47, }, ] : [ @@ -313,9 +318,9 @@ var a = b /*eslint-disable-line no-undef,no-unused-vars*/`, message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 33, + column: 39, endLine: 2, - endColumn: 41, + endColumn: 47, }, { message: @@ -329,21 +334,21 @@ var a = b /*eslint-disable-line no-undef,no-unused-vars*/`, }, { code: `/*eslint no-undef:off*/ -//eslint-disable-next-line -var a = b`, + //eslint-disable-next-line + var a = b`, errors: [ { message: "ESLint rules are disabled but never reported.", line: 2, - column: 1, + column: 7, endLine: 2, - endColumn: 27, + endColumn: 33, suggestions: [ { desc: "Remove `eslint-disable` comment.", fix: { - range: [24, 50], + range: [30, 56], text: "", }, }, @@ -353,36 +358,36 @@ var a = b`, }, { code: `/*eslint no-undef:off*/ -/*eslint-disable-next-line*/ -var a = b`, + /*eslint-disable-next-line*/ + var a = b`, errors: [ { message: "ESLint rules are disabled but never reported.", line: 2, - column: 1, + column: 7, endLine: 2, - endColumn: 29, + endColumn: 35, }, ], }, { code: `/*eslint no-undef:off*/ -//eslint-disable-next-line no-undef -var a = b`, + //eslint-disable-next-line no-undef + var a = b`, errors: [ { message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 28, + column: 34, endLine: 2, - endColumn: 36, + endColumn: 42, suggestions: [ { desc: "Remove `eslint-disable` comment.", fix: { - range: [24, 59], + range: [30, 65], text: "", }, }, @@ -392,32 +397,32 @@ var a = b`, }, { code: `/*eslint no-undef:off*/ -/*eslint-disable-next-line no-undef*/ -var a = b`, + /*eslint-disable-next-line no-undef*/ + var a = b`, errors: [ { message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 28, + column: 34, endLine: 2, - endColumn: 36, + endColumn: 42, }, ], }, { code: `/*eslint no-undef:off, no-unused-vars:off*/ -//eslint-disable-next-line no-undef,no-unused-vars -var a = b`, + //eslint-disable-next-line no-undef,no-unused-vars + var a = b`, errors: semver.satisfies(Linter.version, ">=8.0.0") ? [ { message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 28, + column: 34, endLine: 2, - endColumn: 36, + endColumn: 42, suggestions: [], }, ] @@ -426,9 +431,9 @@ var a = b`, message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 28, + column: 34, endLine: 2, - endColumn: 36, + endColumn: 42, suggestions: [], }, { @@ -444,17 +449,17 @@ var a = b`, }, { code: `/*eslint no-undef:off, no-unused-vars:off*/ -/*eslint-disable-next-line no-undef,no-unused-vars*/ -var a = b`, + /*eslint-disable-next-line no-undef,no-unused-vars*/ + var a = b`, errors: semver.satisfies(Linter.version, ">=8.0.0") ? [ { message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 28, + column: 34, endLine: 2, - endColumn: 36, + endColumn: 42, }, ] : [ @@ -462,9 +467,9 @@ var a = b`, message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 28, + column: 34, endLine: 2, - endColumn: 36, + endColumn: 42, }, { message: @@ -478,21 +483,21 @@ var a = b`, }, { code: `/*eslint no-undef:off*/ -/*eslint-disable*/ -var a = b`, + /*eslint-disable*/ + var a = b`, errors: [ { message: "ESLint rules are disabled but never reported.", line: 2, - column: 1, + column: 7, endLine: 2, - endColumn: 19, + endColumn: 25, suggestions: [ { desc: "Remove `eslint-disable` comment.", fix: { - range: [24, 42], + range: [30, 48], text: "", }, }, @@ -502,21 +507,21 @@ var a = b`, }, { code: `/*eslint no-undef:off*/ -/*eslint-disable no-undef*/ -var a = b`, + /*eslint-disable no-undef*/ + var a = b`, errors: [ { message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 18, + column: 24, endLine: 2, - endColumn: 26, + endColumn: 32, suggestions: [ { desc: "Remove `eslint-disable` comment.", fix: { - range: [24, 51], + range: [30, 57], text: "", }, }, @@ -526,17 +531,17 @@ var a = b`, }, { code: `/*eslint no-undef:off, no-unused-vars:off*/ -/*eslint-disable no-undef,no-unused-vars*/ -var a = b`, + /*eslint-disable no-undef,no-unused-vars*/ + var a = b`, errors: semver.satisfies(Linter.version, ">=8.0.0") ? [ { message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 18, + column: 24, endLine: 2, - endColumn: 26, + endColumn: 32, suggestions: [], }, ] @@ -545,9 +550,9 @@ var a = b`, message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 18, + column: 24, endLine: 2, - endColumn: 26, + endColumn: 32, suggestions: [], }, { @@ -556,29 +561,29 @@ var a = b`, line: 2, column: 27, endLine: 2, - endColumn: 41, + endColumn: 47, suggestions: [], }, ], }, { code: `/*eslint no-undef:off*/ -/*eslint-disable*/ -var a = b -/*eslint-enable*/`, + /*eslint-disable*/ + var a = b + /*eslint-enable*/`, errors: [ { message: "ESLint rules are disabled but never reported.", line: 2, - column: 1, + column: 7, endLine: 2, - endColumn: 19, + endColumn: 25, suggestions: [ { desc: "Remove `eslint-disable` comment.", fix: { - range: [24, 42], + range: [30, 48], text: "", }, }, @@ -588,22 +593,22 @@ var a = b }, { code: `/*eslint no-undef:off*/ -/*eslint-disable no-undef*/ -var a = b -/*eslint-enable*/`, + /*eslint-disable no-undef*/ + var a = b + /*eslint-enable*/`, errors: [ { message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 18, + column: 24, endLine: 2, - endColumn: 26, + endColumn: 32, suggestions: [ { desc: "Remove `eslint-disable` comment.", fix: { - range: [24, 51], + range: [30, 57], text: "", }, }, @@ -613,18 +618,18 @@ var a = b }, { code: `/*eslint no-undef:off, no-unused-vars:off*/ -/*eslint-disable no-undef,no-unused-vars*/ -var a = b -/*eslint-enable*/`, + /*eslint-disable no-undef,no-unused-vars*/ + var a = b + /*eslint-enable*/`, errors: semver.satisfies(Linter.version, ">=8.0.0") ? [ { message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 18, + column: 24, endLine: 2, - endColumn: 26, + endColumn: 32, suggestions: [], }, ] @@ -633,9 +638,9 @@ var a = b message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 18, + column: 24, endLine: 2, - endColumn: 26, + endColumn: 32, suggestions: [], }, { @@ -644,29 +649,29 @@ var a = b line: 2, column: 27, endLine: 2, - endColumn: 41, + endColumn: 47, suggestions: [], }, ], }, { code: `/*eslint no-undef:error*/ -/*eslint-disable*/ -/*eslint-enable*/ -var a = b//eslint-disable-line no-undef`, + /*eslint-disable*/ + /*eslint-enable*/ + var a = b//eslint-disable-line no-undef`, errors: [ { message: "ESLint rules are disabled but never reported.", line: 2, - column: 1, + column: 7, endLine: 2, - endColumn: 19, + endColumn: 25, suggestions: [ { desc: "Remove `eslint-disable` comment.", fix: { - range: [26, 44], + range: [32, 50], text: "", }, }, @@ -676,38 +681,38 @@ var a = b//eslint-disable-line no-undef`, }, { code: `/*eslint no-undef:error*/ -/*eslint-disable*/ -/*eslint-enable*/ -var a = b/*eslint-disable-line no-undef*/`, + /*eslint-disable*/ + /*eslint-enable*/ + var a = b/*eslint-disable-line no-undef*/`, errors: [ { message: "ESLint rules are disabled but never reported.", line: 2, - column: 1, + column: 7, endLine: 2, - endColumn: 19, + endColumn: 25, }, ], }, { code: `/*eslint no-undef:error*/ -/*eslint-disable no-undef*/ -/*eslint-enable no-undef*/ -var a = b//eslint-disable-line no-undef`, + /*eslint-disable no-undef*/ + /*eslint-enable no-undef*/ + var a = b//eslint-disable-line no-undef`, errors: [ { message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 18, + column: 24, endLine: 2, - endColumn: 26, + endColumn: 32, suggestions: [ { desc: "Remove `eslint-disable` comment.", fix: { - range: [26, 53], + range: [32, 59], text: "", }, }, @@ -717,70 +722,70 @@ var a = b//eslint-disable-line no-undef`, }, { code: `/*eslint no-undef:error*/ -/*eslint-disable no-undef*/ -/*eslint-enable no-undef*/ -var a = b/*eslint-disable-line no-undef*/`, + /*eslint-disable no-undef*/ + /*eslint-enable no-undef*/ + var a = b/*eslint-disable-line no-undef*/`, errors: [ { message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 18, + column: 24, endLine: 2, - endColumn: 26, + endColumn: 32, }, ], }, { code: `/*eslint no-undef:error, no-unused-vars:error*/ -/*eslint-disable no-undef,no-unused-vars*/ -/*eslint-enable no-undef*/ -var a = b//eslint-disable-line no-undef`, + /*eslint-disable no-undef,no-unused-vars*/ + /*eslint-enable no-undef*/ + var a = b//eslint-disable-line no-undef`, errors: [ { message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 18, + column: 24, endLine: 2, - endColumn: 26, + endColumn: 32, suggestions: [], }, ], }, { code: `/*eslint no-undef:error, no-unused-vars:error*/ -/*eslint-disable no-undef,no-unused-vars*/ -/*eslint-enable no-undef*/ -var a = b/*eslint-disable-line no-undef*/`, + /*eslint-disable no-undef,no-unused-vars*/ + /*eslint-enable no-undef*/ + var a = b/*eslint-disable-line no-undef*/`, errors: [ { message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 18, + column: 24, endLine: 2, - endColumn: 26, + endColumn: 32, }, ], }, { code: `/*eslint no-undef:error, no-unused-vars:error*/ -/*eslint-disable - no-undef, - no-unused-vars, - eqeqeq -*/ -var a = b -/*eslint-enable*/`, + /*eslint-disable + no-undef, + no-unused-vars, + eqeqeq + */ + var a = b + /*eslint-enable*/`, errors: [ { message: "'eqeqeq' rule is disabled but never reported.", line: 5, - column: 5, + column: 11, endLine: 5, - endColumn: 11, + endColumn: 17, suggestions: [], }, ], @@ -809,7 +814,7 @@ var a = b }, { code: `/*eslint no-undef:off*/ -var a = b //eslint-disable-line`, + var a = b //eslint-disable-line`, errors: [ { message: @@ -819,14 +824,14 @@ var a = b //eslint-disable-line`, message: "ESLint rules are disabled but never reported.", line: 2, - column: 11, + column: 17, endLine: 2, - endColumn: 32, + endColumn: 38, suggestions: [ { desc: "Remove `eslint-disable` comment.", fix: { - range: [34, 55], + range: [40, 61], text: "", }, }, @@ -837,7 +842,7 @@ var a = b //eslint-disable-line`, }, { code: `/*eslint no-undef:off*/ -var a = b /*eslint-disable-line*/`, + var a = b /*eslint-disable-line*/`, errors: [ { message: @@ -847,16 +852,16 @@ var a = b /*eslint-disable-line*/`, message: "ESLint rules are disabled but never reported.", line: 2, - column: 11, + column: 17, endLine: 2, - endColumn: 34, + endColumn: 40, }, ], reportUnusedDisableDirectives: true, }, { code: `/*eslint no-undef:off*/ -var a = b //eslint-disable-line no-undef`, + var a = b //eslint-disable-line no-undef`, errors: [ { message: @@ -866,14 +871,14 @@ var a = b //eslint-disable-line no-undef`, message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 33, + column: 39, endLine: 2, - endColumn: 41, + endColumn: 47, suggestions: [ { desc: "Remove `eslint-disable` comment.", fix: { - range: [34, 64], + range: [40, 70], text: "", }, }, @@ -884,7 +889,7 @@ var a = b //eslint-disable-line no-undef`, }, { code: `/*eslint no-undef:off*/ -var a = b /*eslint-disable-line no-undef*/`, + var a = b /*eslint-disable-line no-undef*/`, errors: [ { message: @@ -894,9 +899,9 @@ var a = b /*eslint-disable-line no-undef*/`, message: "'no-undef' rule is disabled but never reported.", line: 2, - column: 33, + column: 39, endLine: 2, - endColumn: 41, + endColumn: 47, }, ], reportUnusedDisableDirectives: true, @@ -906,15 +911,15 @@ var a = b /*eslint-disable-line no-undef*/`, ? [ { code: `/*eslint no-undef:off*/ -var a = b //eslint-disable-line -- description`, + var a = b //eslint-disable-line -- description`, errors: [ { message: "ESLint rules are disabled but never reported.", line: 2, - column: 11, + column: 17, endLine: 2, - endColumn: 47, + endColumn: 53, }, ], }, @@ -942,13 +947,13 @@ var a = b //eslint-disable-line -- description`, for (const key of Object.keys(expected)) { assert.deepStrictEqual( actual[key], - expected[key], - `'${key}' is not expected.` + expected[key as keyof typeof expected], + `'${key}' is not expected.`, ) } } - } - ) + }, + ), ) } }) diff --git a/tests/lib/rules/no-unused-enable.ts b/tests/lib/rules/no-unused-enable.test.ts similarity index 94% rename from tests/lib/rules/no-unused-enable.ts rename to tests/lib/rules/no-unused-enable.test.ts index 73c4bf7..91c40dc 100644 --- a/tests/lib/rules/no-unused-enable.ts +++ b/tests/lib/rules/no-unused-enable.test.ts @@ -2,11 +2,9 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" - -const semver = require("semver") -const { Linter, RuleTester } = require("eslint") -const rule = require("../../../lib/rules/no-unused-enable") +import { Linter, RuleTester } from "eslint" +import * as semver from "semver" +import rule from "../../../lib/rules/no-unused-enable.ts" const tester = new RuleTester() tester.run("no-unused-enable", rule, { diff --git a/tests/lib/rules/no-use.ts b/tests/lib/rules/no-use.test.ts similarity index 92% rename from tests/lib/rules/no-use.ts rename to tests/lib/rules/no-use.test.ts index 861f372..b96abb3 100644 --- a/tests/lib/rules/no-use.ts +++ b/tests/lib/rules/no-use.test.ts @@ -2,11 +2,11 @@ * @author Toru Nagashima * See LICENSE file in root directory for full license. */ -"use strict" +import cssPlugin from "@eslint/css" +import { Linter, RuleTester } from "eslint" +import semver from "semver" +import rule from "../../../lib/rules/no-use.ts" -const semver = require("semver") -const { Linter, RuleTester } = require("eslint") -const rule = require("../../../lib/rules/no-use") const tester = new RuleTester() tester.run("no-use", rule, { @@ -70,10 +70,10 @@ tester.run("no-use", rule, { code: "/* eslint-disable */ a {}", options: [{ allow: ["eslint-disable"] }], plugins: { - css: require("@eslint/css").default, + css: cssPlugin, }, language: "css/css", - }, + } as any, ] : []), ], @@ -128,11 +128,11 @@ tester.run("no-use", rule, { { code: "/* eslint-disable */ a {}", plugins: { - css: require("@eslint/css").default, + css: cssPlugin, }, language: "css/css", errors: ["Unexpected ESLint directive comment."], - }, + } as any, ] : []), ], diff --git a/tests/lib/rules/require-description.test.ts b/tests/lib/rules/require-description.test.ts new file mode 100644 index 0000000..29e314a --- /dev/null +++ b/tests/lib/rules/require-description.test.ts @@ -0,0 +1,209 @@ +/** + * @author Yosuke Ota + * See LICENSE file in root directory for full license. + */ +import { Linter, RuleTester } from "eslint" +import * as semver from "semver" +import rule from "../../../lib/rules/require-description.ts" +const tester = new RuleTester() + +if (semver.satisfies(Linter.version, ">=7.0.0")) { + // This rule can only be used with ESLint v7.x or later. + tester.run("require-description", rule, { + valid: [ + '/* eslint eqeqeq: "off", curly: "error" -- Here\'s a description about why this configuration is necessary. */', + "/* eslint-disable -- description */", + "/* eslint-enable -- description */", + "/* exported -- description */", + "/* global -- description */", + "/* globals -- description */", + "/* eslint-env -- description */", + "/* just eslint in a normal comment */", + "// eslint-disable-line -- description", + "// eslint-disable-next-line -- description", + "/* eslint-disable-line -- description */", + "/* eslint-disable-next-line -- description */", + "// eslint-disable-line eqeqeq -- description", + "// eslint-disable-next-line eqeqeq -- description", + { + code: "/* eslint */", + options: [{ ignore: ["eslint"] }], + }, + { + code: "/* eslint-env */", + options: [{ ignore: ["eslint-env"] }], + }, + { + code: "/* eslint-enable */", + options: [{ ignore: ["eslint-enable"] }], + }, + { + code: "/* eslint-disable */", + options: [{ ignore: ["eslint-disable"] }], + }, + { + code: "// eslint-disable-line", + options: [{ ignore: ["eslint-disable-line"] }], + }, + { + code: "// eslint-disable-next-line", + options: [{ ignore: ["eslint-disable-next-line"] }], + }, + { + code: "/* eslint-disable-line */", + options: [{ ignore: ["eslint-disable-line"] }], + }, + { + code: "/* eslint-disable-next-line */", + options: [{ ignore: ["eslint-disable-next-line"] }], + }, + { + code: "/* exported */", + options: [{ ignore: ["exported"] }], + }, + { + code: "/* global */", + options: [{ ignore: ["global"] }], + }, + { + code: "/* globals */", + options: [{ ignore: ["globals"] }], + }, + ], + invalid: [ + { + code: "/* eslint */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: '/* eslint eqeqeq: "off", curly: "error" */', + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "/* eslint-env */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "/* eslint-env node */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "/* eslint-enable */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "/* eslint-enable eqeqeq */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "/* eslint-disable */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "/* eslint-disable eqeqeq */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "// eslint-disable-line", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "// eslint-disable-line eqeqeq", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "// eslint-disable-next-line", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "// eslint-disable-next-line eqeqeq", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "/* eslint-disable-line */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "/* eslint-disable-line eqeqeq */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "/* eslint-disable-next-line */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "/* eslint-disable-next-line eqeqeq */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "/* exported */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "/* global */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "/* global _ */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "/* globals */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + { + code: "/* globals _ */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + // empty description + { + code: "/* eslint-disable-next-line eqeqeq -- */", + errors: [ + "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", + ], + }, + ], + }) +} diff --git a/tests/lib/rules/require-description.ts b/tests/lib/rules/require-description.ts deleted file mode 100644 index 56b6f08..0000000 --- a/tests/lib/rules/require-description.ts +++ /dev/null @@ -1,241 +0,0 @@ -/** - * @author Yosuke Ota - * See LICENSE file in root directory for full license. - */ -"use strict" - -const semver = require("semver") -const { Linter, RuleTester } = require("eslint") -const rule = require("../../../lib/rules/require-description") -const tester = new RuleTester() - -if (!semver.satisfies(Linter.version, ">=7.0.0")) { - // This rule can only be used with ESLint v7.x or later. - return -} - -tester.run("require-description", rule, { - valid: [ - '/* eslint eqeqeq: "off", curly: "error" -- Here\'s a description about why this configuration is necessary. */', - "/* eslint-disable -- description */", - "/* eslint-enable -- description */", - "/* exported -- description */", - "/* global -- description */", - "/* globals -- description */", - "/* eslint-env -- description */", - "/* just eslint in a normal comment */", - "// eslint-disable-line -- description", - "// eslint-disable-next-line -- description", - "/* eslint-disable-line -- description */", - "/* eslint-disable-next-line -- description */", - "// eslint-disable-line eqeqeq -- description", - "// eslint-disable-next-line eqeqeq -- description", - { - code: "/* eslint */", - options: [{ ignore: ["eslint"] }], - }, - { - code: "/* eslint-env */", - options: [{ ignore: ["eslint-env"] }], - }, - { - code: "/* eslint-enable */", - options: [{ ignore: ["eslint-enable"] }], - }, - { - code: "/* eslint-disable */", - options: [{ ignore: ["eslint-disable"] }], - }, - { - code: "// eslint-disable-line", - options: [{ ignore: ["eslint-disable-line"] }], - }, - { - code: "// eslint-disable-next-line", - options: [{ ignore: ["eslint-disable-next-line"] }], - }, - { - code: "/* eslint-disable-line */", - options: [{ ignore: ["eslint-disable-line"] }], - }, - { - code: "/* eslint-disable-next-line */", - options: [{ ignore: ["eslint-disable-next-line"] }], - }, - { - code: "/* exported */", - options: [{ ignore: ["exported"] }], - }, - { - code: "/* global */", - options: [{ ignore: ["global"] }], - }, - { - code: "/* globals */", - options: [{ ignore: ["globals"] }], - }, - // Language plugin - ...(semver.satisfies(Linter.version, ">=9.6.0") - ? [ - { - code: "/* eslint-disable */ a {}", - options: [{ ignore: ["eslint-disable"] }], - plugins: { - css: require("@eslint/css").default, - }, - language: "css/css", - }, - ] - : []), - ], - invalid: [ - { - code: "/* eslint */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: '/* eslint eqeqeq: "off", curly: "error" */', - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "/* eslint-env */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "/* eslint-env node */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "/* eslint-enable */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "/* eslint-enable eqeqeq */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "/* eslint-disable */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "/* eslint-disable eqeqeq */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "// eslint-disable-line", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "// eslint-disable-line eqeqeq", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "// eslint-disable-next-line", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "// eslint-disable-next-line eqeqeq", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "/* eslint-disable-line */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "/* eslint-disable-line eqeqeq */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "/* eslint-disable-next-line */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "/* eslint-disable-next-line eqeqeq */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "/* exported */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "/* global */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "/* global _ */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "/* globals */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - { - code: "/* globals _ */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - // empty description - { - code: "/* eslint-disable-next-line eqeqeq -- */", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - // Language plugin - ...(semver.satisfies(Linter.version, ">=9.6.0") - ? [ - { - code: "/* eslint-disable */ a {}", - plugins: { - css: require("@eslint/css").default, - }, - language: "css/css", - errors: [ - "Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.", - ], - }, - ] - : []), - ], -}) diff --git a/tests/types/configs.test-d.cts b/tests/types/configs.test-d.cts index 540bd48..0843bf5 100644 --- a/tests/types/configs.test-d.cts +++ b/tests/types/configs.test-d.cts @@ -1,18 +1,26 @@ import configs = require("@eslint-community/eslint-plugin-eslint-comments/configs") import expectTypeModule = require("expect-type") +import eslintV9ConfigModule = require("eslint-v9/config") -import type { Linter } from "eslint" +import type { Linter } from "eslint" with { "resolution-mode": "require" }; import expectTypeOf = expectTypeModule.expectTypeOf +import defineConfig = eslintV9ConfigModule.defineConfig expectTypeOf(configs) .toHaveProperty("recommended") - .toMatchTypeOf() + .toExtend() -expectTypeOf([configs.recommended]).toMatchTypeOf() +expectTypeOf([configs.recommended]).toExtend() -expectTypeOf(configs.recommended).toMatchTypeOf() +configs.recommended satisfies Linter.FlatConfig -expectTypeOf(configs) - .toHaveProperty("recommended") - .toMatchTypeOf() +expectTypeOf().toExtend() + +expectTypeOf(defineConfig).toBeCallableWith< + Pick< + typeof configs.recommended, + | "rules" + | "name" + >[] +>() diff --git a/tests/types/configs.test-d.mts b/tests/types/configs.test-d.mts index 9e9ff73..345cc9e 100644 --- a/tests/types/configs.test-d.mts +++ b/tests/types/configs.test-d.mts @@ -1,15 +1,30 @@ -import configs from "@eslint-community/eslint-plugin-eslint-comments/configs" -import type { Linter } from "eslint" -import { expectTypeOf } from "expect-type" +import * as configs from "@eslint-community/eslint-plugin-eslint-comments/configs"; +import type { Linter } from "eslint" with { "resolution-mode": "import" }; +import { defineConfig } from "eslint-v9/config"; +import { expectTypeOf } from "expect-type"; expectTypeOf(configs) .toHaveProperty("recommended") - .toMatchTypeOf() + .toExtend() -expectTypeOf([configs.recommended]).toMatchTypeOf() +expectTypeOf([configs.recommended]).toExtend() -expectTypeOf(configs.recommended).toMatchTypeOf() +expectTypeOf(configs.recommended).toExtend() expectTypeOf(configs) .toHaveProperty("recommended") - .toMatchTypeOf() + .toExtend() + +expectTypeOf([configs.recommended]).toExtend() + +configs.recommended satisfies Linter.FlatConfig + +expectTypeOf().toExtend() + +expectTypeOf(defineConfig).toBeCallableWith< + Pick< + typeof configs.recommended, + | "rules" + | "name" + >[] +>() diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..d089852 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "noEmit": false, + "outDir": "./dist" + }, + "extends": "./tsconfig.json", + "include": ["lib", "configs.ts", "index.ts"] +} diff --git a/tsconfig.json b/tsconfig.json index 99d54ef..35026e2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,20 +1,30 @@ { "compilerOptions": { - "allowSyntheticDefaultImports": false, - "esModuleInterop": false, + "allowImportingTsExtensions": true, + "allowSyntheticDefaultImports": true, + "declaration": true, + "declarationMap": true, + "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "isolatedModules": true, "lib": ["ESNext"], - "module": "NodeNext", - "moduleResolution": "NodeNext", + "module": "nodenext", + "moduleDetection": "force", + "moduleResolution": "nodenext", "noEmit": true, + "noEmitOnError": true, + "noErrorTruncation": true, + "outDir": "./dist", "resolveJsonModule": true, + "rewriteRelativeImportExtensions": true, + "rootDir": "./", "skipLibCheck": true, + "sourceMap": true, "strict": true, - "target": "ESNext", + "target": "esnext", + "types": ["node", "mocha"], "useDefineForClassFields": true, - "useUnknownInCatchVariables": true, - "verbatimModuleSyntax": true + "useUnknownInCatchVariables": true }, - "include": ["."] + "include": ["**/*"] } diff --git a/tsup.config.mts b/tsup.config.mts new file mode 100644 index 0000000..b44a704 --- /dev/null +++ b/tsup.config.mts @@ -0,0 +1,55 @@ +import * as path from 'node:path' +import { fileURLToPath } from 'node:url' +import type { Options } from 'tsup' +import { defineConfig } from 'tsup' +import packageJson from './package.json' with { type: 'json' } + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +const tsupConfig = defineConfig((overrideOptions): Options[] => { + const commonOptions = { + clean: false, + entry: { + index: path.join(__dirname, 'index.ts'), + configs: path.join(__dirname, 'configs.ts'), + }, + removeNodeProtocol: false, + shims: true, + sourcemap: true, + splitting: false, + target: ['esnext', 'node20'], + tsconfig: path.join(__dirname, 'tsconfig.build.json'), + ...overrideOptions, + } as const satisfies Options + + return [ + { + ...commonOptions, + name: `${packageJson.name} Modern ESM`, + format: ['esm'], + }, + { + ...commonOptions, + name: `${packageJson.name} CJS Development`, + format: ['cjs'], + }, + { + ...commonOptions, + name: `${packageJson.name} ESM Type definitions`, + dts: { + only: true, + }, + format: ['esm'], + }, + { + ...commonOptions, + name: `${packageJson.name} CJS Type definitions`, + dts: { + only: true, + }, + format: ['cjs'], + }, + ] as const satisfies Options[] +}) + +export default tsupConfig diff --git a/types/configs.d.ts b/types/configs.d.ts deleted file mode 100644 index 011b635..0000000 --- a/types/configs.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { Linter } from "eslint" - -declare namespace Configs { - import defaultExports = Configs - - export const recommended: Linter.FlatConfig - - export { defaultExports as default } -} - -export = Configs diff --git a/types/index.d.ts b/types/index.d.ts deleted file mode 100644 index 765fd2c..0000000 --- a/types/index.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { ESLint, Linter } from "eslint" - -export declare const configs: { recommended: Linter.FlatConfig } - -export declare const rules: NonNullable - -export declare const utils: { patch: (ruleId?: string) => void } From 70b63c0af8e5eff1b135a9834bed931f74e3ac1b Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Tue, 25 Nov 2025 04:38:28 -0600 Subject: [PATCH 6/7] migrate to `tsdown` --- configs.ts | 9 ++++---- package.json | 8 +++---- tsdown.config.mts | 57 +++++++++++++++++++++++++++++++++++++++++++++++ tsup.config.mts | 55 --------------------------------------------- 4 files changed, 66 insertions(+), 63 deletions(-) create mode 100644 tsdown.config.mts delete mode 100644 tsup.config.mts diff --git a/configs.ts b/configs.ts index 8a4046f..9c9a073 100644 --- a/configs.ts +++ b/configs.ts @@ -1,9 +1,7 @@ import type { ESLint, Linter, Rule } from "eslint" import { rulesRecommended } from "./lib/configs/recommended.ts" import { rules } from "./lib/rules.ts" -import packageJson from "./package.json" with { type: "json" }; - -const { name, version } = packageJson +import packageJson from "./package.json" with { type: "json" } const plugin: { meta: { @@ -22,7 +20,10 @@ const plugin: { "require-description": Rule.RuleModule } } = { - meta: { name, version }, + meta: { + name: packageJson.name, + version: packageJson.version, + }, rules, } as const satisfies ESLint.Plugin diff --git a/package.json b/package.json index 478f4ec..793ee2b 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,6 @@ "types": "./dist/index.d.ts", "type": "module", "files": [ - "configs.js", "lib", "dist" ], @@ -43,6 +42,7 @@ ] } }, + "sideEffects": false, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" }, @@ -65,7 +65,7 @@ "@types/semver": "^7.7.1", "@vuepress/plugin-pwa": "^1.9.9", "cross-spawn": "^7.0.3", - "esbuild": "^0.19.3", + "esbuild": "^0.27.0", "eslint": "^8.57.1", "eslint-v9": "npm:eslint@^9.39.1", "expect-type": "^1.2.2", @@ -76,13 +76,13 @@ "opener": "^1.5.2", "rimraf": "^3.0.2", "semver": "^7.7.3", - "tsup": "^8.5.1", + "tsdown": "^0.16.6", "typescript": "^5.9.3", "vite-plugin-eslint4b": "^0.6.0", "vitepress": "^1.0.0-rc.15" }, "scripts": { - "build": "rimraf dist/ && tsup --config=tsup.config.mts", + "build": "rimraf dist/ && tsdown --config-loader='unrun' --config=tsdown.config.mts", "prepack": "npm run clean && npm run build", "preversion": "npm test", "version": "node scripts/update && git add .", diff --git a/tsdown.config.mts b/tsdown.config.mts new file mode 100644 index 0000000..3959914 --- /dev/null +++ b/tsdown.config.mts @@ -0,0 +1,57 @@ +import * as path from "node:path" +import { fileURLToPath } from "node:url" +import type { InlineConfig, UserConfig } from "tsdown" +import { defineConfig } from "tsdown" +import packageJson from "./package.json" with { type: "json" } + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +const tsdownConfig = defineConfig((cliOptions) => { + const commonOptions = { + clean: false, + cwd: __dirname, + dts: { + emitJs: false, + newContext: true, + oxc: false, + resolver: "tsc", + sideEffects: false, + sourcemap: true, + }, + entry: { + index: "index.ts", + configs: "configs.ts", + }, + minify: "dce-only", + treeshake: { + annotations: true, + commonjs: true, + moduleSideEffects: false, + }, + failOnWarn: true, + fixedExtension: false, + format: ["es", "cjs"], + hash: false, + nodeProtocol: true, + shims: true, + sourcemap: true, + outExtensions: ({ format }) => ({ + dts: format === "cjs" ? ".d.cts" : ".d.ts", + js: format === "cjs" ? ".cjs" : ".js", + }), + outDir: "dist", + platform: "node", + target: ["esnext", "node20"], + tsconfig: path.join(__dirname, "tsconfig.build.json"), + ...cliOptions, + } as const satisfies InlineConfig + + return [ + { + ...commonOptions, + name: `${packageJson.name} Modern Dual Format`, + }, + ] as const satisfies UserConfig[] +}) + +export default tsdownConfig diff --git a/tsup.config.mts b/tsup.config.mts deleted file mode 100644 index b44a704..0000000 --- a/tsup.config.mts +++ /dev/null @@ -1,55 +0,0 @@ -import * as path from 'node:path' -import { fileURLToPath } from 'node:url' -import type { Options } from 'tsup' -import { defineConfig } from 'tsup' -import packageJson from './package.json' with { type: 'json' } - -const __dirname = path.dirname(fileURLToPath(import.meta.url)) - -const tsupConfig = defineConfig((overrideOptions): Options[] => { - const commonOptions = { - clean: false, - entry: { - index: path.join(__dirname, 'index.ts'), - configs: path.join(__dirname, 'configs.ts'), - }, - removeNodeProtocol: false, - shims: true, - sourcemap: true, - splitting: false, - target: ['esnext', 'node20'], - tsconfig: path.join(__dirname, 'tsconfig.build.json'), - ...overrideOptions, - } as const satisfies Options - - return [ - { - ...commonOptions, - name: `${packageJson.name} Modern ESM`, - format: ['esm'], - }, - { - ...commonOptions, - name: `${packageJson.name} CJS Development`, - format: ['cjs'], - }, - { - ...commonOptions, - name: `${packageJson.name} ESM Type definitions`, - dts: { - only: true, - }, - format: ['esm'], - }, - { - ...commonOptions, - name: `${packageJson.name} CJS Type definitions`, - dts: { - only: true, - }, - format: ['cjs'], - }, - ] as const satisfies Options[] -}) - -export default tsupConfig From 060a6548813ae051ff3286d6782c3ac63e75bb96 Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Tue, 25 Nov 2025 05:30:14 -0600 Subject: [PATCH 7/7] fix CI --- .github/workflows/ci.yml | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 94ce7dc..9d2677e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,17 @@ name: CI -on: [push, pull_request, workflow_dispatch] +on: + push: + branches: + # default semantic-release branches + - +([0-9])?(.{+([0-9]),x}).x + - main + - next + - next-major + - beta + - alpha + pull_request: + schedule: + - cron: 0 0 * * 0 concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -52,16 +64,16 @@ jobs: node: 22 os: ubuntu-latest # On old ESLint versions - # - eslint: 7 - # node: 22 - # os: ubuntu-latest - # - eslint: 6 - # node: 22 - # os: ubuntu-latest + - eslint: 7 + node: 22 + os: ubuntu-latest + - eslint: 6 + node: 22 + os: ubuntu-latest # On the minimum supported ESLint/Node.js version - # - eslint: 6.0.0 - # node: 12.22.0 - # os: ubuntu-latest + - eslint: 6.0.0 + node: 12.22.0 + os: ubuntu-latest runs-on: ${{ matrix.os }} steps: - name: ⬇️ Checkout repo @@ -120,7 +132,7 @@ jobs: matrix: eslint: [8, 9] - ts: ["5.7", "5.8"] + ts: ["5.7", "5.8", "5.9"] steps: - name: ⬇️ Checkout repo @@ -168,7 +180,11 @@ jobs: - name: ⎔ Setup node uses: actions/setup-node@v4 with: - node-version: 22 + node-version: lts/* + + # npm 11.5.1 or later is required so update to latest to be sure + - name: Update npm + run: npm install -g npm@latest - name: 📥 Install dependencies run: npm install --legacy-peer-deps