docs(门禁说明): 补 bom-guard 与 setview-check 两篇详解
票面: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),与本笔无关,等台账回填。
This commit is contained in:
+4
-2
@@ -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 生成文件豁免) | 全部 | 已写 |
|
||||
|
||||
@@ -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 提交正文留存 |
|
||||
@@ -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 字符位两条像素教训 |
|
||||
Reference in New Issue
Block a user