fix(ui): stop shiki template-call backtracking from OOMing the renderer
Shiki's bundled JS/TS/JSX/TSX grammars carry a `template-call` rule whose triple-nested lookahead sends the Oniguruma WASM engine into exponential backtracking on ordinary backtick templates. The WASM heap grows until the renderer runs out of memory and the window goes black. Strip `template-call` from those four grammars as they are loaded in the Shiki worker. Plain backticks and simple tagged templates still highlight; only the rare `ident<TypeArgs>` tagged-template form loses its specialized type-argument coloring. Add a 5s per-request budget on the worker client as a safety net for any other pathological pattern. Matching is synchronous inside the worker, so the only way to reclaim its heap is to terminate it from the main thread. A timed-out request resolves `null` like any other failure, so the caller keeps plain text and nothing is written to the result cache. Closes #2587
This commit is contained in:
@@ -11,6 +11,8 @@ import {
|
||||
type BundledLanguageModule = { default: LanguageRegistration[] };
|
||||
|
||||
const loadBundledGrammar = async (id: (typeof TEMPLATE_CALL_LANGUAGE_IDS)[number]): Promise<LanguageRegistration> => {
|
||||
// SAFETY: `id` comes from TEMPLATE_CALL_LANGUAGE_IDS, and every Shiki bundled
|
||||
// language module default-exports its grammar array.
|
||||
const mod = (await bundledLanguages[id]()) as BundledLanguageModule;
|
||||
return mod.default[0];
|
||||
};
|
||||
@@ -45,6 +47,7 @@ describe('sanitizeTemplateCallGrammar', () => {
|
||||
});
|
||||
|
||||
test('highlights template-literal fixtures within a tight budget after sanitize', async () => {
|
||||
// SAFETY: the javascript bundle default-exports its grammar array.
|
||||
const mod = (await bundledLanguages.javascript()) as BundledLanguageModule;
|
||||
const patched = mod.default.map((grammar) => sanitizeTemplateCallGrammar(grammar));
|
||||
|
||||
|
||||
@@ -42,4 +42,4 @@ export const TEMPLATE_CALL_LANGUAGE_IDS = ['javascript', 'typescript', 'jsx', 't
|
||||
export type TemplateCallLanguageId = (typeof TEMPLATE_CALL_LANGUAGE_IDS)[number];
|
||||
|
||||
export const isTemplateCallLanguageId = (lang: string): lang is TemplateCallLanguageId =>
|
||||
(TEMPLATE_CALL_LANGUAGE_IDS as readonly string[]).includes(lang);
|
||||
TEMPLATE_CALL_LANGUAGE_IDS.some((id) => id === lang);
|
||||
|
||||
Reference in New Issue
Block a user