File indexing completed on 2025-01-18 09:10:43
0001
0002
0003 """
0004 This produces a structured normalized report json file based on warnings generated by another tool.
0005 Currently implemented is clang-tidy warnings.
0006 """
0007
0008 import argparse
0009 import re
0010 import os
0011 from fnmatch import fnmatch
0012 from pathlib import Path
0013
0014 from item import Item, ItemCollection
0015
0016
0017 def parse_clang_tidy_item(itemstr):
0018 try:
0019 m = re.match(
0020 r"(?P<file>[/.\-+\w]+):(?P<line>\d+):(?P<col>\d+): (?P<sev>.*?):(?P<msg>[\s\S]*)\[(?P<code>.*)\]\n(?P<info>[\s\S]*)",
0021 itemstr,
0022 )
0023
0024 lines = itemstr.split("\n")
0025
0026 item = Item(
0027 path=Path(m.group("file")),
0028 line=int(m.group("line")),
0029 col=int(m.group("col")),
0030
0031 message=m.group("msg").strip() + "\n" + "\n".join(lines[1:]),
0032 code=m.group("code"),
0033 severity=m.group("sev"),
0034 )
0035
0036
0037
0038 return item
0039 except:
0040 print("Failed parsing clang-tidy item:")
0041 print("-" * 20)
0042 print(itemstr)
0043 print("-" * 20)
0044 raise
0045
0046
0047 def parse_clang_tidy_output(output):
0048
0049 itemstr = output
0050 itemstr = re.sub(r"Enabled checks:\n[\S\s]+?\n\n", "", itemstr)
0051 itemstr = re.sub(r"clang-tidy-\d\.\d.*\n?", "", itemstr)
0052 itemstr = re.sub(r"clang-apply-.*", "", itemstr)
0053 itemstr = re.sub(r".*-header-filter.*", "", itemstr)
0054
0055 items = []
0056 prevstart = 0
0057
0058 matches = list(
0059 re.finditer(r"([\w/.\-+]+):(\d+):(\d+): (?:(?:warning)|(?:error)):", itemstr)
0060 )
0061 for idx, m in enumerate(matches):
0062
0063 start, end = m.span()
0064 if idx > 0:
0065 item = itemstr[prevstart:start]
0066 items.append(item)
0067 if idx + 1 == len(matches):
0068 item = itemstr[start:]
0069 items.append(item)
0070 prevstart = start
0071
0072 items = set(map(parse_clang_tidy_item, sorted(items)))
0073
0074 return items
0075
0076
0077 def main():
0078 p = argparse.ArgumentParser(description=__doc__)
0079 p.add_argument("inputfile", help="The input file containing the warnings")
0080 p.add_argument(
0081 "output", default="codereport_clang_tidy.json", help="The resulting JSON file"
0082 )
0083 p.add_argument(
0084 "--exclude",
0085 "-e",
0086 action="append",
0087 default=[],
0088 help="Exclude files that match any of these patterns",
0089 )
0090 p.add_argument(
0091 "--filter",
0092 action="append",
0093 default=[],
0094 help="Only include files that match any of these patterns",
0095 )
0096 p.add_argument(
0097 "--ignore",
0098 action="append",
0099 default=[],
0100 help="Ignore items with codes matching any of these patterns",
0101 )
0102 p.add_argument("--cwd", type=Path)
0103 p.add_argument("--strip-common", action="store_true")
0104
0105 args = p.parse_args()
0106
0107 with open(args.inputfile, "r", encoding="utf-8") as f:
0108 inputstr = f.read()
0109 items = parse_clang_tidy_output(inputstr)
0110
0111 def select(item):
0112 accept = True
0113 if len(args.filter) > 0:
0114 accept = accept and all(fnmatch(item.path, e) for e in args.filter)
0115
0116 accept = accept and not any(fnmatch(item.path, e) for e in args.exclude)
0117
0118 accept = accept and not any(fnmatch(item.code, i) for i in args.ignore)
0119
0120 return accept
0121
0122 items = list(filter(select, items))
0123
0124 if args.cwd:
0125 for item in items:
0126 item.path = (args.cwd / item.path).resolve()
0127
0128 if args.strip_common:
0129 prefix = Path(os.path.commonprefix([i.path for i in items]))
0130
0131 def subpath(m):
0132 path, line, col = m.groups()
0133 path = Path(path).resolve().relative_to(prefix)
0134 return f"{path}:{line}:{col}:"
0135
0136 for item in items:
0137 item.path = item.path.relative_to(prefix)
0138
0139 item.message = re.sub(r"([\w/.\-+]+):(\d+):(\d+):", subpath, item.message)
0140
0141 print("Write to", args.output)
0142 with open(args.output, "w+") as jf:
0143 jf.write(ItemCollection(root=items).model_dump_json(indent=2))
0144
0145
0146 if "__main__" == __name__:
0147 main()