1// Lightweight single-pass TS highlighter, ported from the homegrown one in
2// public/landing/lib/TrackCloud.jsx. Outputs HTML with <span class="alc-tok-*">
3// elements; the styling for those classes lives in src/styles/marketing.css.
4//
5// This isn't a real tokenizer — it's a regex sweep that's good enough for the
6// short snippets shown in the marketing pages. Real prose code blocks should
7// use expressive-code via standard MDX ```ts fences.
8
9const KEYWORDS = new Set([
10 "import",
11 "export",
12 "default",
13 "class",
14 "extends",
15 "const",
16 "let",
17 "var",
18 "function",
19 "return",
20 "yield",
21 "if",
22 "else",
23 "for",
24 "of",
25 "in",
26 "as",
27 "new",
28 "typeof",
29 "async",
30 "await",
31 "from",
32 "interface",
33 "type",
34 "null",
35 "true",
36 "false",
37 "this",
38 "void",
39 "throw",
40]);
41
42const RE =
43 /(\/\/[^\n]*)|(\/\*[\s\S]*?\*\/)|("(?:[^"\\]|\\.)*")|('(?:[^'\\]|\\.)*')|(`(?:[^`\\]|\\.)*`)|\b([A-Za-z_$][a-zA-Z0-9_$]*)\b|\b(\d+)\b/g;
44
45function escape(s: string): string {
46 return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
47}
48
49export function highlightTS(src: string): string {
50 let out = "";
51 let last = 0;
52 let m: RegExpExecArray | null;
53 RE.lastIndex = 0;
54 while ((m = RE.exec(src)) !== null) {
55 if (m.index > last) out += escape(src.slice(last, m.index));
56 if (m[1] || m[2]) {
57 out += `<span class="alc-tok-c">${escape(m[0])}</span>`;
58 } else if (m[3] || m[4] || m[5]) {
59 out += `<span class="alc-tok-s">${escape(m[0])}</span>`;
60 } else if (m[6]) {
61 const id = m[6];
62 if (KEYWORDS.has(id)) {
63 out += `<span class="alc-tok-k">${id}</span>`;
64 } else if (/^[A-Z]/.test(id)) {
65 out += `<span class="alc-tok-t">${id}</span>`;
66 } else {
67 out += `<span class="alc-tok-v">${id}</span>`;
68 }
69 } else if (m[7]) {
70 out += `<span class="alc-tok-n">${m[0]}</span>`;
71 }
72 last = RE.lastIndex;
73 }
74 if (last < src.length) out += escape(src.slice(last));
75 return out;
76}