detect and check
Seed a bindings list from your source, then guard it in CI — with the caveats that make both commands honest.
Two commands answer one question — which OCCT symbols does this code actually reference? — in the two directions that matter.
npx libcascade detect src # onboarding: seed a bindings list
npx libcascade check src # CI: fail when a referenced symbol is not boundNeither command is a size tool, and neither ever removes anything. Read the caveats below before acting on either output. Both are marked experimental.
Why they exist: the failure asymmetry
A bindings list that is missing a symbol links successfully. The wasm
builds, the manifest can pass, the package publishes — and the first time your
code touches the missing class it throws a BindingError at runtime.
libcascade build cannot catch that. Only running the code path can.
check converts that failure class into a build-time error. That is the whole
value proposition, and it is a correctness one, not a size one.
Why they are not size tools
The measurement: dropping 14% of the symbols bought 0.9% of brotli size.
The ~5,400 embind registrations are GC roots, so unbound symbols free glue, not
kernel code. (--gufa, the obvious next lever, is a measured size regression
on top of that.)
Trimming a binding set is still worth doing for the reasons in
Trim symbols — startup work, honest dependency surface — but
do not expect detect to hand you megabytes.
detect — the first bindings list
Writing the initial bindings array is the scariest step of custom-build
onboarding. detect scans your source for symbol references, closes over the
symbol catalog, and prints a paste-ready fragment with per-symbol provenance:
npx libcascade detect src lib bindings: [
'BRepBuilderAPI_MakeShape', // closure: base of BRepPrimAPI_MakeBox
'BRepPrimAPI_MakeBox', // seed: src/shapes.ts:41
'gp_XYZ', // closure: member type of gp_Pnt
],--json emits the same result machine-readably.
The output is a starting set, not a minimal one. Symbols your source does not reference today include roadmap-reserved capacity and anything your own C++ wrapper files call. Review the list. Never diff it against an existing config and delete the difference.
check — the drift guard
npx libcascade check srccheck recomputes the referenced set from your source and fails when any of it
is missing from bindings ∪ customBindings[].symbols, naming each symbol, the
first file:line that references it, and the fix:
libcascade check: 1 referenced symbol is not bound by libcascade.config.ts.
ChFi2d_FilletAPI
first referenced at src/fillet.ts:13It exits non-zero, so it belongs in CI next to your typecheck:
- run: npx tsc --noEmit
- run: npx libcascade check srcSymbols bound under an OCCT typedef alias (TColgp_Array1OfPnt for
NCollection_Array1_gp_Pnt) count as bound. Names that are not in the catalog
at all — your customBindings symbols, Emscripten runtime members such as
oc.FS, plain typos — are never failures. --verbose lists them as ignored
along with the scan's caveats.
How the scan works
| Rule | Behaviour |
|---|---|
| Strong signal | oc.Symbol (also this.oc.Symbol) |
| Weak signal | any bare identifier that exactly matches a catalog name and contains an underscore — this is what catches type-only imports such as import type { TopoDS_Shape } |
| Single-word names | Draft, Expr, BRepTools and friends are excluded from bare matching because they collide with ordinary identifiers. Write them as oc.BRepTools to be seen. |
| Overload suffixes | Geom2d_Line_1 resolves to Geom2d_Line, but only when the full name is not itself a symbol |
| Excluded paths | .d.ts, node_modules, dist, build, out, coverage |
| Comments | blanked before matching, so a comment naming a deliberately-omitted class is not a reference |
| Strings | scanned — which is what makes oc['gp_Pnt'] visible |
| Builtins | OCJS, TopoDS and friends are registered unconditionally, so they are never detected or demanded |
.d.ts files are excluded for a specific reason: an OCCT .d.ts declares every
symbol, which would make the scan vacuous.
Limits you must not forget
The scanner is regex and token based, not AST based — the toolchain's runtime dependencies stay at two small packages. It therefore cannot see:
- dynamic access —
oc[name]wherenameis a variable - names built by concatenation
- symbols only your C++ wrapper files call
- anything reached through a dependency's compiled code
So:
checkpassing is not a proof. It proves no symbol is missing from the written references it can see. An unexercised code path can still hit a runtimeBindingError.detectoutput is a seed. It is not an audit of what you could drop.- Neither command edits your config. They print; you decide.
Related
- CLI reference — flags and exit behaviour.
- Trim symbols — the bindings list as a design signal.
- Config reference — where the list lives.