Files
scriptc/tests/harness/coverage.test.ts
T
2026-10-20 11:36:33 -05:00

737 lines
30 KiB
TypeScript

import { execFileSync, spawnSync } from "node:child_process";
import { globSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { expect, test } from "vitest";
import {
analyze,
coverageEnvelope,
coveragePasses,
renderCoverage,
renderExplainDynamic,
} from "@scriptc/compiler";
import { shardSelect, shardSuffix } from "./shard.js";
const repoRoot = join(import.meta.dirname, "../..");
const fixture = (name: string) => join(repoRoot, "tests/coverage-fixtures", name);
function report(path: string): string {
const { coverage } = analyze(path);
return renderCoverage(
{ ...coverage, file: coverage.file.replace(repoRoot + "/", "") },
{ root: repoRoot },
);
}
test("mixed program: percentage and grouped blockers", async () => {
await expect(report(fixture("mixed.ts"))).toMatchFileSnapshot("__snapshots__/coverage-mixed.txt");
});
test("unsupported operations appear as native compilation blockers", async () => {
// constructs no flag fixes stay under "blockers".
await expect(report(fixture("dynamic-mix.ts"))).toMatchFileSnapshot(
"__snapshots__/coverage-dynamic-mix.txt",
);
});
test("unreached blockers report in their own group", async () => {
// Bodies nothing on the entry path reaches lower only for analysis:
// their blockers render dim, under "in unreached code" — and the same
// program BUILDS (the differential dead-strip tests pin that side).
await expect(report(fixture("unreached.ts"))).toMatchFileSnapshot(
"__snapshots__/coverage-unreached.txt",
);
});
test("WebAssembly modules the build can see report with their sources", async () => {
// A resolved path, a resolved .so shared library and an inline literal
// (glue.js), a packaged .wasm (wasm-fixture-pkg), and package.json's
// scriptc.wasm list. A resolved .so that is not a module is not reported.
await expect(report(fixture("wasm/main.ts"))).toMatchFileSnapshot(
"__snapshots__/coverage-wasm.txt",
);
const { coverage } = analyze(fixture("wasm/main.ts"), { wasm: [fixture("wasm/add.wasm")] });
const modules = coverage.wasmModules ?? [];
expect(
modules.map((m) => [
m.path?.replace(repoRoot + "/", "") ?? "inline",
m.status,
m.unsupported,
m.sources.map((s) => s.kind),
]),
).toEqual([
["inline", "valid", [], ["inline"]],
["tests/coverage-fixtures/wasm/add.wasm", "valid", [], ["path", "config"]],
["tests/coverage-fixtures/wasm/ext.so", "valid", [], ["path"]],
[
"tests/coverage-fixtures/wasm/node_modules/wasm-fixture-pkg/lib/core.wasm",
"invalid",
[],
["package"],
],
["tests/coverage-fixtures/wasm/build/generated.wasm", "unsupported", ["simd"], ["config"]],
]);
const envelope = coverageEnvelope(coverage, { compilerVersion: "test" });
expect(envelope.wasmModules?.map((m) => m.sha256.length)).toEqual([64, 64, 64, 64, 64]);
});
test("divergences report as warnings, never as blockers", async () => {
const { coverage } = analyze(fixture("divergences.ts"));
expect(coverage.diagnostics).toEqual([]);
expect((coverage.divergences ?? []).map((d) => d.code)).toEqual([
"SC6001",
"SC6001",
"SC6002",
"SC6003",
"SC6004",
]);
expect(coveragePasses(coverage, "blockers")).toBe(true);
expect(coveragePasses(coverage, "divergences")).toBe(false);
await expect(report(fixture("divergences.ts"))).toMatchFileSnapshot(
"__snapshots__/coverage-divergences.txt",
);
});
test("record width copies report at every storing site", async () => {
const { coverage } = analyze(fixture("width-copies.ts"));
expect(coverage.diagnostics).toEqual([]);
expect((coverage.divergences ?? []).map((d) => d.code)).toEqual([
"SC6001",
"SC6001",
"SC6001",
"SC6001",
"SC6001",
]);
await expect(report(fixture("width-copies.ts"))).toMatchFileSnapshot(
"__snapshots__/coverage-width-copies.txt",
);
});
test("ordinary code reports no divergence", () => {
const { coverage } = analyze(fixture("no-divergences.ts"));
expect(coverage.diagnostics).toEqual([]);
expect(coverage.divergences ?? []).toEqual([]);
});
test("statements of a signature-blocked function count as not static", () => {
const { coverage } = analyze(fixture("blocked-signature.ts"));
const un = coverage.unreached?.stats;
expect(coverage.stats.functionsSkipped + (un?.functionsSkipped ?? 0)).toBe(1);
// Two top-level statements plus the four the blocked body holds.
expect(coverage.stats.statementsTotal + (un?.statementsTotal ?? 0)).toBe(6);
expect(coverage.stats.statementsFailed + (un?.statementsFailed ?? 0)).toBe(5);
});
test("a failed unreached JavaScript statement is always explained", () => {
const { coverage } = analyze(fixture("unreached-js-fence.js"));
const failed =
coverage.stats.statementsFailed + (coverage.unreached?.stats.statementsFailed ?? 0);
expect(failed).toBe(1);
const explained = [
...coverage.diagnostics,
...(coverage.runtimeFences ?? []),
...(coverage.unreached?.diagnostics ?? []),
...(coverage.unreached?.runtimeFences ?? []),
];
expect(explained.map((d) => d.code)).toEqual(["SC1090"]);
expect(report(fixture("unreached-js-fence.js"))).toContain(
"at tests/coverage-fixtures/unreached-js-fence.js:7:",
);
});
test("blocker groups carry a location and a hint", () => {
const out = report(fixture("mixed.ts"));
expect(out).toMatch(/at tests\/coverage-fixtures\/mixed\.ts:\d+:\d+/);
expect(out).toContain("hint: ");
});
test("TypeScript errors caused by scriptc's type world say so", () => {
const { coverage, sourceTexts } = analyze(fixture("type-world/main.ts"));
expect(coverage.preflightFailed).toBe(true);
const world = coverage.diagnostics.filter((d) => d.code === "SC0001" && d.typeWorld === true);
expect(world.map((d) => d.hint)).toEqual([
expect.stringContaining('ES2025 library regardless of the project\'s "lib"'),
expect.stringContaining("'Deno' is another runtime's global"),
]);
const out = renderCoverage(coverage, { sourceTexts });
expect(out).toContain("come from scriptc's type world");
const envelope = coverageEnvelope(coverage, { compilerVersion: "test", sourceTexts });
expect(envelope.phase).toBe("preflight");
expect(envelope.diagnostics.every((d) => d.category === "environment")).toBe(true);
});
test("coercing parseFloat and Number.parse* calls name their argument condition", () => {
const { coverage } = analyze(fixture("parse-coercions.ts"));
expect(coverage.diagnostics.map((d) => [d.code, d.message])).toEqual([
["SC2012", "'parseFloat' with a 'boolean' argument cannot compile statically"],
["SC2012", "'Number.parseInt' with a 'number' argument cannot compile statically"],
]);
for (const d of coverage.diagnostics) expect(d.hint).toContain("convert the argument explicitly");
});
test("the diagnostics envelope is versioned and located", () => {
const { coverage, sourceTexts } = analyze(fixture("mixed.ts"));
const envelope = coverageEnvelope(coverage, { compilerVersion: "test", sourceTexts });
expect(envelope).toMatchObject({
schema: "scriptc-diagnostics",
schemaVersion: 1,
command: "coverage",
success: false,
phase: "compile",
stats: { statementsTotal: 13, statementsStatic: 12, statementsFailed: 1, percentStatic: 92 },
});
expect(envelope.groups).toHaveLength(1);
expect(envelope.diagnostics[0]).toMatchObject({
code: "SC1031",
category: "unsupported",
severity: "error",
scope: "reached",
line: 13,
column: 7,
group: envelope.groups[0]!.id,
});
expect(envelope.diagnostics[0]!.hint.length).toBeGreaterThan(0);
});
test("CLI: --print=diagnostics and --fail-on set the output and exit status", () => {
const scriptcCli = join(repoRoot, "packages/cli/src/main.ts");
const run = (...args: string[]) =>
spawnSync(process.execPath, ["--import", "tsx", scriptcCli, "coverage", ...args], {
cwd: repoRoot,
encoding: "utf8",
env: { ...process.env, SCRIPTC_NO_CACHE: "1" },
});
const plain = run(fixture("mixed.ts"));
expect(plain.status).toBe(0);
const gated = run(fixture("mixed.ts"), "--fail-on=blockers");
expect(gated.status).toBe(1);
const json = run(fixture("mixed.ts"), "--print=diagnostics", "--fail-on=blockers");
expect(json.status).toBe(1);
const envelope = JSON.parse(json.stdout) as { schemaVersion: number; success: boolean };
expect(envelope).toMatchObject({ schemaVersion: 1, success: false });
expect(run(fixture("divergences.ts"), "--fail-on=blockers").status).toBe(0);
expect(run(fixture("divergences.ts"), "--fail-on=divergences").status).toBe(1);
// Unreached blockers cannot fail a build, so they never fail the gate.
expect(run(fixture("unreached.ts"), "--fail-on=blockers").status).toBe(0);
const bad = run(fixture("mixed.ts"), "--fail-on=everything");
expect(bad.status).toBe(1);
expect(bad.stderr).toContain('unknown --fail-on value "everything"');
});
test("CLI: build --print=diagnostics reports failures and divergence warnings as JSON", () => {
const scriptcCli = join(repoRoot, "packages/cli/src/main.ts");
const outDir = mkdtempSync(join(tmpdir(), "scriptc-build-envelope-"));
try {
const build = (file: string, ...args: string[]) =>
spawnSync(
process.execPath,
[
"--import",
"tsx",
scriptcCli,
"build",
file,
"--emit=ir",
"-o",
join(outDir, "out.ir.json"),
...args,
],
{ cwd: repoRoot, encoding: "utf8", env: { ...process.env, SCRIPTC_NO_CACHE: "1" } },
);
const failed = build(fixture("mixed.ts"), "--print=diagnostics");
expect(failed.status).toBe(1);
expect(JSON.parse(failed.stdout)).toMatchObject({
command: "build",
success: false,
phase: "compile",
diagnostics: [{ code: "SC1031", line: 13, column: 7 }],
});
const warned = build(fixture("divergences.ts"), "--print=diagnostics");
expect(warned.status).toBe(0);
const envelope = JSON.parse(warned.stdout) as {
success: boolean;
artifact: string;
diagnostics: { code: string; severity: string }[];
};
expect(envelope.success).toBe(true);
expect(envelope.artifact).toBe(join(outDir, "out.ir.json"));
expect(envelope.diagnostics.map((d) => d.severity)).toEqual(Array(5).fill("warning"));
const human = build(fixture("divergences.ts"));
expect(human.status).toBe(0);
expect(human.stderr).toContain("warning SC6001");
expect(human.stderr).toContain("5 warnings: the program compiled");
} finally {
rmSync(outDir, { recursive: true, force: true });
}
});
test("coverage counts the statements of dynamic-tier functions", async () => {
expect(analyze(join(repoRoot, "tests/corpus/400-fib.ts")).coverage.stats.statementsDynamic).toBe(
0,
);
const { coverage, sourceTexts } = analyze(fixture("tier-demotions.ts"));
expect(coverage.stats.statementsDynamic).toBe(1);
expect(coverage.tiers).toBeUndefined();
expect(coverageEnvelope(coverage, { compilerVersion: "test", sourceTexts }).stats).toMatchObject({
statementsTotal: 5,
statementsStatic: 3,
statementsDynamic: 1,
statementsFailed: 2,
});
await expect(report(fixture("tier-demotions.ts"))).toMatchFileSnapshot(
"__snapshots__/coverage-tier-demotions.txt",
);
});
test("--explain-dynamic attributes each refusal to the unit the dynamic tier would take", async () => {
const { coverage, sourceTexts } = analyze(fixture("tier-demotions.ts"), { explainDynamic: true });
const tiers = coverage.tiers!;
expect(tiers.mode).toBe("auto");
expect(tiers.statementsDynamic).toBe(1);
expect(tiers.dynamicFunctions.map((fn) => fn.name)).toContain("readName");
const requests = [...tiers.requests].sort((a, b) => a.loc.start - b.loc.start);
expect(
requests.map((r) => [r.code, r.seed, r.scope, r.unit.kind, r.unit.name, r.enclosing.name]),
).toEqual([
["SC1090", "reflection", "reached", "class", "Point", "<top level>"],
["SC2020", "operation", "reached", "binding", "n", "precise"],
]);
expect(tiers.units.map((u) => [u.kind, u.name, u.requests, u.sites])).toEqual([
["class", "Point", 1, 0],
["function", "<top level>", 0, 1],
["function", "precise", 0, 1],
["binding", "n", 1, 0],
]);
const envelope = coverageEnvelope(coverage, { compilerVersion: "test", sourceTexts });
expect(envelope.tiers?.requests[0]).toMatchObject({ unit: { kind: expect.any(String) } });
const relative = JSON.parse(JSON.stringify(tiers).replaceAll(repoRoot + "/", "")) as typeof tiers;
await expect(renderExplainDynamic(relative)).toMatchFileSnapshot(
"__snapshots__/coverage-explain-dynamic.txt",
);
expect(renderExplainDynamic(relative, { query: "Point" })).toContain("class Point");
expect(renderExplainDynamic(relative, { query: "Point" })).not.toContain("binding n");
expect(renderExplainDynamic(relative, { query: "nothing" })).toContain(
"no demoted unit matches 'nothing'",
);
});
test("CLI: --tier and --explain-dynamic", () => {
const scriptcCli = join(repoRoot, "packages/cli/src/main.ts");
const outDir = mkdtempSync(join(tmpdir(), "scriptc-tier-"));
const run = (...args: string[]) =>
spawnSync(process.execPath, ["--import", "tsx", scriptcCli, ...args], {
cwd: repoRoot,
encoding: "utf8",
env: { ...process.env, SCRIPTC_NO_CACHE: "1" },
});
try {
const explained = run(
"coverage",
fixture("tier-demotions.ts"),
"--explain-dynamic",
"--tier=static-only",
);
expect(explained.status).toBe(0);
expect(explained.stdout).toContain("in dynamic tier 1");
expect(explained.stdout).toContain("--tier=static-only");
expect(explained.stdout).toContain("binding n in precise");
const one = run("coverage", fixture("tier-demotions.ts"), "--explain-dynamic=Point");
expect(one.stdout).toContain("class Point");
expect(one.stdout).not.toContain("binding n in precise");
const bad = run("coverage", fixture("tier-demotions.ts"), "--tier=dynamic-only");
expect(bad.status).toBe(1);
expect(bad.stderr).toContain('unknown --tier value "dynamic-only"');
const misplaced = run("build", fixture("tier-demotions.ts"), "--explain-dynamic");
expect(misplaced.status).toBe(1);
expect(misplaced.stderr).toContain("--explain-dynamic is a coverage option");
// Until demotion lands, static-only compiles exactly like the default.
const ir = (name: string, ...args: string[]) => {
const out = join(outDir, name);
const built = run(
"build",
join(repoRoot, "tests/corpus/400-fib.ts"),
"--emit=ir",
"-o",
out,
...args,
);
expect(built.status).toBe(0);
return readFileSync(out, "utf8");
};
expect(ir("static-only.ir.json", "--tier=static-only")).toBe(ir("auto.ir.json"));
} finally {
rmSync(outDir, { recursive: true, force: true });
}
});
test("fully static program reports 100%", () => {
const out = report(join(repoRoot, "tests/corpus/400-fib.ts"));
expect(out).toContain("(100%)");
expect(out).toContain("fully static");
});
test("fully static JavaScript program reports 100%", () => {
const out = report(join(repoRoot, "tests/corpus/1590-js-unannotated.js"));
expect(out).toContain("(100%)");
expect(out).toContain("fully static");
});
test.each(["3062-runtime-optional-record-guards.ts", "3063-runtime-optional-class-guards.ts"])(
"runtime-optional guard receivers stay static: %s",
(name) => {
{
const { coverage } = analyze(join(repoRoot, "tests/corpus", name), {});
expect(coverage.preflightFailed).toBe(false);
expect(coverage.diagnostics).toEqual([]);
expect(coverage.stats.statementsFailed).toBe(0);
}
},
);
test("coverage preserves function-local class capture environments", () => {
const { coverage } = analyze(join(repoRoot, "tests/corpus/local-class-unknown.ts"));
expect(coverage.preflightFailed).toBe(false);
expect(coverage.diagnostics).toEqual([]);
expect(coverage.stats.statementsFailed).toBe(0);
expect(coverage.unreached?.diagnostics ?? []).toEqual([]);
});
test("module stream aliases remain static in separately declared functions", () => {
const { coverage } = analyze(join(repoRoot, "tests/corpus/stdio-write-errors.mjs"));
expect(coverage.preflightFailed).toBe(false);
expect(coverage.diagnostics).toEqual([]);
expect(coverage.runtimeFences ?? []).toEqual([]);
expect(coverage.stats.statementsFailed).toBe(0);
});
test("runtime-optional refined unions report blockers without crashing", () => {
{
const { coverage } = analyze(
join(repoRoot, "tests/diagnostics/runtime-optional-refined-union.ts"),
{},
);
expect(coverage.preflightFailed).toBe(false);
expect(coverage.diagnostics.map((d) => d.code)).toEqual(["SC1090"]);
expect(coverage.stats.statementsFailed).toBe(1);
}
});
test("a rejected class reached only through a receiver probe reports its own diagnostic once", () => {
const { coverage } = analyze(join(repoRoot, "tests/diagnostics/deferred-class-receiver/main.ts"));
const messages = coverage.diagnostics.map((d) => d.message);
expect(
messages.filter((m) => m.startsWith("'MapIterator<string>'-typed class fields")),
).toHaveLength(1);
expect(messages.some((m) => m.startsWith("constructing through a class value"))).toBe(true);
expect(coverage.unreached?.diagnostics ?? []).toEqual([]);
});
test("settled generic rest-order fences count each source statement once", () => {
const { coverage } = analyze(join(repoRoot, "tests/diagnostics/retained-generic-rest-order.ts"));
expect(coverage.diagnostics.map((d) => d.code)).toEqual(["SC1031", "SC2004"]);
expect(coverage.stats.statementsFailed).toBe(2);
});
test("inferred JavaScript compiles natively", async () => {
// The js-gap fixture turns noImplicitAny off. Its inferred parameter
// uses checked native values alongside the JSDoc-typed neighbor.
await expect(report(fixture("js-gap/gap.js"))).toMatchFileSnapshot(
"__snapshots__/coverage-js-gap.txt",
);
});
test("JavaScript builtin aliases keep their deferred value fence", async () => {
await expect(report(fixture("js-builtin-callable-alias.js"))).toMatchFileSnapshot(
"__snapshots__/coverage-js-builtin-callable-alias.txt",
);
});
test("any-typed checked-dynamic operators compile natively", () => {
const file = join(repoRoot, "tests/corpus/2856-dynamic-any-local-operators.ts");
const staticCoverage = analyze(file).coverage;
expect(staticCoverage.diagnostics).toEqual([]);
expect(staticCoverage.stats.statementsFailed).toBe(0);
});
test("npm packages compile by default", async () => {
// Every site of a package-declared value groups into one SC2013 line
// naming the package ("values from the 'mathkit' package ..."), inside
// the two views of the same program stay in sync.
await expect(
report(join(repoRoot, "tests/corpus/native-npm-imports/main.ts")),
).toMatchFileSnapshot("__snapshots__/coverage-npm-packages.txt");
});
test("import fences no longer stop analysis: percentage plus module blockers", async () => {
// The fenced module reports ONE grouped blocker (the import line plus
// every use of its bindings carry the same message); the rest of the
// program still counts. Lives in the node-types fixture: builtin
// specifiers only RESOLVE (typecheck) with @types/node adopted — the
// fallback world turns them into tsc errors, which stay fatal. The same
// file must FAIL a build (compile() keeps preflight fatal).
await expect(
report(join(repoRoot, "tests/fixtures/node-types/import-fences.ts")),
).toMatchFileSnapshot("__snapshots__/coverage-import-fences.txt");
});
test("external host declarations unblock application coverage without inventing runtime semantics", () => {
const root = fixture("external-types");
const entry = join(root, "main.ts");
const externalTypes = { "@native-sdk/core": join(root, "native-sdk-core.d.ts") };
const unmapped = analyze(entry).coverage;
expect(unmapped.preflightFailed).toBe(true);
expect(
unmapped.diagnostics.some((d) => d.code === "SC0001" && d.message.includes("@native-sdk/core")),
).toBe(true);
const { coverage } = analyze(entry, { externalTypes });
expect(coverage.preflightFailed).toBe(false);
expect(coverage.stats.statementsTotal).toBeGreaterThan(0);
expect(coverage.diagnostics.some((d) => d.code === "SC0001")).toBe(false);
expect(
coverage.diagnostics.some(
(d) =>
d.code === "SC1010" &&
d.message.includes("external host module") &&
d.message.includes("--external-types"),
),
).toBe(true);
const out = renderCoverage({
...coverage,
file: "tests/coverage-fixtures/external-types/main.ts",
});
expect(out).toContain("statements analyzed");
expect(out).toContain("@native-sdk/core");
});
test("external declaration barrels leave type-only local code fully analyzable", () => {
const root = fixture("external-types");
const { coverage } = analyze(join(root, "type-only.ts"), {
externalTypes: { "@native-sdk/core": join(root, "native-sdk-core.d.ts") },
});
expect(coverage.preflightFailed).toBe(false);
expect(coverage.diagnostics).toEqual([]);
expect(coverage.stats.statementsFailed).toBe(0);
});
test("external host mappings fence dynamic imports", () => {
const root = fixture("external-types");
const { coverage } = analyze(join(root, "dynamic.ts"), {
externalTypes: { "@native-sdk/core": join(root, "native-sdk-core.d.ts") },
});
expect(coverage.preflightFailed).toBe(false);
expect(coverage.stats.statementsFailed).toBeGreaterThan(0);
expect(
coverage.diagnostics.some(
(d) =>
d.code === "SC1010" &&
d.message.includes("@native-sdk/core") &&
d.message.includes("--external-types"),
),
).toBe(true);
});
test("external host mappings fence side-effect CommonJS requires", () => {
const root = fixture("external-types");
const { coverage } = analyze(join(root, "side-effect.cjs"), {
externalTypes: { "@native-sdk/core": join(root, "native-sdk-core.d.ts") },
});
expect(coverage.preflightFailed).toBe(false);
expect(coverage.stats.statementsFailed).toBeGreaterThan(0);
const blockers = [...coverage.diagnostics, ...(coverage.runtimeFences ?? [])];
expect(
blockers.some(
(d) =>
d.code === "SC1010" &&
d.message.includes("@native-sdk/core") &&
d.message.includes("external host module"),
),
).toBe(true);
});
test("shared external declarations retain the specifier selected by each local facade", () => {
const root = fixture("external-types");
const declaration = join(root, "native-sdk-core.d.ts");
const { coverage } = analyze(join(root, "facade-consumer.ts"), {
externalTypes: {
"@native-sdk/core": declaration,
"@native-sdk/unused": declaration,
},
});
expect(coverage.preflightFailed).toBe(false);
const valueBlockers = coverage.diagnostics.filter(
(d) => d.code === "SC1010" && d.message.startsWith("values from"),
);
expect(valueBlockers.some((d) => d.message.includes("@native-sdk/core"))).toBe(true);
expect(valueBlockers.some((d) => d.message.includes("@native-sdk/unused"))).toBe(true);
});
test("external star facades do not claim local exports", () => {
const root = fixture("external-types");
const { coverage } = analyze(join(root, "mixed-star-consumer.ts"), {
externalTypes: { "@native-sdk/core": join(root, "native-sdk-core.d.ts") },
});
expect(coverage.preflightFailed).toBe(false);
expect(coverage.stats.statementsTotal).toBe(2);
expect(coverage.stats.statementsFailed).toBe(0);
expect(
coverage.diagnostics.some(
(d) =>
d.code === "SC1010" && d.message.includes("the '@native-sdk/core' external host module"),
),
).toBe(true);
expect(
coverage.diagnostics.some((d) => d.code === "SC1010" && d.message.startsWith("values from")),
).toBe(false);
});
test("external host mappings take precedence over overlapping npm-static packages", () => {
const root = fixture("external-types");
const entry = join(repoRoot, "tests/fixtures/npm-static/slash-cli.ts");
const { coverage } = analyze(entry, {
npmStatic: ["slash"],
externalTypes: { slash: join(root, "slash-host.d.ts") },
});
expect(coverage.preflightFailed).toBe(false);
expect(coverage.npmStatic).toEqual([
{
package: "slash",
status: "refused",
detail: 'mapped as an external host module by --external-types ("slash")',
},
]);
expect(coverage.diagnostics.some((d) => d.code === "SC2013")).toBe(false);
expect(
coverage.diagnostics.some((d) => d.code === "SC1010" && d.message.includes("'slash'")),
).toBe(true);
});
test("CLI accepts repeatable --external-types mappings for coverage", () => {
const root = fixture("external-types");
const scriptcCli = join(repoRoot, "packages/cli/src/main.ts");
const out = execFileSync(
process.execPath,
[
"--import",
"tsx",
scriptcCli,
"coverage",
join(root, "main.ts"),
"--external-types",
`@native-sdk/core=${join(root, "native-sdk-core.d.ts")}`,
"--external-types",
`@native-sdk/unused=${join(root, "native-sdk-core.d.ts")}`,
],
{ cwd: repoRoot, encoding: "utf8" },
);
expect(out).toContain("statements analyzed");
expect(out).toContain("@native-sdk/core");
expect(out).not.toContain("Cannot find module");
});
test("external type mappings reject TypeScript paths patterns", () => {
const root = fixture("external-types");
const entry = join(root, "type-only.ts");
const declaration = join(root, "native-sdk-core.d.ts");
expect(() => analyze(entry, { externalTypes: { "@native-sdk/*": declaration } })).toThrow(
"expected an exact bare package specifier",
);
const scriptcCli = join(repoRoot, "packages/cli/src/main.ts");
const cli = spawnSync(
process.execPath,
[
"--import",
"tsx",
scriptcCli,
"coverage",
entry,
"--external-types",
`@native-sdk/*=${declaration}`,
],
{ cwd: repoRoot, encoding: "utf8" },
);
expect(cli.status).toBe(1);
expect(cli.stderr).toContain("expected an exact bare package specifier");
});
test("external type mappings reject non-declaration and unreadable API paths", () => {
const root = fixture("external-types");
const entry = join(root, "type-only.ts");
expect(() =>
analyze(entry, { externalTypes: { "@native-sdk/core": join(root, "main.ts") } }),
).toThrow("expected a .d.ts, .d.mts, or .d.cts file");
expect(() =>
analyze(entry, { externalTypes: { "@native-sdk/core": join(root, "missing.d.ts") } }),
).toThrow("does not name a readable file");
});
test("type errors block analysis", () => {
const out = report(fixture("type-errors.ts"));
expect(out).toContain("not analyzable");
expect(out).toContain("1 TypeScript error");
});
test("type errors render the same code frame a build prints", () => {
const { coverage, sourceTexts } = analyze(fixture("type-errors.ts"));
const out = renderCoverage(coverage, { sourceTexts });
expect(out).toContain("not analyzable");
expect(out).toContain("error SC0001");
// The frame gutter with the offending line, exactly the build renderer.
expect(out).toMatch(/\d+ \| /);
expect(out).toContain("^");
});
test("bare '.' and '..' imports are relative edges (the vercel-CLI dialect)", () => {
// `from '..'` / `from '.'` resolve as directory imports to the target's
// index module — exactly what the project's bundler-resolution checker
// answered — so the graph loads and everything compiles statically.
const out = report(fixture("dot-parent/main.ts"));
expect(out).toContain("(100%)");
expect(out).toContain("fully static");
});
test("a cycle whose top level calls a builtin with a builtin function value is admitted", () => {
// vercel's link.ts cluster: `promisify(fs.readFile)` at a cycle member's
// top level. Callee and callable argument are both dts-rooted — no user
// code can run in the init window — so no SC1016 and the analysis
// renders statement counts (the statement itself may stay a
// statement-LEVEL blocker; that is a different fence).
const out = report(fixture("cycle-inert-builtin/main.ts"));
expect(out).not.toContain("circular imports");
expect(out).toContain("statements analyzed");
});
test(`every corpus program is 100% static (corpus and coverage agree${shardSuffix()})`, async () => {
// The differential corpus compiles by definition; coverage must agree.
// The sweep runs minutes on slow machines and each analyze() blocks the
// worker thread — without the periodic yield, vitest's worker RPC starves
// ("Timeout calling onTaskUpdate") even while every check passes, and the
// default per-test timeout is far too small for a whole-corpus analysis.
let n = 0;
const flatEntries = shardSelect(
["ts", "js", "mjs", "cjs"]
.flatMap((ext) => globSync(join(repoRoot, `tests/corpus/*.${ext}`)))
.sort(),
(file) => file.slice(repoRoot.length + 1),
);
for (const file of flatEntries) {
const firstLine = readFileSync(file, "utf8").split("\n", 1)[0] ?? "";
// `// @deferred-fences: N` on the first line: a JS program that
// DELIBERATELY carries N runtime-fence statements on untaken paths
// (the deferred-fence corpus) — it still passes the differential
// oracle because the fences never execute.
const deferred = /^\/\/ @deferred-fences:\s*(\d+)\s*$/.exec(firstLine);
const { coverage } = analyze(file, {});
expect.soft(coverage.diagnostics, file).toEqual([]);
// Corpus programs match Node by definition: a divergence warning on one
// is a false positive.
expect.soft(coverage.divergences ?? [], file).toEqual([]);
expect.soft(coverage.stats.statementsFailed, file).toBe(deferred ? Number(deferred[1]) : 0);
if (++n % 10 === 0) await new Promise((r) => setImmediate(r));
}
}, 600_000);