Compare commits

...
Author SHA1 Message Date
94829325fb fix(lint): only remove paired whole-document Markdown fences (#2741 takeover)
lint --fix stripped the opening ```markdown fence and the closing fence
at EOF independently, so a legitimate document that merely ENDS in a
fenced code example lost its closing fence. PR #2741 paired the two
fences correctly, but its regex tail (`[^\S\r\n]*$`) required the
closing fence at absolute end-of-string — a wrapped file with a normal
trailing newline no longer matched, so detection kept flagging
code-fence-wrap while --fix silently did nothing.

Fix: one shared DOC_FENCE_WRAPPER regex (paired fences, `\s*$` tail
tolerating trailing newlines) used by BOTH detection and fixContent, so
they can never disagree again. Detection also stops flagging documents
that only end in a legit fenced example.

Co-authored-by: codxt <codxt@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 14:53:07 -07:00
2 changed files with 32 additions and 4 deletions
+11 -4
View File
@@ -68,6 +68,12 @@ const LLM_PREAMBLES = [
/^Absolutely\.?\s*Here[^.\n]*\.?\s*\n*/gim,
];
// A whole-document Markdown wrapper: an opening ```markdown fence at the very
// start AND a closing fence at the very end (trailing newline allowed). Both
// fences must be present — a closing fence at EOF may belong to a legitimate
// final code example. Shared by detection + fix so they can never disagree.
const DOC_FENCE_WRAPPER = /^```(?:markdown|md)[^\S\r\n]*\r?\n([\s\S]*?)\r?\n```\s*$/;
// ── Rules ──────────────────────────────────────────────────────────
/**
@@ -127,7 +133,7 @@ export function lintContent(content: string, filePath: string, opts: LintContent
}
// Rule: Wrapping code fences (```markdown ... ```)
if (content.match(/^```(?:markdown|md)\s*\n/m) && content.match(/\n```\s*$/m)) {
if (DOC_FENCE_WRAPPER.test(content)) {
issues.push({
file: filePath, line: 1, rule: 'code-fence-wrap',
message: 'Page wrapped in ```markdown code fences (LLM artifact)',
@@ -291,9 +297,10 @@ export function fixContent(content: string): string {
fixed = fixed.replace(pattern, '');
}
// Fix wrapping code fences
fixed = fixed.replace(/^```(?:markdown|md)\s*\n/, '');
fixed = fixed.replace(/\n```\s*$/, '');
// Fix a whole-document markdown wrapper only when both fences are present
// (runs after preamble stripping, so a preamble-then-wrapper page unwraps too).
const wrapper = fixed.match(DOC_FENCE_WRAPPER);
if (wrapper) fixed = wrapper[1];
// Clean up excessive blank lines left by fixes
fixed = fixed.replace(/\n{3,}/g, '\n\n');
+21
View File
@@ -97,6 +97,27 @@ describe('fixContent', () => {
expect(fixed).toContain('# Title');
});
test('removes wrapping code fences when file has a trailing newline', () => {
const fixed = fixContent('```markdown\n# T\n```\n');
expect(fixed).toBe('# T\n');
});
test('preserves a normal document ending in a fenced example', () => {
const input = '# Format Guide\n\n## Example\n\n```yaml\nkind: Example\n```\n';
expect(fixContent(input)).toBe(input);
});
test('preserves multiple fenced examples', () => {
const input = '# Examples\n\n```json\n{"ok":true}\n```\n\n```yaml\nok: true\n```\n';
expect(fixContent(input)).toBe(input);
});
test('does not flag code-fence-wrap for a document ending in a fenced example', () => {
const input = '# Format Guide\n\n## Example\n\n```yaml\nkind: Example\n```\n';
const issues = lintContent(input, 'test.md');
expect(issues.some(i => i.rule === 'code-fence-wrap')).toBe(false);
});
test('cleans up excessive blank lines after fix', () => {
const input = 'Of course. Here is the brain page.\n\n\n\n# Title\n\nContent.';
const fixed = fixContent(input);