新增代码规范检查钩子(规范V1.0 可机械校验项)并修复存量违规
新增 scripts/check_coding_standard.py + coding-standard 钩子(S1~S9: 头文件保护宏、文件头注释、宏大写、_t 后缀、switch default、 禁 strcpy/sprintf/goto/Tab、函数注释) 修复存量违规:NET_* 字节序宏大写化、net_fd_set 等类型加 _t 后缀、 net_inet_ntoa 的 sprintf 改 snprintf、补 4 处函数注释与 ui_task.c 文件头 docs/代码规范/pre-commit使用说明.md:新增 2.8 节,修正失效文档路径
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
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
|
||||
S7 (6.3.3) 禁止使用 goto
|
||||
S8 (4.1.1) 禁止 Tab 缩进(使用 4 个空格)
|
||||
S9 (5.3) 函数定义必须有函数注释(含「函数功能」)
|
||||
|
||||
语义类规则(函数长度、单一职责、错误处理、递归等)无法机械可靠判定,不在此脚本范围。
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
|
||||
# 顶层函数定义行:类型 名称 ( 参数 ),可选同行左大括号
|
||||
FUNC_DEF_RE = re.compile(
|
||||
r'^[A-Za-z_][A-Za-z0-9_\s\*]*\s+[a-z_][a-z0-9_]*\s*\([^;{]*\)\s*\{?\s*$'
|
||||
)
|
||||
CTRL_RE = re.compile(r'^\s*(if|for|while|switch|return|do|else)\b')
|
||||
|
||||
|
||||
def check_file(path):
|
||||
with open(path, 'r', encoding='utf-8', errors='replace') as f:
|
||||
text = f.read()
|
||||
lines = text.splitlines()
|
||||
v = [] # (行号或'-', 规则, 说明)
|
||||
|
||||
# S1 (2.3.1) .h 头文件保护宏
|
||||
if path.endswith('.h') and \
|
||||
not re.search(r'#\s*ifndef\s+__[A-Z0-9_]+_H\b', text):
|
||||
v.append(('-', 'S1 2.3.1', '缺少 __XXX_H 格式的头文件保护宏'))
|
||||
|
||||
# S2 (5.2) 文件头注释
|
||||
head = '\n'.join(lines[:60])
|
||||
if '模块名称' not in head and '作者' not in head:
|
||||
v.append(('-', 'S2 5.2', '缺少文件头注释(模块名称 / 作者)'))
|
||||
|
||||
# S3 (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)))
|
||||
|
||||
# S4 (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)))
|
||||
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)))
|
||||
|
||||
# S5 (6.3.2) switch 必须有 default
|
||||
for m in re.finditer(r'\bswitch\s*\(', text):
|
||||
i = text.find('{', m.start())
|
||||
if i == -1:
|
||||
continue
|
||||
j, d = i, 0
|
||||
while j < len(text):
|
||||
if text[j] == '{':
|
||||
d += 1
|
||||
elif text[j] == '}':
|
||||
d -= 1
|
||||
if d == 0:
|
||||
break
|
||||
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 分支'))
|
||||
|
||||
# S6 (7.5) 不安全字符串函数
|
||||
for m in re.finditer(r'\b(strcpy|strcat|sprintf|gets)\s*\(', text):
|
||||
ln = text[:m.start()].count('\n') + 1
|
||||
v.append((ln, 'S6 7.5', '不安全字符串函数: %s (改用 strncpy/strncat/snprintf)'
|
||||
% m.group(1)))
|
||||
|
||||
# S7 (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'))
|
||||
|
||||
# S8 (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 个空格)'))
|
||||
break
|
||||
|
||||
# S9 (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'
|
||||
% line.strip()[:50]))
|
||||
|
||||
return v
|
||||
|
||||
|
||||
def main():
|
||||
failed = False
|
||||
for path in sys.argv[1:]:
|
||||
try:
|
||||
v = check_file(path)
|
||||
except OSError as e:
|
||||
print('cannot read %s: %s' % (path, e))
|
||||
failed = True
|
||||
continue
|
||||
if v:
|
||||
failed = True
|
||||
print('%s: %d 处规范违规' % (path, len(v)))
|
||||
for ln, rule, msg in v:
|
||||
print(' L%-4s [%s] %s' % (ln, rule, msg))
|
||||
if failed:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user