Skip to content

Commit 2e7abe4

Browse files
committed
Normalize React language ids for syntax highlighting
- Map `typescriptreact` and `javascriptreact` to Shiki-supported languages - Use normalized cache keys and fall back cleanly when highlighting fails
1 parent d938c86 commit 2e7abe4

1 file changed

Lines changed: 24 additions & 7 deletions

File tree

apps/web/src/lib/syntaxHighlighting.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,21 @@ import { LRUCache } from "./lruCache";
44
import { fnv1a32, resolveDiffThemeName, type DiffThemeName } from "./diffRendering";
55

66
const CODE_FENCE_LANGUAGE_REGEX = /(?:^|\s)language-([^\s]+)/;
7+
8+
/**
9+
* Map VSCode language identifiers that don't match Shiki's bundled language names.
10+
* VSCode uses e.g. "typescriptreact" / "javascriptreact" while Shiki expects "tsx" / "jsx".
11+
*/
12+
const VSCODE_TO_SHIKI_LANG: Record<string, string> = {
13+
typescriptreact: "tsx",
14+
javascriptreact: "jsx",
15+
};
16+
17+
/** Normalise a language identifier so Shiki can resolve it. */
18+
function normalizeLanguage(language: string): string {
19+
return VSCODE_TO_SHIKI_LANG[language] ?? language;
20+
}
21+
722
const MAX_HIGHLIGHT_CACHE_ENTRIES = 500;
823
const MAX_HIGHLIGHT_CACHE_MEMORY_BYTES = 50 * 1024 * 1024;
924
const highlightedCodeCache = new LRUCache<string>(
@@ -61,24 +76,25 @@ export function setCachedHighlightedHtml(
6176
}
6277

6378
export function getHighlighterPromise(language: string): Promise<DiffsHighlighter> {
64-
const cached = highlighterPromiseCache.get(language);
79+
const normalized = normalizeLanguage(language);
80+
const cached = highlighterPromiseCache.get(normalized);
6581
if (cached) return cached;
6682

6783
const promise = getSharedHighlighter({
6884
themes: [resolveDiffThemeName("dark"), resolveDiffThemeName("light")],
69-
langs: [language as SupportedLanguages],
85+
langs: [normalized as SupportedLanguages],
7086
preferredHighlighter: "shiki-js",
7187
}).catch((err) => {
72-
highlighterPromiseCache.delete(language);
73-
if (language === "text") {
88+
highlighterPromiseCache.delete(normalized);
89+
if (normalized === "text") {
7490
// "text" itself failed — Shiki cannot initialize at all, surface the error
7591
throw err;
7692
}
7793
// Language not supported by Shiki — fall back to "text"
7894
return getHighlighterPromise("text");
7995
});
8096

81-
highlighterPromiseCache.set(language, promise);
97+
highlighterPromiseCache.set(normalized, promise);
8298
return promise;
8399
}
84100

@@ -88,12 +104,13 @@ export function renderHighlightedCodeHtml(
88104
language: string,
89105
themeName: DiffThemeName,
90106
): string {
107+
const normalized = normalizeLanguage(language);
91108
try {
92-
return highlighter.codeToHtml(code, { lang: language, theme: themeName });
109+
return highlighter.codeToHtml(code, { lang: normalized, theme: themeName });
93110
} catch (error) {
94111
// Log highlighting failures for debugging while falling back to plain text
95112
console.warn(
96-
`Code highlighting failed for language "${language}", falling back to plain text.`,
113+
`Code highlighting failed for language "${normalized}", falling back to plain text.`,
97114
error instanceof Error ? error.message : error,
98115
);
99116
return highlighter.codeToHtml(code, { lang: "text", theme: themeName });

0 commit comments

Comments
 (0)