From 8fd7e2b8ae760cb473331599d629f376196b1d44 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Aug 2026 19:59:32 +0000 Subject: [PATCH] =?UTF-8?q?fix(metadata-core):=20=E8=AE=A9=20test/=20?= =?UTF-8?q?=E5=B1=82=E7=9C=9F=E7=9A=84=E8=BF=9B=20tsc=EF=BC=8C`@ts-expect-?= =?UTF-8?q?error`=20=E4=B8=8D=E5=86=8D=E6=98=AF=E5=B9=BD=E7=81=B5=E6=A3=80?= =?UTF-8?q?=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `packages/metadata-core/tsconfig.json` 的 `include` 是 `["src/**/*"]`,而六个测试 文件住在 sibling 的 `test/` 树里——没有任何 `exclude` 指向它们,它们只是落在 include 根之外。于是 `typecheck`(裸 `tsc --noEmit`,读的就是这份 config)从没读过它们, `TESTS_COVERED` 也看不见(只统计 include 根之下,本包 testFiles 计数为 0)。 `test/types.test.ts:51` 那条 `@ts-expect-error` 因此从不被求值。 反向验证走的是常规方向:在新 program 下删掉该指令行,`test/types.test.ts(51,7)` 报 TS2353 —— `version` 不在 `Pick< MetaRef, 'org' | 'type' | 'name' >` 里;而在 origin/main 的旧 program(裸 `tsc --noEmit`)下删掉同一行,退出码仍是 0。指令钉的是 真事实,缺的只是编译它的程序,所以保留指令、补程序。 - `packages/metadata-core/tsconfig.test.json`:build config 的 sibling, `package.json` 的 `typecheck` 指名它(`tsc --noEmit -p tsconfig.test.json`)。 只改 `rootDir`(`src` → 包根)和 `noEmit`:继承的 `rootDir: src` 会对全部六个 `test/**` 报 TS6059,那是检查本身配错,不是测试写错;而在 BUILD config 里放宽 `rootDir` 不行 —— 那份 config 是要 emit 的(`dev: tsc --watch`、`outDir: dist`), 包根 rootDir 会把 `dist/index.js` 挪成 `dist/src/index.js` 并开始写 `dist/test/**/*.test.js`,后者正是 ci.yml 拦的东西。 - 与 spec/client 两先例不同,module 语义**不动**:本包有 `"type": "module"`, NodeNext 已按 ESM 读这些文件,且比 `bundler` 更严(保住必须发布的 `.js` 导入 后缀)。strictness 一律继承,不放松。 - 无 `test-typecheck-debt.json`:整个 test 层在新 config 下 0 error,逐文件 EXACT 棘轮(spec 691、client 6 才需要)在这里什么也不装,却要多一个 `tsx` 依赖。零残 余时裸 `tsc -p` 是更强的门:任何错误立即红,没有台账可加。 - 同 PR 删除 `scripts/check-type-check-coverage.mjs` 的 `PHANTOM_PIN_DEBT` 条目, RECONCILED 本来也会强制删。台账就此清空——把 `typecheck` 改回裸 `tsc --noEmit` 验证过:PINS_CHECKED 立刻红,这条门是承重的。 Fixes #5476 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01V7WetGmnfoXNn8cLieKKmx --- packages/metadata-core/package.json | 2 +- packages/metadata-core/tsconfig.test.json | 61 +++++++++++++++++++++++ scripts/check-type-check-coverage.mjs | 16 +++--- 3 files changed, 72 insertions(+), 7 deletions(-) create mode 100644 packages/metadata-core/tsconfig.test.json diff --git a/packages/metadata-core/package.json b/packages/metadata-core/package.json index e1ee72fa93..0beb3445bf 100644 --- a/packages/metadata-core/package.json +++ b/packages/metadata-core/package.json @@ -29,7 +29,7 @@ "clean": "rm -rf dist", "test": "vitest run", "test:watch": "vitest", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json" }, "keywords": [ "objectstack", diff --git a/packages/metadata-core/tsconfig.test.json b/packages/metadata-core/tsconfig.test.json new file mode 100644 index 0000000000..eeaaecbd53 --- /dev/null +++ b/packages/metadata-core/tsconfig.test.json @@ -0,0 +1,61 @@ +// The TEST-layer type-check program (#5476, the mechanism #5286/PR #5478 set for +// `packages/spec` and PR #5546 carried to `packages/client`). `tsconfig.json` +// beside this one stays as it is: it is the BUILD config, and `package.json`'s +// `typecheck` script NAMES this sibling (`tsc --noEmit -p tsconfig.test.json`), +// because a config no script invokes is exactly the phantom this change is about. +// +// THIS PACKAGE'S HOLE WAS A DIFFERENT SHAPE from spec's and client's, and that +// is what dictates the two options below. Neither of those two configs excluded +// `test/**` — they excluded `**/*.test.ts`, so the repair was to put an excluded +// region back. Here nothing was excluded at all: `include` is `["src/**/*"]` and +// the six files under `test/` simply live outside that root, so no `exclude` +// entry names them and TESTS_COVERED (which counts only under the include roots) +// never saw them either — this package's testFiles count was 0. The two test +// files that live under `src/` (`protocol-handshake.test.ts`, +// `objects/sys-view-definition.object.test.ts`) were compiled all along; the +// sibling `test/` tree was not. +// +// What differs from the build config, and what deliberately does NOT: +// - `rootDir` widens from `src` to the package root. It steers emit layout +// only, and this program emits nothing (`noEmit`), but inherited as `src` it +// reports TS6059 ("not under rootDir") for all six `test/**` files — the +// check being misconfigured, not the tests being wrong. Widening it in the +// BUILD config instead is not an option: `tsc` there emits (`dev`: +// `tsc --watch`, `outDir: dist`), so a package-root `rootDir` would relocate +// `dist/index.js` to `dist/src/index.js` — breaking `main`/`exports` — and +// start writing `dist/test/**/*.test.js`, which ci.yml gates against ("No +// compiled test files in any dist"). Emit constraints belong to the build +// config; this one has none. +// - MODULE SEMANTICS ARE UNTOUCHED, unlike spec's and client's siblings. Those +// packages have no `"type": "module"`, so the build config's NodeNext +// compiled their ESM tests as CJS and reported errors about the CHECK +// (TS2835, TS1470, TS2550); switching to `esnext`/`bundler` was fidelity to +// how vitest executes them. `@objectstack/metadata-core` IS `"type": +// "module"`, so NodeNext already reads these files as ESM — and it is the +// STRICTER of the two, since it holds the `.js` import extensions this +// package must ship (`bundler` resolution would let a missing extension +// compile here and fail at runtime under Node). Nothing to fix, so nothing +// is changed. +// - STRICTNESS IS UNTOUCHED. `strict`, `noUnusedLocals`, `noUnusedParameters`, +// `noImplicitReturns` and the rest are inherited from the root config. +// Nothing here may loosen a type rule; if a test does not compile, that is +// the finding. +// +// There is NO `test-typecheck-debt.json` beside this config, on purpose. The +// whole test layer compiles at ZERO errors under it, so the per-file EXACT +// shrink-only ledger (`scripts/check-test-typecheck.mts`, which spec and client +// wire because they carry 691 and 6 residual errors) would hold nothing while +// costing this package a `tsx` dependency and two more scripts. A bare +// `tsc --noEmit -p tsconfig.test.json` is the strictly stronger gate at zero +// residue: ANY error here is red immediately, with no ledger to be added to. +// If this package ever acquires residue that cannot be fixed in its own PR, +// that is the moment to wire the shared script — not before. +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true, + "rootDir": "." + }, + "include": ["src/**/*", "test/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/scripts/check-type-check-coverage.mjs b/scripts/check-type-check-coverage.mjs index 6cdaa33a9e..07096820e9 100644 --- a/scripts/check-type-check-coverage.mjs +++ b/scripts/check-type-check-coverage.mjs @@ -275,10 +275,13 @@ const TEST_DEBT = { // spec took -- put the file in a tsc program (drop the exclusion, widen // `include`, or add a sibling `tsconfig.test.json` the typecheck script names) // -- and then delete the entry, which RECONCILED forces anyway. -const PHANTOM_PIN_DEBT = { - 'packages/metadata-core/test/types.test.ts': - 'Outside the program for a different reason, and one no exclusion names: `include` is `["src/**/*"]` while this file lives in a sibling `test/` tree, so TESTS_COVERED never saw it either (its testFiles count is 0). Repair is to widen `include` or add a test config; tracked by #5476, not by #5286 (which scoped itself to packages/spec).', -}; +// +// EMPTY, and that is the intended end state: both seeds #5286 planted have been +// repaired and their entries deleted -- `packages/client` in #5449 (PR #5546), +// `packages/metadata-core/test/types.test.ts` in #5476, each by naming a sibling +// `tsconfig.test.json` in its `typecheck` script. A new entry here is not the +// route for the next such finding; PINS_CHECKED going red is. +const PHANTOM_PIN_DEBT = {}; /** * The `packages:` globs from pnpm-workspace.yaml. Blank lines and comments are @@ -380,8 +383,9 @@ function configsNamedByTypecheck(scripts) { * * `pinFiles` is PINS_CHECKED's input: test files carrying a `@ts-expect-error` * directive that no invoked program compiles. The scan walks the whole package, - * not just the include roots -- `packages/metadata-core/test/` is outside - * `include` with no exclusion naming it, and that is just as unchecked. + * not just the include roots -- `packages/metadata-core/test/` sat outside + * `include` with no exclusion naming it (until #5476 put it in a program), and + * that is just as unchecked. * * @returns {{excludesTests: boolean, testFiles: number, pinFiles: string[]}} */