fix(lint): 零字段对象是「无法判断」而非「没有这个字段」(#4383) - #4385
Merged
Merged
Conversation
…such field" (#4383) `hook-body-write-unknown-field` and `action-body-write-unknown-field` reported EVERY field write to an object that declares no `fields` — an external object, or a datasource-introspected schema whose columns are resolved at runtime. `indexObjectFields` returns an empty Set for such an object rather than `undefined`, and both rules only asked "is this object in the stack?" (`targetSets.every((s) => s !== undefined)`, `if (!known) continue`). An empty Set is neither undefined nor falsy, so it became the answer to `has(field)` — and that answer is always false. The field map is not empty, it is UNKNOWN. Two other rules in the same family already drew that distinction with their reasons written down (validate-searchable-fields skip #2, and validate-flow-node-writes, which added the guard in #4369 because it gates). Two of four had it: the drift shape #3583 and #4330 exist to remove. Fixed once, not twice. The guard now lives in a shared `judgeableFieldsOf(index, objectName)` returning the declared names only when they are a sound basis for a "resolves to nothing" judgement, and `undefined` for both unjudgeable cases. All three write-set rules route through it, so a fourth cannot repeat the omission. Internal to the family — not re-exported from the package barrel, same as `indexObjectFields` and `IMPLICIT_FIELDS`. One semantic call worth naming: a multi-target hook where only SOME targets are judgeable is now skipped entirely. The `ctx.input` finding fires only when a field is missing from every target, and an unjudgeable target is one the field might well exist on — judging the remainder would assert "missing everywhere" on evidence that does not cover everywhere. Consistent with the rule's stated asymmetry: prefer a missed finding to a false one. No behaviour change for objects that declare fields; a test sits next to each new skip so the guard cannot swallow the real finding. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 2 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #4383
问题
hook-body-write-unknown-field和action-body-write-unknown-field会把每一个写到「不声明fields的对象」的字段标成未知字段——external 对象、数据源自省 schema(列在运行时解析)都属于这一类。修复前实测:
根因
indexObjectFields对这类对象返回的是空 Set,不是undefined。而两条规则只问了「这个对象在不在 stack 里」:空 Set 既不是 undefined 也不是 falsy,于是它成了
has(field)的答案——而这个答案恒为false。那张字段表不是空的,是未知的。
为什么算 bug 而不是保守取舍
同一家族里另外两条规则早就区分了这两者,而且各自写了理由:
validate-searchable-fieldsskip ✨ Set up Copilot instructions #2 —— "external objects and datasource-introspected schemas whose columns are resolved at runtime";validate-flow-node-writes(feat(lint): flow update_record 写不存在字段从盲区变为 gating error (#4271) #4369)—— 因为它 gating,加了这道 guard。四条里两条有、两条没有。正是 #3583 建套件、#4330 收拢 system-fields 在处理的漂移形状。而且它直接违反那两条规则自己头注释里的原则:「a false positive kills an advisory lint」。
修法:修一次,不是修两次
新增共享的
judgeableFieldsOf(index, objectName)—— 仅当声明的字段名足以支撑一个「这个名字解析不到任何东西」的判断时才返回它们,两种无法判断的情形(跨包对象、零字段对象)都返回undefined。三条 write-set 规则的查表全部改走它,所以第四条规则不可能再漏掉同一道 guard。它属于家族内部,不从包 barrel 再导出,与
indexObjectFields、IMPLICIT_FIELDS一致。一个需要点名的语义决定
多目标 hook 里只要有一个目标无法判断,现在整体跳过,而不是拿剩下的凑合判。
理由:
ctx.input的 finding 只在字段在每一个目标上都不存在时才报,而那个无法判断的目标恰恰是它可能存在的地方。只判断其余目标,等于用覆盖不到"每一个"的证据去断言"每一个都没有"。这与该规则自己声明的不对称取向一致:宁可漏报,不可误报。验证
[];normal: ["hook-body-write-unknown-field"]),每个新增 skip 旁边都配了一条这样的测试,防止 guard 把真 finding 一起吞掉;@objectstack/lint42 文件 / 665 测试全绿(新增 5 条),typecheck干净。🤖 Generated with Claude Code