feat(门禁): 编号门禁 id-check 落地(36→37 钩子)+ 批 1 编号单词化改名
新增门禁本体 - scripts/check_ids.py:实现 docs/编号规范.md §8 八项校验;默认 warn-only, 唯一硬拦项是「可判定比例跌破基线」(防回退的本体) - scripts/id_registry.json:66 个前缀族,逐项来自 §4 总表 + 全库实测游标, 偏差一律写进 cursor_note,不凭记忆编造 - scripts/id_aliases.json:旧号 → 新号 / 作废标记(照搬 section_aliases.json 范式) - 顺带收编 check_test_traceability.py:正则补齐大写后缀与无模块码形态, 比对范围扩到 test/host 与 7 篇「单元测试说明.md」,另加 --board-only 回退旧口径 批 1 改名(用户授权解禁「不改写存量号」) - 代码规范条款 S1~S19 → CODE-RULE-nn:78 处 / 2 文件 - UI 像素回归状态 S0~S7 → MENU-STATE-nn:61 处 / 3 文件(冻结在 07) - 两族属主登记处 = 脚本自身的规则表 / STATES 表,文档只作引用方 - 新增 scripts/rename_ids.py(§7.3 规划脚本):映射表驱动、显式 scope、默认干跑、 自动写 scoped 别名,跳过 [示例]/[计划] 行与变更记录表,按原字节读写保 BOM 行尾 - 已应用映射表存档 scripts/rename_maps/batch1.json 作审计轨迹 有牙实测(证明门禁不是空转) - 注入 Z-99 / 裸形 / 悬空 / 越游标 / 未紧邻 / 跨文档重复定义 六例, 分别被校验 1/2/3/5/6/4 点名,--strict 退出码 1;临时档删除后工作树干净 验证与同步 - 可判定比例基线 82.3% → 82.7%(已 --update-baseline 入档);错误级 1312 → 1291 - ci_local.py 全绿(L0 + L0.5 共 1104 断言);ui-golden Passed; check_doc_links.py --all 无法判定 0 - 未改任何 C/H 源文件,故本次不需 @build - 文档:AGENTS.md 与 pre-commit使用说明.md 计数 36→37 并新增 §2.25 详解; 编号规范 §4.1 补两行、§8.1 落地记录、§8.2 规范与仓库偏差、§8.3 迁移批次记录
This commit is contained in:
@@ -5,24 +5,24 @@ C 语言代码规范检查(《嵌入式C语言代码规范_V1.0》可机械化
|
||||
由 pre-commit 调用:python scripts/check_coding_standard.py <file...>,发现违规退出码 1。
|
||||
|
||||
检查项(括号内为规范条款号):
|
||||
S1 (2.3.1) .h 文件必须有 XXX_H 格式的头文件保护宏(禁止 __ 双下划线前缀)
|
||||
S2 (5.2) 文件开头必须有文件头注释(含 模块名称 / 作者)
|
||||
S3 (3.4.1) #define 宏名必须全大写 + 下划线 + 数字
|
||||
S4 (3.5.1) typedef 类型名必须以 _t 结尾
|
||||
S5 (6.3.2) switch 必须包含 default 分支
|
||||
S6 (7.5) 禁用不安全字符串/输入函数 strcpy/strcat/sprintf/gets/scanf/strtok
|
||||
S7 (6.3.3) 禁止使用 goto
|
||||
S8 (4.1.1) 禁止 Tab 缩进(使用 4 个空格)
|
||||
S9 (5.3) 函数定义必须有函数注释(含「函数功能」)
|
||||
S10 (3.3.1) 函数名必须全小写(HAL_ 厂商 API 回调名豁免)
|
||||
S11 (2.3.5) 禁止相对/绝对路径 include
|
||||
S12 (7.6) 禁止可变参数函数(...)
|
||||
S13 (7.7) 禁止递归函数
|
||||
S15 (6.1.4) 文件作用域指针变量必须初始化为 NULL
|
||||
S16 (3.2.2) 文件作用域非 static 全局变量必须 g_ 前缀
|
||||
S17 (2.2) 非 static 函数必须在配对 .h 中声明(HAL_ 豁免;无配对头则跳过)
|
||||
S18 (6.3.1) 禁止 Yoda 条件(字面量/常量必须在运算符右侧)
|
||||
S19 (4.4.2) 函数定义左大括号必须与右括号同行
|
||||
CODE-RULE-01 (2.3.1) .h 文件必须有 XXX_H 格式的头文件保护宏(禁止 __ 双下划线前缀)
|
||||
CODE-RULE-02 (5.2) 文件开头必须有文件头注释(含 模块名称 / 作者)
|
||||
CODE-RULE-03 (3.4.1) #define 宏名必须全大写 + 下划线 + 数字
|
||||
CODE-RULE-04 (3.5.1) typedef 类型名必须以 _t 结尾
|
||||
CODE-RULE-05 (6.3.2) switch 必须包含 default 分支
|
||||
CODE-RULE-06 (7.5) 禁用不安全字符串/输入函数 strcpy/strcat/sprintf/gets/scanf/strtok
|
||||
CODE-RULE-07 (6.3.3) 禁止使用 goto
|
||||
CODE-RULE-08 (4.1.1) 禁止 Tab 缩进(使用 4 个空格)
|
||||
CODE-RULE-09 (5.3) 函数定义必须有函数注释(含「函数功能」)
|
||||
CODE-RULE-10 (3.3.1) 函数名必须全小写(HAL_ 厂商 API 回调名豁免)
|
||||
CODE-RULE-11 (2.3.5) 禁止相对/绝对路径 include
|
||||
CODE-RULE-12 (7.6) 禁止可变参数函数(...)
|
||||
CODE-RULE-13 (7.7) 禁止递归函数
|
||||
CODE-RULE-15 (6.1.4) 文件作用域指针变量必须初始化为 NULL
|
||||
CODE-RULE-16 (3.2.2) 文件作用域非 static 全局变量必须 g_ 前缀
|
||||
CODE-RULE-17 (2.2) 非 static 函数必须在配对 .h 中声明(HAL_ 豁免;无配对头则跳过)
|
||||
CODE-RULE-18 (6.3.1) 禁止 Yoda 条件(字面量/常量必须在运算符右侧)
|
||||
CODE-RULE-19 (4.4.2) 函数定义左大括号必须与右括号同行
|
||||
|
||||
语义类规则(函数长度、单一职责、错误处理等)无法机械可靠判定,不在此脚本范围。
|
||||
"""
|
||||
@@ -114,36 +114,36 @@ def check_file(path):
|
||||
lines = text.splitlines()
|
||||
v = [] # (行号或'-', 规则, 说明)
|
||||
|
||||
# S1 (2.3.1) .h 头文件保护宏
|
||||
# CODE-RULE-01 (2.3.1) .h 头文件保护宏
|
||||
if path.endswith('.h') and \
|
||||
not re.search(r'#\s*ifndef\s+(?<!_)[A-Z][A-Z0-9_]*_H\b', text):
|
||||
v.append(('-', 'S1 2.3.1', '缺少 XXX_H 格式的头文件保护宏(禁止 __ 前缀)'))
|
||||
v.append(('-', 'CODE-RULE-01 2.3.1', '缺少 XXX_H 格式的头文件保护宏(禁止 __ 前缀)'))
|
||||
|
||||
# S2 (5.2) 文件头注释
|
||||
# CODE-RULE-02 (5.2) 文件头注释
|
||||
head = '\n'.join(lines[:60])
|
||||
if '模块名称' not in head and '作者' not in head:
|
||||
v.append(('-', 'S2 5.2', '缺少文件头注释(模块名称 / 作者)'))
|
||||
v.append(('-', 'CODE-RULE-02 5.2', '缺少文件头注释(模块名称 / 作者)'))
|
||||
|
||||
# S3 (3.4.1) 宏名全大写
|
||||
# CODE-RULE-03 (3.4.1) 宏名全大写
|
||||
for m in re.finditer(r'^\s*#\s*define\s+([A-Za-z_][A-Za-z0-9_]*)', text, re.M):
|
||||
if re.search(r'[a-z]', m.group(1)):
|
||||
ln = text[:m.start()].count('\n') + 1
|
||||
v.append((ln, 'S3 3.4.1', '宏名须全大写+下划线: %s' % m.group(1)))
|
||||
v.append((ln, 'CODE-RULE-03 3.4.1', '宏名须全大写+下划线: %s' % m.group(1)))
|
||||
|
||||
# S4 (3.5.1) typedef 类型名 _t 后缀
|
||||
# CODE-RULE-04 (3.5.1) typedef 类型名 _t 后缀
|
||||
for m in re.finditer(r'typedef\s+(?:struct|enum|union)\s*\{.*?\}\s'
|
||||
r'([A-Za-z_][A-Za-z0-9_]*)\s*;', text, re.S):
|
||||
if not m.group(1).endswith('_t'):
|
||||
ln = text[:m.start()].count('\n') + 1
|
||||
v.append((ln, 'S4 3.5.1', 'typedef 类型名须以 _t 结尾: %s' % m.group(1)))
|
||||
v.append((ln, 'CODE-RULE-04 3.5.1', 'typedef 类型名须以 _t 结尾: %s' % m.group(1)))
|
||||
for m in re.finditer(r'typedef\s+(?:unsigned\s+|signed\s+)?'
|
||||
r'(?:char|short|int|long|float|double)\s+\*?\s*'
|
||||
r'([A-Za-z_][A-Za-z0-9_]*)\s*;', text):
|
||||
if not m.group(1).endswith('_t'):
|
||||
ln = text[:m.start()].count('\n') + 1
|
||||
v.append((ln, 'S4 3.5.1', 'typedef 类型名须以 _t 结尾: %s' % m.group(1)))
|
||||
v.append((ln, 'CODE-RULE-04 3.5.1', 'typedef 类型名须以 _t 结尾: %s' % m.group(1)))
|
||||
|
||||
# S5 (6.3.2) switch 必须有 default
|
||||
# CODE-RULE-05 (6.3.2) switch 必须有 default
|
||||
for m in re.finditer(r'\bswitch\s*\(', text):
|
||||
i = text.find('{', m.start())
|
||||
if i == -1:
|
||||
@@ -159,45 +159,45 @@ def check_file(path):
|
||||
j += 1
|
||||
if not re.search(r'\bdefault\s*:', text[i:j + 1]):
|
||||
ln = text[:m.start()].count('\n') + 1
|
||||
v.append((ln, 'S5 6.3.2', 'switch 缺少 default 分支'))
|
||||
v.append((ln, 'CODE-RULE-05 6.3.2', 'switch 缺少 default 分支'))
|
||||
|
||||
# S6 (7.5) 不安全字符串/输入函数
|
||||
# CODE-RULE-06 (7.5) 不安全字符串/输入函数
|
||||
for m in re.finditer(r'\b(strcpy|strcat|sprintf|gets|scanf|strtok)\s*\(', text):
|
||||
ln = text[:m.start()].count('\n') + 1
|
||||
v.append((ln, 'S6 7.5', '不安全字符串/输入函数: %s (改用安全版本)'
|
||||
v.append((ln, 'CODE-RULE-06 7.5', '不安全字符串/输入函数: %s (改用安全版本)'
|
||||
% m.group(1)))
|
||||
|
||||
# S7 (6.3.3) 禁止 goto
|
||||
# CODE-RULE-07 (6.3.3) 禁止 goto
|
||||
m = re.search(r'\bgoto\s+[A-Za-z_]\w*', text)
|
||||
if m:
|
||||
ln = text[:m.start()].count('\n') + 1
|
||||
v.append((ln, 'S7 6.3.3', '禁止使用 goto'))
|
||||
v.append((ln, 'CODE-RULE-07 6.3.3', '禁止使用 goto'))
|
||||
|
||||
# S8 (4.1.1) 禁止 Tab
|
||||
# CODE-RULE-08 (4.1.1) 禁止 Tab
|
||||
if '\t' in text:
|
||||
for idx, line in enumerate(lines, 1):
|
||||
if '\t' in line:
|
||||
v.append((idx, 'S8 4.1.1', '禁止 Tab 缩进(使用 4 个空格)'))
|
||||
v.append((idx, 'CODE-RULE-08 4.1.1', '禁止 Tab 缩进(使用 4 个空格)'))
|
||||
break
|
||||
|
||||
# S9 (5.3) 函数定义必须有函数注释
|
||||
# CODE-RULE-09 (5.3) 函数定义必须有函数注释
|
||||
for idx, line in enumerate(lines, 1):
|
||||
if (FUNC_DEF_RE.match(line) and '=' not in line and not CTRL_RE.match(line)):
|
||||
window = '\n'.join(lines[max(0, idx - 26):idx - 1])
|
||||
if '函数功能' not in window:
|
||||
v.append((idx, 'S9 5.3', '函数定义缺少函数注释(函数功能): %s'
|
||||
v.append((idx, 'CODE-RULE-09 5.3', '函数定义缺少函数注释(函数功能): %s'
|
||||
% line.strip()[:50]))
|
||||
|
||||
masked = _mask(text)
|
||||
mlines = masked.splitlines()
|
||||
defs = _fn_defs(masked)
|
||||
|
||||
# S10 (3.3.1) 函数名必须全小写(HAL_ 厂商 API 回调名豁免)
|
||||
# CODE-RULE-10 (3.3.1) 函数名必须全小写(HAL_ 厂商 API 回调名豁免)
|
||||
for name, ln, _body, _pre, _bl, _pl in defs:
|
||||
if re.search(r'[A-Z]', name) and not name.startswith('HAL_'):
|
||||
v.append((ln, 'S10 3.3.1', '函数名须全小写: %s' % name))
|
||||
v.append((ln, 'CODE-RULE-10 3.3.1', '函数名须全小写: %s' % name))
|
||||
|
||||
# S11 (2.3.5) 禁止相对/绝对路径 include(基于原始文本,字符串掩码会吃掉引号)
|
||||
# CODE-RULE-11 (2.3.5) 禁止相对/绝对路径 include(基于原始文本,字符串掩码会吃掉引号)
|
||||
# 规则本意 = 禁"用路径找文件":`#include "../../x.h"`、`#include "App/foo/x.h"`、
|
||||
# `#include <C:/x.h>`、`#include <..\..\x.h>`。
|
||||
# ⚠ 2026-10-11 细化:原实现把**任何含 `/` 的 include** 都判违规,于是
|
||||
@@ -211,19 +211,19 @@ def check_file(path):
|
||||
if form == '"' and '/' in inc:
|
||||
bad = True
|
||||
if bad:
|
||||
v.append((_line(text, m.start()), 'S11 2.3.5',
|
||||
v.append((_line(text, m.start()), 'CODE-RULE-11 2.3.5',
|
||||
'禁止相对/绝对路径 include: %s' % inc))
|
||||
|
||||
# S12 (7.6) 禁止可变参数函数
|
||||
# CODE-RULE-12 (7.6) 禁止可变参数函数
|
||||
for m in re.finditer(r'\(\s*[^();{}]*\.\.\.[^();{}]*\)\s*[;{]', masked):
|
||||
v.append((_line(masked, m.start()), 'S12 7.6', '禁止可变参数函数(...)'))
|
||||
v.append((_line(masked, m.start()), 'CODE-RULE-12 7.6', '禁止可变参数函数(...)'))
|
||||
|
||||
# S13 (7.7) 禁止递归函数
|
||||
# CODE-RULE-13 (7.7) 禁止递归函数
|
||||
for name, ln, body, _pre, _bl, _pl in defs:
|
||||
if re.search(r'\b%s\s*\(' % re.escape(name), body):
|
||||
v.append((ln, 'S13 7.7', '递归函数: %s' % name))
|
||||
v.append((ln, 'CODE-RULE-13 7.7', '递归函数: %s' % name))
|
||||
|
||||
# S15 (6.1.4) 文件作用域指针变量必须初始化为 NULL
|
||||
# CODE-RULE-15 (6.1.4) 文件作用域指针变量必须初始化为 NULL
|
||||
for idx, line in enumerate(mlines, 1):
|
||||
if not line or line[0] in ' \t':
|
||||
continue
|
||||
@@ -235,10 +235,10 @@ def check_file(path):
|
||||
continue
|
||||
if '*' not in line or '=' in line:
|
||||
continue
|
||||
v.append((idx, 'S15 6.1.4', '文件作用域指针变量须初始化为 NULL: %s'
|
||||
v.append((idx, 'CODE-RULE-15 6.1.4', '文件作用域指针变量须初始化为 NULL: %s'
|
||||
% line.strip()[:50]))
|
||||
|
||||
# S16 (3.2.2) 文件作用域非 static 全局变量必须 g_ 前缀
|
||||
# CODE-RULE-16 (3.2.2) 文件作用域非 static 全局变量必须 g_ 前缀
|
||||
for idx, line in enumerate(mlines, 1):
|
||||
if not line or line[0] in ' \t' or line[0] in '}]':
|
||||
continue
|
||||
@@ -253,10 +253,10 @@ def check_file(path):
|
||||
if not m2:
|
||||
continue
|
||||
if not m2.group(1).startswith('g_'):
|
||||
v.append((idx, 'S16 3.2.2', '非 static 全局变量须 g_ 前缀: %s'
|
||||
v.append((idx, 'CODE-RULE-16 3.2.2', '非 static 全局变量须 g_ 前缀: %s'
|
||||
% line.strip()[:50]))
|
||||
|
||||
# S17 (2.2) 非 static 函数必须在配对头文件声明(HAL_ 豁免;无配对头则跳过)
|
||||
# CODE-RULE-17 (2.2) 非 static 函数必须在配对头文件声明(HAL_ 豁免;无配对头则跳过)
|
||||
if path.endswith('.c'):
|
||||
hpath = path[:-2] + '.h'
|
||||
if os.path.exists(hpath):
|
||||
@@ -271,19 +271,19 @@ def check_file(path):
|
||||
continue
|
||||
if re.search(r'\b%s\s*\(' % re.escape(name), htext):
|
||||
continue
|
||||
v.append((ln, 'S17 2.2', '非 static 函数须在配对头文件声明: %s' % name))
|
||||
v.append((ln, 'CODE-RULE-17 2.2', '非 static 函数须在配对头文件声明: %s' % name))
|
||||
|
||||
# S18 (6.3.1) 禁止 Yoda 条件(常量在右)
|
||||
# CODE-RULE-18 (6.3.1) 禁止 Yoda 条件(常量在右)
|
||||
for m in re.finditer(r'\b(0[xX][0-9a-fA-F]+(?:[uUlL]+)?|\d+(?:[uUlL]+)?'
|
||||
r'|NULL|true|false)\s*(==|!=|<=|>=|<|>)\s+[A-Za-z_]',
|
||||
masked):
|
||||
v.append((_line(masked, m.start()), 'S18 6.3.1',
|
||||
v.append((_line(masked, m.start()), 'CODE-RULE-18 6.3.1',
|
||||
'Yoda 条件(常量应在右): %s' % ' '.join(m.group(0).split())))
|
||||
|
||||
# S19 (4.4.2) 函数定义左大括号必须与右括号同行
|
||||
# CODE-RULE-19 (4.4.2) 函数定义左大括号必须与右括号同行
|
||||
for name, ln, _body, _pre, bl, pl in defs:
|
||||
if bl != pl:
|
||||
v.append((ln, 'S19 4.4.2', '函数定义左大括号须与右括号同行: %s' % name))
|
||||
v.append((ln, 'CODE-RULE-19 4.4.2', '函数定义左大括号须与右括号同行: %s' % name))
|
||||
|
||||
return v
|
||||
|
||||
|
||||
Reference in New Issue
Block a user