analyzeScratch

tcmts.cli.main.analyzeScratch · #nm3dhbni · term · runs on the server

uses node:fs

Analyze a scratch file (text instead of what's in it, if given), saying what's wrong with it (a syntax error alone, as syntaxError says it: the rest follows from it). Undefined if something is; null if, given how.own (the file's own lines, the rest being definitions added to it), every problem is in what was added, and saying so is the caller's. how.ignore: diagnostics not to report, by code.

import type { Codebase } from "#codebase/tcmts/cli/codebase";
import { firstSyntaxError, syntaxError } from "#codebase/tcmts/cli/main";
import { red, yellow } from "#codebase/tcmts/cli/style";
import { TCM_REACT_GONE, TCM_SERVER_GONE } from "#codebase/tcmts/core/analyze";
import type { Analysis } from "#codebase/tcmts/core/analyze";
import { shortHash } from "#codebase/tcmts/core/hash";
import fs from "node:fs";
import path from "node:path";
import { stripVTControlCharacters } from "node:util";

export function analyzeScratch(
  cb: Codebase,
  file: string,
  into?: string,
  text?: string,
  how: { ignore?: number[]; own?: number } = {},
): Analysis | undefined | null {
  cb.refreshShim();
  const a = cb.analyze(
    file,
    cb.scratchSalts(into),
    how.ignore ?? [],
    text === undefined ? undefined : new Map([[file, text]]),
  );
  for (const w of a.warnings) console.log(yellow(`warning: ${w}`));
  // a syntax error: what else the checker says follows from reading the code wrong (type errors by the screenful),
  // so it alone is said
  const syntax = firstSyntaxError(a, file);
  if (syntax) {
    console.log(syntaxError(syntax, text ?? fs.readFileSync(file, "utf8")));
    console.log(red(`\n${path.basename(file)} doesn't parse; nothing else was checked.`));
    return undefined;
  }
  if (a.diagnostics.length || a.errors.length) {
    // a version imported by its hash whose module isn't in the cache: that's the problem, and what else the checker
    // says follows from it (what it imports has no type), so it's said alone, with what to do
    const missing = a.diagnostics.flatMap((d) => {
      const m =
        d.code === 2307 && /Cannot find module '[^']*\/([a-z2-7]{52})\.tsx?'/.exec(d.message);
      return m ? [{ d, hash: m[1]! }] : [];
    });
    if (missing.length) {
      for (const { d, hash } of missing) {
        const at = stripVTControlCharacters(d.formatted).split("\n")[0]!.split(" - ")[0];
        const now = cb.store.patched(hash);
        const name = cb.store.pathsOf(now)[0];
        const ns = name?.slice(0, Math.max(0, name.lastIndexOf("."))) ?? "";
        const from = ns ? `#codebase/${ns.replaceAll(".", "/")}` : "#codebase";
        console.log(
          red(
            `${at}: ${
              !cb.store.getDef(hash)
                ? `imports ${shortHash(hash)} by its hash, which the codebase doesn't have`
                : name && now === hash
                  ? `imports ${name} by its hash, from a module the cache doesn't have: import it by its name, from "${from}"`
                  : name
                    ? `uses ${shortHash(hash)}, a version of ${name} nothing names now: import the current one by its name, from "${from}" (\`tcm edit\` writes it so)`
                    : `uses ${shortHash(hash)} (made as ${cb.store.getDef(hash)!.nameHint}), which nothing names, by its hash, from a module the cache doesn't have: \`tcm edit\` what uses it again, which writes the module`
            }`,
          ),
        );
      }
      const rest = a.diagnostics.length - missing.length + a.errors.length;
      if (rest)
        console.log(
          `(and ${rest} more problem${rest > 1 ? "s" : ""} the checker found, which may follow from ${missing.length > 1 ? "those" : "that"}: \`tcm check\` again once it's fixed)`,
        );
      console.log(red(`\n${path.basename(file)} has problems; nothing was checked.`));
      return undefined;
    }
    for (const d of a.diagnostics) process.stdout.write(d.formatted);
    // (the modules tcm generated for React's routes and data, and the server API: libraries now)
    if (a.diagnostics.some((d) => d.code === 2307 && d.message.includes("'#tcm/react'")))
      console.log(red(TCM_REACT_GONE));
    if (a.diagnostics.some((d) => d.code === 2307 && d.message.includes("'#tcm/server'")))
      console.log(red(TCM_SERVER_GONE));
    for (const e of a.errors) console.log(red(e));
    // (the line a diagnostic is at, as `file:12:5 - error` says)
    const line = (formatted: string) => {
      const m = / ?:(\d+):\d+ - /.exec(stripVTControlCharacters(formatted).split("\n")[0]!);
      return m ? Number(m[1]) : undefined;
    };
    const theirs =
      how.own !== undefined &&
      !a.errors.length &&
      a.diagnostics.every((d) => (line(d.formatted) ?? 0) > how.own!);
    if (theirs) return null;
    console.log(
      red(
        `\n${path.basename(file)} has problems; nothing was ${a.diagnostics.length ? "checked" : "hashed"}.`,
      ),
    );
    return undefined;
  }
  return a;
}

uses

used by