unsavedIn

tcmts.cli.merge.unsavedIn · #vprtn7gr · term · runs on the server

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

What a scratch file holds that isn't saved as it is: each definition whose code (with its doc and comments) isn't that of one its name holds now, as tcm edit writes it (a conflicted name's: either side's), by name; "other code" for anything else (a watch expression, a statement, a comment of its own). Nothing: replacing the file loses nothing, as with one tcm update left behind, though what it holds is a side of a conflict now.

import type { Codebase } from "#codebase/tcmts/cli/codebase";
import { scratchText } from "#codebase/tcmts/cli/merge";
import { joinName } from "#codebase/tcmts/cli/mirror";
import { scratchNamespace } from "#codebase/tcmts/core/analyze";
import { parse } from "#codebase/tcmts/core/ts";
import {
  isClassDeclaration as ts_isClassDeclaration, isEnumDeclaration as ts_isEnumDeclaration,
  isFunctionDeclaration as ts_isFunctionDeclaration, isIdentifier as ts_isIdentifier,
  isImportDeclaration as ts_isImportDeclaration, isInterfaceDeclaration as ts_isInterfaceDeclaration,
  isTypeAliasDeclaration as ts_isTypeAliasDeclaration, isVariableStatement as ts_isVariableStatement,
} from "typescript/ast";

export function unsavedIn(cb: Codebase, file: string, text: string): string[] {
  const ns = scratchNamespace(text) ?? "";
  // each statement as written: what's before it (its doc, comments) but `// tcm:` lines, then it (with or without
  // the semicolon ending it: `tcm edit` writes none)
  const statements = (t: string) =>
    parse(file, t).statements.map((st) => ({
      st,
      text: t
        .slice(st.pos, st.end)
        .split("\n")
        .filter((l) => !/^\s*\/\/\s*tcm:/.test(l))
        .join("\n")
        .trim()
        .replace(/;$/, ""),
    }));
  const out: string[] = [];
  let other = false;
  const mine = statements(text);
  for (const { st, text: written } of mine) {
    if (ts_isImportDeclaration(st)) continue;
    const declared = ts_isVariableStatement(st)
      ? st.declarationList.declarations.flatMap((d) =>
          ts_isIdentifier(d.name) ? [d.name.text] : [],
        )
      : (ts_isFunctionDeclaration(st) ||
            ts_isClassDeclaration(st) ||
            ts_isInterfaceDeclaration(st) ||
            ts_isTypeAliasDeclaration(st) ||
            ts_isEnumDeclaration(st)) &&
          st.name
        ? [st.name.text]
        : [];
    if (!declared.length) {
      other = true;
      continue;
    }
    const p =
      /\/\/\s*tcm:\s*path\s+(\S+)/.exec(text.slice(st.pos, st.getStart()))?.[1] ??
      joinName(ns, declared[0]!);
    const saved = cb.store.hashesAt(p).some((h) => {
      const rec = cb.store.getDef(h);
      return (
        !!rec &&
        rec.kind !== "file" &&
        statements(scratchText(cb, rec, p)).some((s) => s.text === written)
      );
    });
    if (!saved && !out.includes(p)) out.push(p);
  }
  // (a comment after the last statement is the file's own)
  if (
    text
      .slice(mine.at(-1)?.st.end ?? 0)
      .replace(/\/\/\s*tcm:.*$/gm, "")
      .trim()
  )
    other = true;
  return [...out, ...(other ? ["other code"] : [])];
}

uses

used by