unsavedIn
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
- tcmts.cli.codebase.Codebase
- tcmts.cli.merge.scratchText
- tcmts.cli.mirror.joinName
- tcmts.core.analyze.scratchNamespace
- tcmts.core.ts.parse