diff --git a/__tests__/report-unapplied-fixes.spec.ts b/__tests__/report-unapplied-fixes.spec.ts new file mode 100644 index 0000000..eed7b54 --- /dev/null +++ b/__tests__/report-unapplied-fixes.spec.ts @@ -0,0 +1,94 @@ +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.', + ]); + }); + + 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/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..6fdb4df --- /dev/null +++ b/src/utils/report-unapplied-fixes.ts @@ -0,0 +1,22 @@ +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 +// @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] ${sanitizeTerminalText(item.path)}: ${count} fixes were not applied due to conflicts.` + ); + } + } + + return warnings; +};