diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 23c105a71..60ed2a3b1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,6 +87,10 @@ jobs: run: npm install - name: Check Types run: npm run test:types + - name: Check Types (TypeScript 5.x) + run: npm run test:types:5.x + - name: Check Types (TypeScript 5.3) + run: npm run test:types:5.3 are_the_types_wrong: name: Are the types wrong? diff --git a/eslint.config-content.js b/eslint.config-content.js index 506ab8af5..a1bcc887e 100644 --- a/eslint.config-content.js +++ b/eslint.config-content.js @@ -3,7 +3,7 @@ import markdown from "./src/index.js"; export default defineConfig([ globalIgnores( - ["**/*.js", "**/.cjs", "**/.mjs"], + ["**/*.js", "**/.cjs", "**/.mjs", "tests/fixtures/"], "markdown/content/ignores", ), diff --git a/eslint.config.js b/eslint.config.js index 858a4e7eb..19cf1c39f 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -134,9 +134,18 @@ export default defineConfig([ }, }, { + name: "json/json", plugins: { json }, files: ["**/*.json", ".c8rc"], language: "json/json", extends: ["json/recommended"], }, + { + name: "json/jsonc", + plugins: { json }, + files: ["**/*.jsonc", "**/tsconfig*.json"], + language: "json/jsonc", + languageOptions: { allowTrailingCommas: true }, + extends: ["json/recommended"], + }, ]); diff --git a/examples/typescript/package.json b/examples/typescript/package.json index fe1d19147..c0e27186d 100644 --- a/examples/typescript/package.json +++ b/examples/typescript/package.json @@ -7,7 +7,7 @@ "devDependencies": { "@eslint/js": "^10.0.1", "eslint": "^10.0.3", - "typescript": "^5.9.3", - "typescript-eslint": "^8.57.0" + "typescript": "^6.0.3", + "typescript-eslint": "^8.59.1" } } diff --git a/package.json b/package.json index 51abe5127..0a7e45a7e 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,10 @@ "test": "mocha \"tests/**/*.test.js\" --timeout 30000", "test:coverage": "c8 npm test", "test:jsr": "npx -y jsr@latest publish --dry-run", - "test:types": "tsc -p tests/types/tsconfig.json" + "test:types": "tsc -p tests/types/tsconfig.json", + "test:types:5.x": "npx -p typescript@5.x -y -- tsc -p tests/types/tsconfig.json", + "test:types:5.3": "npx -p typescript@5.3 -y -- tsc -p tests/types/tsconfig.legacy.json", + "test:types:all": "npm run test:types && npm run test:types:5.x && npm run test:types:5.3" }, "devDependencies": { "@arethetypeswrong/cli": "^0.18.2", @@ -90,7 +93,7 @@ "mocha": "^11.7.5", "prettier": "3.8.3", "semver": "^7.7.3", - "typescript": "^5.9.3", + "typescript": "^6.0.3", "yorkie": "^2.0.0" }, "dependencies": { diff --git a/src/index.js b/src/index.js index 1a827a031..cd90e053b 100644 --- a/src/index.js +++ b/src/index.js @@ -129,8 +129,8 @@ const plugin = { }, }; -// @ts-expect-error -recommendedPlugins.markdown = processorPlugins.markdown = plugin; +Object.assign(recommendedPlugins, { markdown: plugin }); +Object.assign(processorPlugins, { markdown: plugin }); export default plugin; export { MarkdownSourceCode }; diff --git a/src/language/markdown-source-code.js b/src/language/markdown-source-code.js index ddb8f84c0..97c54aacf 100644 --- a/src/language/markdown-source-code.js +++ b/src/language/markdown-source-code.js @@ -21,7 +21,7 @@ import { lineEndingPattern } from "../util.js"; /** * @import { Position } from "unist"; - * @import { Root, Node, Html } from "mdast"; + * @import { Parent, Root, Node, Html } from "mdast"; * @import { TraversalStep, FileProblem, DirectiveType, RulesConfig } from "@eslint/core"; * @import { MarkdownLanguageOptions } from "../types.js"; */ @@ -77,7 +77,7 @@ function extractInlineConfigCommentsFromHTML(node, sourceCode) { /** @type {Array} */ const comments = []; - /** @type {RegExpExecArray} */ + /** @type {RegExpExecArray | null} */ let match; while ((match = htmlComment.exec(node.value))) { @@ -124,7 +124,7 @@ export class MarkdownSourceCode extends TextSourceCodeBase { /** * Cache of parent nodes. - * @type {WeakMap} + * @type {WeakMap} */ #parents = new WeakMap(); @@ -163,7 +163,7 @@ export class MarkdownSourceCode extends TextSourceCodeBase { /** * Returns the parent of the given node. * @param {Node} node The node to get the parent of. - * @returns {Node|undefined} The parent of the node. + * @returns {Parent|undefined} The parent of the node. */ getParent(node) { return this.#parents.get(node); @@ -303,6 +303,12 @@ export class MarkdownSourceCode extends TextSourceCodeBase { /** @type {Array} */ const steps = (this.#steps = []); + /** + * Recursively visits a node and its children. + * @param {Node} node The node to visit. + * @param {Parent} [parent] The parent of the node. + * @returns {void} + */ const visit = (node, parent) => { // first set the parent this.#parents.set(node, parent); @@ -318,13 +324,15 @@ export class MarkdownSourceCode extends TextSourceCodeBase { // save HTML nodes if (node.type === "html") { - this.#htmlNodes.push(node); + this.#htmlNodes.push(/** @type {Html} */ (node)); } // then visit the children - if (node.children) { - node.children.forEach(child => { - visit(child, node); + if ("children" in node) { + const parentNode = /** @type {Parent} */ (node); + + parentNode.children.forEach(child => { + visit(child, parentNode); }); } diff --git a/src/processor.js b/src/processor.js index 3b75fb32e..7b1e48504 100644 --- a/src/processor.js +++ b/src/processor.js @@ -42,14 +42,14 @@ const blocksCache = new Map(); /** * Performs a depth-first traversal of the Markdown AST. * @param {Node} node A Markdown AST node. - * @param {{[key: string]: (node?: Node) => void}} callbacks A map of node types to callbacks. + * @param {{[key: string]: (() => void) | ((node: Code) => void) | ((node: Html) => void)}} callbacks A map of node types to callbacks. * @returns {void} */ function traverse(node, callbacks) { if (callbacks[node.type]) { - callbacks[node.type](node); + /** @type {(node: Node) => void} */ (callbacks[node.type])(node); } else { - callbacks["*"](); + /** @type {() => void} */ (callbacks["*"])(); } const parent = /** @type {Parent} */ (node); @@ -339,7 +339,11 @@ function preprocess(sourceText, filename) { return blocks.map((block, index) => { const [language] = block.lang.trim().split(" "); const fileExtension = Object.hasOwn(languageToFileExtension, language) - ? languageToFileExtension[language] + ? languageToFileExtension[ + /** @type {keyof typeof languageToFileExtension} */ ( + language + ) + ] : language; return { @@ -412,6 +416,7 @@ function adjustBlock(block) { return null; } + /** @type {Pick} */ const out = { line: lineInCode + blockStart, column: message.column + block.rangeMap[lineInCode].indent, diff --git a/tests/types/tsconfig.json b/tests/types/tsconfig.json index 8387c00c5..94376dbd1 100644 --- a/tests/types/tsconfig.json +++ b/tests/types/tsconfig.json @@ -1,12 +1,17 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "emitDeclarationOnly": false, + "allowJs": false, + "checkJs": false, "noEmit": true, "rootDir": "../..", "strict": true, - "exactOptionalPropertyTypes": true + "strictNullChecks": true, + "useUnknownInCatchVariables": true, + "exactOptionalPropertyTypes": true, + "verbatimModuleSyntax": true, + "erasableSyntaxOnly": true }, "files": [], - "include": ["**/*.test.ts", "**/*.test.cts", "../../dist"] + "include": [".", "../../dist"] } diff --git a/tests/types/tsconfig.legacy.json b/tests/types/tsconfig.legacy.json new file mode 100644 index 000000000..11f712e18 --- /dev/null +++ b/tests/types/tsconfig.legacy.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "allowJs": false, + "checkJs": false, + "noEmit": true, + "rootDir": "../..", + "strict": true, + "strictNullChecks": true, + "useUnknownInCatchVariables": true, + "exactOptionalPropertyTypes": true, + "verbatimModuleSyntax": true + // "erasableSyntaxOnly" is not supported in TypeScript < 5.8. + }, + "files": [], + "include": [".", "../../dist"] +} diff --git a/tests/types/types.test.ts b/tests/types/types.test.ts index e0a00094f..2603e5281 100644 --- a/tests/types/types.test.ts +++ b/tests/types/types.test.ts @@ -194,6 +194,7 @@ const invalidLanguageOptions2: MarkdownLanguageOptions = { sourceCode.getIndexFromLoc({ line: 1, column: 1 }) satisfies number; sourceCode.getRange(node) satisfies SourceRange; sourceCode.getParent(node) satisfies Node | undefined; + sourceCode.getParent(node) satisfies Parent | undefined; sourceCode.getAncestors(node) satisfies Node[]; sourceCode.getText(node) satisfies string; sourceCode.applyInlineConfig().configs[0].loc.start diff --git a/tsconfig.json b/tsconfig.json index 2c2f76737..db9fdfac5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,10 @@ "outDir": "dist", "target": "ES2022", "moduleResolution": "NodeNext", - "module": "NodeNext" + "module": "NodeNext", + "rootDir": "./src", + "strictNullChecks": false, + "useUnknownInCatchVariables": false, + "types": [] } }