mirror of https://github.com/sysown/proxysql
lint: canonicalize clang-tidy diagnostic paths and filter to repo include/lib in normalize-clang-tidy.py
parent
f5d77bb095
commit
656d310996
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1 @@
|
||||
107605 warnings generated.
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,3 @@
|
||||
/usr/include/openssl/macros.h:139:0: error: #error "The requested API level higher than the configured API compatibility level" [preprocessorErrorDirective]
|
||||
# error "The requested API level higher than the configured API compatibility level"
|
||||
^
|
||||
@ -1,70 +1,135 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import yaml
|
||||
import os
|
||||
|
||||
"""
|
||||
Normalizer for clang-tidy outputs.
|
||||
normalize-clang-tidy.py
|
||||
|
||||
Normalize clang-tidy outputs (export-fixes YAML or textual stderr/stdout)
|
||||
and emit lines in the form:
|
||||
|
||||
Behavior:
|
||||
- If passed an export-fixes YAML (the old behavior), parse it and emit normalized lines.
|
||||
- If passed a textual clang-tidy stderr/stdout file (from running clang-tidy without export-fixes), parse those diagnostics as well.
|
||||
<absolute-canonical-file>:<line>: <check> - <message>
|
||||
|
||||
Emitted line format:
|
||||
<file>:<line>: <check> - <message>
|
||||
Only diagnostics whose canonical file path is under <repo_root>/include/ or
|
||||
<repo_root>/lib/ are emitted. This prevents diagnostics originating from
|
||||
deps/ (e.g., include/../deps/...) from slipping through.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import yaml
|
||||
import subprocess
|
||||
|
||||
|
||||
def get_repo_root():
|
||||
try:
|
||||
out = subprocess.check_output(["git", "rev-parse", "--show-toplevel"], stderr=subprocess.DEVNULL)
|
||||
return os.path.realpath(out.decode().strip())
|
||||
except Exception:
|
||||
return os.path.realpath(os.getcwd())
|
||||
|
||||
|
||||
if len(sys.argv) != 2:
|
||||
print("Usage: normalize-clang-tidy.py <export-fixes.yaml-or-raw-output>")
|
||||
sys.exit(2)
|
||||
|
||||
path = sys.argv[1]
|
||||
if not os.path.exists(path):
|
||||
# No diagnostics
|
||||
sys.exit(0)
|
||||
|
||||
content = open(path, 'r', errors='ignore').read()
|
||||
diagnostics = set()
|
||||
|
||||
# Try YAML first
|
||||
try:
|
||||
data = yaml.safe_load(content)
|
||||
if isinstance(data, dict) and 'Diagnostics' in data:
|
||||
for diag in data.get('Diagnostics', []):
|
||||
msg = diag.get('DiagnosticMessage', {})
|
||||
file = msg.get('FilePath', '<unknown>')
|
||||
offset = msg.get('FileOffset', 0)
|
||||
# Map offset to line if possible
|
||||
try:
|
||||
with open(file, 'rb') as fh:
|
||||
b = fh.read()
|
||||
line_no = b[:offset].count(b"\n") + 1
|
||||
except Exception:
|
||||
line_no = 0
|
||||
check = diag.get('CheckName', '')
|
||||
message = msg.get('Message', '').strip()
|
||||
# Only emit diagnostics that are within the repository include/ or lib/
|
||||
# paths to avoid noise from deps/ headers. This mirrors the header
|
||||
# filter behavior used when running clang-tidy.
|
||||
if '/include/' in file or '/lib/' in file:
|
||||
diagnostics.add(f"{file}:{line_no}: {check} - {message}")
|
||||
else:
|
||||
raise Exception("not yaml diagnostics")
|
||||
except Exception:
|
||||
# Fallback: parse clang-tidy textual output lines
|
||||
# Typical clang-tidy message format:
|
||||
# /path/to/file:123:45: warning: message [check-name]
|
||||
# We capture file, line, message and check-name
|
||||
for line in content.splitlines():
|
||||
m = re.match(r"(?P<file>[^:]+):(?P<line>\d+):(?P<col>\d+:)?\s*(?P<kind>warning|error|note):?\s*(?P<msg>.*)\s*\[(?P<check>[^\]]+)\]$", line)
|
||||
if m:
|
||||
file = m.group('file')
|
||||
def canonical_path(path, repo_root):
|
||||
if not path:
|
||||
return None
|
||||
# Ignore placeholders like <built-in> or <unknown>
|
||||
if path.startswith("<") and path.endswith(">"):
|
||||
return None
|
||||
if os.path.isabs(path):
|
||||
return os.path.realpath(path)
|
||||
# If relative, interpret relative to repo root
|
||||
return os.path.realpath(os.path.join(repo_root, path))
|
||||
|
||||
|
||||
def is_in_repo_include_or_lib(cpath, repo_root):
|
||||
if not cpath:
|
||||
return False
|
||||
inc = os.path.realpath(os.path.join(repo_root, "include"))
|
||||
lib = os.path.realpath(os.path.join(repo_root, "lib"))
|
||||
try:
|
||||
# os.path.commonpath raises ValueError if paths are on different drives
|
||||
common_inc = os.path.commonpath([cpath, inc])
|
||||
if common_inc == inc:
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
common_lib = os.path.commonpath([cpath, lib])
|
||||
if common_lib == lib:
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def offset_to_line(cpath, offset):
|
||||
try:
|
||||
with open(cpath, 'rb') as fh:
|
||||
data = fh.read()
|
||||
# offset is a byte offset; count newlines before it
|
||||
return data[:offset].count(b"\n") + 1
|
||||
except Exception:
|
||||
return 0
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
print("Usage: normalize-clang-tidy.py <export-fixes.yaml-or-raw-output>")
|
||||
return 2
|
||||
|
||||
path = sys.argv[1]
|
||||
if not os.path.exists(path):
|
||||
# Nothing to normalize
|
||||
return 0
|
||||
|
||||
repo_root = get_repo_root()
|
||||
content = open(path, 'r', errors='ignore').read()
|
||||
diagnostics = set()
|
||||
|
||||
# Try parsing as export-fixes YAML first
|
||||
try:
|
||||
data = yaml.safe_load(content)
|
||||
if isinstance(data, dict) and 'Diagnostics' in data:
|
||||
for diag in data.get('Diagnostics', []):
|
||||
msg = diag.get('DiagnosticMessage', {}) or {}
|
||||
raw_file = msg.get('FilePath')
|
||||
cpath = canonical_path(raw_file, repo_root)
|
||||
if not cpath:
|
||||
continue
|
||||
if not is_in_repo_include_or_lib(cpath, repo_root):
|
||||
continue
|
||||
offset = msg.get('FileOffset', None)
|
||||
if offset is None:
|
||||
line_no = msg.get('FileLine', 0) or 0
|
||||
else:
|
||||
line_no = offset_to_line(cpath, int(offset))
|
||||
check = diag.get('CheckName') or diag.get('DiagnosticName') or ''
|
||||
message = (msg.get('Message') or '').strip()
|
||||
diagnostics.add(f"{cpath}:{line_no}: {check} - {message}")
|
||||
else:
|
||||
# Not the expected YAML structure; fall back to textual parsing
|
||||
raise ValueError("not yaml diagnostics")
|
||||
except Exception:
|
||||
# Fallback: parse clang-tidy textual output lines
|
||||
# Typical clang-tidy message format:
|
||||
# /path/to/file:123:45: warning: message [check-name]
|
||||
pat = re.compile(r"(?P<file>[^:]+):(?P<line>\d+):(?P<col>\d+:)?\s*(?P<kind>warning|error|note):?\s*(?P<msg>.*)\s*\[(?P<check>[^\]]+)\]$")
|
||||
for line in content.splitlines():
|
||||
m = pat.match(line)
|
||||
if not m:
|
||||
continue
|
||||
raw_file = m.group('file')
|
||||
cpath = canonical_path(raw_file, repo_root)
|
||||
if not cpath:
|
||||
continue
|
||||
if not is_in_repo_include_or_lib(cpath, repo_root):
|
||||
continue
|
||||
line_no = m.group('line')
|
||||
check = m.group('check')
|
||||
message = m.group('msg').strip()
|
||||
diagnostics.add(f"{file}:{line_no}: {check} - {message}")
|
||||
diagnostics.add(f"{cpath}:{line_no}: {check} - {message}")
|
||||
|
||||
for l in sorted(diagnostics):
|
||||
print(l)
|
||||
|
||||
|
||||
for l in sorted(diagnostics):
|
||||
print(l)
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
||||
Loading…
Reference in new issue