fix(highlight): highlight Svelte components (#3487)

* fix(highlight): highlight Svelte components

`.svelte` had no entry in the extension table, so `highlightCode` fell through
to one unstyled token per line. That is the same table the file pane, the diff
tokens, markdown fences and the web editor all resolve through, so a component
rendered as plain text everywhere in the app.

Register `@replit/codemirror-lang-svelte`. Its `svelteLanguage` already wires
the mixed parsing for the `<script>` and `<style>` blocks, so the script gets
JS/TS roles, the style block gets CSS roles, and the template gets tag,
directive and mustache roles. It comes from the same publisher as the
`@replit/codemirror-lang-csharp` entry already in this package, and only the
grammar itself is declared: its CodeMirror peers resolve the same way the C#
grammar's peers already do.

* test(server): cover Svelte diff highlighting

The review surface in the issue is the diff, and the daemon is what tokenizes
it (`diff-highlighter.ts` calls `highlightCode`), so the regression belongs
here too: script, template block and markup roles on a `.svelte` hunk.
This commit is contained in:
Lorenzo Fiore
2026-08-17 12:43:11 +00:00
committed by GitHub
parent f3202ac419
commit 1917fdd131
7 changed files with 161 additions and 1 deletions
@@ -160,6 +160,21 @@ index 1234567..abcdefg 100644
</config>
`;
const SVELTE_DIFF = `diff --git a/Counter.svelte b/Counter.svelte
index 1234567..abcdefg 100644
--- a/Counter.svelte
+++ b/Counter.svelte
@@ -1,7 +1,7 @@
<script lang="ts">
- let count: number = 1;
+ let count: number = 2;
</script>
{#if count > 0}
<span class="badge">{count}</span>
{/if}
`;
describe("parseDiff", () => {
it("parses a simple diff with one hunk", () => {
const files = parseDiff(SIMPLE_DIFF);
@@ -471,6 +486,33 @@ describe("highlightDiffFromHunks", () => {
expect(addedLine!.tokens!.some((t) => t.text === "2" && t.style === "number")).toBe(true);
});
it("adds syntax highlighting tokens to Svelte components", () => {
const files = parseDiff(SVELTE_DIFF);
const highlighted = highlightDiffFromHunks(files[0]);
const hunk = highlighted.hunks[0];
const scriptLine = hunk.lines[1];
expect(scriptLine.tokens).toBeDefined();
expect(scriptLine.tokens!.some((t) => t.text === "script" && t.style === "tag")).toBe(true);
const addedLine = hunk.lines.find(
(line) => line.type === "add" && line.content.includes("count"),
);
expect(addedLine?.tokens).toBeDefined();
expect(addedLine!.tokens!.some((t) => t.text === "let" && t.style === "keyword")).toBe(true);
expect(addedLine!.tokens!.some((t) => t.text === "number" && t.style === "type")).toBe(true);
expect(addedLine!.tokens!.some((t) => t.text === "2" && t.style === "number")).toBe(true);
const blockLine = hunk.lines.find((line) => line.content.includes("{#if"));
expect(blockLine?.tokens).toBeDefined();
expect(blockLine!.tokens!.some((t) => t.text === "if" && t.style === "keyword")).toBe(true);
expect(blockLine!.tokens!.some((t) => t.text === "count" && t.style === "variable")).toBe(true);
const markupLine = hunk.lines.find((line) => line.content.includes("<span"));
expect(markupLine?.tokens).toBeDefined();
expect(markupLine!.tokens!.some((t) => t.text === "span" && t.style === "tag")).toBe(true);
});
it("adds syntax highlighting tokens to Objective-C file extensions", () => {
const files = parseDiff(OBJECTIVE_C_DIFF);
const highlighted = highlightDiffFromHunks(files[0]);