From ebd5c81ad026c098f30c022f7793d227b2df7a58 Mon Sep 17 00:00:00 2001 From: luojiyin Date: Fri, 10 Jul 2026 17:32:55 +0800 Subject: [PATCH] fix(batch-lint): keep files with notAppliedFixes (#86) --- __tests__/batch-lint.spec.ts | 33 ++++++++++++++++++++++++++++++++- src/utils/batch-lint.ts | 12 +++++++++--- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/__tests__/batch-lint.spec.ts b/__tests__/batch-lint.spec.ts index 6d3ab1a..55d6f09 100644 --- a/__tests__/batch-lint.spec.ts +++ b/__tests__/batch-lint.spec.ts @@ -3,7 +3,14 @@ import { availableParallelism, tmpdir } from 'os'; import * as path from 'path'; import { Piscina } from 'piscina'; import type { LintMdRulesConfig } from '@lint-md/core'; -import { STAT_CONCURRENCY_LIMIT, batchLint, getMaxFileSize, resolveAdaptiveConcurrency, runTasksWithLimit } from '../src/utils/batch-lint'; +import { STAT_CONCURRENCY_LIMIT, batchLint, getMaxFileSize, keepLintItem, resolveAdaptiveConcurrency, runTasksWithLimit } from '../src/utils/batch-lint'; +import type { BatchLintItem } from '../src/types'; + +const makeItem = (overrides: Partial = {}): BatchLintItem => ({ + path: 'doc.md', + lintResult: [], + ...overrides, +}); const RULES_NO_EMPTY_LIST: LintMdRulesConfig = { 'no-empty-list': 2, @@ -178,6 +185,30 @@ describe('batchLint', () => { }); }); +describe('keepLintItem', () => { + test('keeps items with a non-empty lint report', () => { + expect(keepLintItem(makeItem({ lintResult: [{ message: 'x', name: 'y', content: 'z', severity: 2, loc: { start: { line: 1, column: 1 }, end: { line: 1, column: 1 } } }] }))).toBe(true); + }); + + test('drops items with empty lint report and null fixedResult', () => { + expect(keepLintItem(makeItem({ fixedResult: null }))).toBe(false); + }); + + test('drops items with empty lint report and empty notAppliedFixes', () => { + expect(keepLintItem(makeItem({ fixedResult: { result: 'x', notAppliedFixes: [] } }))).toBe(false); + }); + + test('keeps items with empty lint report but non-empty notAppliedFixes', () => { + expect( + keepLintItem( + makeItem({ + fixedResult: { result: 'x', notAppliedFixes: [{ range: [0, 1], text: 'y' }] }, + }) + ) + ).toBe(true); + }); +}); + describe('getMaxFileSize', () => { let tmpDir: string; diff --git a/src/utils/batch-lint.ts b/src/utils/batch-lint.ts index 88fbd43..0a4d1ef 100644 --- a/src/utils/batch-lint.ts +++ b/src/utils/batch-lint.ts @@ -86,6 +86,14 @@ export const resolveAdaptiveConcurrency = async ( return Math.min(Math.max(limit, 1), mdFilePaths.length); }; +// Keep a file's result when it has lint findings, or — in fix mode — when +// core left fixes unapplied due to conflicts. notAppliedFixes can in theory +// occur without a lint report, so we must not drop it (see #86 / P1-6 +// "partially-unfixed is observable", which the #89 stderr warning surfaces). +export const keepLintItem = (item: BatchLintItem): boolean => + item.lintResult.length > 0 + || Boolean(item.fixedResult?.notAppliedFixes?.length); + export const batchLint = async ( threadsCount: number, mdFilePaths: string[], @@ -117,9 +125,7 @@ export const batchLint = async ( concurrency ); - return results.filter((item) => { - return item.lintResult.length > 0; - }); + return results.filter(keepLintItem); } finally { await lintWorkerPool.destroy();