Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down
2 changes: 1 addition & 1 deletion eslint.config-content.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import markdown from "./src/index.js";

export default defineConfig([
globalIgnores(
["**/*.js", "**/.cjs", "**/.mjs"],
["**/*.js", "**/.cjs", "**/.mjs", "tests/fixtures/"],
Copy link
Copy Markdown
Member Author

@lumirlumir lumirlumir Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This addition prevents the warning that appears when I run npm run lint:

Image

Reference:

["**/examples/", "coverage/", "dist/", "src/build/", "tests/fixtures/"],

"markdown/content/ignores",
),

Expand Down
9 changes: 9 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
},
]);
4 changes: 2 additions & 2 deletions examples/typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.0"
}
}
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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": {
Expand Down
4 changes: 2 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand Down
24 changes: 16 additions & 8 deletions src/language/markdown-source-code.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
*/
Expand Down Expand Up @@ -77,7 +77,7 @@ function extractInlineConfigCommentsFromHTML(node, sourceCode) {
/** @type {Array<InlineConfigComment>} */
const comments = [];

/** @type {RegExpExecArray} */
/** @type {RegExpExecArray | null} */
let match;

while ((match = htmlComment.exec(node.value))) {
Expand Down Expand Up @@ -124,7 +124,7 @@ export class MarkdownSourceCode extends TextSourceCodeBase {

/**
* Cache of parent nodes.
* @type {WeakMap<Node, Node>}
* @type {WeakMap<Node, Parent|undefined>}
*/
#parents = new WeakMap();

Expand Down Expand Up @@ -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.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is why the PR is marked as fix.

If the value is not undefined, the node is added to the private weakmap #parents only after the "children" in node check on line 331. In the Markdown AST (Mdast), a node with a children property always extends the Parent type, so using Parent instead of Node seems more accurate here.

*/
getParent(node) {
return this.#parents.get(node);
Expand Down Expand Up @@ -303,6 +303,12 @@ export class MarkdownSourceCode extends TextSourceCodeBase {
/** @type {Array<VisitNodeStep>} */
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);
Expand All @@ -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) {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the Markdown AST (Mdast), if a node has children they are always represented as an array (even when empty), so I think refactoring node.children to "children" in node is fine here.

const parentNode = /** @type {Parent} */ (node);

parentNode.children.forEach(child => {
visit(child, parentNode);
});
}

Expand Down
13 changes: 9 additions & 4 deletions src/processor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -412,6 +416,7 @@ function adjustBlock(block) {
return null;
}

/** @type {Pick<Message, "line" | "column" | "endLine" | "suggestions">} */
const out = {
line: lineInCode + blockStart,
column: message.column + block.rangeMap[lineInCode].indent,
Expand Down
11 changes: 8 additions & 3 deletions tests/types/tsconfig.json
Original file line number Diff line number Diff line change
@@ -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
Comment on lines +12 to +13
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To prevent the same issue as in eslint/json#88, I've also set the verbatimModuleSyntax and erasableSyntaxOnly options to true, as in the JSON type test.

https://github.com/eslint/json/blob/main/tests/types/tsconfig.json#L12-L13

},
"files": [],
"include": ["**/*.test.ts", "**/*.test.cts", "../../dist"]
"include": [".", "../../dist"]
}
17 changes: 17 additions & 0 deletions tests/types/tsconfig.legacy.json
Original file line number Diff line number Diff line change
@@ -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"]
}
1 change: 1 addition & 0 deletions tests/types/types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
"outDir": "dist",
"target": "ES2022",
"moduleResolution": "NodeNext",
"module": "NodeNext"
"module": "NodeNext",
"rootDir": "./src",
"strictNullChecks": false,
"useUnknownInCatchVariables": false,
"types": ["mdast", "unist"]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these types packages included globally?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing that out. I misunderstood how the types field works.

I've now realized how it works and updated it in 6399827.

}
}