Skip to content

[finding] check:type-check-coverage has no invariant for a tsconfig include that names a GENERATED directory the package's typecheck script never generates #10880

Description

@os-zhuang

What

scripts/check-type-check-coverage.mjs holds three invariants — COVERED, REAL, TESTS_COVERED. REAL is the closest one to this gap, and its own words are:

a declared typecheck script actually invokes tsc. A script that echoes, lints, or runs tests is not type coverage.

There is no invariant one notch further along: the script invokes tsc, but over a program whose completeness depends on generated files nothing in the script produces.

The measured instance

apps/docs/tsconfig.json includes .next/types/**/*.ts and .next/dev/types/**/*.ts. Those directories are produced by next typegen, which the wired typecheck script did not run. Measured on origin/main @ 7d483e1e5f, .next deleted:

pnpm --filter @objectstack/docs typecheck   ->   exit 0

Green, having compiled none of the generated route types. Running typegen first put 6 more files in the program (tsc --listFiles: 1225 -> 1231), including a 160-line validator.ts that checks 13 route entry points. An ablation confirmed the difference is load-bearing: a layout given a params shape its route cannot supply left the bare script at exit 0 / 0 errors and made the typegen'd one fail with TS2344.

That specific instance is repaired in #10879 — the script now generates what it reads. Nothing mechanical stops it, or a future Next/app package, from regressing.

Why it may be worth a card

The gate already parses every tsconfig in every package and already reads each typecheck script (readTsconfig, configsNamedByTypecheck). The missing question is cheap to ask with what it has:

  • for each include entry rooted in a generated / gitignored directory, does the package's typecheck script contain the command that produces it?
  • and the stricter half: an include entry naming a generated root that no declared generator covers is a config promising coverage nothing produces — which is what .next/dev/types/**/*.ts is for a plain tsc run.

A small declared table (generated root -> the command that must appear in the script) keeps the Next-specific knowledge out of the logic. Today it would hold one row.

Note the second bullet cannot be answered by deleting the offending glob: Next owns that array. writeConfigurationDefaults re-adds .next/dev/types/**/*.ts on the next next dev / next build, measured directly by running that routine against a narrowed copy. So a guard here has to be able to say "declared, and deliberately not generated" as well as "generated by the script".

Coordination

This would edit scripts/check-type-check-coverage.mjs, the same file #10756 is dispatched against. Different invariant and different failure direction — #10756 is a source directory falling outside tsc, this is a generated directory whose absence makes a check read green — but whichever lands second has to rebase onto the first. Worth sequencing rather than running in parallel.

Found while implementing #10871; deliberately kept out of that PR's diff, which is two files in apps/docs. Unassigned.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions