From c80ca02fa3cf7a1add3f61b24521e54704181704 Mon Sep 17 00:00:00 2001 From: luojiyin Date: Fri, 10 Jul 2026 17:19:51 +0800 Subject: [PATCH 1/2] feat(fix-mode): warn to stderr when fixes not applied (#85) --- __tests__/report-unapplied-fixes.spec.ts | 79 ++++++++++++++++++++++++ src/lint-md.ts | 5 ++ src/utils/report-unapplied-fixes.ts | 21 +++++++ 3 files changed, 105 insertions(+) create mode 100644 __tests__/report-unapplied-fixes.spec.ts create mode 100644 src/utils/report-unapplied-fixes.ts diff --git a/__tests__/report-unapplied-fixes.spec.ts b/__tests__/report-unapplied-fixes.spec.ts new file mode 100644 index 0000000..82c4732 --- /dev/null +++ b/__tests__/report-unapplied-fixes.spec.ts @@ -0,0 +1,79 @@ +import { getUnappliedFixesWarnings } from '../src/utils/report-unapplied-fixes'; +import type { BatchLintItem } from '../src/types'; + +const makeItem = (overrides: Partial = {}): BatchLintItem => ({ + path: 'doc.md', + lintResult: [], + ...overrides, +}); + +describe('getUnappliedFixesWarnings', () => { + test('returns no warnings for an empty result', () => { + expect(getUnappliedFixesWarnings([])).toEqual([]); + }); + + test('returns no warnings when fixedResult is null', () => { + const result: BatchLintItem[] = [makeItem({ fixedResult: null })]; + expect(getUnappliedFixesWarnings(result)).toEqual([]); + }); + + test('returns no warnings when notAppliedFixes is empty', () => { + const result: BatchLintItem[] = [ + makeItem({ fixedResult: { result: 'x', notAppliedFixes: [] } }), + ]; + expect(getUnappliedFixesWarnings(result)).toEqual([]); + }); + + test('warns once with the correct count for a single unapplied fix', () => { + const result: BatchLintItem[] = [ + makeItem({ + path: 'a.md', + fixedResult: { + result: 'x', + notAppliedFixes: [{ range: [0, 1], text: 'y' }], + }, + }), + ]; + expect(getUnappliedFixesWarnings(result)).toEqual([ + '[lint-md] a.md: 1 fixes were not applied due to conflicts.', + ]); + }); + + test('reports the count for multiple unapplied fixes and preserves the path', () => { + const result: BatchLintItem[] = [ + makeItem({ + path: 'conflict.md', + fixedResult: { + result: 'x', + notAppliedFixes: [ + { range: [0, 1], text: 'y' }, + { range: [2, 3], text: 'z' }, + { range: [4, 5], text: 'w' }, + ], + }, + }), + ]; + expect(getUnappliedFixesWarnings(result)).toEqual([ + '[lint-md] conflict.md: 3 fixes were not applied due to conflicts.', + ]); + }); + + test('only warns for files that have unapplied fixes', () => { + const result: BatchLintItem[] = [ + makeItem({ + path: 'ok.md', + fixedResult: { result: 'x', notAppliedFixes: [] }, + }), + makeItem({ + path: 'bad.md', + fixedResult: { + result: 'x', + notAppliedFixes: [{ range: [0, 1], text: 'y' }], + }, + }), + ]; + expect(getUnappliedFixesWarnings(result)).toEqual([ + '[lint-md] bad.md: 1 fixes were not applied due to conflicts.', + ]); + }); +}); diff --git a/src/lint-md.ts b/src/lint-md.ts index 2a234d5..b07bc67 100644 --- a/src/lint-md.ts +++ b/src/lint-md.ts @@ -18,6 +18,7 @@ import type { CLIOptions, ThreadCount } from './types'; import { loadMdFiles } from './utils/load-md-files'; import { getReportData } from './utils/get-report-data'; import { filterFilesByMaxSize } from './utils/filter-by-max-size'; +import { getUnappliedFixesWarnings } from './utils/report-unapplied-fixes'; program .version( @@ -178,6 +179,10 @@ program .map(({ path, fixedResult }) => () => safeWriteFile(path, fixedResult!.result)), effectiveThreads ); + + for (const warning of getUnappliedFixesWarnings(lintResult)) { + console.error(warning); + } } } catch (e) { diff --git a/src/utils/report-unapplied-fixes.ts b/src/utils/report-unapplied-fixes.ts new file mode 100644 index 0000000..13facca --- /dev/null +++ b/src/utils/report-unapplied-fixes.ts @@ -0,0 +1,21 @@ +import type { BatchLintItem } from '../types'; + +// Returns stderr warning lines for files whose --fix pass left fixes +// unapplied due to conflicts. `fixedResult.notAppliedFixes` (from +// @lint-md/core) contains only the final-round conflicting fixes after +// core stopped dropping them across rounds, so the range coordinates are +// based on the returned result text and safe to report directly. +export const getUnappliedFixesWarnings = (lintResult: BatchLintItem[]): string[] => { + const warnings: string[] = []; + + for (const item of lintResult) { + const count = item.fixedResult?.notAppliedFixes?.length ?? 0; + if (count > 0) { + warnings.push( + `[lint-md] ${item.path}: ${count} fixes were not applied due to conflicts.` + ); + } + } + + return warnings; +}; From b4906172a04c543ef2f3b25d6826908ae2bd4380 Mon Sep 17 00:00:00 2001 From: luojiyin Date: Fri, 10 Jul 2026 17:26:03 +0800 Subject: [PATCH 2/2] fix(fix-mode): sanitize file path in unapplied-fixes warning --- __tests__/report-unapplied-fixes.spec.ts | 15 +++++++++++++++ src/utils/report-unapplied-fixes.ts | 3 ++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/__tests__/report-unapplied-fixes.spec.ts b/__tests__/report-unapplied-fixes.spec.ts index 82c4732..eed7b54 100644 --- a/__tests__/report-unapplied-fixes.spec.ts +++ b/__tests__/report-unapplied-fixes.spec.ts @@ -76,4 +76,19 @@ describe('getUnappliedFixesWarnings', () => { '[lint-md] bad.md: 1 fixes were not applied due to conflicts.', ]); }); + + test('sanitizes file path in warning output', () => { + const result: BatchLintItem[] = [ + makeItem({ + path: 'bad\u001B[31m.md\nnext-line', + fixedResult: { + result: 'x', + notAppliedFixes: [{ range: [0, 1], text: 'y' }], + }, + }), + ]; + + expect(getUnappliedFixesWarnings(result)[0]).not.toContain('\u001B'); + expect(getUnappliedFixesWarnings(result)[0]).not.toContain('\n'); + }); }); diff --git a/src/utils/report-unapplied-fixes.ts b/src/utils/report-unapplied-fixes.ts index 13facca..6fdb4df 100644 --- a/src/utils/report-unapplied-fixes.ts +++ b/src/utils/report-unapplied-fixes.ts @@ -1,4 +1,5 @@ import type { BatchLintItem } from '../types'; +import { sanitizeTerminalText } from './sanitize-terminal'; // Returns stderr warning lines for files whose --fix pass left fixes // unapplied due to conflicts. `fixedResult.notAppliedFixes` (from @@ -12,7 +13,7 @@ export const getUnappliedFixesWarnings = (lintResult: BatchLintItem[]): string[] const count = item.fixedResult?.notAppliedFixes?.length ?? 0; if (count > 0) { warnings.push( - `[lint-md] ${item.path}: ${count} fixes were not applied due to conflicts.` + `[lint-md] ${sanitizeTerminalText(item.path)}: ${count} fixes were not applied due to conflicts.` ); } }