diff --git a/__tests__/mdx-lint.spec.ts b/__tests__/mdx-lint.spec.ts
new file mode 100644
index 0000000..7374cc9
--- /dev/null
+++ b/__tests__/mdx-lint.spec.ts
@@ -0,0 +1,129 @@
+import { spawnSync } from "child_process";
+import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "fs";
+import { tmpdir } from "os";
+import * as path from "path";
+import lintWorker from "../src/utils/lint-worker";
+
+const CLI = path.resolve(__dirname, "../lib/src/lint-md.js");
+const RULES = { "space-around-alphabet": 2 };
+const MDX = [
+ 'import Widget from "中文English";',
+ "",
+ 'export const title = "中文English";',
+ "",
+ '',
+ "",
+ '{"中文English"}',
+ "",
+ "正文English内容。",
+ "",
+].join("\n");
+
+describe("MDX lint", () => {
+ let tmpDir: string;
+ let configPath: string;
+
+ beforeEach(() => {
+ tmpDir = mkdtempSync(path.join(tmpdir(), "lint-md-mdx-"));
+ configPath = path.join(tmpDir, ".lintmdrc");
+ writeFileSync(configPath, JSON.stringify({ rules: RULES }), "utf8");
+ });
+
+ afterEach(() => {
+ rmSync(tmpDir, { recursive: true, force: true });
+ });
+
+ test("lints Markdown text", async () => {
+ const filePath = path.join(tmpDir, "document.md");
+ writeFileSync(filePath, "正文English内容。\n", "utf8");
+
+ const result = await lintWorker({
+ filePath,
+ rules: RULES,
+ isFixMode: false,
+ });
+
+ expect(result.diagnostics).toHaveLength(2);
+ expect(
+ result.diagnostics.map(({ ruleId, line }) => [ruleId, line])
+ ).toEqual([
+ ["space-around-alphabet", 1],
+ ["space-around-alphabet", 1],
+ ]);
+ });
+
+ test.each([".mdx", ".MDX"])(
+ "lints %s through the CLI and skips JSX, ESM, and expressions",
+ (extension) => {
+ const filePath = path.join(tmpDir, `document${extension}`);
+ writeFileSync(filePath, MDX, "utf8");
+
+ const result = spawnSync(
+ process.execPath,
+ [CLI, filePath, "--config", configPath],
+ { encoding: "utf8" }
+ );
+
+ expect(result.error).toBeUndefined();
+ expect(result.status).toBe(1);
+ expect(result.stdout).toContain(filePath);
+ expect(result.stdout).toContain("9:10");
+ expect(result.stdout).toContain("9:17");
+ expect(result.stdout).toContain("2 problems");
+ }
+ );
+
+ test("routes Markdown and MDX through the CLI", () => {
+ const markdownPath = path.join(tmpDir, "document.md");
+ const mdxPath = path.join(tmpDir, "document.mdx");
+ writeFileSync(markdownPath, "正文English内容。\n", "utf8");
+ writeFileSync(mdxPath, MDX, "utf8");
+
+ const result = spawnSync(
+ process.execPath,
+ [CLI, markdownPath, mdxPath, "--config", configPath],
+ { encoding: "utf8" }
+ );
+
+ expect(result.error).toBeUndefined();
+ expect(result.status).toBe(1);
+ expect(result.stdout).toContain(markdownPath);
+ expect(result.stdout).toContain(mdxPath);
+ expect(result.stdout).toContain("4 problems");
+ });
+
+ test("rejects a direct worker fix request for MDX", async () => {
+ const filePath = path.join(tmpDir, "document.mdx");
+ writeFileSync(filePath, MDX, "utf8");
+
+ await expect(
+ lintWorker({ filePath, rules: RULES, isFixMode: true })
+ ).rejects.toThrow("--fix does not support .mdx files");
+ expect(readFileSync(filePath, "utf8")).toBe(MDX);
+ });
+
+ test.each([".mdx", ".MDX"])(
+ "rejects %s fix requests before changing any file",
+ (extension) => {
+ const markdownPath = path.join(tmpDir, "document.md");
+ const mdxPath = path.join(tmpDir, `document${extension}`);
+ const markdown = "正文English内容。\n";
+ writeFileSync(markdownPath, markdown, "utf8");
+ writeFileSync(mdxPath, MDX, "utf8");
+
+ const result = spawnSync(
+ process.execPath,
+ [CLI, markdownPath, mdxPath, "--fix", "--config", configPath],
+ { encoding: "utf8" }
+ );
+
+ expect(result.error).toBeUndefined();
+ expect(result.status).toBe(1);
+ expect(result.stderr).toContain(
+ "--fix does not support .mdx files. No files were changed."
+ );
+ expect(readFileSync(markdownPath, "utf8")).toBe(markdown);
+ expect(readFileSync(mdxPath, "utf8")).toBe(MDX);
+ }
+ );
+});
diff --git a/package.json b/package.json
index bf5f697..87b9313 100644
--- a/package.json
+++ b/package.json
@@ -54,7 +54,7 @@
]
},
"dependencies": {
- "@lint-md/core": "^2.5.2",
+ "@lint-md/core": "^2.6.0",
"chalk": "^4",
"commander": "^9.4.1",
"glob": "^13.0.6",
diff --git a/scripts/test-package.mjs b/scripts/test-package.mjs
index b4790ce..7044cef 100644
--- a/scripts/test-package.mjs
+++ b/scripts/test-package.mjs
@@ -87,14 +87,17 @@ try {
const fixtureDirectory = join(temporaryDirectory, "fixtures");
const cleanFile = join(fixtureDirectory, "clean.md");
+ const cleanMdxFile = join(fixtureDirectory, "clean.mdx");
const fixableFile = join(fixtureDirectory, "fixable.md");
mkdirSync(fixtureDirectory);
cpSync(join(projectRoot, "examples", "space-around.md"), fixableFile, {
recursive: false,
});
writeFileSync(cleanFile, cleanMarkdown, "utf8");
+ writeFileSync(cleanMdxFile, '# Hello\n\n\n', "utf8");
run(lintMd, [cleanFile]);
+ run(lintMd, [cleanMdxFile]);
const originalFixableContent = readFileSync(fixableFile, "utf8");
run(lintMd, [fixableFile, "--fix"]);
const fixedContent = readFileSync(fixableFile, "utf8");
diff --git a/src/utils/lint-worker.ts b/src/utils/lint-worker.ts
index 28907a7..6ab1205 100644
--- a/src/utils/lint-worker.ts
+++ b/src/utils/lint-worker.ts
@@ -1,15 +1,29 @@
import { readFile } from "fs/promises";
+import { extname } from "path";
import { fixMarkdown, lintMarkdown } from "@lint-md/core";
+import type { LintMdResult } from "@lint-md/core";
import type { LintWorkerOptions } from "../types";
import { toBatchLintItem } from "./to-batch-lint-item";
const lintWorker = async (options: LintWorkerOptions) => {
const { filePath, rules, isFixMode } = options;
+ const isMdx = extname(filePath).toLowerCase() === ".mdx";
+
+ if (isMdx && isFixMode) {
+ throw new Error("--fix does not support .mdx files");
+ }
const content = await readFile(filePath, "utf8");
- const result = isFixMode
- ? fixMarkdown(content, { rules })
- : lintMarkdown(content, { rules: rules ?? {} });
+ let result: LintMdResult;
+ if (isMdx) {
+ const { lintMdx } =
+ require("@lint-md/core/mdx") as typeof import("@lint-md/core/mdx");
+ result = lintMdx(content, { rules: rules ?? {} });
+ } else {
+ result = isFixMode
+ ? fixMarkdown(content, { rules })
+ : lintMarkdown(content, { rules: rules ?? {} });
+ }
// Core returns fixedResult even when no rule changed the text. Compare with
// the content we already read so the main thread writes only real changes.
diff --git a/src/utils/load-md-files.ts b/src/utils/load-md-files.ts
index 990bb81..c2a245c 100644
--- a/src/utils/load-md-files.ts
+++ b/src/utils/load-md-files.ts
@@ -25,7 +25,13 @@ export const loadMdFiles = async (
for (const entry of entries) {
if (entry.isSymbolicLink()) continue;
const fullPath = entry.fullpath();
- if (extensions.some((ext) => fullPath.endsWith(ext))) files.add(fullPath);
+ if (
+ extensions.some((ext) =>
+ fullPath.toLowerCase().endsWith(ext.toLowerCase())
+ )
+ ) {
+ files.add(fullPath);
+ }
}
// Sort so lint report order stays stable across OS, filesystem, and