renameEverywhere

tcmts.cli.mirror.renameEverywhere · #l5ez6oj7 · term · runs on the server

uses node:path

Rename definitions at every reference in the mirror's files, as an editor's rename would.

import type { CodebaseCore } from "#codebase/tcmts/cli/codebase";
import { applyEdits, findDeclarationName, nearbyFiles } from "#codebase/tcmts/cli/mirror";
import type { Edit, Mirror } from "#codebase/tcmts/cli/mirror";
import { createProgram } from "#codebase/tcmts/core/program";
import fs from "node:fs";
import path_1 from "node:path";
import {
  isIdentifier as ts_isIdentifier, isShorthandPropertyAssignment as ts_isShorthandPropertyAssignment,
} from "typescript/ast";
import type { NodeHandle as ts_NodeHandle } from "typescript/sync";

export function renameEverywhere(
  cb: CodebaseCore,
  m: Mirror,
  renames: { rel: string; from: string; to: string }[],
): Map<string, string> {
  const files = Object.keys(m.files).map((rel) => path_1.join(m.dir, rel));
  // and the files around the mirror that may use the names (scripts, tests): renamed too, not checked
  const near = nearbyFiles(
    m,
    renames.map((r) => r.from),
  );
  const { program, service } = createProgram(
    [...files, ...near],
    cb.compilerOptions(m.dir, "import"),
  );
  const edits = new Map<string, Edit[]>();
  for (const r of renames) {
    const file = path_1.join(m.dir, r.rel);
    const sf = program.getSourceFile(file);
    if (!sf) throw new Error(`${r.rel} isn't in the program`);
    const name = findDeclarationName(sf, r.from);
    if (!name) throw new Error(`no declaration of ${r.from} in ${r.rel}`);
    for (const entry of service.getReferencedSymbolsForNode(name, name.getStart(sf))) {
      for (const ref of entry.references as unknown as ts_NodeHandle[]) {
        const node = ref.resolve();
        if (!node || !ts_isIdentifier(node) || node.text !== r.from) continue;
        const refFile = node.getSourceFile();
        const text = ts_isShorthandPropertyAssignment(node.parent) ? `${r.from}: ${r.to}` : r.to;
        const list = edits.get(refFile.fileName) ?? [];
        if (!list.some((e) => e.start === node.getStart(refFile)))
          list.push({ start: node.getStart(refFile), end: node.end, text });
        edits.set(refFile.fileName, list);
      }
    }
  }
  const outside = new Map<string, string>();
  const mirrored = new Set(files);
  for (const [file, list] of edits) {
    const text = fs.readFileSync(file, "utf8");
    if (!mirrored.has(file)) outside.set(file, text);
    fs.writeFileSync(file, applyEdits(text, list));
  }
  return outside;
}

uses

used by