runScratch

tcmts.studio.runScratch · #lsnmqexr · term · runs on the server

uses node:util

Run a scratch file's watches (its top-level statements: what each comes to), as tcm check does, for a notebook (text being what it holds; nothing, while it has problems). Values are kept by hash, as a test's results are; one whose code reaches outside (the clock, the network, files...) runs only when all asks, every time, and otherwise says what it waits on. Its tests' outcomes too: those kept (a pass is kept by hash), and, when all asks, each run.

import { reaches } from "#codebase/tcmts/cli/effects";
import { classify } from "#codebase/tcmts/cli/main";
import { evaluateCached, isTestPath, runCached } from "#codebase/tcmts/cli/testing";
import { keysForResults } from "#codebase/tcmts/cli/vitest";
import type { ProjectDef } from "#codebase/tcmts/core/analyze";
import { analyzedScratch, editableCodebase, scratchPath } from "#codebase/tcmts/studio";
import type { ScratchValue } from "#codebase/tcmts/studio";
import { stripVTControlCharacters } from "node:util";

export async function runScratch(
  file: string,
  text: string,
  all: boolean,
): Promise<ScratchValue[]> {
  const cb = editableCodebase();
  const abs = scratchPath(cb, file, text);
  if (!/\.tsx?$/.test(file)) return [];
  const a = analyzedScratch(cb, abs, text);
  if (a.diagnostics.length || a.errors.length) return [];
  const src = cb.source([...a.defs, ...a.watches]);
  const outside = reaches(
    cb,
    a.watches.map((w) => w.hash),
    src,
  );
  const waits = (h: string) => (outside.get(h) ?? []).filter((e) => e !== "output");
  const run = all ? a.watches : a.watches.filter((w) => !waits(w.hash).length);
  // (one that writes to the console runs every time: what it wrote is shown with it, and isn't kept)
  const writes = (h: string) => (outside.get(h) ?? []).includes("output");
  const ran = new Map([
    ...evaluateCached(
      cb,
      run.filter((w) => !writes(w.hash)),
      src,
      false,
    ),
    ...evaluateCached(
      cb,
      run.filter((w) => writes(w.hash)),
      src,
      true,
    ),
  ]);
  // the tests it holds: their results kept by hash (only passes), run when asked
  let tests: ScratchValue[] = [];
  try {
    const rows = classify(cb, a.defs, a.namespace ?? "").filter(
      (r) => r.def.kind !== "type" && isTestPath(r.path),
    );
    const lineOf = (r: (typeof rows)[number]) =>
      text
        .slice(0, (r.def as ProjectDef).range.nameStart ?? (r.def as ProjectDef).range.start)
        .split("\n").length;
    if (all && rows.length) {
      const out = runCached(
        cb,
        rows.map((r) => ({ path: r.path, rec: r.def })),
        src,
        false,
      );
      tests = rows.flatMap((r) => {
        const o = out.find((x) => x.path === r.path);
        return o
          ? [
              {
                line: lineOf(r),
                ok: o.result.ok,
                value: o.result.ok
                  ? `passes (${Math.round(o.result.ms)}ms)`
                  : stripVTControlCharacters(o.result.message),
                test: r.path,
                ...(o.cached ? { cached: true } : {}),
              },
            ]
          : [];
      });
    } else if (rows.length) {
      const keys = keysForResults(
        cb,
        rows.map((r) => r.def.hash),
        src,
      );
      tests = rows.map((r) => {
        const t = cb.store.testResult(r.def.hash, keys.get(r.def.hash)!);
        return t
          ? {
              line: lineOf(r),
              ok: t.ok,
              value: t.ok ? "passes" : stripVTControlCharacters(t.message),
              test: r.path,
              cached: true,
            }
          : { line: lineOf(r), ok: true, value: "", test: r.path, untested: true };
      });
    }
  } catch {
    tests = [];
  }
  return [
    ...tests,
    ...a.watches.flatMap((w): ScratchValue[] => {
      const r = ran.get(w.line);
      if (r)
        return [
          {
            line: w.line,
            ok: r.ok,
            value: stripVTControlCharacters(r.value),
            ...(r.html !== undefined ? { html: r.html } : {}),
            ...(r.output ? { output: stripVTControlCharacters(r.output) } : {}),
            ...(r.cached ? { cached: true } : {}),
          },
        ];
      return all ? [] : [{ line: w.line, ok: true, value: "", waits: waits(w.hash) }];
    }),
  ];
}

uses

used by