Render Astro diffs containing empty expressions (#6763)

* fix(highlight): render Astro diffs containing empty expressions

Empty Astro expressions produced zero-length nested JavaScript ranges, which rejected the whole diff. Leave those expressions in the markup tree and preserve highlighting around them.

* test(highlight): make empty Astro expression assertions explicit

---------

Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
This commit is contained in:
paseo-bot[bot]
2026-10-13 08:00:47 +02:00
committed by GitHub
parent 1839de5b1d
commit eb051ea81e
3 changed files with 56 additions and 4 deletions
@@ -144,6 +144,34 @@ const title = "Hello";
);
});
it("highlights Astro markup containing only an empty expression", () => {
const code = "<p>{}</p>";
const tokens = highlightCode(code, "Empty.astro").flat();
expect(tokens.map((token) => token.text).join("")).toBe(code);
expect(tokens).toContainEqual({ text: "p", style: "tag" });
});
it.each([
"<p>{title} {} {title}</p>",
'<p class="{} {title}">{title}</p>',
"<p class={} title={title}>{title}</p>",
])("highlights Astro containing an empty expression: %s", (markup) => {
const code = `---\nconst title = "Hello";\n---\n${markup}\n<style>.page { color: red; }</style>`;
const lines = highlightCode(code, "Empty.astro");
expect(lines).toHaveLength(code.split("\n").length);
for (const [index, tokens] of lines.entries()) {
expect(tokens.map((token) => token.text).join("")).toBe(code.split("\n")[index]);
}
expect(lines.flat()).toContainEqual({ text: "const", style: "keyword" });
expect(lines.flat()).toContainEqual({ text: "p", style: "tag" });
expect(lines.flat()).toContainEqual({ text: "color", style: "property" });
expect(lines.flat()).toContainEqual({ text: "title", style: "variable" });
});
it("keeps an Astro expression open through nested template strings", () => {
const code = "<p>{`a ${`}`} b`}</p>";
+6 -4
View File
@@ -45,10 +45,12 @@ function maskExpressions(text: string): string {
}
function expressionOverlays(node: SyntaxNodeRef, input: Input): Range[] | null {
const overlays = findExpressions(input.read(node.from, node.to)).map(({ from, to }) => ({
from: node.from + from + 1,
to: node.from + to,
}));
const overlays = findExpressions(input.read(node.from, node.to))
.filter(({ from, to }) => to > from + 1)
.map(({ from, to }) => ({
from: node.from + from + 1,
to: node.from + to,
}));
return overlays.length > 0 ? overlays : null;
}
@@ -625,6 +625,28 @@ describe("highlightDiffFromHunks", () => {
});
describe("highlightDiffWithFileContent", () => {
it("renders an Astro diff with an empty expression and keeps surrounding highlighting", async () => {
const diff = ASTRO_DIFF.replace("{title}", "{title} {}");
const file = parseDiff(diff)[0];
const highlighted = await highlightDiffWithFileContent(file, ".", {
oldFileContent:
'---\nconst title = "Hello";\n---\n<main class="page">\n <h1>{title} {}</h1>\n</main>',
newFileContent:
'---\nconst title = "Hello, Astro";\n---\n<main class="page">\n <h1>{title} {}</h1>\n</main>',
});
expect(highlighted.hunks[0].lines.find((line) => line.type === "add")?.tokens).toContainEqual({
text: '"Hello, Astro"',
style: "string",
});
expect(highlighted.hunks[0].lines.map(({ content }) => content)).toEqual(
file.hunks[0].lines.map(({ content }) => content),
);
expect(
highlightDiffFromHunks(file).hunks[0].lines.find((line) => line.type === "add")?.tokens,
).toContainEqual({ text: '"Hello, Astro"', style: "string" });
});
it("leaves files with an oversized line unhighlighted", async () => {
const oversizedLine = `const payload = "${"x".repeat(10_001)}";`;
const [file] = parseDiff(