From 1917fdd1315085e4f390b08299a313077d050f3d Mon Sep 17 00:00:00 2001 From: Lorenzo Fiore Date: Mon, 17 Aug 2026 14:43:11 +0200 Subject: [PATCH] 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 `", + "", + "{#if count > 0}", + ' {count}', + "{/if}", + "", + "", + ].join("\n"); + const result = highlightCode(code, "Counter.svelte"); + + expect(result[1].find((t) => t.text === "let")?.style).toBe("keyword"); + expect(result[1].find((t) => t.text === "number")?.style).toBe("type"); + + expect(result[4].find((t) => t.text === "if")?.style).toBe("keyword"); + expect(result[4].find((t) => t.text === "count")?.style).toBe("variable"); + + expect(result[5].find((t) => t.text === "span")?.style).toBe("tag"); + expect(result[5].find((t) => t.text === "class")?.style).toBe("attribute"); + expect(result[5].find((t) => t.text === "count")?.style).toBe("variable"); + + expect(result[9].find((t) => t.text === "badge")?.style).toBe("class"); + expect(result[10].find((t) => t.text === "color")?.style).toBe("property"); + }); + it("highlights TSX code with correct dialect", () => { const code = 'const el =
hello
;'; const result = highlightCode(code, "test.tsx"); diff --git a/packages/highlight/src/__tests__/parsers.test.ts b/packages/highlight/src/__tests__/parsers.test.ts index 43ac1dd03..06dc90a38 100644 --- a/packages/highlight/src/__tests__/parsers.test.ts +++ b/packages/highlight/src/__tests__/parsers.test.ts @@ -23,6 +23,7 @@ describe("isLanguageSupported", () => { expect(isLanguageSupported("test.cs")).toBe(true); expect(isLanguageSupported("test.nix")).toBe(true); expect(isLanguageSupported("test.ex")).toBe(true); + expect(isLanguageSupported("Counter.svelte")).toBe(true); }); it("returns false for unsupported file extensions", () => { @@ -63,6 +64,7 @@ describe("getSupportedExtensions", () => { expect(extensions).toContain("cs"); expect(extensions).toContain("nix"); expect(extensions).toContain("json"); + expect(extensions).toContain("svelte"); }); }); diff --git a/packages/highlight/src/parsers.ts b/packages/highlight/src/parsers.ts index a5caa079b..b28a0e53a 100644 --- a/packages/highlight/src/parsers.ts +++ b/packages/highlight/src/parsers.ts @@ -16,6 +16,7 @@ import { parser as xmlParser } from "@lezer/xml"; import { parser as yamlParser } from "@lezer/yaml"; import { csharpLanguage } from "@replit/codemirror-lang-csharp"; import { nixLanguage } from "@replit/codemirror-lang-nix"; +import { svelteLanguage } from "@replit/codemirror-lang-svelte"; import { parser as elixirParser } from "lezer-elixir"; import type { Parser } from "@lezer/common"; @@ -49,6 +50,8 @@ const languagesByExtension: Record = { // HTML html: language(htmlParser), htm: language(htmlParser), + // Svelte + svelte: svelteLanguage, // XML xml: language(xmlParser), // Java diff --git a/packages/server/src/server/utils/diff-highlighter.test.ts b/packages/server/src/server/utils/diff-highlighter.test.ts index 58e3c16b1..07abdb496 100644 --- a/packages/server/src/server/utils/diff-highlighter.test.ts +++ b/packages/server/src/server/utils/diff-highlighter.test.ts @@ -160,6 +160,21 @@ index 1234567..abcdefg 100644 `; +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 @@ + + + {#if count > 0} + {count} + {/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(" 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]);