diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dcfd299..1e9fe2f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,7 +55,7 @@ jobs: CI: true test_types: - name: Test Types (core) + name: Test Types runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -65,13 +65,20 @@ jobs: with: node-version: "lts/*" - - name: npm install and test types + - name: npm install and test types (core) working-directory: packages/core run: | npm install npm run build npm run test:types + - name: npm install and test types (plugin-kit) + working-directory: packages/plugin-kit + run: | + npm install + npm run build + npm run test:types + jsr_test: name: Verify JSR Publish runs-on: ubuntu-latest diff --git a/packages/core/tests/types/cjs-import.test.cts b/packages/core/tests/types/cjs-import.test.cts new file mode 100644 index 0000000..2fe5e2b --- /dev/null +++ b/packages/core/tests/types/cjs-import.test.cts @@ -0,0 +1,10 @@ +/** + * @fileoverview CommonJS type import test for ESLint Core. + * @author Francesco Trotta + */ + +//----------------------------------------------------------------------------- +// Imports +//----------------------------------------------------------------------------- + +import "@eslint/core"; diff --git a/packages/core/tests/types/tsconfig.json b/packages/core/tests/types/tsconfig.json index 7bbf5d8..b3220a7 100644 --- a/packages/core/tests/types/tsconfig.json +++ b/packages/core/tests/types/tsconfig.json @@ -5,5 +5,5 @@ "rootDir": "../..", "strict": true }, - "files": ["../../dist/esm/types.d.ts", "types.test.ts"] + "include": [".", "../../dist"] } diff --git a/packages/plugin-kit/build-cts.js b/packages/plugin-kit/build-cts.js new file mode 100644 index 0000000..ade7ae1 --- /dev/null +++ b/packages/plugin-kit/build-cts.js @@ -0,0 +1,16 @@ +/** + * @fileoverview Rewrites import expressions for CommonJS compatibility. + * This script creates "dist/cjs/index.d.cts" from "dist/esm/index.d.ts" by modifying imports + * from `"./types.ts"` to `"./types.cts"`. + * + * @author Francesco Trotta + */ + +import { readFile, writeFile } from "node:fs/promises"; + +const oldSourceText = await readFile("dist/esm/index.d.ts", "utf-8"); +const newSourceText = oldSourceText.replaceAll( + 'import("./types.ts")', + 'import("./types.cts")', +); +await writeFile("dist/cjs/index.d.cts", newSourceText); diff --git a/packages/plugin-kit/package.json b/packages/plugin-kit/package.json index 8113a4f..ebe694e 100644 --- a/packages/plugin-kit/package.json +++ b/packages/plugin-kit/package.json @@ -32,12 +32,13 @@ "homepage": "https://github.com/eslint/rewrite#readme", "scripts": { "build:dedupe-types": "node ../../tools/dedupe-types.js dist/cjs/index.cjs dist/esm/index.js", - "build:cts": "node -e \"fs.copyFileSync('dist/esm/index.d.ts', 'dist/cjs/index.d.cts')\"", + "build:cts": "node ./build-cts.js", "build": "rollup -c && npm run build:dedupe-types && tsc -p tsconfig.esm.json && npm run build:cts", - "test:jsr": "npx jsr@latest publish --dry-run", "pretest": "npm run build", "test": "mocha tests/", - "test:coverage": "c8 npm test" + "test:coverage": "c8 npm test", + "test:jsr": "npx jsr@latest publish --dry-run", + "test:types": "tsc -p tests/types/tsconfig.json" }, "keywords": [ "eslint", @@ -45,8 +46,11 @@ "eslint-plugin" ], "license": "Apache-2.0", - "devDependencies": { + "dependencies": { "@eslint/core": "^0.9.1", + "levn": "^0.4.1" + }, + "devDependencies": { "@types/levn": "^0.4.0", "c8": "^9.1.0", "mocha": "^10.4.0", @@ -56,8 +60,5 @@ }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "dependencies": { - "levn": "^0.4.1" } } diff --git a/packages/plugin-kit/rollup.config.js b/packages/plugin-kit/rollup.config.js index 03a11e1..5b813a8 100644 --- a/packages/plugin-kit/rollup.config.js +++ b/packages/plugin-kit/rollup.config.js @@ -16,7 +16,7 @@ export default { plugins: [ copy({ targets: [ - { src: "src/types.ts", dest: "dist/cjs" }, + { src: "src/types.ts", dest: "dist/cjs", rename: "types.cts" }, { src: "src/types.ts", dest: "dist/esm" }, ], }), diff --git a/packages/plugin-kit/tests/types/cjs-import.test.cts b/packages/plugin-kit/tests/types/cjs-import.test.cts new file mode 100644 index 0000000..98a6ed1 --- /dev/null +++ b/packages/plugin-kit/tests/types/cjs-import.test.cts @@ -0,0 +1,10 @@ +/** + * @fileoverview CommonJS type import test for ESLint Plugin Kit. + * @author Francesco Trotta + */ + +//----------------------------------------------------------------------------- +// Imports +//----------------------------------------------------------------------------- + +import "@eslint/plugin-kit"; diff --git a/packages/plugin-kit/tests/types/tsconfig.json b/packages/plugin-kit/tests/types/tsconfig.json new file mode 100644 index 0000000..b3220a7 --- /dev/null +++ b/packages/plugin-kit/tests/types/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "noEmit": true, + "rootDir": "../..", + "strict": true + }, + "include": [".", "../../dist"] +} diff --git a/packages/plugin-kit/tests/types/types.test.ts b/packages/plugin-kit/tests/types/types.test.ts new file mode 100644 index 0000000..6643887 --- /dev/null +++ b/packages/plugin-kit/tests/types/types.test.ts @@ -0,0 +1,104 @@ +/** + * @fileoverview Type tests for ESLint Plugin Kit. + * @author Francesco Trotta + */ + +//----------------------------------------------------------------------------- +// Imports +//----------------------------------------------------------------------------- + +import { + BooleanConfig, + CallMethodStep, + ConfigCommentParser, + Directive, + DirectiveType, + RulesConfig, + SourceLocation, + SourceRange, + StringConfig, + TextSourceCodeBase, + VisitNodeStep, +} from "@eslint/plugin-kit"; + +//----------------------------------------------------------------------------- +// Tests +//----------------------------------------------------------------------------- + +// CallMethodStep +class TestCallMethodStep extends CallMethodStep { + constructor({ target, args }: { target: string; args: [string, number] }) { + super({ target, args }); + } +} +const step2 = new TestCallMethodStep({ target: "foo", args: ["foo", 42] }); +step2.args satisfies unknown[]; +step2.kind satisfies 2; +step2.target satisfies string; +step2.type satisfies "call"; + +// ConfigCommentParser +const configCommentParser = new ConfigCommentParser(); +configCommentParser.parseDirective("foo") satisfies + | { label: string; value: string; justification: string } + | undefined; +const jsonLikeConfig = configCommentParser.parseJSONLikeConfig("bar"); +if (jsonLikeConfig.ok) { + jsonLikeConfig.config satisfies RulesConfig; +} else { + jsonLikeConfig.error.message satisfies string; +} +configCommentParser.parseListConfig("baz") satisfies BooleanConfig; +configCommentParser.parseStringConfig("qux") satisfies StringConfig; + +// Directive +void ((type: "disable" | "enable" | "disable-next-line" | "disable-line") => { + const directive = new Directive({ + type, + node: {}, + value: "foo", + justification: "bar", + }); + directive.justification satisfies string; + directive.node satisfies unknown; + directive.type satisfies DirectiveType; + directive.value satisfies string; +}); + +// TextSourceCodeBase +class TestTextSourceCode extends TextSourceCodeBase { + declare ast: { foo: string; bar: number }; + constructor({ + text, + ast, + }: { + text: string; + ast: { foo: string; bar: number }; + }) { + super({ text, ast, lineEndingPattern: /\r\n|[\r\n\u2028\u2029]/u }); + } +} +const sourceCode = new TestTextSourceCode({ + text: "text", + ast: { foo: "ABC", bar: 123 }, +}); +sourceCode.ast satisfies { foo: string; bar: number }; +sourceCode.getAncestors({}) satisfies object[]; +sourceCode.getLoc({}) satisfies SourceLocation; +sourceCode.getParent({}) satisfies object | undefined; +sourceCode.getRange({}) satisfies SourceRange; +sourceCode.getText() satisfies string; +sourceCode.getText({}, 0, 1) satisfies string; + +// VisitNodeStep +class TestVisitNodeStep extends VisitNodeStep { + constructor({ target, phase }: { target: object; phase: 1 | 2 }) { + super({ target, phase, args: ["foo", 42] }); + } +} +const step1 = new TestVisitNodeStep({ target: { foo: "bar" }, phase: 2 }); +step1.args satisfies unknown[]; +step1.kind satisfies 1; +step1.phase satisfies 1 | 2; +step1.target satisfies object; +step1.type satisfies "visit";