respelled

tcmts.cli.main.respelled · #zlglfa4x · term · runs on the server

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

A definition as analyzed, with the names its references are written with changed as names says (find_1 → find): the same code, so the same hash (a reference is by hash, whatever it's written as), read with other names. A shorthand property the file had to write out ({ find: find_1 }) is one again ({ find }, which hashes the same). How a definition saved from a file holding others too is stored as it reads alone.

import { parse } from "#codebase/tcmts/core/ts";
import type { DefRecord } from "#codebase/tcmts/core/types";
import { SyntaxKind as ts_SyntaxKind } from "typescript/ast";
import type {
  Identifier as ts_Identifier, Node as ts_Node, PropertyAssignment as ts_PropertyAssignment,
} from "typescript/ast";

export function respelled(def: DefRecord, names: ReadonlyMap<string, string>): DefRecord {
  if (!names.size) return def;
  let text = def.sourceText;
  let shift = 0;
  const changed = new Set<number>();
  let spans = [...def.spans]
    .sort((a, b) => a.start - b.start)
    .map((s, i) => {
      const start = s.start + shift;
      const end = s.end + shift;
      const was = text.slice(start, end);
      const to = s.literal ? undefined : names.get(was);
      if (to === undefined) return { ...s, start, end };
      text = text.slice(0, start) + to + text.slice(end);
      shift += to.length - was.length;
      changed.add(i);
      return { ...s, start, end: start + to.length };
    });
  // (a property's value now named as its key, `{ find: find }`, as the parser has it)
  const outs = (s: DefRecord["spans"][number], i: number) =>
    changed.has(i) && s.pos === "value" && !s.shorthand;
  if (spans.some(outs)) {
    const sf = parse(def.jsx ? "def.tsx" : "def.ts", text);
    const keys = new Map<number, number>();
    const walk = (n: ts_Node) => {
      if (n.kind === ts_SyntaxKind.PropertyAssignment) {
        const p = n as ts_PropertyAssignment;
        if (
          p.name.kind === ts_SyntaxKind.Identifier &&
          p.initializer.kind === ts_SyntaxKind.Identifier &&
          (p.name as ts_Identifier).text === (p.initializer as ts_Identifier).text
        )
          keys.set(p.initializer.getStart(sf), p.name.getStart(sf));
      }
      n.forEachChild(walk, (arr) => void arr.forEach(walk));
    };
    walk(sf);
    let cut = 0;
    spans = spans.map((s, i) => {
      const key = outs(s, i) ? keys.get(s.start) : undefined;
      if (key === undefined) return cut ? { ...s, start: s.start - cut, end: s.end - cut } : s;
      const k = key - cut;
      const start = s.start - cut;
      const name = text.slice(start, s.end - cut);
      text = text.slice(0, k) + text.slice(start);
      cut += start - k;
      return { ...s, start: k, end: k + name.length, shorthand: name };
    });
  }
  // (and its own name, as declared: not part of the hash, unless it's a function's or a class's, which keep theirs)
  return {
    ...def,
    sourceText: text,
    spans,
    nameHint: def.nominal ? def.nameHint : (names.get(def.nameHint) ?? def.nameHint),
  };
}

uses

used by