highlight

tcmts.cli.pages.highlight · #jzcofghf · term · runs on the server

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;
}

uses

used by