apply

tcmts.cli.main.apply · #msw75jyp · term · runs on the server

uses tcmts.cli.style.dim, which uses node:util

Store the analyzed definitions and move names: add only binds free names, update also replaces.

import type { Codebase } from "#codebase/tcmts/cli/codebase";
import { checkDocExamples } from "#codebase/tcmts/cli/docs";
import { checkStoredRows, printDependents, printRows, STATUS_LABEL, textChanged } from "#codebase/tcmts/cli/main";
import type { Row, Status } from "#codebase/tcmts/cli/main";
import { forgetConflict } from "#codebase/tcmts/cli/merge";
import { bold, dim, red, yellow } from "#codebase/tcmts/cli/style";
import { shortHash } from "#codebase/tcmts/core/hash";

export function apply(
  cb: Codebase,
  rows: Row[],
  mode: "add" | "update",
  command: string,
  quiet: boolean,
): boolean {
  // (what already used a version nothing names: only what this leaves so is said, below; by name, as a definition
  // re-derived for another change can still use it, and isn't left so by this)
  const staleBefore = new Set(cb.store.unnamedUses().map((u) => `${u.path} ${u.uses}`));
  cb.putDefs(rows.map((r) => r.def));
  const patches = new Map<string, string>();
  const skipped: Row[] = [];
  cb.store.transaction(() => {
    for (const r of rows) {
      if (r.status === "new" || r.status === "alias") {
        cb.store.addName(r.path, r.def.hash, r.def.kind, command);
        // (code stored before, written differently now: it reads as it's written)
        if (textChanged(cb, r.def)) cb.store.setText(r.def, command);
      }
      // the same code with another doc, or written differently: on this branch it reads as it's written now (its
      // hash, and every other branch, untouched), a change in the branch's history like any
      else if (r.status === "doc" || r.status === "text") cb.store.setText(r.def, command);
      else if (r.status === "update" || r.status === "conflict") {
        if (mode === "add") {
          skipped.push(r);
          continue;
        }
        const olds = cb.store.hashesAt(r.path, r.def.kind);
        // (saved over a conflict, it's what the name holds now: the conflict is resolved, once)
        if (r.status === "conflict") forgetConflict(cb, r.path, r.def.kind);
        for (const old of olds) {
          patches.set(old, r.def.hash);
          cb.store.addPatch(old, r.def.hash);
          cb.store.clearTodo(old);
        }
        cb.store.replaceNames(r.path, olds, r.def.hash, r.def.kind, command);
        if (textChanged(cb, r.def)) cb.store.setText(r.def, command);
      }
    }
  });
  if (quiet) {
    const counts = new Map<Status, number>();
    for (const r of rows)
      if (!skipped.includes(r)) counts.set(r.status, (counts.get(r.status) ?? 0) + 1);
    console.log(
      [...counts].map(([s, n]) => `${STATUS_LABEL[s].trim()} ${n}`).join(dim(" · ")) ||
        dim("nothing to do"),
    );
  } else
    printRows(
      rows.filter((r) => !skipped.includes(r)),
      (d) => cb.store.shownSignature(d),
    );
  for (const r of skipped)
    console.log(
      `${yellow("  ~ skipped  ")} ${bold(r.path)} ${dim("already names a different definition; use `tcm update`")}`,
    );
  checkStoredRows(
    cb,
    rows.map((r) => r.def),
  );
  if (patches.size) {
    const { updated, failed } = cb.propagate(patches);
    printDependents(updated);
    if (failed.length)
      console.log(
        red(`\n  ${failed.length} dependent definition(s) no longer typecheck; see \`tcm todo\``),
      );
  }
  // a definition this left using a version nothing names (re-derived against one that was itself replaced) is wrong,
  // and said now rather than found later
  // (one that couldn't follow is to do: `tcm todo` says so)
  const toDo = new Set(cb.store.todos().map((t) => t.hash));
  const stale = cb.store
    .unnamedUses()
    .filter((u) => !staleBefore.has(`${u.path} ${u.uses}`) && !toDo.has(u.hash));
  for (const u of stale)
    console.log(
      red(
        `  ! ${u.path} now uses a version of ${u.madeAs} nothing names (${shortHash(u.uses)}): \`tcm edit ${u.path}\` and \`tcm update\` point it at the current one (and please report this)`,
      ),
    );
  cb.refreshShim();
  // examples in docs are code too: the ones just saved, and the ones naming anything just replaced, must still
  // typecheck (an import's library docs are prose for readers, so they're left alone)
  if (!quiet)
    for (const p of checkDocExamples(
      cb,
      rows.map((r) => r.def.hash),
    ))
      console.log(yellow(`  ! ${p}`));
  return skipped.length === 0;
}

uses

used by