新增S10~S19代码规范检查并统一time_sync_task命名
check_coding_standard.py 新增 9 条机械检查(S14 并入 S6): - S10 (3.3.1) 函数名全小写(HAL_ 厂商回调豁免) - 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 函数须在配对头文件声明 - S18 (6.3.1) 禁止 Yoda 条件(常量在右) - S19 (4.4.2) 函数定义左大括号与右括号同行 S6 词表扩充 scanf/strtok timeSyncTask 重命名为 time_sync_task(3 处代码引用) 更新 pre-commit 使用说明规则表
This commit is contained in:
@@ -10,13 +10,23 @@ C 语言代码规范检查(《嵌入式C语言代码规范(V1.0)》可机
|
||||
S3 (3.4.1) #define 宏名必须全大写 + 下划线 + 数字
|
||||
S4 (3.5.1) typedef 类型名必须以 _t 结尾
|
||||
S5 (6.3.2) switch 必须包含 default 分支
|
||||
S6 (7.5) 禁用不安全字符串函数 strcpy/strcat/sprintf/gets
|
||||
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) 函数定义左大括号必须与右括号同行
|
||||
|
||||
语义类规则(函数长度、单一职责、错误处理、递归等)无法机械可靠判定,不在此脚本范围。
|
||||
语义类规则(函数长度、单一职责、错误处理等)无法机械可靠判定,不在此脚本范围。
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
@@ -26,6 +36,77 @@ FUNC_DEF_RE = re.compile(
|
||||
)
|
||||
CTRL_RE = re.compile(r'^\s*(if|for|while|switch|return|do|else)\b')
|
||||
|
||||
C_KEYWORDS = {'if', 'for', 'while', 'switch', 'do', 'else', 'return',
|
||||
'sizeof', 'struct', 'union', 'enum'}
|
||||
|
||||
NAME_PAREN_RE = re.compile(r'([A-Za-z_][A-Za-z0-9_]*)\s*\(')
|
||||
|
||||
|
||||
def _mask(text):
|
||||
"""注释与字符串字面量替换为等长空白(保留换行,行号不变),防止误报。
|
||||
|
||||
单次组合正则扫描,避免字符串内的 // 或注释内的引号造成顺序误掩。
|
||||
"""
|
||||
def _repl(m):
|
||||
return ''.join(c if c == '\n' else ' ' for c in m.group(0))
|
||||
pat = re.compile(r'"(\\.|[^"\\\n])*"|/\*.*?\*/|//[^\n]*', re.S)
|
||||
return pat.sub(_repl, text)
|
||||
|
||||
|
||||
def _line(text, pos):
|
||||
return text[:pos].count('\n') + 1
|
||||
|
||||
|
||||
def _fn_defs(masked):
|
||||
"""线性扫描找函数定义 name(...) {,返回 [(name, name行, body, pre, 大括号行, 右括号行)]。
|
||||
|
||||
body 为定义大括号内文本,pre 为名称前 200 字符(用于判定 static)。
|
||||
"""
|
||||
out = []
|
||||
n = len(masked)
|
||||
for m in NAME_PAREN_RE.finditer(masked):
|
||||
name = m.group(1)
|
||||
if name in C_KEYWORDS or name == 'defined':
|
||||
continue
|
||||
ls = masked.rfind('\n', 0, m.start()) + 1
|
||||
if masked[ls:].lstrip()[:1] == '#':
|
||||
continue
|
||||
i = m.end() - 1 # '(' 下标
|
||||
depth, j = 0, i
|
||||
ok = False
|
||||
while j < n:
|
||||
c = masked[j]
|
||||
if c == '(':
|
||||
depth += 1
|
||||
elif c == ')':
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
ok = True
|
||||
break
|
||||
elif c in '{};':
|
||||
break
|
||||
j += 1
|
||||
if not ok:
|
||||
continue
|
||||
k = j + 1
|
||||
while k < n and masked[k] in ' \t\r\n':
|
||||
k += 1
|
||||
if k >= n or masked[k] != '{':
|
||||
continue
|
||||
depth, b = 0, k
|
||||
while b < n:
|
||||
if masked[b] == '{':
|
||||
depth += 1
|
||||
elif masked[b] == '}':
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
break
|
||||
b += 1
|
||||
out.append((name, _line(masked, m.start()), masked[k + 1:b],
|
||||
masked[max(0, m.start() - 200):m.start()],
|
||||
_line(masked, k), _line(masked, j)))
|
||||
return out
|
||||
|
||||
|
||||
def check_file(path):
|
||||
with open(path, 'r', encoding='utf-8', errors='replace') as f:
|
||||
@@ -80,10 +161,10 @@ def check_file(path):
|
||||
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):
|
||||
# S6 (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 (改用 strncpy/strncat/snprintf)'
|
||||
v.append((ln, 'S6 7.5', '不安全字符串/输入函数: %s (改用安全版本)'
|
||||
% m.group(1)))
|
||||
|
||||
# S7 (6.3.3) 禁止 goto
|
||||
@@ -107,6 +188,93 @@ def check_file(path):
|
||||
v.append((idx, 'S9 5.3', '函数定义缺少函数注释(函数功能): %s'
|
||||
% line.strip()[:50]))
|
||||
|
||||
masked = _mask(text)
|
||||
mlines = masked.splitlines()
|
||||
defs = _fn_defs(masked)
|
||||
|
||||
# S10 (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))
|
||||
|
||||
# S11 (2.3.5) 禁止相对/绝对路径 include(基于原始文本,字符串掩码会吃掉引号)
|
||||
for m in re.finditer(r'^\s*#\s*include\s*[<"]([^">]+)[">]', text, re.M):
|
||||
inc = m.group(1)
|
||||
if '/' in inc or '\\' in inc or '..' in inc:
|
||||
v.append((_line(text, m.start()), 'S11 2.3.5',
|
||||
'禁止相对/绝对路径 include: %s' % inc))
|
||||
|
||||
# S12 (7.6) 禁止可变参数函数
|
||||
for m in re.finditer(r'\(\s*[^();{}]*\.\.\.[^();{}]*\)\s*[;{]', masked):
|
||||
v.append((_line(masked, m.start()), 'S12 7.6', '禁止可变参数函数(...)'))
|
||||
|
||||
# S13 (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))
|
||||
|
||||
# S15 (6.1.4) 文件作用域指针变量必须初始化为 NULL
|
||||
for idx, line in enumerate(mlines, 1):
|
||||
if not line or line[0] in ' \t':
|
||||
continue
|
||||
if not line.rstrip().endswith(';') or '(' in line or '#' in line:
|
||||
continue
|
||||
first = line.split(None, 1)[0]
|
||||
if first in ('extern', 'typedef', 'struct', 'enum', 'union', 'static',
|
||||
'return', 'if', 'for', 'while', 'switch', 'do', 'else'):
|
||||
continue
|
||||
if '*' not in line or '=' in line:
|
||||
continue
|
||||
v.append((idx, 'S15 6.1.4', '文件作用域指针变量须初始化为 NULL: %s'
|
||||
% line.strip()[:50]))
|
||||
|
||||
# S16 (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
|
||||
if not line.rstrip().endswith(';') or '(' in line or '#' in line:
|
||||
continue
|
||||
first = line.split(None, 1)[0]
|
||||
if first in ('extern', 'typedef', 'struct', 'enum', 'union', 'static',
|
||||
'const', 'volatile', 'return', 'if', 'for', 'while',
|
||||
'switch', 'do', 'else'):
|
||||
continue
|
||||
m2 = re.search(r'([A-Za-z_][A-Za-z0-9_]*)\s*(\[[^\]]*\])?\s*;\s*$', line)
|
||||
if not m2:
|
||||
continue
|
||||
if not m2.group(1).startswith('g_'):
|
||||
v.append((idx, 'S16 3.2.2', '非 static 全局变量须 g_ 前缀: %s'
|
||||
% line.strip()[:50]))
|
||||
|
||||
# S17 (2.2) 非 static 函数必须在配对头文件声明(HAL_ 豁免;无配对头则跳过)
|
||||
if path.endswith('.c'):
|
||||
hpath = path[:-2] + '.h'
|
||||
if os.path.exists(hpath):
|
||||
with open(hpath, 'r', encoding='utf-8', errors='replace') as f:
|
||||
htext = f.read()
|
||||
for name, ln, _body, pre, _bl, _pl in defs:
|
||||
if name.startswith('HAL_'):
|
||||
continue
|
||||
seg = pre[pre.rfind(';') + 1:]
|
||||
seg = seg[seg.rfind('}') + 1:]
|
||||
if re.search(r'\bstatic\b', seg):
|
||||
continue
|
||||
if re.search(r'\b%s\s*\(' % re.escape(name), htext):
|
||||
continue
|
||||
v.append((ln, 'S17 2.2', '非 static 函数须在配对头文件声明: %s' % name))
|
||||
|
||||
# S18 (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',
|
||||
'Yoda 条件(常量应在右): %s' % ' '.join(m.group(0).split())))
|
||||
|
||||
# S19 (4.4.2) 函数定义左大括号必须与右括号同行
|
||||
for name, ln, _body, _pre, bl, pl in defs:
|
||||
if bl != pl:
|
||||
v.append((ln, 'S19 4.4.2', '函数定义左大括号须与右括号同行: %s' % name))
|
||||
|
||||
return v
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user