From 4af0702434549b5c017d4683d921f5c0a39b2c6d Mon Sep 17 00:00:00 2001 From: luojiyin Date: Sat, 12 Sep 2026 18:43:25 +0800 Subject: [PATCH 1/3] refactor(batch-lint): consume canonical diagnostics and summary --- __tests__/batch-lint.spec.ts | 23 ++++-- __tests__/keep-lint-item.spec.ts | 17 +++-- __tests__/lint-worker.spec.ts | 27 +++++-- __tests__/report-execution-errors.spec.ts | 8 +- __tests__/report-incomplete-fixes.spec.ts | 8 +- __tests__/report-unapplied-fixes.spec.ts | 8 +- __tests__/run-file-lint.spec.ts | 24 +++++- __tests__/run-stdin-lint.spec.ts | 18 ++++- __tests__/summarize-lint-results.spec.ts | 59 ++++++++++----- __tests__/to-batch-lint-item.spec.ts | 90 ++++++++++++++++++++--- package.json | 2 +- src/types.ts | 8 +- src/utils/batch-lint.ts | 2 +- src/utils/summarize-lint-results.ts | 31 ++++---- src/utils/to-batch-lint-item.ts | 5 +- 15 files changed, 243 insertions(+), 87 deletions(-) diff --git a/__tests__/batch-lint.spec.ts b/__tests__/batch-lint.spec.ts index 8835760..954d97e 100644 --- a/__tests__/batch-lint.spec.ts +++ b/__tests__/batch-lint.spec.ts @@ -15,7 +15,13 @@ import { makeNotAppliedFix } from "./helpers/not-applied-fix"; const makeItem = (overrides: Partial = {}): BatchLintItem => ({ path: "doc.md", - lintResult: [], + diagnostics: [], + summary: { + errorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + }, ...overrides, }); @@ -60,8 +66,8 @@ describe("batchLint", () => { fileB, ]); actionableResults.forEach((item) => { - expect(Array.isArray(item.lintResult)).toBe(true); - expect(item.lintResult.length).toBeGreaterThan(0); + expect(Array.isArray(item.diagnostics)).toBe(true); + expect(item.diagnostics.length).toBeGreaterThan(0); expect(item.fixedResult == null).toBe(true); }); }); @@ -79,7 +85,7 @@ describe("batchLint", () => { expect(actionableResults).toHaveLength(1); expect(actionableResults[0].path).toBe(file); - expect(actionableResults[0].lintResult[0].name).toBe("no-empty-list"); + expect(actionableResults[0].diagnostics[0].ruleId).toBe("no-empty-list"); }); }); @@ -207,13 +213,14 @@ describe("keepLintItem", () => { expect( keepLintItem( makeItem({ - lintResult: [ + diagnostics: [ { message: "x", - name: "y", - content: "z", + ruleId: "y", + line: 1, + column: 1, severity: 2, - loc: { + range: { start: { line: 1, column: 1 }, end: { line: 1, column: 1 }, }, diff --git a/__tests__/keep-lint-item.spec.ts b/__tests__/keep-lint-item.spec.ts index 1511120..07341c8 100644 --- a/__tests__/keep-lint-item.spec.ts +++ b/__tests__/keep-lint-item.spec.ts @@ -19,7 +19,13 @@ const baseItem = ( } return { path: "a.md", - lintResult: [], + diagnostics: [], + summary: { + errorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + }, fixedResult, ...rest, }; @@ -30,15 +36,16 @@ describe("keepLintItem", () => { expect( keepLintItem( baseItem({ - lintResult: [ + diagnostics: [ { - loc: { + range: { start: { line: 1, column: 1 }, end: { line: 1, column: 2 }, }, message: "x", - name: "r", - content: "x", + ruleId: "r", + line: 1, + column: 1, severity: 2 as any, }, ], diff --git a/__tests__/lint-worker.spec.ts b/__tests__/lint-worker.spec.ts index 6878384..7bd64fd 100644 --- a/__tests__/lint-worker.spec.ts +++ b/__tests__/lint-worker.spec.ts @@ -47,9 +47,14 @@ describe("lintWorker executionErrors passthrough", () => { mockedLintMarkdown.mockReturnValue({ lintResult: [], + diagnostics: [], + summary: { + errorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + }, fixedResult: null, - fixableErrorCount: 0, - fixableWarningCount: 0, executionErrors, } as any); @@ -68,9 +73,14 @@ describe("lintWorker executionErrors passthrough", () => { mockedLintMarkdown.mockReturnValue({ lintResult: [], + diagnostics: [], + summary: { + errorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + }, fixedResult: null, - fixableErrorCount: 0, - fixableWarningCount: 0, } as any); const result = await lintWorker({ @@ -89,9 +99,14 @@ describe("lintWorker executionErrors passthrough", () => { mockedFixMarkdown.mockReturnValue({ lintResult: [], + diagnostics: [], + summary: { + errorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + }, fixedResult: { result: "甲 [链接](https://example.com) 乙\n" }, - fixableErrorCount: 0, - fixableWarningCount: 0, executionErrors: [], } as any); diff --git a/__tests__/report-execution-errors.spec.ts b/__tests__/report-execution-errors.spec.ts index 90b972b..5aac931 100644 --- a/__tests__/report-execution-errors.spec.ts +++ b/__tests__/report-execution-errors.spec.ts @@ -10,7 +10,13 @@ const makeItem = ( executionErrors?: RuleExecutionError[] ): BatchLintItem => ({ path, - lintResult: [], + diagnostics: [], + summary: { + errorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + }, ...(executionErrors ? { executionErrors } : {}), }); diff --git a/__tests__/report-incomplete-fixes.spec.ts b/__tests__/report-incomplete-fixes.spec.ts index 2c8298a..fe0f691 100644 --- a/__tests__/report-incomplete-fixes.spec.ts +++ b/__tests__/report-incomplete-fixes.spec.ts @@ -26,7 +26,13 @@ const makeItem = ( } return { path: "docs/example.md", - lintResult: [], + diagnostics: [], + summary: { + errorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + }, fixedResult, ...rest, }; diff --git a/__tests__/report-unapplied-fixes.spec.ts b/__tests__/report-unapplied-fixes.spec.ts index 6d41ddd..c535ad3 100644 --- a/__tests__/report-unapplied-fixes.spec.ts +++ b/__tests__/report-unapplied-fixes.spec.ts @@ -4,7 +4,13 @@ import { makeNotAppliedFix } from "./helpers/not-applied-fix"; const makeItem = (overrides: Partial = {}): BatchLintItem => ({ path: "doc.md", - lintResult: [], + diagnostics: [], + summary: { + errorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + }, ...overrides, }); diff --git a/__tests__/run-file-lint.spec.ts b/__tests__/run-file-lint.spec.ts index 4c46d28..6245a64 100644 --- a/__tests__/run-file-lint.spec.ts +++ b/__tests__/run-file-lint.spec.ts @@ -208,7 +208,13 @@ describe("runFileLint", () => { test("reports rule failures to stderr and returns failure without timing", async () => { const failedResult: BatchLintItem = { path: "failed.md", - lintResult: [], + diagnostics: [], + summary: { + errorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + }, executionErrors: [ { ruleName: "broken-rule", @@ -247,12 +253,24 @@ describe("runFileLint", () => { test("writes actionable fixes and reports metrics for all results", async () => { const cleanResult: BatchLintItem = { path: "clean.md", - lintResult: [], + diagnostics: [], + summary: { + errorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + }, fixedResult: { result: "clean", notAppliedFixes: [] }, }; const actionableResult: BatchLintItem = { path: "actionable.md", - lintResult: [], + diagnostics: [], + summary: { + errorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + }, fixedResult: { result: "fixed", notAppliedFixes: [] }, }; const allResults = [cleanResult, actionableResult]; diff --git a/__tests__/run-stdin-lint.spec.ts b/__tests__/run-stdin-lint.spec.ts index 0cf8747..94de17d 100644 --- a/__tests__/run-stdin-lint.spec.ts +++ b/__tests__/run-stdin-lint.spec.ts @@ -68,9 +68,14 @@ describe("runStdinLint", () => { test("reports rule failures to stderr and returns failure without timing", () => { jest.spyOn(lintCore, "lintMarkdown").mockReturnValue({ lintResult: [], + diagnostics: [], + summary: { + errorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + }, fixedResult: null, - fixableErrorCount: 0, - fixableWarningCount: 0, executionErrors: [ { ruleName: "broken-rule", @@ -112,9 +117,14 @@ describe("runStdinLint", () => { test("keeps fix output pipe-safe when a rule fails", () => { jest.spyOn(lintCore, "fixMarkdown").mockReturnValue({ lintResult: [], + diagnostics: [], + summary: { + errorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + }, fixedResult: null, - fixableErrorCount: 0, - fixableWarningCount: 0, executionErrors: [ { ruleName: "broken-rule", diff --git a/__tests__/summarize-lint-results.spec.ts b/__tests__/summarize-lint-results.spec.ts index c14dd81..9688385 100644 --- a/__tests__/summarize-lint-results.spec.ts +++ b/__tests__/summarize-lint-results.spec.ts @@ -1,22 +1,32 @@ -import type { LintReportItem } from "@lint-md/core"; +import type { LintDiagnostic, LintSummary } from "@lint-md/core"; import type { BatchLintItem } from "../src/types"; import { summarizeLintResults } from "../src/utils/summarize-lint-results"; -const makeReportItem = ( +const makeDiagnostic = ( severity: number, - overrides: Partial = {} -): LintReportItem => ({ - name: "rule-x", + overrides: Partial = {} +): LintDiagnostic => ({ + ruleId: "rule-x", message: "some problem", - content: "x", severity, - loc: { start: { line: 1, column: 1 }, end: { line: 1, column: 2 } }, + line: 1, + column: 1, + range: { start: { line: 1, column: 1 }, end: { line: 1, column: 2 } }, + ...overrides, +}); + +const makeSummary = (overrides: Partial = {}): LintSummary => ({ + errorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, ...overrides, }); const makeItem = (overrides: Partial = {}): BatchLintItem => ({ path: "doc.md", - lintResult: [], + diagnostics: [], + summary: makeSummary(), ...overrides, }); @@ -24,7 +34,8 @@ describe("summarizeLintResults", () => { test("counts errors and warnings separately", () => { const summary = summarizeLintResults([ makeItem({ - lintResult: [makeReportItem(2), makeReportItem(2), makeReportItem(1)], + diagnostics: [makeDiagnostic(2), makeDiagnostic(2), makeDiagnostic(1)], + summary: makeSummary({ errorCount: 2, warningCount: 1 }), }), ]); @@ -38,14 +49,22 @@ describe("summarizeLintResults", () => { const summary = summarizeLintResults([ makeItem({ path: "a.md", - lintResult: [makeReportItem(2), makeReportItem(1)], - fixableErrorCount: 3, + diagnostics: [makeDiagnostic(2), makeDiagnostic(1)], + summary: makeSummary({ + errorCount: 1, + warningCount: 1, + fixableErrorCount: 3, + }), }), makeItem({ path: "b.md", - lintResult: [makeReportItem(2), makeReportItem(2), makeReportItem(1)], - fixableErrorCount: 1, - fixableWarningCount: 4, + diagnostics: [makeDiagnostic(2), makeDiagnostic(2), makeDiagnostic(1)], + summary: makeSummary({ + errorCount: 2, + warningCount: 1, + fixableErrorCount: 1, + fixableWarningCount: 4, + }), }), ]); @@ -61,16 +80,17 @@ describe("summarizeLintResults", () => { const summary = summarizeLintResults([ makeItem({ path: "doc\u0007.md", - lintResult: [ - makeReportItem(2, { - loc: { + diagnostics: [ + makeDiagnostic(2, { + range: { start: { line: 3, column: 5 }, end: { line: 3, column: 6 }, }, message: "bad\nmessage", - name: "rule\tid", + ruleId: "rule\tid", }), ], + summary: makeSummary({ errorCount: 1 }), }), ]); @@ -91,8 +111,7 @@ describe("summarizeLintResults", () => { test("drops files without lint problems", () => { const summary = summarizeLintResults([ makeItem({ - fixableErrorCount: 3, - fixableWarningCount: 4, + summary: makeSummary({ fixableErrorCount: 3, fixableWarningCount: 4 }), }), ]); diff --git a/__tests__/to-batch-lint-item.spec.ts b/__tests__/to-batch-lint-item.spec.ts index 644c3c0..9d01317 100644 --- a/__tests__/to-batch-lint-item.spec.ts +++ b/__tests__/to-batch-lint-item.spec.ts @@ -1,23 +1,32 @@ import type { LintMdFixResult, LintMdLintResult } from "@lint-md/core"; import { toBatchLintItem } from "../src/utils/to-batch-lint-item"; +import { summarizeLintResults } from "../src/utils/summarize-lint-results"; describe("toBatchLintItem", () => { test("adapts a lint result", () => { const result: LintMdLintResult = { lintResult: [], diagnostics: [], + summary: { + errorCount: 0, + warningCount: 0, + fixableErrorCount: 1, + fixableWarningCount: 2, + }, fixedResult: null, - fixableErrorCount: 1, - fixableWarningCount: 2, executionErrors: [], }; expect(toBatchLintItem("doc.md", result)).toEqual({ path: "doc.md", - lintResult: [], + diagnostics: [], + summary: { + errorCount: 0, + warningCount: 0, + fixableErrorCount: 1, + fixableWarningCount: 2, + }, fixedResult: null, - fixableErrorCount: 1, - fixableWarningCount: 2, executionErrors: [], }); }); @@ -28,21 +37,80 @@ describe("toBatchLintItem", () => { notAppliedFixes: [], }; const result: LintMdFixResult = { - lintResult: [], diagnostics: [], + summary: { + errorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + }, fixedResult, - fixableErrorCount: 0, - fixableWarningCount: 0, executionErrors: [], }; expect(toBatchLintItem("doc.md", result)).toEqual({ path: "doc.md", - lintResult: [], + diagnostics: [], + summary: { + errorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + }, fixedResult, - fixableErrorCount: 0, - fixableWarningCount: 0, executionErrors: [], }); }); + + test("uses the canonical diagnostic range instead of the legacy location", () => { + const result: LintMdLintResult = { + lintResult: [ + { + name: "legacy-rule", + message: "problem", + content: "problem", + severity: 2, + loc: { + start: { line: 1, column: 1 }, + end: { line: 1, column: 2 }, + }, + }, + ], + diagnostics: [ + { + ruleId: "canonical-rule", + message: "problem", + severity: 2, + line: 8, + column: 9, + range: { + start: { line: 8, column: 9 }, + end: { line: 8, column: 10 }, + }, + }, + ], + summary: { + errorCount: 1, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + }, + fixedResult: null, + fixableErrorCount: 1, + fixableWarningCount: 0, + executionErrors: [], + }; + + const summary = summarizeLintResults([toBatchLintItem("doc.md", result)]); + + expect(summary.files[0].messages).toEqual([ + { + line: 8, + column: 9, + message: "problem", + ruleId: "canonical-rule", + severity: 2, + }, + ]); + }); }); diff --git a/package.json b/package.json index eb0baa1..4d64085 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ ] }, "dependencies": { - "@lint-md/core": "^2.3.1", + "@lint-md/core": "^2.5.0", "chalk": "^4", "commander": "^9.4.1", "glob": "^13.0.6", diff --git a/src/types.ts b/src/types.ts index c85bc0c..22d1f86 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,7 +1,8 @@ /** CLI 配置 */ import type { LintMdRulesConfig, - LintReportItem, + LintDiagnostic, + LintSummary, FixedResult, RuleExecutionError, } from "@lint-md/core"; @@ -23,10 +24,9 @@ export interface LintWorkerOptions { /** batchLint 单个文件的 lint 结果 */ export interface BatchLintItem { path: string; - lintResult: LintReportItem[]; + diagnostics: LintDiagnostic[]; + summary: LintSummary; fixedResult?: FixedResult | null; - fixableErrorCount?: number; - fixableWarningCount?: number; // Per-round, per-phase rule execution errors from @lint-md/core 2.1.5 // (core #185). CLI surfaces these as stderr warnings and exits 1 // regardless of --suppress-warnings. diff --git a/src/utils/batch-lint.ts b/src/utils/batch-lint.ts index 1027b4e..edd09d4 100644 --- a/src/utils/batch-lint.ts +++ b/src/utils/batch-lint.ts @@ -23,7 +23,7 @@ const resolveWorkerFilename = (): string => { // errors so the #96 stderr warning + exit(1) have a target. Older cores // that predate these fields leave them undefined and are filtered as before. export const keepLintItem = (item: BatchLintItem): boolean => - item.lintResult.length > 0 || + item.diagnostics.length > 0 || Boolean(item.fixedResult?.notAppliedFixes?.length) || isIncompleteFix(item) || (item.executionErrors?.length ?? 0) > 0; diff --git a/src/utils/summarize-lint-results.ts b/src/utils/summarize-lint-results.ts index 47f1d71..3450113 100644 --- a/src/utils/summarize-lint-results.ts +++ b/src/utils/summarize-lint-results.ts @@ -1,4 +1,4 @@ -import type { LintReportItem } from "@lint-md/core"; +import type { LintDiagnostic } from "@lint-md/core"; import type { BatchLintItem } from "../types"; export interface LintMessageSummary { @@ -6,7 +6,7 @@ export interface LintMessageSummary { line: number; message: string; ruleId: string; - severity: LintReportItem["severity"]; + severity: LintDiagnostic["severity"]; } export interface FileLintSummary { @@ -36,32 +36,27 @@ export const summarizeLintResults = (items: BatchLintItem[]): LintSummary => { }; for (const item of items) { - const errorCount = item.lintResult.filter( - ({ severity }) => severity === 2 - ).length; - const warningCount = item.lintResult.filter( - ({ severity }) => severity === 1 - ).length; + const { errorCount, warningCount, fixableErrorCount, fixableWarningCount } = + item.summary; if (errorCount + warningCount === 0) { continue; } - const fixableErrorCount = item.fixableErrorCount ?? 0; - const fixableWarningCount = item.fixableWarningCount ?? 0; - summary.files.push({ errorCount, filePath: item.path, fixableErrorCount, fixableWarningCount, - messages: item.lintResult.map(({ loc, message, name, severity }) => ({ - column: loc.start.column, - line: loc.start.line, - message, - ruleId: name, - severity, - })), + messages: item.diagnostics.map( + ({ range, message, ruleId, severity }) => ({ + column: range!.start.column, + line: range!.start.line, + message, + ruleId, + severity, + }) + ), warningCount, }); summary.errorCount += errorCount; diff --git a/src/utils/to-batch-lint-item.ts b/src/utils/to-batch-lint-item.ts index 42dc322..bdf4299 100644 --- a/src/utils/to-batch-lint-item.ts +++ b/src/utils/to-batch-lint-item.ts @@ -6,9 +6,8 @@ export const toBatchLintItem = ( result: LintMdResult ): BatchLintItem => ({ path, - lintResult: result.lintResult, + diagnostics: result.diagnostics, + summary: result.summary, fixedResult: result.fixedResult, - fixableErrorCount: result.fixableErrorCount, - fixableWarningCount: result.fixableWarningCount, executionErrors: result.executionErrors, }); From b3c97b4d6c5ed46223d15c72db294033b0a48148 Mon Sep 17 00:00:00 2001 From: luojiyin Date: Sat, 12 Sep 2026 18:53:41 +0800 Subject: [PATCH 2/3] fix(summarize-lint-results): use diagnostic coordinates --- __tests__/summarize-lint-results.spec.ts | 2 ++ __tests__/to-batch-lint-item.spec.ts | 3 +++ src/utils/summarize-lint-results.ts | 6 +++--- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/__tests__/summarize-lint-results.spec.ts b/__tests__/summarize-lint-results.spec.ts index 9688385..16d2026 100644 --- a/__tests__/summarize-lint-results.spec.ts +++ b/__tests__/summarize-lint-results.spec.ts @@ -82,6 +82,8 @@ describe("summarizeLintResults", () => { path: "doc\u0007.md", diagnostics: [ makeDiagnostic(2, { + line: 3, + column: 5, range: { start: { line: 3, column: 5 }, end: { line: 3, column: 6 }, diff --git a/__tests__/to-batch-lint-item.spec.ts b/__tests__/to-batch-lint-item.spec.ts index 9d01317..d5e06aa 100644 --- a/__tests__/to-batch-lint-item.spec.ts +++ b/__tests__/to-batch-lint-item.spec.ts @@ -37,6 +37,7 @@ describe("toBatchLintItem", () => { notAppliedFixes: [], }; const result: LintMdFixResult = { + lintResult: [], diagnostics: [], summary: { errorCount: 0, @@ -44,6 +45,8 @@ describe("toBatchLintItem", () => { fixableErrorCount: 0, fixableWarningCount: 0, }, + fixableErrorCount: 0, + fixableWarningCount: 0, fixedResult, executionErrors: [], }; diff --git a/src/utils/summarize-lint-results.ts b/src/utils/summarize-lint-results.ts index 3450113..701b5f4 100644 --- a/src/utils/summarize-lint-results.ts +++ b/src/utils/summarize-lint-results.ts @@ -49,9 +49,9 @@ export const summarizeLintResults = (items: BatchLintItem[]): LintSummary => { fixableErrorCount, fixableWarningCount, messages: item.diagnostics.map( - ({ range, message, ruleId, severity }) => ({ - column: range!.start.column, - line: range!.start.line, + ({ line, column, message, ruleId, severity }) => ({ + column, + line, message, ruleId, severity, From 85aa62dd1eb32065c2c5a46a585c73de57f17deb Mon Sep 17 00:00:00 2001 From: luojiyin Date: Sat, 12 Sep 2026 18:57:54 +0800 Subject: [PATCH 3/3] test(to-batch-lint-item): complete legacy result fixture --- __tests__/to-batch-lint-item.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/__tests__/to-batch-lint-item.spec.ts b/__tests__/to-batch-lint-item.spec.ts index d5e06aa..ac068be 100644 --- a/__tests__/to-batch-lint-item.spec.ts +++ b/__tests__/to-batch-lint-item.spec.ts @@ -13,6 +13,8 @@ describe("toBatchLintItem", () => { fixableErrorCount: 1, fixableWarningCount: 2, }, + fixableErrorCount: 1, + fixableWarningCount: 2, fixedResult: null, executionErrors: [], };