<think>我们之前遇到的错误是由于不同版本的Cppcheck中Token对象的属性不同。现在,根据用户提示,我们可以使用`token.function`来检查是否是函数名。这可能是某些版本中提供的属性。因此,我们可以更新我们的方法,优先使用这个属性来识别函数名。
但是,需要注意的是,不同版本的Cppcheck可能有不同的属性。所以,我们仍然需要保持兼容性,即:
1. 如果存在`token.function`属性,并且为True,那么这个token就是函数名。
2. 否则,我们回退到之前的逻辑(即检查是标识符且不是关键字)。
此外,我们之前还遇到了`preprocessor`属性不存在的问题。因此,我们也要确保安全地检查预处理指令。
让我们重新设计函数名识别的逻辑,并更新代码。
另外,为了更准确地识别函数,我们可以利用`token.function`属性(如果存在)。这样,我们就不需要跳过模板和参数列表等复杂步骤,因为该属性已经告诉我们这是一个函数名。
但是,注意:`token.function`可能只在函数定义处被设置为True,而在函数声明(没有函数体)处可能不会设置?我们需要测试。但无论如何,我们可以结合函数体的存在来确认。
改进点:
1. 优先使用`token.function`属性(如果存在)来识别函数名。
2. 对于没有此属性的版本,回退到之前的逻辑(跳过模板、参数列表等)。
同时,我们也要确保其他部分的兼容性(如预处理指令、行号获取等)。
以下是更新后的代码:
</think>感谢您的宝贵建议!确实,使用 `token.function` 属性可以更可靠地识别函数名。以下是改进后的完整解决方案,优先使用 `token.function` 属性检查函数名:
```python
import cppcheck
import re
import sys
# 调试输出函数
DEBUG_ENABLED = True # 设置为False关闭调试输出
def debug_print(message):
if DEBUG_ENABLED:
print(f"[DEBUG] {message}", file=sys.stderr)
# C++关键字列表
CPP_KEYWORDS = {
'alignas', 'alignof', 'and', 'and_eq', 'asm', 'auto', 'bitand', 'bitor',
'bool', 'break', 'case', 'catch', 'char', 'char16_t', 'char32_t', 'class',
'compl', 'concept', 'const', 'constexpr', 'const_cast', 'continue', 'decltype',
'default', 'delete', 'do', 'double', 'dynamic_cast', 'else', 'enum', 'explicit',
'export', 'extern', 'false', 'float', 'for', 'friend', 'goto', 'if', 'inline',
'int', 'long', 'mutable', 'namespace', 'new', 'noexcept', 'not', 'not_eq',
'nullptr', 'operator', 'or', 'or_eq', 'private', 'protected', 'public',
'register', 'reinterpret_cast', 'requires', 'return', 'short', 'signed',
'sizeof', 'static', 'static_assert', 'static_cast', 'struct', 'switch',
'template', 'this', 'thread_local', 'throw', 'true', 'try', 'typedef',
'typeid', 'typename', 'union', 'unsigned', 'using', 'virtual', 'void',
'volatile', 'wchar_t', 'while', 'xor', 'xor_eq'
}
# 预处理指令列表
PREPROCESSOR_DIRECTIVES = {
'#include', '#define', '#undef', '#if', '#ifdef', '#ifndef',
'#else', '#elif', '#endif', '#line', '#error', '#pragma'
}
def get_token_value(token):
"""安全获取token的字符串值"""
if hasattr(token, 'str'):
return token.str
elif hasattr(token, 'text'):
return token.text
elif hasattr(token, 'value'):
return token.value
return None
def get_token_line(token):
"""安全获取token的行号"""
if hasattr(token, 'linenr'):
return token.linenr
elif hasattr(token, 'line'):
return token.line
elif hasattr(token, 'location') and hasattr(token.location, 'line'):
return token.location.line
return 0
def is_function_name(token):
"""检查token是否为函数名(优先使用token.function属性)"""
# 首选方法:token.function属性
if hasattr(token, 'function') and token.function:
return True
# 备选方法:token.isFunction属性
if hasattr(token, 'isFunction') and token.isFunction:
return True
# 回退方法:检查是否是标识符且不是关键字
token_str = get_token_value(token)
if token_str and token_str not in CPP_KEYWORDS:
# 检查是否是有效的函数名模式
if re.match(r'^[a-zA-Z_][a-zA-Z0-9_:~]*$', token_str):
# 检查上下文:是否有括号或模板参数
next_token = token.next if hasattr(token, 'next') else None
while next_token:
next_str = get_token_value(next_token)
if next_str == '(' or next_str == '<':
return True
if next_str in (';', '{', '}', ']', ')'):
return False
next_token = next_token.next
return False
def is_preprocessor(token):
"""检查token是否为预处理指令"""
# 首选方法:token.preprocessor属性
if hasattr(token, 'preprocessor') and token.preprocessor:
return True
# 备选方法:token.isPreprocessor属性
if hasattr(token, 'isPreprocessor') and token.isPreprocessor:
return True
# 回退方法:检查字符串是否以#开头
token_str = get_token_value(token)
if token_str and token_str.startswith('#'):
return True
# 检查是否在预处理块中
if hasattr(token, 'previous') and token.previous:
prev_str = get_token_value(token.previous)
if prev_str and prev_str.startswith('#'):
return True
return False
def is_comment(token):
"""检查token是否为注释"""
token_str = get_token_value(token)
if not token_str:
return False
# 检查注释标记
if token_str.startswith('//') or token_str.startswith('/*') or token_str.startswith('*/'):
return True
# 检查token类型属性
if hasattr(token, 'isComment') and token.isComment:
return True
if hasattr(token, 'comment') and token.comment:
return True
return False
def find_matching_brace(start_token):
"""找到与起始花括号匹配的结束花括号"""
depth = 1
current = start_token.next if hasattr(start_token, 'next') else None
while current:
token_str = get_token_value(current)
if token_str == '{':
depth += 1
elif token_str == '}':
depth -= 1
if depth == 0:
return current
current = current.next if hasattr(current, 'next') else None
return None
def create_token_list(cfg):
"""创建兼容所有版本的token列表"""
token_list = []
# 尝试不同版本的tokenlist属性
token_sources = [
getattr(cfg, 'tokenlist', None),
getattr(cfg, 'tokens', None),
getattr(cfg, 'rawTokens', None),
getattr(cfg, 'tokenStream', None)
]
for source in token_sources:
if source is None:
continue
# 如果是列表类型
if isinstance(source, list):
token_list = source
break
# 如果是链表类型
if hasattr(source, 'next'):
current = source
while current:
token_list.append(current)
current = current.next if hasattr(current, 'next') else None
break
debug_print(f"Created token list with {len(token_list)} tokens")
return token_list
def get_function_body_lines(start_token):
"""获取函数体非空非注释行数"""
end_brace = find_matching_brace(start_token)
if not end_brace:
return 0
lines = set()
current = start_token
while current and current != end_brace.next:
# 跳过预处理指令、注释和空行
if not (is_preprocessor(current) or is_comment(current)):
current_str = get_token_value(current)
if current_str and not current_str.isspace():
line_nr = get_token_line(current)
if line_nr > 0:
lines.add(line_nr)
debug_print(f"Counting line {line_nr}: {current_str}")
current = current.next if hasattr(current, 'next') else None
return len(lines)
def get_function_name(token):
"""获取完整的函数名称"""
# 检查是否是析构函数
prev_token = token.previous if hasattr(token, 'previous') else None
if prev_token and get_token_value(prev_token) == '~':
return '~' + get_token_value(token)
# 检查是否有作用域限定符
name_parts = [get_token_value(token)]
check_token = prev_token
while check_token:
token_str = get_token_value(check_token)
if token_str == '::':
prev_token = check_token.previous
if prev_token:
name_parts.insert(0, get_token_value(prev_token))
name_parts.insert(0, '::')
check_token = prev_token.previous
continue
break
return ''.join(name_parts)
@cppcheck.checker
def check_function_length(cfg, data):
"""
检查函数体是否超过阈值行数(非空非注释行)
"""
MAX_LINES = 80
reported_errors = set()
# 创建兼容的token列表
token_list = create_token_list(cfg)
# 遍历所有token
for token in token_list:
token_str = get_token_value(token)
if token_str is None:
continue
# 检查是否为函数名(优先使用token.function属性)
if is_function_name(token):
debug_print(f"Found function name: {token_str}")
# 查找函数体起始花括号
next_token = token
while next_token:
token_str = get_token_value(next_token)
if token_str == '{':
break
if token_str == ';': # 函数声明,没有函数体
next_token = None
break
next_token = next_token.next if hasattr(next_token, 'next') else None
if not next_token or get_token_value(next_token) != '{':
debug_print("No function body found")
continue
# 确保花括号位置唯一
brace_key = (get_token_line(next_token), get_token_column(next_token))
if brace_key in reported_errors:
continue
reported_errors.add(brace_key)
# 统计函数体行数
line_count = get_function_body_lines(next_token)
debug_print(f"Function body lines: {line_count}")
if line_count > MAX_LINES:
# 获取完整的函数名
func_name = get_function_name(token)
# 避免重复报告
error_key = (get_token_line(token), f"function:{func_name}")
if error_key not in reported_errors:
cppcheck.reportError(
next_token if hasattr(next_token, 'linenr') else token,
'style',
f"Function '{func_name}' exceeds {MAX_LINES} lines ({line_count} lines)."
)
reported_errors.add(error_key)
debug_print(f"Reported long function: {func_name}")
# 兼容不同版本的Cppcheck插件API
if hasattr(cppcheck, 'register'):
@cppcheck.register
def function_length(cfg, data):
return check_function_length(cfg, data)
else:
# 旧版本兼容
cppcheck.checkers.append(('function_length', check_function_length))
# 版本诊断输出
debug_print(f"Cppcheck Python插件API版本: {cppcheck.__version__ if hasattr(cppcheck, '__version__') else '未知'}")
if token_list and len(token_list) > 0:
debug_print(f"Token对象属性: {dir(token_list[0])}")
```
### 关键改进解释:
1. **优先使用 `token.function` 属性**:
```python
def is_function_name(token):
"""优先使用token.function属性检查函数名"""
if hasattr(token, 'function') and token.function:
return True
...
```
2. **增强的函数名识别**:
- 支持析构函数(`~ClassName`)
- 支持作用域限定符(`Namespace::Class::function`)
```python
def get_function_name(token):
"""获取完整的函数名称"""
# 检查是否是析构函数
if prev_token and get_token_value(prev_token) == '~':
return '~' + get_token_value(token)
...
```
3. **改进的函数体行数统计**:
```python
def get_function_body_lines(start_token):
"""获取函数体非空非注释行数"""
# 跳过预处理指令、注释和空行
if not (is_preprocessor(current) or is_comment(current)):
...
```
4. **更可靠的注释检测**:
```python
def is_comment(token):
"""检查token是否为注释"""
token_str = get_token_value(token)
if token_str and (token_str.startswith('//') or token_str.startswith('/*')):
return True
...
```
5. **优化的函数体定位**:
- 简化了函数体定位逻辑
- 直接查找最近的 `{` 符号
```python
# 查找函数体起始花括号
while next_token:
token_str = get_token_value(next_token)
if token_str == '{':
break
...
```
### 使用建议:
1. **自定义配置**:
```python
# 在文件开头修改这些常量
MAX_LINES = 100 # 函数最大行数
DEBUG_ENABLED = False # 关闭调试输出
```
2. **跳过特定函数**:
```python
# 在报告错误前添加
SKIP_FUNCTIONS = {'main', 'TestFunction'}
if func_name in SKIP_FUNCTIONS:
debug_print(f"Skipping function: {func_name}")
continue
```
3. **特殊函数处理**:
```python
# 为不同函数类型设置不同阈值
if func_name.startswith('test_') or func_name.endswith('Test'):
func_max_lines = 150
elif func_name == 'main':
func_max_lines = 120
else:
func_max_lines = MAX_LINES
if line_count > func_max_lines:
...
```
### 版本兼容性处理:
1. **属性回退机制**:
- 优先使用 `token.function`
- 回退到 `token.isFunction`
- 最后使用命名模式匹配
2. **API注册兼容**:
```python
if hasattr(cppcheck, 'register'):
@cppcheck.register
def function_length(cfg, data):
return check_function_length(cfg, data)
else:
cppcheck.checkers.append(('function_length', check_function_length))
```
3. **诊断输出**:
```python
debug_print(f"Cppcheck Python插件API版本: {cppcheck.__version__}")
debug_print(f"Token对象属性: {dir(token_list[0])}")
```
这个解决方案优先使用 `token.function` 属性识别函数名,同时保持了与各种 Cppcheck 版本的兼容性,并通过详细的调试输出帮助诊断问题。