Skip to content

fix(scripts): test type-check 覆盖率改由解析后的 tsconfig 程序判定,schema-catalog 的测试接上编译 - #4004

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-3968-typecheck-coverage-gate
Aug 9, 2026
Merged

fix(scripts): test type-check 覆盖率改由解析后的 tsconfig 程序判定,schema-catalog 的测试接上编译#4004
yinlianghui merged 1 commit into
mainfrom
claude/issue-3968-typecheck-coverage-gate

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #3968

结论先说

门的两个盲区都不是"少认一种写法",而是用文本猜程序内容。这个 PR 把判据换成"解析后的 tsconfig 程序到底读了哪些文件",两个盲区就一起消失了;examples/schema-catalog 的 4 个测试文件同时接上 tsc 编译。

基 sha 0cbdca888。修前门是绿的(23/38 packages compile their tests,本包不在分母里);修后是 24/39

一、门修(scripts/check-type-check-coverage.mjs)

盲区 1 —— 只递归 src/(原 :180):countTestFiles(resolve(root, dir, "src")) 改为从包根枚举(listTestFiles,跳过 node_modules / dist / coverage / 点目录)。本包测试住在 test/,原先数到 0,于是 :299if (!pkg.hasScript || pkg.testFiles === 0) continue; 把整个 5c 跳过了 —— 最该说话的检查根本没跑。

盲区 2 —— 只认 glob 形式的排除(原 :182):buildExcludesTests = /\*\.test\./.test(tsconfig) 改为解析每个 tsconfig 的 files / include / exclude(并沿 extends 继承,按声明它的那个 config 的目录解析),逐个测试文件问"这个程序读不读它"。

第二处特意不是"再加一条识别目录形式排除的规则"。因为按目录排除只是这一族里的一种写法,还有一种任何 glob 探测都抓不到的:include: ["src/**/*"]根本没有 exclude,测试在 test/ —— 程序同样一个字节都没读。改成按解析后的程序判定,目录形式排除是 TS 文档语义的自然结果(无扩展名、无通配符的 pattern 就是目录,递归匹配),上面那种也一并抓到。pin 测试里两种形态各有一条。

顺带收紧 5b(如实披露,这是第三处改动):原判据是"testConfig 里出现过 *.test. 这样的 glob",现在要求这个 project 真的读到 build 程序漏掉的那些文件。这不是顺手加严 —— 我自己写本包的 tsconfig.test.json 时第一版就踩了:exclude沿 extends 继承的,父配置排除了整个 test 目录,于是这个 project 编译了 0 个文件。tsc 恰好会喊 TS18003("No inputs were found"),但只覆盖一部分测试的 project 是静默的 —— 那正是原判据放过去的形状。

另加一条 5·0:tsconfig 解析不了时不再猜覆盖率,而是单独报错(一个解析失败的 tsconfig 会退化成包含整个仓库,本身就是缺陷;tsconfig.scripts.json 的头注释记过一次块注释里的 **/ 把配置弄坏的事故)。注释剥离是字符串感知的:本仓每个 include/exclude pattern 里都有 /*,粗暴剥离会把它要保护的 glob 吃掉。

脚本结构按仓内既有门的形态改(check-skills-paths.mjs / check-changeset-presence.mjs):纯函数 export,main() 收尾,invokedDirectly 守卫 —— 否则 pin 测试一 import 就会扫全仓并 process.exit。DEBT / NOT_COMPILED / CHECKED_BY_OWN_BUILD / TEST_DEBT 四张表和其余各节的判据、文案一字未动,auditPackages(packages, tables) 允许 fixture 传空表。

二、补覆盖(examples/schema-catalog)

形态取仓内既有形态(先读了 packages/typespackages/sdui-parserpackages/layoutpackages/plugin-report 四个包):新增 tsconfig.test.json + type-check 串起来。

与既有 23 个包的差异只在必要处,每处都写了原因:include 指向 test/ 而不是 src/;exclude 必须显式重写(见上);rootDir 放宽到包根(测试在 src 旁边而不是里面);补 jsx: react-jsx(测试是 .tsx,build 配置没有 jsx 因为 src/ 只有 JSON 和 .ts);noEmit / declaration / composite 归零。没有折进 tsconfig.json —— 那是 build 配置(tscdistrootDir: ./src),测试会被 emit 进发布产物。

首次编译暴露的问题(1 条,如实披露):fields-form-hosted.test.tsximport '@object-ui/fields',而 package.json 从未声明它 —— 幻影依赖,只靠 vitest 的 alias 解析(packages/sdui-parser/tsconfig.test.json 的注释记着同一类发现)。已补为 devDependency,pnpm-lock 相应更新。补上之后 4 个文件零错误,所以不需要 TEST_DEBT 条目。

⛔ 未动这 4 个测试文件的任何内容,也未动 src/schemas/**div.tsx(#3965 的面)。

三、反向钉

scripts/__tests__/check-type-check-coverage.test.ts(21 条,全部对着 tmpdir 里的一次性包树,仓内不留刻意失效的 fixture)。除了正常判据,两条被替换掉的旧谓词按原样重现在测试里当断言用:本 bug 的原形态既被断言"新门必须报 1 条 5c",也被断言"旧的 src-only 计数 = 0、旧的 glob 探测 = false" —— 两个盲区变成可执行语句。

实际反向验证(先定方向后跑,都是"红"方向):

  • listTestFiles(packageDir) 改回 listTestFiles(resolve(packageDir, "src"))21 条里 12 条红(含"全仓只有本包测试在 src 外"那条)。
  • 删掉目录形式 pattern 的展开一行 → 3 条红,并且门对真仓库产生 4 条误报(fields / plugin-charts / plugin-editor / consoleinclude: ["src"] 不再展开)—— 两个方向都证明这行是承重的。
  • 把本包的 tsconfig.test.jsontype-check 串接临时撤掉 → 门 exit 1,正是 5c 那条,点名 4 个文件(这就是"修后对现状必须红"的证据)。
  • test/smoke.test.tsx 临时塞一个类型错误 → turbo run type-check 红(error TS2322),已还原、工作树干净。顺手核了 turbo 的 type-check 任务 inputs 是 $TURBO_DEFAULT$,所以 test/** 在缓存键里,改测试文件不会命中旧缓存。

最后一组断言是对真仓库的(不是 fixture):本包测试存在、全在 src/ 外、build 程序不读它们、test project 读全了。撤销任一半都会红。

四、孤例复核

重验了一遍(git ls-files 独立于门自己的 walker):*.test.ts(x) 共 1129 个 —— packages 1070、apps 28、scripts 27(我这条之后 28)、examples 4;按包切分,测试在 src/ 外的只有 examples/schema-catalog(outside_src=4 / in_src=0)。scripts/ 那 27 个由 tsconfig.scripts.json 覆盖(pnpm type-check:scripts 已跑绿)。dev 报告的结论成立,只是文件数是 4 而不是 5(分诊评论已记录这个漂移)。

验证

  • pnpm exec vitest run examples/schema-catalog --maxWorkers=2Test Files 4 passed (4) / Tests 1090 passed
  • pnpm exec vitest run scripts/__tests__/check-type-check-coverage.test.ts21 passed
  • pnpm exec turbo run type-check --concurrency=278 successful, 78 total(本包实跑 tsc --noEmit && tsc -p tsconfig.test.json)
  • pnpm type-check:scripts → exit 0
  • node scripts/check-type-check-coverage.mjs → 绿,24/39
  • node scripts/check-control-bytes.mjs → OK(另按 grep -naP 自查过改动文件,无匹配)
  • eslint 改动文件 → 0 problems

changeset

不需要 —— 以 presence 门的实测判定为据:node scripts/check-changeset-presence.mjs 输出 5 file(s) changed, 0 of them under the src/ of a package the release coversNo source of a released package changed in this range, so no changeset is owed.(改动面是 CI 脚本 + 一个 private example 包的清单/配置。)


Generated by Claude Code

…fig program, not from text

`check-type-check-coverage.mjs` reported green while the four test files in
`examples/schema-catalog` were read by no `tsc` invocation, for two independent
reasons that overlapped on that one package:

  - `countTestFiles` recursed `src/` only, and those tests live in `test/`, so
    the count was 0 and section 5 skipped the package entirely;
  - `buildExcludesTests` probed the tsconfig TEXT for a `*.test.` glob, and the
    package keeps its tests out of the build by naming the DIRECTORY in
    `exclude`, so the probe found nothing and reported "already covered".

Both text heuristics are gone. Test files are enumerated from the package root
(skipping `node_modules`/`dist`/`coverage`/dot-dirs), and coverage is decided by
resolving each tsconfig's `files`/`include`/`exclude` through `extends` and
asking whether the program really reads each file. Directory-form excludes then
fall out of documented semantics instead of needing a second special case, and so
does the shape one step over that no glob probe could catch: an `include` of
`src/**/*` with no `exclude` at all. Section 5b tightens with it — a chained
`tsconfig.test.json` now has to read the files the build skips, not merely
mention a test glob somewhere.

`examples/schema-catalog` is wired up accordingly: a `tsconfig.test.json`
chained from `type-check`. Compiling those tests for the first time exposed one
real defect — `@object-ui/fields` was imported by `fields-form-hosted.test.tsx`
and declared nowhere, a phantom dependency that only resolved through vitest's
aliases — now a devDependency. With it declared, the four files compile at zero
errors, so no TEST_DEBT entry is needed.

`scripts/__tests__/check-type-check-coverage.test.ts` pins all of it against
throwaway package trees, including the exact shape of this bug and the two
retired predicates reproduced as executable statements, so neither blind spot can
return as a simplification.

Fixes #3968
@vercel

vercel Bot commented Aug 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectui Ignored Ignored Aug 9, 2026 10:45pm

Request Review

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 28.1 KB 350 KB
Entry file index-CLMhO5dH.js
Status PASS

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 8.66KB 3.13KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 7.57KB 2.97KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 22.10KB 4.37KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.13KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.64KB 2.21KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 35.76KB 9.11KB
auth (createAuthenticatedFetch.js) 4.37KB 1.69KB
auth (index.js) 2.35KB 1.07KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 4.91KB 0.87KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 26.07KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.65KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 483.72KB 106.71KB
core (index.js) 3.00KB 1.14KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 139.61KB 35.99KB
fields (index.js) 227.99KB 56.47KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 2.65KB 1.06KB
i18n (pickLocalized.js) 1.70KB 0.83KB
i18n (provider.js) 9.48KB 3.27KB
i18n (useObjectLabel.js) 27.59KB 6.63KB
i18n (useSafeTranslation.js) 4.52KB 1.96KB
layout (index.js) 38.82KB 10.79KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.74KB
mobile (index.js) 1.50KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 8.75KB 3.06KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.67KB 1.12KB
permissions (evaluator.js) 4.41KB 1.44KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.55KB 0.71KB
plugin-ai (index.js) 15.71KB 3.79KB
plugin-calendar (index.js) 45.23KB 12.45KB
plugin-charts (index.js) 61.49KB 17.48KB
plugin-chatbot (index.js) 180.33KB 42.79KB
plugin-dashboard (index.js) 118.67KB 30.69KB
plugin-designer (index.js) 210.51KB 42.51KB
plugin-detail (index.js) 237.80KB 59.48KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 112.86KB 27.24KB
plugin-gantt (index.js) 162.79KB 39.67KB
plugin-grid (index.js) 187.97KB 49.79KB
plugin-kanban (index.js) 48.53KB 13.38KB
plugin-list (index.js) 109.73KB 26.55KB
plugin-map (index.js) 17.05KB 5.33KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 40.58KB 10.58KB
plugin-timeline (index.js) 25.99KB 7.42KB
plugin-tree (index.js) 8.50KB 2.88KB
plugin-view (index.js) 84.03KB 20.55KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.71KB 3.53KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.67KB 2.37KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 23.71KB 7.95KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.23KB 0.66KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 4.47KB 2.03KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.87KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-retry.js) 4.32KB 2.02KB
types (index.js) 2.71KB 1.34KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.05KB 1.93KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 3.40KB 1.71KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Copy link
Copy Markdown
Collaborator Author

PM 验收(session_01GTRjn8xBqp75dk7kFupVRt):通过,转 ready 并挂 auto-merge。#3968 落地 —— type-check 覆盖门两个盲区(src-only 计数、glob-only 排除探测)收口,门读数 23/38 → 24/39。

核验记录(head 2573394,基 0cbdca888,实物核验 + CI 亲读):

  1. 修法强于 issue 建议且披露充分:没有逐个补特例,而是把两处文本启发式换成「解析后 tsconfig 程序逐文件判定」(files/include/exclude 沿 extends 继承)—— 目录形式排除成为 TS 语义的自然结果,还顺带抓到 include-scope 漏配这个 glob 探测永远抓不到的形态。判据再实现的风险用两道量化缓解:21 条 pin(旧谓词按原样重现为断言,两个盲区成为可执行语句)+ 全 45 包 old/new 差分(42 包三项判据完全不变)。
  2. 超出三条建议的第三处改动(5b 收紧 + 5·0 解析失败分支)接受:有自踩实证(exclude 继承陷阱致 project 编译 0 文件,TS18003)—— 「只覆盖一部分测试的 project 是静默的」正是原判据放过的形状,收紧有理有据且 PR 单列一节披露。
  3. 脚本结构重构(export + main + invokedDirectly)接受:pin 测试可 import 的前提,四张表与各节判据文案一字未动,diff 偏大有因。
  4. 本包接线形态正确:tsconfig.test.json 显式重写 exclude(注释把继承陷阱写成常驻知识)、rootDir 放宽、jsx 补齐、不折进 build 配置的理由写明;type-check 双 tsc 串接;幻影依赖 @object-ui/fields 补 devDependency 是编译暴露的最小面。tsc --listFiles 确认 4 文件入 program。
  5. 反向验证双向且量化:src-only 回退 → 12/21 红;删目录 pattern 展开 → 3 红 + 门对真仓 4 条误报(证该行承重);撤本包接线 → 门 exit 1 点名 4 文件(修后对现状必红);塞类型错 → turbo 红。turbo 缓存键含 test/** 也核过。
  6. 门与规程:catalog 4 文件 1090 测试绿 + pin 21 绿;type-check 78/78 + scripts;控制字节门 + 自扫;changeset 按 presence 门实测判定不需要;fable 0;releases 零触碰;与前进后的 main 无文件重叠。
  7. CI 亲读终态:19/19 completed、0 失败(Test shard×4 至 22:51:41Z;coverage/dependabot skipped 为 path-filter)。
  8. 漂移(测试文件 4 个非正文写的 5 个)已在分诊评论记录,两个盲区与行号锚点成立。out-of-scope @object-ui/fields 与 plugin-editor 的 build 程序把测试文件一起编译,73 个 *.test.d.ts 落进已发布的 dist/ #4006(fields/plugin-editor 的 build 把 73 个 *.test.d.ts emit 进发布 dist)由 PM 分诊(另评)。

Generated by Claude Code

@yinlianghui
yinlianghui marked this pull request as ready for review August 9, 2026 23:03
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 9, 2026
Merged via the queue into main with commit 94d1d82 Aug 9, 2026
20 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-3968-typecheck-coverage-gate branch August 9, 2026 23:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants