spelledAlone

tcmts.cli.main.spelledAlone · #hfx6fsoj · term · runs on the server

uses tcmts.loader.render, which uses node:path

A definition saved from a file holding others too (rows, analyzed from text), spelled as it reads alone, as tcm view shows it: what writing them together made the file call otherwise (find_1, Move_1, join_1, a name another there has) is stored with the name it has alone. A reference to the codebase is written with its name, as alone; an import from outside it keeps the name it's imported as, unless that's another's name with a number (join_1 where the file has a join), which alone it doesn't need. Only names change, so its hash doesn't (see respelled); a function's or class's own name, part of its hash, stays.

import type { Codebase } from "#codebase/tcmts/cli/codebase";
import { importBindings, respelled, sameNames } from "#codebase/tcmts/cli/main";
import type { Row } from "#codebase/tcmts/cli/main";
import { declarations } from "#codebase/tcmts/cli/mirror";
import { parse } from "#codebase/tcmts/core/ts";
import { render } from "#codebase/tcmts/loader";
import { SyntaxKind as ts_SyntaxKind } from "typescript/ast";
import type { Identifier as ts_Identifier, Node as ts_Node } from "typescript/ast";

export function spelledAlone(cb: Codebase, file: string, text: string, row: Row, rows: Row[]): Row {
  const def = row.def;
  if (def.kind === "file") return row;
  const paths = new Map(rows.map((r) => [r.def.hash, r.path]));
  const src = cb.source(
    rows.map((r) => r.def),
    (h) => paths.get(h) ?? cb.store.pathsOf(h)[0],
  );
  let alone: ReturnType<typeof render>;
  try {
    alone = render([def], src, cb.lay, { style: "scratch", omitImports: true });
  } catch {
    return row;
  }
  const start = alone.starts.get(def.hash);
  if (start === undefined) return row;
  const body = alone.text.slice(start).trimEnd().split("\n");
  const was = def.sourceText.split("\n");
  if (body.length !== was.length) return row;
  const names = new Map<string, string>();
  for (const [i, l] of was.entries()) if (l !== body[i]) sameNames(l, body[i]!, names);
  // an import the file numbered, as it has another by the name
  const bound = new Set([...importBindings(file, text).keys(), ...declarations(file, text).keys()]);
  // (the names it has alone: identifiers, not a comment's or a string's words)
  const taken = new Set<string>();
  const walk = (n: ts_Node) => {
    if (n.kind === ts_SyntaxKind.Identifier) taken.add((n as ts_Identifier).text);
    n.forEachChild(walk, (arr) => void arr.forEach(walk));
  };
  walk(parse(file, body.join("\n")));
  for (const s of def.spans) {
    if (!s.ext || s.literal) continue;
    const written = def.sourceText.slice(s.start, s.end);
    const base = /^(.+)_\d+$/.exec(written)?.[1];
    if (base && written !== s.ext.export && bound.has(base) && !taken.has(base))
      names.set(written, base);
  }
  for (const [from, to] of names) if (from === to) names.delete(from);
  if (def.nominal) names.delete(def.nameHint);
  return names.size ? { ...row, def: respelled(def, names) } : row;
}

uses

used by