-
Notifications
You must be signed in to change notification settings - Fork 372
[rendering-scripts] fix: preserve fenced code blocks in template rendering #24862
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4,7 +4,7 @@ import path from "path"; | |||||||||||||||||||||||||
| import { fileURLToPath } from "url"; | ||||||||||||||||||||||||||
| const __filename = fileURLToPath(import.meta.url), | ||||||||||||||||||||||||||
| __dirname = path.dirname(__filename), | ||||||||||||||||||||||||||
| core = { info: vi.fn(), setFailed: vi.fn() }; | ||||||||||||||||||||||||||
| core = { info: vi.fn(), warning: vi.fn(), setFailed: vi.fn() }; | ||||||||||||||||||||||||||
| global.core = core; | ||||||||||||||||||||||||||
| const { isTruthy } = require("./is_truthy.cjs"), | ||||||||||||||||||||||||||
| interpolatePromptScript = fs.readFileSync(path.join(__dirname, "interpolate_prompt.cjs"), "utf8"), | ||||||||||||||||||||||||||
|
|
@@ -200,5 +200,37 @@ describe("renderMarkdownTemplate - Additional Edge Cases", () => { | |||||||||||||||||||||||||
| const output = renderMarkdownTemplate("{{#if false}}\nContent\n{{/if}}Line after"); | ||||||||||||||||||||||||||
| expect(output).toBe("Line after"); | ||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||
| describe("fenced code blocks", () => { | ||||||||||||||||||||||||||
| (it("should preserve {{#if false}} markers inside a fenced code block (regression)", () => { | ||||||||||||||||||||||||||
| const input = "```js\n{{#if false}}\nHidden\n{{/if}}\n```"; | ||||||||||||||||||||||||||
| const output = renderMarkdownTemplate(input); | ||||||||||||||||||||||||||
| expect(output).toBe(input); | ||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||
| it("should preserve {{#if true}} markers inside a fenced code block", () => { | ||||||||||||||||||||||||||
| const input = "```js\n{{#if true}}\nVisible\n{{/if}}\n```"; | ||||||||||||||||||||||||||
| const output = renderMarkdownTemplate(input); | ||||||||||||||||||||||||||
| expect(output).toBe(input); | ||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||
| it("should process conditionals outside fenced blocks while preserving inside", () => { | ||||||||||||||||||||||||||
| const input = "{{#if false}}\nRemove this\n{{/if}}\n```js\n{{#if false}}\nKeep this\n{{/if}}\n```"; | ||||||||||||||||||||||||||
| const output = renderMarkdownTemplate(input); | ||||||||||||||||||||||||||
| expect(output).toBe("```js\n{{#if false}}\nKeep this\n{{/if}}\n```"); | ||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||
| it("should preserve fence count (no fence markers lost or gained)", () => { | ||||||||||||||||||||||||||
| const input = "```js\n{{#if false}}\nHidden\n{{/if}}\n```"; | ||||||||||||||||||||||||||
| const output = renderMarkdownTemplate(input); | ||||||||||||||||||||||||||
| expect((output.match(/`{3,}/g) || []).length).toBe((input.match(/`{3,}/g) || []).length); | ||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||
| it("should preserve multiple fenced code blocks unchanged", () => { | ||||||||||||||||||||||||||
| const input = "```js\ncode 1\n```\n\n```py\ncode 2\n```"; | ||||||||||||||||||||||||||
| const output = renderMarkdownTemplate(input); | ||||||||||||||||||||||||||
| expect(output).toBe(input); | ||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||
| it("should handle fenced blocks adjacent to conditionals", () => { | ||||||||||||||||||||||||||
| const input = "{{#if true}}\nKeep\n{{/if}}\n```python\nprint('hello')\n```"; | ||||||||||||||||||||||||||
| const output = renderMarkdownTemplate(input); | ||||||||||||||||||||||||||
| expect(output).toBe("Keep\n```python\nprint('hello')\n```"); | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
| expect(output).toBe("Keep\n```python\nprint('hello')\n```"); | |
| expect(output).toBe("Keep\n```python\nprint('hello')\n```"); | |
| }), | |
| it("should preserve indented fenced code blocks unchanged", () => { | |
| const input = " ```js\n {{#if false}}\n Hidden\n {{/if}}\n ```"; | |
| const output = renderMarkdownTemplate(input); | |
| expect(output).toBe(input); | |
| }), | |
| it("should preserve an outer 4-backtick fence containing an inner triple-backtick block", () => { | |
| const input = "````md\n```js\n{{#if false}}\nHidden\n{{/if}}\n```\n````"; | |
| const output = renderMarkdownTemplate(input); | |
| expect(output).toBe(input); |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -24,9 +24,20 @@ function renderMarkdownTemplate(markdown) { | |||||||||||||||||||||||||||||||||
| core.info(`[renderMarkdownTemplate] Starting template rendering`); | ||||||||||||||||||||||||||||||||||
| core.info(`[renderMarkdownTemplate] Input length: ${markdown.length} characters`); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // Preserve fenced code blocks to avoid processing {{#if}} markers inside them | ||||||||||||||||||||||||||||||||||
| const _codeBlocks = []; | ||||||||||||||||||||||||||||||||||
| const _FENCE_PH = "\x00FENCE\x00"; | ||||||||||||||||||||||||||||||||||
| const _stripped = markdown.replace(/`{3,}[^\n]*\n[\s\S]*?\n`{3,}[ \t]*/g, m => { | ||||||||||||||||||||||||||||||||||
| _codeBlocks.push(m); | ||||||||||||||||||||||||||||||||||
| return `${_FENCE_PH}${_codeBlocks.length - 1}${_FENCE_PH}`; | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+27
to
+32
|
||||||||||||||||||||||||||||||||||
| // Preserve fenced code blocks to avoid processing {{#if}} markers inside them | |
| const _codeBlocks = []; | |
| const _FENCE_PH = "\x00FENCE\x00"; | |
| const _stripped = markdown.replace(/`{3,}[^\n]*\n[\s\S]*?\n`{3,}[ \t]*/g, m => { | |
| _codeBlocks.push(m); | |
| return `${_FENCE_PH}${_codeBlocks.length - 1}${_FENCE_PH}`; | |
| // Preserve fenced code blocks to avoid processing {{#if}} markers inside them. | |
| // Match only a closing fence with the same indentation and backtick length as the opener, | |
| // so outer 4+ backtick fences are not terminated by inner triple-backtick lines. | |
| const _codeBlocks = []; | |
| const _FENCE_PH = "\x00FENCE\x00"; | |
| const _FENCED_CODE_BLOCK_RE = /(^|\n)([ \t]*)(`{3,})[^\n]*\n[\s\S]*?\n\2\3[ \t]*(?=\n|$)/g; | |
| const _stripped = markdown.replace(_FENCED_CODE_BLOCK_RE, (m, lineStart) => { | |
| const block = m.slice(lineStart.length); | |
| _codeBlocks.push(block); | |
| return `${lineStart}${_FENCE_PH}${_codeBlocks.length - 1}${_FENCE_PH}`; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -86,4 +86,36 @@ describe("renderMarkdownTemplate", () => { | |||||||||||||||||||||||||||||||||||||||||||||||
| const output = renderMarkdownTemplate(" {{#if true}}\n Content\n {{/if}}\nNext line"); | ||||||||||||||||||||||||||||||||||||||||||||||||
| expect(output).toBe(" Content\nNext line"); | ||||||||||||||||||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||||||||||||||||||
| describe("fenced code blocks", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| it("should preserve {{#if false}} markers inside a fenced code block (regression)", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const input = "```js\n{{#if false}}\nHidden\n{{/if}}\n```"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const output = renderMarkdownTemplate(input); | ||||||||||||||||||||||||||||||||||||||||||||||||
| expect(output).toBe(input); | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
| it("should preserve {{#if true}} markers inside a fenced code block", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const input = "```js\n{{#if true}}\nVisible\n{{/if}}\n```"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const output = renderMarkdownTemplate(input); | ||||||||||||||||||||||||||||||||||||||||||||||||
| expect(output).toBe(input); | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
| it("should process conditionals outside fenced blocks while preserving inside", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const input = "{{#if false}}\nRemove this\n{{/if}}\n```js\n{{#if false}}\nKeep this\n{{/if}}\n```"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const output = renderMarkdownTemplate(input); | ||||||||||||||||||||||||||||||||||||||||||||||||
| expect(output).toBe("```js\n{{#if false}}\nKeep this\n{{/if}}\n```"); | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
| it("should preserve fence count (no fence markers lost or gained)", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const input = "```js\n{{#if false}}\nHidden\n{{/if}}\n```"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const output = renderMarkdownTemplate(input); | ||||||||||||||||||||||||||||||||||||||||||||||||
| expect((output.match(/`{3,}/g) || []).length).toBe((input.match(/`{3,}/g) || []).length); | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
| it("should preserve multiple fenced code blocks unchanged", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const input = "```js\ncode 1\n```\n\n```py\ncode 2\n```"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const output = renderMarkdownTemplate(input); | ||||||||||||||||||||||||||||||||||||||||||||||||
| expect(output).toBe(input); | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
| it("should handle fenced blocks with language tag and conditional outside", () => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const input = "{{#if true}}\nKeep\n{{/if}}\n```python\nprint('hello')\n```"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const output = renderMarkdownTemplate(input); | ||||||||||||||||||||||||||||||||||||||||||||||||
| expect(output).toBe("Keep\n```python\nprint('hello')\n```"); | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
| }); | |
| }); | |
| it("should preserve indented fenced code blocks unchanged", () => { | |
| const input = " ```js\n{{#if false}}\nHidden\n{{/if}}\n ```"; | |
| const output = renderMarkdownTemplate(input); | |
| expect(output).toBe(input); | |
| }); | |
| it("should process conditionals outside indented fenced blocks while preserving inside", () => { | |
| const input = "{{#if false}}\nRemove this\n{{/if}}\n ```js\n{{#if false}}\nKeep this\n{{/if}}\n ```"; | |
| const output = renderMarkdownTemplate(input); | |
| expect(output).toBe(" ```js\n{{#if false}}\nKeep this\n{{/if}}\n ```"); | |
| }); | |
| it("should preserve outer fences of 4-6 backticks containing an inner triple-backtick block", () => { | |
| const input = "````markdown\n```js\n{{#if false}}\nHidden\n{{/if}}\n```\n````"; | |
| const output = renderMarkdownTemplate(input); | |
| expect(output).toBe(input); | |
| }); | |
| it("should preserve fence count for nested fenced blocks with longer outer fences", () => { | |
| const input = "`````\n```js\n{{#if true}}\nVisible\n{{/if}}\n```\n`````"; | |
| const output = renderMarkdownTemplate(input); | |
| expect((output.match(/`{3,}/g) || []).length).toBe((input.match(/`{3,}/g) || []).length); | |
| expect(output).toBe(input); | |
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same issue as render_template.cjs: the fenced-code-block extraction regex can terminate an outer 4–6 backtick fence at an inner closing ``` line, which is common when embedding Markdown that itself contains triple-backtick code blocks. Use an opening-fence capture + backreference (and multiline line-start anchoring) so only a matching-length closing fence ends the preserved block.