From fa80b17d5f027107946490b494933cdc1f2b8c1e Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Sun, 25 Oct 2026 15:33:46 +0800 Subject: [PATCH] =?UTF-8?q?docs(=E9=97=A8=E7=A6=81=E8=AF=B4=E6=98=8E):=20?= =?UTF-8?q?=E8=A1=A5=20bom-guard=20=E4=B8=8E=20setview-check=20=E4=B8=A4?= =?UTF-8?q?=E7=AF=87=E8=AF=A6=E8=A7=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 票面:task-20 合入后 README 索引上挂着我名的两篇(Lead 指定挂批 4 MR)。按本目录八段结构写, 「拦下过什么真事」一栏用同日实测,作者自己的也算。 - bom-guard.md:立项依据是 2026-10-25 一天内 5 次(道E 4 次 + 道B 1 次);症状 #870-D / #8 / #274 一类**假语法错**,位置不指向真因;MSVC 与既有钩子全照不出来;"人工逐文件比 HEAD"只防得 住记得做的人。两条刻意不报的边界(新文件无 BOM、反向加 BOM)+ 一条刻意不做(不自动补,整文件 重写会把可修的丢 BOM 变成不可修的 U+FFFD)+ 三处互指防漂移,各写清理由。也记了本门禁自己的 立项提交被 gitlint 拦过(正文一行 92>80,量出来仍按了提交)——这一栏只挑别人的事写就等于暗示 写门禁的人不需要门禁。 - setview-check.md:立论点是"分层边界制造的物理盲区"——BSP 不得 include App 头,行表 id 只能是 裸数字 + 注名,编译期无法自证;像素断言又只看可见 8 行,40 项分 5 页,错位一行时第 1 页可能 看着完全正常。另收两条脚本自身实测坑:\w+ 吃不下带引号的单位导致**整行静默跳过**(故加行数 硬闸)、打印中文时 CP936 半路崩(门禁脚本崩在半路 = 一条都没检查)。退出码 1 与 2 分开:把 "我没读懂"报成"你写错了"是检查类脚本最坏的一种失败。 - README.md:索引加两行;"那 46 个钩子" → 48、"40 个 hook → 23 篇" → 42 → 25 篇(不同步就是在 让索引说谎)。AGENTS 里"两篇待补"那句已过期,但 AGENTS 是单写者文件,留给 Lead 收。 跳过的门禁(三条同族):mem-numbers / mem-budget / size-gate(L0c),与本笔无关,等台账回填。 --- docs/门禁说明/README.md | 6 +- docs/门禁说明/bom-guard.md | 94 +++++++++++++++++++++++++++++ docs/门禁说明/setview-check.md | 106 +++++++++++++++++++++++++++++++++ 3 files changed, 204 insertions(+), 2 deletions(-) create mode 100644 docs/门禁说明/bom-guard.md create mode 100644 docs/门禁说明/setview-check.md diff --git a/docs/门禁说明/README.md b/docs/门禁说明/README.md index 375c46be..b6700cda 100644 --- a/docs/门禁说明/README.md +++ b/docs/门禁说明/README.md @@ -1,6 +1,6 @@ # 门禁逐项说明(每种门禁一篇) -> 这一目录回答一个问题:**提交时那 46 个钩子到底在干什么、拦谁、放谁、什么时候会骗人。** +> 这一目录回答一个问题:**提交时那 48 个钩子到底在干什么、拦谁、放谁、什么时候会骗人。** > 每个门禁一篇,八段固定结构:查什么 / 为什么要它 / 触发面 / 报错怎么读 / 好处 / > **限制与盲区** / 豁免与基线 / 改门禁本身要注意什么。**最后一节是这套文档存在的理由**—— > 一份只看"拦了什么"的门禁文档,会让人以为绿就是对的。 @@ -21,7 +21,7 @@ ⚠ 增量拦截最容易误会的一点:**绿灯不等于没问题**,只等于"没有比上次更差"。 每个增量拦截门禁的基线数字都写进它自己的文档,读那篇时第一眼看的就是"现在在册多少条"。 -## 2. 索引(40 个 hook → 23 篇 + 1 根在 docs/测试说明/) +## 2. 索引(42 个 hook → 25 篇 + 1 根在 docs/测试说明/) | 门禁文档 | 覆盖的 hook id | 一句话它在查什么 | 改了哪类文件才会跑 | 文档 | |---|---|---|---|---| @@ -41,6 +41,8 @@ | [`test-suite-switch.md`](test-suite-switch.md) | `test-suite-switch` | 板级套件开关必须互斥;全注释 = 产品构建 | `test/board/test_config.h` | 已写 | | [`font-charset.md`](font-charset.md) | `font-charset` | UI 用到的汉字是否都在子集字体里(缺字 = 白框) | `Drivers/BSP/UI/**`、字体脚本 | 已写 | | [`ui-golden.md`](ui-golden.md) | `ui-golden` | UI 像素回归:菜单 8 状态 + 内容页 11 页黄金参考图比对 | UI 源码、模拟器 | 已写 | +| [`bom-guard.md`](bom-guard.md) | `bom-guard` | 带 BOM 的文件被编辑工具剥掉 BOM(ARMCC 换解码 → 假语法错) | `Drivers/BSP/UI/**` 等名单内文件 | 已写 | +| [`setview-check.md`](setview-check.md) | `setview-check` | 定值查看行表与 `prot_set` 枚举序 / `g_prot_set_meta` 上下限是否同一份口径 | `tools/setview_check/**`、`ui_w_setview.*` | 已写 | | [`host-unit-tests.md`](host-unit-tests.md) | 16 个 `*-unit-test` | 主机侧单测(同一机制 16 个实例):逻辑层在无硬件的 PC 上编译并跑断言 | 各自被测模块与其测试目录 | 已写 | | 详解在 [`../测试说明/主机单测覆盖率口径.md`](../测试说明/主机单测覆盖率口径.md)(口径 / 分母 / 基线 / 盲区都在那篇,本目录不重复) | `test-coverage` | **覆盖率总闸**:跑全部 28 套主机单测 + 已覆盖行只增不减的回退台账 | `App/`、`Drivers/BSP/`、`test/host/`、脚本与基线 | 已写(在 `docs/测试说明/`) | | [`trailing-whitespace.md`](trailing-whitespace.md) | `trailing-whitespace` | 行尾空白(改写型;CubeMX/Keil 生成文件豁免) | 全部 | 已写 | diff --git a/docs/门禁说明/bom-guard.md b/docs/门禁说明/bom-guard.md new file mode 100644 index 00000000..1ed7f47e --- /dev/null +++ b/docs/门禁说明/bom-guard.md @@ -0,0 +1,94 @@ +# `bom-guard` — 带 BOM 文件被剥掉即拦(防"假语法错") + +> 一句话:编辑工具会**静默剥掉**文件原有的 UTF-8 BOM,ARMCC 于是换了一套解码假设,报出来的 +> 却是 `#870-D` / `#8` / `#274` 这类**语法错**——错的位置和内容都不指向真因。这条门禁让"补回 +> 3 个字节"这件事在提交时就必须处理,而不是等下一个人构建失败再去猜。 + +## 1. 它在查什么 + +对 pre-commit 传来的、落在名单内的文件,比较 **HEAD 版本**与**工作区版本**的前 3 字节: +HEAD 是 `EF BB BF` 而工作区不是 → FAIL 并列出文件与补救动作。只此一条判定,没有存量豁免。 + +名单(三处互指,见 §6.2):`Drivers/BSP/UI/**`、`App/task/ui_task.c`、`App/cfg/cfg_file.c`、 +`Lib/lv_conf.h`。 + +## 2. 为什么要它(拦下过什么真事) + +- **立项直接来自一天内 5 次踩坑**:2026-10-25 道E 在 task-15(UI 页面批次)里连踩 4 次, + 道B 在批 1 踩过 1 次。每次的症状都一样:改完 UI 源文件、`@build` 突然报一串语法错,而改动 + 本身是加注释或改常量;`MDK-ARM/build_log.txt` 里的行号指向中文注释所在的**上一行**, + 照着报错去查会查不出任何东西。真因只是文件开头少了 3 个字节。 +- **别的门禁和编译器都不会响**:MSVC(模拟器侧)带不带 BOM 都编得过、0 警告;`no-mojibake` + 查的是 `U+FFFD`(已经损坏的痕迹),BOM 刚丢时文件字节完全合法,它看不见;`coding-standard` + 也不看编码。也就是说,这条坑当时唯一的暴露点是"重编该文件",而**历史 0 警告不代表安全**。 +- **人工审计只防得住记得做的人**:立项前道E 的做法是每次提交前逐文件比 HEAD 查 BOM, + 四次里有一次是靠"看图发现满屏横杠"顺手查的,不是靠纪律——纪律型检查不成立(同类口径见 `docs/代码规范/pre-commit使用说明.md` §5.5)。 +- 顺带一条同族事实(写在这里而不是别处):**本门禁自己的立项提交被 `gitlint` 拦过一次**—— + 正文有一行 92 > 80,作者量出来了仍然按了提交。「拦下过什么真事」这一栏如果只挑别人的事写, + 就等于在暗示写门禁的人不需要门禁,那是不成立的。 + +## 3. 触发面 + +`.pre-commit-config.yaml` 里 `bom-guard` 的 `files` 正则(等价于 §1 名单)。不在名单内的文件 +**不报也不提示**——比如有人在 `App/log/` 新建带 BOM 的文件,本门禁不管(那种文件按约定就不该 +带 BOM;真带上了,编译器会替我们响)。 + +⚠ `files` 不命中时钩子是 **Skipped**,`Skipped ≠ Passed`:一次只改文档的提交里看不到它变绿 +是正常的,不代表它检查过了。 + +## 4. 报错怎么读 / 单独跑 + +```powershell +# 常规由 pre-commit 自动跑;人工核全仓(含没改过的文件): +& '.venv\Scripts\python.exe' scripts/check_bom_drift.py --all +# 自证判定(临时 git 仓库,含两条边界用例): +& '.venv\Scripts\python.exe' scripts/check_bom_drift.py --selftest +``` + +报错会点名文件并给补救动作:**只把 3 个字节 `EF BB BF` 补回文件最前面**。**不要**整文件重写 +(`Set-Content` / 脚本按系统默认编码重打中文会把"可修的丢 BOM"变成"不可修的 `U+FFFD`", +那是 `no-mojibake` 都救不回来的损坏)。 + +## 5. 好处 + +1. 把一类"只有重编译才暴露、且报错完全误导人"的缺陷挡在提交点,且提示直接给出正确修法; +2. 逼着 BOM 名单保持单一口径(§6.2 的三处互指)——名单一旦散着改,本门禁立刻红; +3. 判定极简(两个 3 字节比较),几乎不会有误报,也就不容易被 `--no-verify` 搪塞过去。 + +## 6. 限制、盲区与刻意的不判定 + +### 6.1 只查"有 → 无"这一个方向 +"HEAD 无 BOM、工作区加了 BOM" **不报**。那是另一个方向的改动,可能是有意的(把某个文件纳入 +BOM 名单),也可能是无意的;报红只会逼人 `--no-verify`,而**养成绕门禁的习惯比漏检一次贵得多**。 +"新增文件没有 BOM" 同样不报:名单里的新文件(如新建 `ui_page_xxx.c`)由创建者负责加 BOM, +漏加会在 `@build` 时以真错误暴露——本门禁不假装自己能替代那一步。 + +### 6.2 名单三处互指(防漂移) +AGENTS 铁律第 2 条的名单、钩子 `files` 正则、脚本内 `BOM_LIST` 必须一致;脚本每次运行都核一次, +不一致即 FAIL。比较时会先脱掉正则里的反斜杠与锚点——**核的是意图一致,不是字节相同**,否则 +`App/task/ui_task\.c$` 会被误判成"没覆盖 App/task/ui_task.c"。 + +### 6.3 不校验文件是否真的含非 ASCII +判定只看编码标记,不看内容。一个纯 ASCII 的名单内文件带不带 BOM 都不影响编译,本门禁不替 +编译器做这种优化判断——它宁可少管,也不要给出"可以不带 BOM"的例外口径(例外一旦被记住, +就会被套用到带中文的文件上)。 + +### 6.4 绕过与提交后不可见 +`--no-verify` 可以直接跳过;合入主干之后再手工删 BOM,要等下一次有人改这个文件才会被比出来。 +这两条是所有 pre-commit 门禁的公共限度,不在本篇展开(→ `docs/代码规范/pre-commit使用说明.md`)。 + +## 7. 豁免与基线 + +无基线、无豁免、无 `--write`:要么 BOM 在,要么别提交。 + +## 8. 改这个门禁本身 + +- 改名单必须同时改三处(§6.2 会拦着); +- 新增判定分支必须先在 `--selftest` 里加用例(含正反两向),它是这篇文档里唯一可执行的部分; +- 别在这里加"自动修复":见 §4 的编码风险。 + +## 9. 变更记录 + +| 日期 | 变更 | +|---|---| +| 2026-10-25 | 首版(道E task-20)。立项依据:同日 task-15 内 4 次 + 批 1 一次 BOM 剥离事故;端到端实测(真剥掉 `ui_w_records.c` 的 BOM → 钩子 rc=1)随 task-20 提交正文留存 | diff --git a/docs/门禁说明/setview-check.md b/docs/门禁说明/setview-check.md new file mode 100644 index 00000000..27e98bbd --- /dev/null +++ b/docs/门禁说明/setview-check.md @@ -0,0 +1,106 @@ +# `setview-check` — 定值查看行表与 `prot_set` 枚举序 / 上下限一致性 + +> 一句话:UI 侧四张定值视图表(`Drivers/BSP/UI/ui_w_setview.c`)与算法侧的定值定义 +> (`App/algo/prot_set.h` 枚举序 + `App/algo/prot_set.c` 的 `g_prot_set_meta`)之间有一条 +> **编译期照不到**的对齐关系——本门禁从外面把它钉住。 + +## 1. 它在查什么 + +脚本用正则读出三份事实,再逐条比对(`tools/setview_check/check_setview_limits.py`): + +1. 四张视图表的 `id` 合起来**恰好覆盖 0..39 各一次**(不多、不少、不重); +2. 每个数值行的 `min`/`max` 等于 `g_prot_set_meta` 的 Q10 上下限按该行小数位换算后的整数; +3. 行 `kind`(数值 / 二态串表)与 `g_prot_set_meta[].is_ctl` 一致; +4. 单位串只允许 ASCII(`V`/`A`/`ms`/`Hz`/NULL)——中文字不进子集字体,见 `font-charset.md`; +5. 小数位查表 `s_dec[]` 与行表 `decimals` 对同一个 `id` 取值一致(组件 `get` 只收到 `id`, + 两处不一致就会显示错位数字)。 + +退出码:**0** 一致;**1** 不一致(打印具体行与期望值);**2** 解析锚点丢失——这是**结构漂移**, +说明 C 侧写法变了、脚本要跟着改,不是"数据错了"。1 与 2 必须分开:把"我没读懂"报成"你写错了" +是检查类脚本最坏的一种失败。 + +## 2. 为什么要它(这不是"多一道保险") + +- **分层边界制造的物理盲区**:`layer-deps` 钩子禁止 `Drivers/BSP/**` include `App/**` 的头, + 所以视图表里的 `id` 只能是**裸数字 + 行尾注名**(`/* PS_OC1_EN */`)。这条对应关系在编译期 + 无法自证:写错一个数字、少写一行、上下限抄错一位小数,编译器与 cppcheck 全都不会响。 +- **像素断言只覆盖可见部分**:一页只有 8 行进入黄金参考,40 项定值分 5 页;行表整体错位一行时, + 第 1 页可能看起来完全正常(名字和值都是"某个真定值"),要翻到第 2、3 页才可能被人看出来, + 而人真去翻的时候往往已经太晚——那时基线已经按错的图刷新过了。 +- 也就是说:**分层是对的,代价必须有人补**。这条门禁就是补的那一步(同类补法见 + `registration-check.md` 用反向校验补"新文件双端注册",`ui-golden.md` 补"改了就画错")。 + +## 3. 触发面 + +`^(tools/setview_check/|Drivers/BSP/UI/ui_w_setview\.(c|h)$)`:改脚本自己、或改视图表所在 +文件时触发。⚠ `files` 不命中时是 **Skipped**,`Skipped ≠ Passed`。 + +本钩子与实现文件是**跨分支**落地的(钩子配置在 `task/bom-guard` 分支,脚本在 `task/pages-ui` +分支):在脚本还没合入的树上,`files` 永不命中 → 钩子自然 skip,合入后自动生效,不需要任何人 +再动配置。这一条写进配置注释,是为了避免下一个人以为"钩子没跑 = 配置坏了"。 + +## 4. 单独跑 + +```powershell +# 仓库根执行(脚本按仓库根的相对路径读三个源文件,别在子目录跑) +& '.venv/Scripts/python.exe' tools/setview_check/check_setview_limits.py +``` + +正常输出只有一行:`行表 40 行 / dec 40 项 / meta 40 项 -> OK`。**行数必须等于 40** 是硬闸: +解析器少吞一行不算通过(见 §6.1)。 + +## 5. 好处 + +1. 把一条"只有真机装载真定值、且人翻到第 2 页之后"才可能暴露的静默错位,提前到提交点; +2. 让整定 4 页能安全复用同一批表:界内界外只有一份定义,不会出现"查看说合法、整定说越界" + 两套边界(复用是当初把表放在 `ui_w_setview.c` 而不是各页面里的原因); +3. 口径变更留下痕迹:`RATIO_IDS` 与文档 §3.2.2 的表注互为引用,改动必须同时解释。 + +## 6. 限制、盲区与写脚本时踩到的两个坑 + +脚本用**正则读 C 源码**,天然脆弱:换一种写法(宏生成表、初始化器拆行、结构体加字段)就可能 +解析不到。以下是同日实测的两个真坑,都已固化进脚本: + +### 6.1 `\w+` 吃不下带引号的单位 → 整行**静默跳过** + +行表项形如 `{ "过流Ⅰ段", 0, 1, 0, 0, "A", NULL }`。最初的行正则用 `\w+` 匹配"最后一个字段", +带引号的 `"A"` 匹配不上 → 整行不匹配 → **一条错误都不报**,脚本照样打 OK。少检一行比检查错 +更难发现(后者有人会去查,前者看起来像已经查过)。修法有两半:字段改用 `\S+` 承接,以及加 +`len(rows) == 40` 的**硬闸**——解析条数本身也要被检查,否则"没解析到"就等价于"没问题"。 + +### 6.2 打印中文 → Windows 控制台按 CP936 写 stdout 会**崩在半路** + +门禁脚本崩在半路等于一条都没检查,而现场看到的是一串 Python traceback 而不是 FAIL。脚本开头 +把 `stdout/stderr` 锁到 UTF-8(`errors="replace"`)。同一条课在本仓一天里踩了两次,第二次才 +写成固定动作——所以两个脚本里都有这段,且都写了为什么。 + +### 6.3 不检查的东西(别把它当万能闸) + +- **名字对不对**不查:`"过流Ⅰ段"` 该不该叫这个名字是语义判断,脚本只核结构一致;名称口径 + 的真源是 `docs/UI设计/页面扩充详细方案.md` §3.2.2~§3.2.5 四张表; +- **上下限本身合不合理**不查:脚本只核"行表等于 meta 换算结果",如果 meta 自己就写错, + 这条门禁会忠实地把错的值判成一致(真机定值核对在台架大窗里做); +- Q10 → 显示小数位的**换算函数本身**由主机单测与像素断言覆盖,不在本脚本内(脚本重算一遍 + 换算是为了独立见证,不复用 C 侧代码)。 + +## 7. 豁免与基线 + +无存量豁免、无基线文件、无 `--write`。定值项数改变(`PS_ID_COUNT` ≠ 40)时必须同步:脚本里的 +期望行数与 id 上界、`ui_data.h` 的 `UI_DATA_SET_N`、以及 `docs/UI设计/UI_数据供给设计说明.md` +的参数通道说明。 + +一条**已修正的原口径**记在这里免得被"改回去":四个返回系数按 2 位小数显示。原方案文档写 3 位, +而 Q10 存不下 0.95(0.95×1024 截成 972 → 真存 0.94921875),3 位会多打一位根本不存在的数字; +§3.2.2 的表已同步改成 2 位并加 ⚠ 指向代码注释。 + +## 8. 改这个门禁本身 + +- 行表写法一改(新增字段、换初始化风格),先跑脚本:退出码 2 就是"脚本要先跟上"; +- 新增校验项时同步新增一条"如果这行解析不到就 FAIL"的期望,不要让新校验继承 §6.1 的静默; +- 想把它扩成通用"表 vs 元数据"检查器之前,先数一下真有几个这样的对象(现在只有一个)。 + +## 9. 变更记录 + +| 日期 | 变更 | +|---|---| +| 2026-10-25 | 首版(道E task-20 追加批;脚本与实现来自 task-15 批 3)。素材:`\w+` 静默跳行、CP936 半路崩、满屏横杠与 4 字符位两条像素教训 |