forked from pfqgauxfb/code-analysis
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
102 lines
3.4 KiB
102 lines
3.4 KiB
"""
|
|
Cppcheck报告解析器模块
|
|
"""
|
|
import re
|
|
import xml.etree.ElementTree as ET
|
|
from pathlib import Path
|
|
from typing import List
|
|
|
|
from .models import CppcheckIssue, IssueLocation
|
|
|
|
|
|
def parse_cppcheck_xml(xml_path: Path) -> List[CppcheckIssue]:
|
|
"""解析cppcheck XML报告"""
|
|
tree = ET.parse(xml_path)
|
|
root = tree.getroot()
|
|
|
|
issues: List[CppcheckIssue] = []
|
|
for error in root.findall("errors/error"):
|
|
issue_id = error.get("id") or "unknown"
|
|
severity = error.get("severity") or "unknown"
|
|
msg = error.get("msg") or (error.get("verbose") or "")
|
|
|
|
locations: List[IssueLocation] = []
|
|
for loc in error.findall("location"):
|
|
file_attr = loc.get("file")
|
|
line_attr = loc.get("line")
|
|
if not file_attr:
|
|
continue
|
|
file_path = Path(file_attr).expanduser().resolve()
|
|
line = int(line_attr) if line_attr and line_attr.isdigit() else None
|
|
locations.append(IssueLocation(file_path=file_path, line=line))
|
|
|
|
if not locations:
|
|
# 有些 error 只有一层 <error file= line=>
|
|
file_attr = error.get("file")
|
|
line_attr = error.get("line")
|
|
if file_attr:
|
|
locations.append(
|
|
IssueLocation(
|
|
file_path=Path(file_attr).expanduser().resolve(),
|
|
line=int(line_attr) if line_attr and str(line_attr).isdigit() else None,
|
|
)
|
|
)
|
|
|
|
issues.append(CppcheckIssue(id=issue_id, severity=severity, message=msg, locations=locations))
|
|
|
|
return issues
|
|
|
|
|
|
def parse_cppcheck_text(text_path: Path) -> List[CppcheckIssue]:
|
|
"""解析 cppcheck 文本日志(常见行格式:
|
|
/path/file.c:111:13: warning: Message [ruleId]
|
|
也包含 note:/information:/error: 等等级
|
|
"""
|
|
content = text_path.read_text(encoding="utf-8", errors="replace")
|
|
issues: List[CppcheckIssue] = []
|
|
|
|
# 常见匹配:路径:行:列: 等级: 消息 [规则]
|
|
pattern = re.compile(r"^(?P<file>[^:\n]+?):(?P<line>\d+)(?::\d+)?\:\s*(?P<sev>warning|error|information|note)\:\s*(?P<msg>.*?)(?:\s*\[(?P<id>[^\]]+)\])?\s*$",
|
|
re.IGNORECASE)
|
|
|
|
for raw_line in content.splitlines():
|
|
m = pattern.match(raw_line.strip())
|
|
if not m:
|
|
continue
|
|
file_path = Path(m.group("file")).expanduser()
|
|
try:
|
|
file_path = file_path.resolve()
|
|
except Exception:
|
|
pass
|
|
line_num = int(m.group("line")) if m.group("line") else None
|
|
sev = (m.group("sev") or "").lower()
|
|
msg = m.group("msg") or ""
|
|
rid = m.group("id") or "unknown"
|
|
issues.append(
|
|
CppcheckIssue(
|
|
id=rid,
|
|
severity=sev,
|
|
message=msg,
|
|
locations=[IssueLocation(file_path=file_path, line=line_num)],
|
|
)
|
|
)
|
|
|
|
return issues
|
|
|
|
|
|
def read_code_snippet(file_path: Path, center_line: Optional[int], context: int = 30) -> str:
|
|
"""读取代码片段"""
|
|
try:
|
|
lines = file_path.read_text(encoding="utf-8", errors="replace").splitlines()
|
|
except Exception:
|
|
return ""
|
|
|
|
if center_line is None:
|
|
start = 0
|
|
end = min(len(lines), 400)
|
|
else:
|
|
start = max(0, center_line - 1 - context)
|
|
end = min(len(lines), center_line - 1 + context)
|
|
snippet = "\n".join(lines[start:end])
|
|
return snippet
|