Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions packages/trilean/test/declaration-shape.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Guards the shape of the built declaration files for the two recursive node trees: every top-level declaration in dist/tree.d.ts and dist/tree.d.cts carries its own `export` modifier, none is declared bare and re-exported through a trailing `export { ... }` list.
//
// Why the shape matters: a consumer that re-exports `z.array(PredicateNodeSchema)` (or the expression equivalent) without an annotation makes TypeScript serialise the inferred type into the consumer's own declaration output. When the tree's declarations are exported in place, TypeScript can name them and the output stays small. When the declaration bundler emits bare `declare const X` plus a trailing `export { X }` list (tsdown before 0.23 did), the consumer's inferred type expands the whole recursive tree and fails with TS7056 ("the inferred type of this node exceeds the maximum length the compiler will serialize") in tsdown, tsc and any other declaration generator. The shape is decided by the bundler, not by anything in src/tree.ts, so a bundler change can reintroduce the failure with no diff to the schemas; this test is what would notice.
//
// Checks the built package like smoke.test.ts does (the `_test:smoke` turbo task depends on `_build`), and is a member of tsconfig.node.json's program, so it may use Node APIs.

import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";

/** Matches a top-level declaration line that lacks `export`: `declare const ...`, `type X = ...`, `interface X ...`. Indented lines belong to a declaration body and are ignored by the anchor. */
const bareTopLevelDeclaration = /^(?:declare|type|interface|const)\s/m;

/** Matches the trailing `export { ... }` list a bundler emits when declarations are not exported in place. */
const trailingExportList = /^export \{/m;

describe.each(["dist/tree.d.ts", "dist/tree.d.cts"])(
"declaration shape of %s",
(relativePath) => {
const source = readFileSync(
new URL(`../${relativePath}`, import.meta.url),
"utf8",
);

it("exports both node-tree schemas in place", () => {
expect(source).toMatch(/^export declare const PredicateNodeSchema:/m);
expect(source).toMatch(/^export declare const ExpressionNodeSchema:/m);
});

it("declares nothing bare and re-exports nothing through a trailing list", () => {
expect(source).not.toMatch(bareTopLevelDeclaration);
expect(source).not.toMatch(trailingExportList);
});
},
);
4 changes: 2 additions & 2 deletions packages/trilean/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ export default defineConfig({
},
},
{
// Checks the built package rather than src/: dist/'s ESM and CJS entry points resolved through package.json's own `exports` map, and the generated schemas/trilean.schema.json. The `_test:smoke` turbo task depends on `_build`, so the output under test is always rebuilt from current source rather than whatever dist/ happened to be left lying around. See test/smoke.test.ts.
// Checks the built package rather than src/: dist/'s ESM and CJS entry points resolved through package.json's own `exports` map, and the generated schemas/trilean.schema.json. The `_test:smoke` turbo task depends on `_build`, so the output under test is always rebuilt from current source rather than whatever dist/ happened to be left lying around. See test/smoke.test.ts and test/declaration-shape.test.ts.
test: {
name: "smoke",
include: ["test/smoke.test.ts"],
include: ["test/smoke.test.ts", "test/declaration-shape.test.ts"],
},
},
{
Expand Down
Loading