Skip to content

fix(spec): gen:docs 给「元素是联合类型」的数组补上括号 —— (string | number)[],不再是 string | number[] (#5338) - #5793

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-5338-union-array-brackets
Aug 6, 2026
Merged

fix(spec): gen:docs 给「元素是联合类型」的数组补上括号 —— (string | number)[],不再是 string | number[] (#5338)#5793
baozhoutao merged 1 commit into
mainfrom
claude/issue-5338-union-array-brackets

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Fixes #5338

前提复核(先于实现,针对 origin/main @ 1624f4a)

立案时的判断全部成立,没有过期:

缺陷本身

TypeScript 里 [] 的结合优先级高于 |,和高于 & 是同一回事:

schema 表达 修复前印出 印出的东西实际含义
数组,元素为 stringnumber string | number[] 一个 string,或者一个 number 数组
数组,元素为 string{ id, label, default } string | { id: string; … }[] 同上,顶层被拆成二选一

单元格声明的类型和 schema 声明的不是同一个。参考页的类型单元格正是元数据作者
(尤其是 AI 作者)直接照抄的那一行:照着 string | number[] 写下一个裸 string,
schema 会当场拒绝,而页面看起来是允许的——这正是 #4001 战役在追的「声明与呈现不符」。

修法

按立案建议,只有一处:hasTopLevelIntersection 放宽为
hasTopLevelUnionOrIntersection,深度扫描同时识别顶层 &|

两者本来就是同一条规则——[] 对这两个运算符都不满足分配律(A & B[]
A & (B[]),A | B[]A | (B[]))——所以共用一次扫描,而不是并列两个函数。
#4912 当时把扫描限定在 &,是因为交叉类型元素是那个 PR 自己引入的,而联合侧是既有
缺陷、重生成 diff 会把真正的 passthrough 修复埋掉;那条注释现在改写成了两半合一的说明。

深度扫描原本就正确忽略 {} / < > / [] / () 内部的运算符,所以
Enum<'a' | 'b'>[]Record< string, string | number >[]
{ k?: string | number }[] 和 markdown 链接都保持原样,不会多出括号。

重生成结果与规模

pnpm --filter @objectstack/spec gen:docs(输入是本分支合入树里的当前 schema):

  • 19 个参考页、42 行单元格、47 处补括号;
  • git status 只有 modified,没有新增页,也没有被删除的页被复活;
  • pnpm --filter @objectstack/spec check:docs 报告 240 generated files in sync(不动点)。

关于规模与立案里「164 处」的差异,这里如实说明,不硬凑:立案的 164 是对整个
json-schema/ 语料
(1608 个文件)按节点扫描得到的原始站点数,同一个共享 shape
在每个引用它的 schema 的 $defs 里都被数一次;而参考页对每个 shape 只渲染一次,或者
干脆渲染成 $ref 链接。我在本分支上复测:原始站点数 415(联合 356 + 交叉 59),
去重后只有 22 个不同的单元格字符串,落到页面上就是 42 行 / 47 处。修复后再跑同一
探针:渲染成未加括号的站点为 0。也就是说覆盖是完全的,只是「站点」和「单元格」
是两个不同的口径。

样例(content/docs/references/automation/state-machine.mdx,| 是 GFM 单元格转义):

-| **entry** | `string \| { type: string; params?: Record< string, any > }[]` | optional |
+| **entry** | `(string \| { type: string; params?: Record< string, any > })[]` | optional |

测试

packages/spec/scripts/format-type.test.ts 新增 7 个用例(该文件共 19 个,全绿),覆盖:
基本类型联合、对象变体联合(取真实的 AiModelsResponse.models 节点)、
JSON Schema 的 type: [a, b] 数组写法、联合里嵌数组变体(取真实的
View.list.filter[].value,外层联合不加括号、内层数组加)、嵌套数组每层各加一次、
联合里混有交叉变体,以及一组「不该加括号」的反向用例。

反向验证(方向事先判定,结果如实记录)。 把扫描收窄回 &(即本 PR 之前的
hasTopLevelIntersection)重跑:

Tests  5 failed | 14 passed (19)
FAIL … (#5338) > brackets a union of primitives instead of letting `[]` claim the last variant
FAIL … (#5338) > brackets a union whose variants are objects (the AiModelsResponse specimen)
FAIL … (#5338) > brackets a JSON Schema type-array element (`type: [a, b]`), which also renders a union
FAIL … (#5338) > brackets the ARRAY VARIANT inside a union, leaving the outer union unbracketed
FAIL … (#5338) > brackets once per array level, so nested arrays stay readable

方向是最常见的那一种(还原缺陷 → 新 pin 变红),因为这些用例断言的是修复产出的正形状
但新块 7 个用例里只红了 5 个,这一点写进了测试文件的注释而不是含糊带过:
「联合里混有交叉变体」那条本来就被 & 那一半括起来了(它两个运算符都有),
「不该加括号」那条断言的是括号不出现——两条都是诚实的非回归用例,不是死 pin。
#4912 的交叉侧 pin 全部保持绿,({ label: string; value: … } & Record< string, any >)[]
行为不变。

其余验证:

  • pnpm --filter @objectstack/spec test319 passed (319) / 8151 passed (8151)
  • pnpm --filter @objectstack/spec typechecktsc --noEmit 通过 + check:test-typecheck: OK
  • node scripts/check-nul-bytes.mjs → OK(5678 个受跟踪文本文件,无裸控制字节)

Changeset

包含 .changeset/format-type-union-array-brackets.md(@objectstack/spec patch)——
本 PR 改的是读者可见的生成参考文档,因此走 changeset 而不是 skip-changeset

已知陷阱处理

#5358:任何 spec build/gen 都会顺手改写 packages/spec/authorable-surface.base.json
本 PR 提交前逐文件核对并 git checkout -- 还原了该文件,git show --stat HEAD 确认
不在本次提交里。


关联:#4912 / PR #5339(同一渲染器,交叉类型侧)、#4001(战役)。

🤖 Generated with Claude Code

https://claude.ai/code/session_01559M8FVm6W6vDLABL3jvdW


Generated by Claude Code

…cells (#5338)

`format-type.ts` appended `[]` straight onto the rendered element, and `[]`
binds tighter than `|` — so an array of `string | number` printed as
`string | number[]`, which states "a string, OR an array of numbers". The cell
and the schema were different types, on the one line metadata authors copy.

Widen #4912's depth scan from `&` to `& | |`: the two operators share one rule
(`[]` is not distributive over either), so `hasTopLevelIntersection` becomes
`hasTopLevelUnionOrIntersection`. Nested operators inside `{}` / `<>` / `[]` /
`()` are still ignored, so `Enum<'a' | 'b'>[]` and `Record<string, string |
number>[]` gain no stray brackets.

Regenerated `content/docs/references/**`: 19 pages, 42 cells, 47 brackets
added; no page created, resurrected or deleted; `check:docs` reports all 240
generated files in sync.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01559M8FVm6W6vDLABL3jvdW
@vercel

vercel Bot commented Aug 6, 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)
objectstack Ignored Ignored Aug 6, 2026 5:47am

Request Review

@github-actions github-actions Bot added the size/m label Aug 6, 2026
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

No hand-written docs reference the 0 changed package(s). ✅

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

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants