highlight
uses tcmts.cli.pages.jsxPieces, which uses tcmts.core.ts.parse, which uses tcmts.core.ts.compiler, which uses process
Source as HTML: tokens classed for colouring (k keyword, x control keyword, s string, n number, c comment,
f called function, t type name), each reference to a definition a link (<a data-hash>), and an import's module
a link to its namespace's or package's page. Uses TypeScript's scanner, re-scanning where it needs context: a
} that resumes a template literal, a / that starts a regex.
import { CONTROL, esc, jsxPieces } from "#codebase/tcmts/cli/pages"; import type { HighlightRef } from "#codebase/tcmts/cli/pages"; import { createScanner as ts_createScanner, LanguageVariant as ts_LanguageVariant, SyntaxKind as ts_SyntaxKind, } from "typescript/ast"; export function highlight(text: string, refs: HighlightRef[], typeHashes: Set<string>, jsx = false): string { const scanner = ts_createScanner(false, ts_LanguageVariant.Standard, text); const at = new Map(refs.map((r) => [r.start, r])); // (the scanner alone reads JSX's text as code: those parts come from the syntax tree) const pieces = jsx ? jsxPieces(text, at) : undefined; // open braces since each template substitution started (`${`): the `}` closing one resumes the template const templates: number[] = []; let previous = ts_SyntaxKind.Unknown; const endsExpression = (k: ts_SyntaxKind) => k === ts_SyntaxKind.Identifier || k === ts_SyntaxKind.CloseParenToken || k === ts_SyntaxKind.CloseBracketToken || k === ts_SyntaxKind.CloseBraceToken || k === ts_SyntaxKind.NumericLiteral || k === ts_SyntaxKind.StringLiteral || k === ts_SyntaxKind.ThisKeyword || k === ts_SyntaxKind.NoSubstitutionTemplateLiteral || k === ts_SyntaxKind.TemplateTail; let out = ""; for (let kind = scanner.scan(); kind !== ts_SyntaxKind.EndOfFile; kind = scanner.scan()) { if (kind === ts_SyntaxKind.OpenBraceToken && templates.length) templates[templates.length - 1]!++; if (kind === ts_SyntaxKind.CloseBraceToken && templates.length) { if (templates.at(-1) === 0) { kind = scanner.reScanTemplateToken(false); if (kind === ts_SyntaxKind.TemplateTail) templates.pop(); } else templates[templates.length - 1]!--; } if (kind === ts_SyntaxKind.TemplateHead) templates.push(0); if ((kind === ts_SyntaxKind.SlashToken || kind === ts_SyntaxKind.SlashEqualsToken) && !endsExpression(previous)) kind = scanner.reScanSlashToken(); const start = scanner.getTokenStart(); const piece = pieces?.get(start); if (piece) { out += piece.html; scanner.resetTokenState(piece.end); // (a JSX element ends an expression) previous = ts_SyntaxKind.Identifier; continue; } const raw = text.slice(start, scanner.getTokenEnd()); const trivia = kind === ts_SyntaxKind.WhitespaceTrivia || kind === ts_SyntaxKind.NewLineTrivia; const ref = at.get(start); // a name followed by a call (or type arguments, then a call): coloured as a function, linked or not const called = kind === ts_SyntaxKind.Identifier && /^\s*(<[^>()]*>)?\s*\(/.test(text.slice(scanner.getTokenEnd(), scanner.getTokenEnd() + 80)); if (ref?.hash && kind === ts_SyntaxKind.Identifier) { // (a link keeps the colour the name would have) out += `<a class="ref${typeHashes.has(ref.hash) ? " t" : called ? " f" : ""}" data-hash="${ref.hash}">${esc(raw)}</a>`; } else if (ref && kind === ts_SyntaxKind.StringLiteral && (ref.namespace !== undefined || ref.package)) { const to = ref.package ? `data-package-name="${esc(ref.package.name)}" data-package-range="${esc(ref.package.range)}"` : `data-namespace="${esc(ref.namespace!)}"`; out += `<a class="ref s" ${to}>${esc(raw)}</a>`; } else { const cls = kind === ts_SyntaxKind.SingleLineCommentTrivia || kind === ts_SyntaxKind.MultiLineCommentTrivia ? "c" : kind >= ts_SyntaxKind.FirstKeyword && kind <= ts_SyntaxKind.LastKeyword ? (CONTROL.has(raw) ? "x" : "k") : kind === ts_SyntaxKind.StringLiteral || kind === ts_SyntaxKind.RegularExpressionLiteral || (kind >= ts_SyntaxKind.FirstTemplateToken && kind <= ts_SyntaxKind.LastTemplateToken) ? "s" : kind === ts_SyntaxKind.NumericLiteral || kind === ts_SyntaxKind.BigIntLiteral ? "n" : called ? "f" : ""; out += cls ? `<span class="${cls}">${esc(raw)}</span>` : esc(raw); } if (!trivia && kind !== ts_SyntaxKind.SingleLineCommentTrivia && kind !== ts_SyntaxKind.MultiLineCommentTrivia) previous = kind; } return out; }