checkPairs

tcmts.cli.semver_checks.checkPairs · #absnbi3y · term · runs on the server

uses node:path

Check changed definitions: old and new versions of each in one program, compared lint by lint.

import type { Codebase } from "#codebase/tcmts/cli/codebase";
import { compare } from "#codebase/tcmts/cli/semver_checks";
import type { Finding, Pair } from "#codebase/tcmts/cli/semver_checks";
import { exportId } from "#codebase/tcmts/core/hash";
import { createProgram } from "#codebase/tcmts/core/program";
import { materialize, moduleFile } from "#codebase/tcmts/loader";
import path_1 from "node:path";
import { isImportDeclaration as ts_isImportDeclaration, isNamedImports as ts_isNamedImports } from "typescript/ast";
import { SymbolFlags as ts_SymbolFlags } from "typescript/sync";
import type { Symbol as ts_Symbol } from "typescript/sync";

export function checkPairs(cb: Codebase, pairs: Pair[]): Finding[] {
  if (!pairs.length) return [];
  const src = cb.source();
  materialize(
    pairs.flatMap((p) => [p.old.hash, p.new.hash]),
    src,
    cb.lay,
  );
  const imports = pairs.flatMap((p, i) =>
    (["old", "new"] as const).map((side) => {
      const rec = p[side];
      return `import ${rec.kind === "type" ? "type " : ""}{ ${exportId(rec.hash)} as ${side === "old" ? "O" : "N"}${i} } from "./${moduleFile(rec.componentHash, src)}";`;
    }),
  );
  // a file in the cache importing both versions of each (never written: the compiler reads it from memory)
  const file = path_1.join(cb.lay.cacheDir, `_semver_${process.pid}.ts`);
  {
    const { program, checker } = createProgram(
      [file],
      { ...cb.recheckOptions(undefined), noEmit: true },
      new Map([[file, imports.join("\n") + "\nexport {};\n"]]),
    );
    const sf = program.getSourceFile(file)!;
    const symbols = new Map<string, ts_Symbol>();
    for (const st of sf.statements) {
      if (
        !ts_isImportDeclaration(st) ||
        !st.importClause?.namedBindings ||
        !ts_isNamedImports(st.importClause.namedBindings)
      )
        continue;
      for (const el of st.importClause.namedBindings.elements) {
        const s = checker.getSymbolAtLocation(el.name);
        if (s)
          symbols.set(
            el.name.text,
            s.flags & ts_SymbolFlags.Alias ? checker.getAliasedSymbol(s) : s,
          );
      }
    }
    return pairs.flatMap((p, i) => {
      const [o, n] = [symbols.get(`O${i}`), symbols.get(`N${i}`)];
      if (!o || !n)
        return [
          { lint: "type_changed", path: p.path, detail: "couldn't be compared" } satisfies Finding,
        ];
      return compare(checker, p, o, n);
    });
  }
}

uses

used by