diff --git a/nix/npm-deps.hash b/nix/npm-deps.hash index be20d58e4..9dd302c37 100644 --- a/nix/npm-deps.hash +++ b/nix/npm-deps.hash @@ -1 +1 @@ -sha256-Ep6Z+J0urY1L66cXGe9bB6tQr8T3TY73GkLrvhf8FmA= +sha256-WblXeNn1wUUaxElDGNXHcWdvvHJhqp7LySanhLDv5lQ= diff --git a/package-lock.json b/package-lock.json index b95f6d4bf..0cd688576 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2573,6 +2573,54 @@ "@lezer/common": "^1.1.0" } }, + "node_modules/@codemirror/lang-css": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz", + "integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.2", + "@lezer/css": "^1.1.7" + } + }, + "node_modules/@codemirror/lang-html": { + "version": "6.4.12", + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.12.tgz", + "integrity": "sha512-pw2ReWKUqSkbvh76RAT4NYxiogRu+PWkR2ukAwO9uOgrm8uipkzjtKKtNpyeAQwHOqxEeSvAXZ6vr3AfyB9y/w==", + "license": "MIT", + "peer": true, + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/css": "^1.1.0", + "@lezer/html": "^1.3.12" + } + }, + "node_modules/@codemirror/lang-javascript": { + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.5.tgz", + "integrity": "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==", + "license": "MIT", + "peer": true, + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/javascript": "^1.0.0" + } + }, "node_modules/@codemirror/language": { "version": "6.12.4", "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.4.tgz", @@ -2596,6 +2644,18 @@ "@codemirror/language": "^6.0.0" } }, + "node_modules/@codemirror/lint": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.7.tgz", + "integrity": "sha512-28/+iWLYxKxsvGYhSYL7zaCZqLz5+FFFDq9tVsvGv9kv8RY4fFAchJ5WX9M3YrrRlTIsECjsXPqeNgnSmNP2dg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.42.0", + "crelt": "^1.0.5" + } + }, "node_modules/@codemirror/search": { "version": "6.7.1", "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.7.1.tgz", @@ -11169,6 +11229,25 @@ "@lezer/lr": "^1.0.0" } }, + "node_modules/@replit/codemirror-lang-svelte": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@replit/codemirror-lang-svelte/-/codemirror-lang-svelte-6.0.0.tgz", + "integrity": "sha512-U2OqqgMM6jKelL0GNWbAmqlu1S078zZNoBqlJBW+retTc5M4Mha6/Y2cf4SVg6ddgloJvmcSpt4hHrVoM4ePRA==", + "license": "MIT", + "peerDependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-css": "^6.0.1", + "@codemirror/lang-html": "^6.2.0", + "@codemirror/lang-javascript": "^6.1.1", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/highlight": "^1.0.0", + "@lezer/javascript": "^1.2.0", + "@lezer/lr": "^1.0.0" + } + }, "node_modules/@replit/codemirror-vim": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/@replit/codemirror-vim/-/codemirror-vim-6.3.0.tgz", @@ -38597,6 +38676,7 @@ "@lezer/yaml": "^1.0.4", "@replit/codemirror-lang-csharp": "^6.2.0", "@replit/codemirror-lang-nix": "^6.0.1", + "@replit/codemirror-lang-svelte": "^6.0.0", "lezer-elixir": "^1.1.2" }, "devDependencies": { diff --git a/packages/highlight/package.json b/packages/highlight/package.json index 67c0acd5b..9bb454fba 100644 --- a/packages/highlight/package.json +++ b/packages/highlight/package.json @@ -47,6 +47,7 @@ "@lezer/yaml": "^1.0.4", "@replit/codemirror-lang-csharp": "^6.2.0", "@replit/codemirror-lang-nix": "^6.0.1", + "@replit/codemirror-lang-svelte": "^6.0.0", "lezer-elixir": "^1.1.2" }, "devDependencies": { diff --git a/packages/highlight/src/__tests__/highlighter.test.ts b/packages/highlight/src/__tests__/highlighter.test.ts index 0e972e9b7..830d4e4ba 100644 --- a/packages/highlight/src/__tests__/highlighter.test.ts +++ b/packages/highlight/src/__tests__/highlighter.test.ts @@ -86,6 +86,38 @@ describe("highlightCode", () => { expect(stringToken?.style).toBe("string"); }); + it("highlights Svelte components across script, markup, and style", () => { + const code = [ + '", + "", + "{#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]);