Skip to content

Commit 12afc85

Browse files
committed
feat: quoted fences support
1 parent f310227 commit 12afc85

3 files changed

Lines changed: 163 additions & 1 deletion

File tree

.changeset/two-poets-raise.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"lingo.dev": patch
3+
---
4+
5+
support for quoted and broken code blocks
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import { describe, it, expect } from "vitest";
2+
import createMdxCodePlaceholderLoader from "./code-placeholder";
3+
import dedent from "dedent";
4+
5+
// Helper regex to capture placeholder tokens
6+
const PLACEHOLDER_REGEX = /---CODE_PLACEHOLDER_[0-9a-f]+---/g;
7+
8+
// Test scenarios covering the different combinations we want to validate
9+
// Each scenario defines the test description, the MDX sample and how many
10+
// distinct code-fences it contains.
11+
const scenarios: { name: string; content: string; count: number }[] = [
12+
{
13+
name: "single fenced block with language tag",
14+
content: dedent`
15+
Paragraph with some code:
16+
17+
\`\`\`js
18+
console.log("foo");
19+
\`\`\`
20+
`,
21+
count: 1,
22+
},
23+
{
24+
name: "single fenced block without language tag",
25+
content: dedent`
26+
Some introductory text.
27+
28+
\`\`\`
29+
generic code
30+
\`\`\`
31+
`,
32+
count: 1,
33+
},
34+
{
35+
name: "block with language + meta parameters (highlight lines / title)",
36+
content: dedent`
37+
Demo with meta parameters:
38+
39+
\`\`\`js {1,2} title="Sample"
40+
console.log("line 1");
41+
console.log("line 2");
42+
\`\`\`
43+
`,
44+
count: 1,
45+
},
46+
{
47+
name: "fence starting immediately after previous text (no preceding newline)",
48+
content: dedent`Paragraph immediately before.\`\`\`js
49+
console.log("adjacent");
50+
\`\`\`
51+
Paragraph immediately after.`,
52+
count: 1,
53+
},
54+
{
55+
name: "fenced block inside a block-quote (> prefix on opening line)",
56+
content: dedent`
57+
> Quoted section starts
58+
>
59+
> \`\`\`ts
60+
let x = 42;
61+
\`\`\`
62+
> End quote
63+
`,
64+
count: 1,
65+
},
66+
{
67+
name: "multiple fenced blocks separated by blank lines",
68+
content: dedent`
69+
First example:
70+
71+
\`\`\`js
72+
console.log("first");
73+
\`\`\`
74+
75+
Second example:
76+
77+
\`\`\`
78+
plain code fence
79+
\`\`\`
80+
`,
81+
count: 2,
82+
},
83+
{
84+
name: "multiple adjacent fenced blocks (no blank lines)",
85+
content: dedent`
86+
\`\`\`js
87+
console.log("block 1");
88+
\`\`\`\n\`\`\`js
89+
console.log("block 2");
90+
\`\`\`
91+
`,
92+
count: 2,
93+
},
94+
{
95+
name: "indented fenced block (leading spaces)",
96+
content: dedent`
97+
Example with indentation:
98+
99+
\`\`\`js
100+
console.log("indented");
101+
\`\`\`
102+
`,
103+
count: 1,
104+
},
105+
{
106+
name: "code block immediately after heading (no blank line)",
107+
content: dedent`## Heading\n\n\`\`\`js\nconsole.log("heading");\n\`\`\``,
108+
count: 1,
109+
},
110+
{
111+
name: "code block inside a list item (indented under bullet)",
112+
content: dedent`
113+
- List item intro:
114+
115+
\`\`\`python
116+
print("bullet")
117+
\`\`\`
118+
`,
119+
count: 1,
120+
},
121+
{
122+
name: "code block wrapped inside JSX component",
123+
content: dedent`
124+
<Wrapper>
125+
\n\n\`\`\`js
126+
console.log("inside component");
127+
\`\`\`
128+
</Wrapper>
129+
`,
130+
count: 1,
131+
},
132+
];
133+
134+
describe("mdx code placeholder loader – extensive combinations", () => {
135+
scenarios.forEach(({ name, content, count }) => {
136+
it(`${name} – placeholder substitution round-trip`, async () => {
137+
const loader = createMdxCodePlaceholderLoader();
138+
loader.setDefaultLocale("en");
139+
140+
// Pull phase: code blocks ⇒ placeholders
141+
const pulled = await loader.pull("en", content);
142+
143+
const placeholders = pulled.match(PLACEHOLDER_REGEX) || [];
144+
expect(placeholders.length).toBe(count);
145+
// The pulled content should no longer contain any back-tick fences
146+
expect(pulled).not.toMatch(/```/);
147+
148+
// Push phase: placeholders ⇒ original code blocks
149+
const pushed = await loader.push("es", pulled);
150+
151+
// Result must contain the original fences back
152+
expect(pushed).toMatch(/```/);
153+
// And placeholders should have disappeared
154+
expect(pushed).not.toMatch(PLACEHOLDER_REGEX);
155+
});
156+
});
157+
});

packages/cli/src/cli/loaders/mdx2/code-placeholder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { createLoader } from "../_utils";
33
import { md5 } from "../../utils/md5";
44
import _ from "lodash";
55

6-
const fenceRegex = /```([\s\S]*?)```/g;
6+
const fenceRegex = /(>\s*)?```([\s\S]*?)```/g;
77

88
function ensureTrailingFenceNewline(_content: string) {
99
let found = false;

0 commit comments

Comments
 (0)