From c19b76a68a0d1911754f7add71bc437b7e4992c5 Mon Sep 17 00:00:00 2001 From: Miroma Date: Sun, 21 Dec 2025 22:45:31 +0100 Subject: [PATCH] fix(md)!: warn if region not found, strip #regions when a region is not in the source file, instead of including the whole file, warn that the #region was not found in it. fix #4625 --- `<<<` snippets now also strip out all #region markers: ```file.ts // #region A // #region B console.log("Hello, World!"); // #endregion // #endregion ``` <<< file.ts#A ...does not include "#region B" anymore --- .../markdown-extensions.test.ts | 4 +- .../node/markdown/plugins/snippet.test.ts | 72 ++++++++++++++++++- src/node/markdown/plugins/snippet.ts | 29 ++++++-- src/node/utils/processIncludes.ts | 10 ++- 4 files changed, 103 insertions(+), 12 deletions(-) diff --git a/__tests__/e2e/markdown-extensions/markdown-extensions.test.ts b/__tests__/e2e/markdown-extensions/markdown-extensions.test.ts index 839f953c..ea41825f 100644 --- a/__tests__/e2e/markdown-extensions/markdown-extensions.test.ts +++ b/__tests__/e2e/markdown-extensions/markdown-extensions.test.ts @@ -216,7 +216,7 @@ describe('Line Numbers', () => { describe('Import Code Snippets', () => { test('basic', async () => { const lines = page.locator('#basic-code-snippet + div code > span') - expect(await lines.count()).toBe(11) + expect(await lines.count()).toBe(9) }) test('specify region', async () => { @@ -269,7 +269,7 @@ describe('Code Groups', () => { // blocks const blocks = div.locator('.blocks > div') - expect(await blocks.nth(0).locator('code > span').count()).toBe(11) + expect(await blocks.nth(0).locator('code > span').count()).toBe(9) expect(await getClassList(blocks.nth(1))).toContain('line-numbers-mode') expect(await getClassList(blocks.nth(1))).toContain('language-ts') expect(await blocks.nth(1).locator('code > span').count()).toBe(3) diff --git a/__tests__/unit/node/markdown/plugins/snippet.test.ts b/__tests__/unit/node/markdown/plugins/snippet.test.ts index 39ea7fd4..852367e0 100644 --- a/__tests__/unit/node/markdown/plugins/snippet.test.ts +++ b/__tests__/unit/node/markdown/plugins/snippet.test.ts @@ -1,7 +1,8 @@ import { dedent, findRegions, - rawPathToToken + rawPathToToken, + stripRegionMarkers } from 'node/markdown/plugins/snippet' import { expect } from 'vitest' @@ -406,5 +407,74 @@ describe('node/markdown/plugins/snippet', () => { expect(extracted).toBe(expected) } }) + + it('handles region names with hyphens and special characters', () => { + const lines = [ + '// #region complex-name_123', + 'const x = 1;', + '// #endregion complex-name_123' + ] + const result = findRegions(lines, 'complex-name_123') + expect(result).toHaveLength(1) + if (result) { + const extracted = result + .flatMap((r) => + lines + .slice(r.start, r.end) + .filter((l) => !(r.re.start.test(l) || r.re.end.test(l))) + ) + .join('\n') + expect(extracted).toBe('const x = 1;') + } + }) + }) + + describe('stripRegionMarkers', () => { + it('removes #region and #endregion lines', () => { + const src = [ + '// #region A', + '// #region B', + 'console.log("Hello, World!");', + '// #endregion B', + '// #endregion A' + ] + expect(stripRegionMarkers(src)).toBe('console.log("Hello, World!");') + }) + + it('removes region markers for various syntaxes', () => { + const src = [ + '', + '
hi
', + '', + '/* #region css */', + 'body {}', + '/* #endregion css */', + '#pragma region cpp', + 'int main(){}', + '#pragma endregion cpp', + '::#region bat', + 'ECHO ON', + 'REM #endregion bat' + ] + const out = stripRegionMarkers(src) + expect(out).not.toContain('#region') + expect(out).not.toContain('#endregion') + expect(out).toContain('
hi
') + expect(out).toContain('body {}') + expect(out).toContain('int main(){}') + expect(out).toContain('ECHO ON') + }) + + it('removes markers even if indented or with extra spaces', () => { + const src = [ + ' // #region spaced ', + '\t/* #region */', + 'code();', + ' // #endregion spaced', + '/* #endregion */' + ] + const out = stripRegionMarkers(src) + expect(out.trim()).toBe('code();') + }) }) }) diff --git a/src/node/markdown/plugins/snippet.ts b/src/node/markdown/plugins/snippet.ts index 756da390..2d1bbcdf 100644 --- a/src/node/markdown/plugins/snippet.ts +++ b/src/node/markdown/plugins/snippet.ts @@ -126,6 +126,17 @@ export function findRegions(lines: string[], regionName: string) { return returned } +export function stripRegionMarkers(lines: string[]): string { + return lines + .filter((l) => { + for (const m of markers) { + if (m.start.test(l) || m.end.test(l)) return false + } + return true + }) + .join('\n') +} + export const snippetPlugin = (md: MarkdownItAsync, srcDir: string) => { const parser: RuleBlock = (state, startLine, endLine, silent) => { const CH = '<'.charCodeAt(0) @@ -182,7 +193,7 @@ export const snippetPlugin = (md: MarkdownItAsync, srcDir: string) => { const [tokens, idx, , { includes }] = args const token = tokens[idx] // @ts-ignore - const [src, regionName] = token.src ?? [] + const [src, region] = token.src ?? [] if (!src) return fence(...args) @@ -204,21 +215,27 @@ export const snippetPlugin = (md: MarkdownItAsync, srcDir: string) => { let content = fs.readFileSync(src, 'utf8').replace(/\r\n/g, '\n') - if (regionName) { + if (region) { const lines = content.split('\n') - const regions = findRegions(lines, regionName) + const regions = findRegions(lines, region) if (regions.length > 0) { content = dedent( - regions - .flatMap((r) => + stripRegionMarkers( + regions.flatMap((r) => lines .slice(r.start, r.end) .filter((l) => !(r.re.start.test(l) || r.re.end.test(l))) ) - .join('\n') + ) ) + } else { + token.content = `No region #${region} found in path: ${src}` + token.info = '' + return fence(...args) } + } else { + content = stripRegionMarkers(content.split('\n')) } token.content = content diff --git a/src/node/utils/processIncludes.ts b/src/node/utils/processIncludes.ts index c13c2809..bf4bcf22 100644 --- a/src/node/utils/processIncludes.ts +++ b/src/node/utils/processIncludes.ts @@ -71,9 +71,13 @@ export function processIncludes( } } - content = regions - .flatMap((region) => lines.slice(region.start, region.end)) - .join('\n') + if (regions.length > 0) { + content = regions + .flatMap((region) => lines.slice(region.start, region.end)) + .join('\n') + } else { + content = `No region or heading #${region} found in path: ${includePath}` + } } if (range) {