diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2c07b42f..5b8bdf14 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,6 +71,21 @@ jobs: run: npm install eslint@${{ matrix.eslint }} - name: Test run: npm run test + test_types: + name: Test Types + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "lts/*" + - name: Install dependencies + run: npm install + - name: Build + run: npm run build + - name: Check Types + run: npm run test:types jsr_test: name: Verify JSR Publish runs-on: ubuntu-latest diff --git a/package.json b/package.json index e0c25fbb..518a576b 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,8 @@ "build:readme": "node tools/update-readme.js", "prepare": "node ./npm-prepare.cjs && npm run build", "test": "c8 mocha \"tests/**/*.test.js\" --timeout 30000", - "test:jsr": "npx jsr@latest publish --dry-run" + "test:jsr": "npx jsr@latest publish --dry-run", + "test:types": "tsc -p tests/types/tsconfig.json" }, "devDependencies": { "@eslint/core": "^0.6.0", diff --git a/src/index.js b/src/index.js index 98075217..543ca9c2 100644 --- a/src/index.js +++ b/src/index.js @@ -49,7 +49,8 @@ const processorRulesConfig = { "unicode-bom": "off", }; -/** @type {Plugin & { languages: Record}} */ +let recommendedPlugins, processorPlugins; + const plugin = { meta: { name: "@eslint/markdown", @@ -88,52 +89,48 @@ const plugin = { }, ], }, - }, -}; - -plugin.configs.recommended = [ - /** @type {Config & {language:string}} */ - ({ - name: "markdown/recommended", - files: ["**/*.md"], - language: "markdown/commonmark", - plugins: { - markdown: plugin, - }, - rules: /** @type {RulesRecord} */ (recommendedRules), - }), -]; - -plugin.configs.processor = [ - { - name: "markdown/recommended/plugin", - plugins: { - markdown: plugin, - }, - }, - { - name: "markdown/recommended/processor", - files: ["**/*.md"], - processor: "markdown/markdown", - }, - { - name: "markdown/recommended/code-blocks", - files: ["**/*.md/**"], - languageOptions: { - parserOptions: { - ecmaFeatures: { - // Adding a "use strict" directive at the top of - // every code block is tedious and distracting, so - // opt into strict mode parsing without the - // directive. - impliedStrict: true, + recommended: [ + { + name: "markdown/recommended", + files: ["**/*.md"], + language: "markdown/commonmark", + plugins: (recommendedPlugins = {}), + rules: recommendedRules, + }, + ], + processor: [ + { + name: "markdown/recommended/plugin", + plugins: (processorPlugins = {}), + }, + { + name: "markdown/recommended/processor", + files: ["**/*.md"], + processor: "markdown/markdown", + }, + { + name: "markdown/recommended/code-blocks", + files: ["**/*.md/**"], + languageOptions: { + parserOptions: { + ecmaFeatures: { + // Adding a "use strict" directive at the top of + // every code block is tedious and distracting, so + // opt into strict mode parsing without the + // directive. + impliedStrict: true, + }, + }, + }, + rules: { + ...processorRulesConfig, }, }, - }, - rules: { - ...processorRulesConfig, - }, + ], }, -]; +}; + +// @ts-expect-error +recommendedPlugins.markdown = processorPlugins.markdown = plugin; export default plugin; diff --git a/tests/types/tsconfig.json b/tests/types/tsconfig.json new file mode 100644 index 00000000..0684aabb --- /dev/null +++ b/tests/types/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "emitDeclarationOnly": false, + "noEmit": true, + "rootDir": "../..", + "strict": true + }, + "files": ["../../dist/esm/index.d.ts", "types.test.ts"] +} diff --git a/tests/types/types.test.ts b/tests/types/types.test.ts new file mode 100644 index 00000000..39db9529 --- /dev/null +++ b/tests/types/types.test.ts @@ -0,0 +1,37 @@ +import markdown from "@eslint/markdown"; +import { ESLint, Linter } from "eslint"; + +markdown satisfies ESLint.Plugin; +markdown.meta.name satisfies string; +markdown.meta.version satisfies string; + +// Check that the processor is defined: +markdown.processors.markdown satisfies object; + +// Check that these languages are defined: +markdown.languages.commonmark satisfies object; +markdown.languages.gfm satisfies object; + +markdown.configs["recommended-legacy"] satisfies Linter.LegacyConfig; +markdown.configs.recommended satisfies Linter.Config[]; +markdown.configs.processor satisfies Linter.Config[]; + +// Check that `plugins` in the recommended config is empty: +const [{ plugins: recommendedPlugins }] = markdown.configs.recommended; +typeof recommendedPlugins satisfies {}; +({}) satisfies typeof recommendedPlugins; + +// Check that `plugins` in the processor config is empty: +const [{ plugins: processorPlugins }] = markdown.configs.processor; +typeof processorPlugins satisfies {}; +({}) satisfies typeof processorPlugins; + +{ + type RecommendedRuleName = + keyof (typeof markdown.configs.recommended)[0]["rules"]; + type RuleName = `markdown/${keyof typeof markdown.rules}`; + type AssertAllNamesIn = never; + + // Check that all recommended rule names match the names of existing rules in this plugin. + null as AssertAllNamesIn; +} diff --git a/tools/build-rules.js b/tools/build-rules.js index e67fe592..4ee4eaae 100644 --- a/tools/build-rules.js +++ b/tools/build-rules.js @@ -33,9 +33,11 @@ for (const ruleId of rules) { } } -const output = `export default { +const output = `const rules = /** @type {const} */ ({ ${recommended.map(id => `"markdown/${id.slice(0, -3)}": "error"`).join(",\n ")} -}; +}); + +export default rules; `; fs.mkdirSync(path.resolve(thisDir, "../src/build"), { recursive: true });