jsxPieces

tcmts.cli.pages.jsxPieces · #bvlhu2q2 · term · runs on the server

uses tcmts.core.ts.parse, which uses tcmts.core.ts.compiler, which uses process

The parts of JSX that highlighting takes from the syntax tree rather than the scanner, by where each starts: text between tags (which the scanner would read as code), closing tags, and tag and attribute names (aria-label is one name). A built-in tag (div) is coloured as a tag (g), a component as a type (t), linked if it's in refs; an attribute name as an attribute (a).

import { esc } from "#codebase/tcmts/cli/pages";
import type { HighlightRef } from "#codebase/tcmts/cli/pages";
import { parse } from "#codebase/tcmts/core/ts";
import {
  isJsxAttribute as ts_isJsxAttribute, isJsxClosingElement as ts_isJsxClosingElement,
  isJsxClosingFragment as ts_isJsxClosingFragment, isJsxOpeningElement as ts_isJsxOpeningElement,
  isJsxSelfClosingElement as ts_isJsxSelfClosingElement, isJsxText as ts_isJsxText,
} from "typescript/ast";
import type { Node as ts_Node } from "typescript/ast";

export function jsxPieces(text: string, refs: Map<number, HighlightRef>): Map<number, { end: number; html: string }> {
  const sf = parse("code.tsx", text);
  const out = new Map<number, { end: number; html: string }>();
  const tag = (n: ts_Node) => {
    const start = n.getStart(sf);
    const raw = text.slice(start, n.end);
    const ref = refs.get(start);
    if (ref?.hash) return `<a class="ref t" data-hash="${ref.hash}">${esc(raw)}</a>`;
    return `<span class="${/^[a-z][\w-]*$/.test(raw) ? "g" : "t"}">${esc(raw)}</span>`;
  };
  const walk = (n: ts_Node): void => {
    if (ts_isJsxText(n)) {
      if (n.end > n.pos) out.set(n.pos, { end: n.end, html: esc(text.slice(n.pos, n.end)) });
      return;
    }
    if (ts_isJsxClosingElement(n)) {
      const start = n.getStart(sf);
      const name = n.tagName;
      out.set(start, { end: n.end, html: esc(text.slice(start, name.getStart(sf))) + tag(name) + esc(text.slice(name.end, n.end)) });
      return;
    }
    if (ts_isJsxClosingFragment(n)) {
      const start = n.getStart(sf);
      out.set(start, { end: n.end, html: esc(text.slice(start, n.end)) });
      return;
    }
    if (ts_isJsxOpeningElement(n) || ts_isJsxSelfClosingElement(n)) out.set(n.tagName.getStart(sf), { end: n.tagName.end, html: tag(n.tagName) });
    if (ts_isJsxAttribute(n)) {
      const start = n.name.getStart(sf);
      out.set(start, { end: n.name.end, html: `<span class="a">${esc(text.slice(start, n.name.end))}</span>` });
    }
    n.forEachChild(walk, (arr) => void arr.forEach(walk));
  };
  walk(sf);
  return out;
}

uses

used by