respelled
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), }; }