runScratch
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
- tcmts.cli.effects.reaches
- tcmts.cli.main.classify
- tcmts.cli.testing.evaluateCached
- tcmts.cli.testing.isTestPath
- tcmts.cli.testing.runCached
- tcmts.cli.vitest.keysForResults
- tcmts.core.analyze.ProjectDef
- tcmts.studio.ScratchValue
- tcmts.studio.analyzedScratch
- tcmts.studio.editableCodebase
- tcmts.studio.scratchPath