Skip to content

refactor(typecheck): derive facade expected names from source files#3476

Merged
caio-pizzol merged 1 commit into
mainfrom
caio-pizzol/SD-typecheck-facade-source-of-truth
May 24, 2026
Merged

refactor(typecheck): derive facade expected names from source files#3476
caio-pizzol merged 1 commit into
mainfrom
caio-pizzol/SD-typecheck-facade-source-of-truth

Conversation

@caio-pizzol
Copy link
Copy Markdown
Contributor

verify-public-facade-emit.cjs previously hand-maintained ~426 expected symbol names across 10 facades. The same names already lived in the facade source files at packages/superdoc/src/public/**, so every facade change required updating two places.

The script now parses each facade source file with the TypeScript AST and derives the expected set directly. source: path.join(PUBLIC_SRC, '<name>.ts') replaces expectedNames: [...] on every entry. Wildcard re-exports (export * and export * as X) are rejected in facade sources so the contract stays explicit and reviewable as source diff.

Why this is not self-confirming

Expected names are NOT derived from the emitted .d.ts: the gate's purpose is to catch drift between source-declared and emitted exports. Comparing emit to itself would be useless. The source .ts file is a different artifact - committed, reviewable, parsed independently - so the gate still catches dts-pipeline regressions.

What the gate still checks

  • Source facade export names match emitted ESM declarations
  • ESM ↔ CJS declarations agree where cjs exists
  • Root command-signature probe (legacy compat regression detector)
  • superdoc/types remains type-only (no export declare const in the CJS shim)
  • No private @superdoc/* workspace specifiers leak
  • No .pnpm/, absolute source paths, or local path leaks

What it does NOT enforce (by design)

No-growth assertions stay where they belong: snapshot.mjs (root / legacy / super-editor inventories) and the root-classification closure gate. Adding a new export to a facade source is now a normal source change reviewed by the PR diff, not gated by a parallel allowlist update.

Footprint

-320 lines net across 12 files. Verifier alone: 857 β†’ 533 lines.

Verified

  • pnpm --filter superdoc run build β†’ PASS. Verifier prints OK across the same 10 entries with the same export counts as before (root 204, types 116, ui 71, ui-react 13, legacy/headless-toolbar 16, legacy/converter 2, legacy/file-zipper 1, legacy/docx-zipper 1, legacy/headless-toolbar-react 1, legacy/headless-toolbar-vue 1).
  • pnpm check:types β†’ PASS (tsc -b tsconfig.references.json clean).
  • pnpm check:public:superdoc --skip-build β†’ PASS, 8 ran / 1 skipped, 123s.
  • Simulated drop from emit: removed hasBodyNumberingReferences from dist/.../legacy/converter.d.ts β†’ verifier failed with missing from emit: hasBodyNumberingReferences and pointed at both source and emit paths.
  • Simulated export * in source: appended export * from '@superdoc/super-editor/file-zipper'; to legacy/file-zipper.ts β†’ verifier failed with legacy/file-zipper.ts:18:1 export * is not allowed in a public facade source; list every name explicitly.

Review: confirm parseFacadeSourceExports handles every shape used by the facade source files (named re-export, type-only re-export, export { default } from, export default ..., export const, export function/class/interface/type/enum). Parser coverage is exercised by the 10 real facade source files during build; those files cover every export shape used today. The simulated export * failure covers the main rejected form.

Follow-ups not in this PR: the source field opens the door to having vite.config.js derive its rollupOptions.input from the same FACADE_ENTRIES list. Not done here to keep blast radius small.

verify-public-facade-emit.cjs previously hand-maintained 426 expected
symbol names across 10 facades (root: 204, types: 116, ui: 71,
ui-react: 13, legacy/headless-toolbar: 16, plus 5 small entries). The
same names already lived in the facade source files at
packages/superdoc/src/public/**, so every facade change required
updating two places.

The script now parses each facade source file with the TypeScript AST
and derives the expected set directly. `source: path.join(PUBLIC_SRC,
'<name>.ts')` replaces `expectedNames: [...]` on every entry. Wildcard
re-exports (`export *` and `export * as X`) are rejected in facade
sources so the contract stays explicit and reviewable as source diff.

Importantly, expected names are NOT derived from the emitted .d.ts:
the gate's purpose is to catch drift between source-declared and
emitted exports. Comparing emit to itself would be self-confirming.

No-growth assertions stay where they belong: snapshot.mjs (root /
legacy / super-editor inventories) and the root-classification
closure gate. Other postbuild checks in this script are unchanged:
ESM/CJS parity, legacy command-signature probe, typeOnly value-decl
detection, leak detection.

Net: -320 lines across 12 files (verifier 857 β†’ 533). Manually
simulated both failure modes before commit:
- dropping hasBodyNumberingReferences from converter.d.ts emit β†’
  verifier fails with 'missing from emit: hasBodyNumberingReferences'
  pointing at source and emit paths.
- adding `export *` to file-zipper.ts source β†’ verifier fails with
  the file:line:col and the 'export * is not allowed in a public
  facade source' rule.

Verified: pnpm --filter superdoc run build PASS (verifier prints OK
across 10 entries with the same counts as before); pnpm check:types
PASS; pnpm check:public:superdoc --skip-build PASS (9 stages, 8 ran /
1 skipped, 123s).
@codecov-commenter
Copy link
Copy Markdown

Codecov Report

βœ… All modified and coverable lines are covered by tests.

πŸ“’ Thoughts on this report? Let us know!

@caio-pizzol caio-pizzol merged commit d7790c3 into main May 24, 2026
72 checks passed
@caio-pizzol caio-pizzol deleted the caio-pizzol/SD-typecheck-facade-source-of-truth branch May 24, 2026 14:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants