fix(spec): gen:docs 保留 passthrough 对象的已声明键 + 开放性标记,不再塌缩成 Record (#4912) - #5339
Conversation
… gen:docs (#4912) The reference-docs type renderer tested `additionalProperties` BEFORE `properties`, treating them as alternatives. JSON Schema spells a `.passthrough()` / `.catchall()` object as BOTH at once, so every object that declared a shape and also accepted extra keys collapsed to a bare `Record<string, any>` — erasing keys the schema requires. `BulkActionParam.options` is the specimen: `label` / `value` are required, yet the page showed `Record<string, any>[]`, i.e. no shape at all. PR #4909 compensated by hand in that key's `.describe()` prose — per-site compensation, not a fix, and the #4001 campaign keeps producing more such sites. Declared keys and openness are independent facts and are now printed independently: `({ label: string; value: string | number | boolean } & Record<string, any>)[]`. The parentheses are load-bearing — `A & B[]` is `A & (B[])` in TypeScript, so an unbracketed array element would state a different type than the schema. Arrays whose element is a top-level UNION have the identical precedence defect on 164 sites, but that one predates this change and is filed as #5338 rather than bundled here. The renderer moves from build-docs.ts to scripts/lib/format-type.ts with unit tests: asserting its output previously required running the whole generator and grepping the emitted .mdx, which is why this collapse went unnoticed. The extraction was verified behavior-neutral first (244 regenerated files, zero diff) before the fix landed. Regenerated: 12 cells across 6 reference pages, every one restoring declared keys, none losing shape. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FTszibd6C8sUCCZnM4VcrL
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckNo hand-written docs reference the 0 changed package(s). ✅ |
…-docs-passthrough-shape
已同步 main(入队前的 os-regen 复核)base 从
结论:两侧都完好本 PR 侧:重新生成后,与 main 侧:退役的 5 张页(animation / dnd / keyboard / offline / touch)保持删除、没有被复活; 一个值得记录的观察:重新生成的产物与 merge commit 完全一致( 一处刻意不动:
|
Fixes #4912
前提复核(先证红)
立案前提在
origin/main(c7406b0)上成立,用当前生成器复现:而
gen:schema给该节点产出的确实是「两者兼有」:{ "type": "object", "properties": { "label": {...}, "value": {...} }, "required": ["label", "value"], "additionalProperties": {} }根因
build-docs.ts的formatType()先判additionalProperties、后判properties,把两者当成了互斥的二选一:JSON Schema 把
.passthrough()/.catchall()对象同时表达为properties+additionalProperties,所以只要一个对象既有形状又开放,它的形状就被整个丢掉。声明键与开放性是两个独立的事实,渲染器把它们做成了互斥。全量语料扫描:130 个节点同时带
properties与开放的additionalProperties(全部是additionalProperties: {}),分布在 40 个 schema 文件里。修法
{ 已声明形状 } & Record< …, … >—— 声明形状在前(读者必须写的键),开放性在后:Record< string, any >[]({ label: string; value: string | number | boolean } & Record< string, any >)[]三个细节:
A & B[]在 TypeScript 里是A & (B[]),不加括号等于声明了另一种类型。用hasTopLevelIntersection()做深度扫描(正确忽略{}/< >/[]/()内部的运算符)后再决定是否加括号。…与& Record不是一回事,单元格两者都印。…= 还有更多已声明键(超过四个时省略);& Record= 还接受未声明键。properties: {}不算形状,继续走原来的 record / opaque 渲染,不会印出{ } & Record< … >。语料里有 6 个这样的退化节点,行为逐字保持不变。渲染器从
build-docs.ts抽到packages/spec/scripts/lib/format-type.ts并配单测。抽出前只能「跑完整个生成器再 grep 产出的 .mdx」才能断言它的输出 —— 这正是该塌缩能在整个 #4001 战役期间无人察觉的原因。抽取本身先证明了行为中性:仅搬移不改逻辑时重生成 244 个文件、diff 为空,然后才落修复。重生成影响面(逐页确认,共 6 页 12 个单元格)
每一处都是恢复被抹掉的声明键,没有任何一页丢失形状:
ui/bulk-actionoptionslabel/value(必填,即立案样本)ui/bulk-actionparamsname/label/help/type…ui/viewgantt×2startDateField/endDateField/titleField…(ListView、ObjectListView 各一份)ui/viewtree×2parentField/labelField/fields/defaultExpandedDepthui/dashboardoptionsdateGranularity/sortBy/sortOrder/limit…api/protocolmessages×3role/content/partssystem/auth-configsocialProvidersclientId/clientSecret/enabled/scope(嵌在Record< string, … >的值位)kernel/startup-orchestratorpluginname/version130 个节点只落到 12 个单元格,是因为其余节点要么在深度限制之外(嵌套对象按设计保持 opaque),要么不出现在被渲染的属性表位置。
反向验证(方向:红 —— 事前即如此预判)
这些用例断言的是修复产出的正形状,不是「某个 finding 消失」,所以恢复缺陷应当让它们变红,而不是出现计数反转或倒置。把 lib 里的分支顺序改回「先
additionalProperties」并去掉数组括号后:绿的那 6 个正是「本来就渲染对的形状」(纯 record、
additionalProperties: false的封闭对象、无形状对象、空properties、$ref链接)—— 这也解释了缺陷为何能存活:渲染器在所有有人想到去看的形状上都是对的。备选渲染形态
issue 里给了两种:
{ … } & Record< … >,或「表格 + 一行『接受额外键』」。本 PR 取前者 —— 参考页的类型单元格历来就是一段可直接抄走的 TypeScript(Enum< 'a' | 'b' >、{ a: string }[]、Record< string, any >都是既有惯例),交叉类型无需改动表格结构即可同时承载两个事实。后者要给每张表加一列或一行,收益只在于更显眼,代价是改动所有页面的表结构。顺带发现(未在本 PR 修)
数组元素是顶层联合时有一模一样的优先级缺陷(
string | number[]≠( string | number )[]),164 处。它早于本次改动,已按 PD#10 立案 #5338(未认领),format-type.ts内留注释指向该单。一并修会让重生成 diff 从 12 行涨到约 170 行,把真正的 passthrough 修复埋掉。验证
未触碰
packages/spec/src/**(无 zod 源改动,故api-surface/authorable-surface不受影响)、未触碰docs/audits/2026-07-unknown-key-strictness-ledger*、content/docs/releases/零改动。Generated by Claude Code