Skip to content

未知键的「你是不是想写」兜底对 camelCase 键系统性偏弱:输入被小写化、候选没有 —— 每个大写字母白扣一次编辑距离 #4990

Description

@xuyushun441-sys

#4001 批 13 拆出(发现,不是本批修复范围)。影响 #4001 整个战役已经落地的每一条未知键错误信息的兜底建议质量。

缺陷

packages/spec/src/shared/suggestions.zod.tsfindClosestMatches():

const normalized = input.toLowerCase().replace(/[-\s]/g, '_');

const scored = candidates
  .map((candidate) => ({
    value: candidate,
    distance: levenshteinDistance(normalized, candidate),
  }))

输入被小写化,候选没有。 于是候选键里的每一个大写字母都要额外付一次替换代价。而 strictUnknownKeyError 给的预算是长度相对的 Math.max(2, Math.floor(key.length / 3)) —— 对短键就是 2。两者一叠加,短的 camelCase 键上一个再普通不过的笔误就够不着了。

按 AGENTS.md Prime Directive #3,「TS config keys → camelCase」是全仓约定,所以受影响的是可授权面的大多数键

实测(origin/main fe83042)

输入            预算   findClosestMatches 结果
hideOn          2      []            ← hiddenOn,真实距离 2,加大写罚分后 3,超预算
hiddenon        2      ["hiddenOn"]  ← 同一个词全小写反而给得出建议
hiddenOnn       3      ["hiddenOn"]
maxLenght       3      ["maxLength"]

第二行是这个缺陷的完整刻画:把键写成全小写的作者,拿到的建议比写对了大小写、只错一两个字母的作者更好。

暴露面是「真实距离 + 候选里的大写字母数 超过预算」的那一类,集中在较短的 camelCase 键上。hideOnhiddenOn 就是标准样本:一个语义完全正确、只是少了个词尾的键,得到的信息里没有任何指向正确拼法的内容 —— 而这一战役的整个卖点,恰恰是「不只是大声,而是可修」。

建议修法

在同一处对候选做同样的归一化,并且只用于打分、回显仍用候选的原始拼写:

const norm = (s: string) => s.toLowerCase().replace(/[-\s]/g, '_');
const normalized = norm(input);
const scored = candidates
  .map((candidate) => ({ value: candidate, distance: levenshteinDistance(normalized, norm(candidate)) }))

注意两点:

  1. data/object.zod.tssuggestKey 是同一套长度相对上界的另一处实现,要一起检查是否同病。
  2. 这会让一批原本没有建议的错误开始给建议,也可能改变个别已有建议的选中项 —— 所以它会动到 未知键静默剥离仍是全仓默认:把 #3405 的 strict 收紧从一个 schema 推广到整个可授权面(ADR-0078 完整性闸门) #4001 已落地各批的错误信息文案,那些批次里有直接断言 message 内容的 pin 测试(如 webhook.test.ts / etl.test.ts / control-flow 系列)。属于横切改动,应独立成 PR 并跑全 spec 套件,不适合搭在某一批收紧上。
  3. 修的时候顺便加一条自证测试:同一个键的全小写形式不应比正确大小写形式得到更好的建议。这条不变量是本 issue 的实质。

现状缓解

#4001 批 13 在 ui/responsive.zod.ts 里为它自己遇到的那一例写了显式 alias(hideOn: 'hiddenOn'),并在注释里记了测量值。那是逐例绕行,不是修复 —— 每个 schema 都要为兜底够不着的短 camelCase 键手写 alias,正是 strictObject 引入时想消掉的那种重复。

相关

按 AGENTS.md Prime Directive #10 归档,未指派。

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions