import re
import sys
from pathlib import Path
+from typing import NamedTuple
+
+class FileWarnings(NamedTuple):
+ name: str
+ count: int
def extract_warnings_from_compiler_output_clang(
"""
# Regex to find warnings in the compiler output
clang_warning_regex = re.compile(
- r"(?P<file>.*):(?P<line>\d+):(?P<column>\d+): warning: (?P<message>.*)"
+ r"(?P<file>.*):(?P<line>\d+):(?P<column>\d+): warning: "
+ r"(?P<message>.*) (?P<option>\[-[^\]]+\])$"
)
compiler_warnings = []
for line in compiler_output.splitlines():
"line": match.group("line"),
"column": match.group("column"),
"message": match.group("message"),
+ "option": match.group("option").lstrip("[").rstrip("]"),
}
)
"""
# Regex to find json arrays at the top level of the file
# in the compiler output
- json_arrays = re.findall(
- r"\[(?:[^[\]]|\[[^\]]*\])*\]", compiler_output
- )
+ json_arrays = re.findall(r"\[(?:[^[\]]|\[[^]]*])*]", compiler_output)
compiler_warnings = []
for array in json_arrays:
try:
"line": location[key]["line"],
"column": location[key]["column"],
"message": warning["message"],
+ "option": warning["option"],
}
)
# Found a caret, start, or end in location so
def get_warnings_by_file(warnings: list[dict]) -> dict[str, list[dict]]:
"""
Returns a dictionary where the key is the file and the data is the warnings
- in that file
+ in that file. Does not include duplicate warnings for a file from list of
+ provided warnings.
"""
warnings_by_file = defaultdict(list)
+ warnings_added = set()
for warning in warnings:
- warnings_by_file[warning["file"]].append(warning)
+ warning_key = (
+ f"{warning['file']}-{warning['line']}-"
+ f"{warning['column']}-{warning['option']}"
+ )
+ if warning_key not in warnings_added:
+ warnings_added.add(warning_key)
+ warnings_by_file[warning["file"]].append(warning)
return warnings_by_file
def get_unexpected_warnings(
- files_with_expected_warnings: set[str],
- files_with_warnings: dict[str, list[dict]],
+ files_with_expected_warnings: set[FileWarnings],
+ files_with_warnings: set[FileWarnings],
) -> int:
"""
Returns failure status if warnings discovered in list of warnings
"""
unexpected_warnings = []
for file in files_with_warnings.keys():
- if file not in files_with_expected_warnings:
+ found_file_in_ignore_list = False
+ for ignore_file in files_with_expected_warnings:
+ if file == ignore_file.name:
+ if len(files_with_warnings[file]) > ignore_file.count:
+ unexpected_warnings.extend(files_with_warnings[file])
+ found_file_in_ignore_list = True
+ break
+ if not found_file_in_ignore_list:
unexpected_warnings.extend(files_with_warnings[file])
if unexpected_warnings:
def get_unexpected_improvements(
- files_with_expected_warnings: set[str],
- files_with_warnings: dict[str, list[dict]],
+ files_with_expected_warnings: set[FileWarnings],
+ files_with_warnings: set[FileWarnings],
) -> int:
"""
Returns failure status if there are no warnings in the list of warnings
"""
unexpected_improvements = []
for file in files_with_expected_warnings:
- if file not in files_with_warnings.keys():
+ if file.name not in files_with_warnings.keys():
unexpected_improvements.append(file)
+ elif len(files_with_warnings[file.name]) < file.count:
+ unexpected_improvements.append(file)
if unexpected_improvements:
print("Unexpected improvements:")
for file in unexpected_improvements:
- print(file)
+ print(file.name)
return 1
return 0
with Path(args.warning_ignore_file_path).open(
encoding="UTF-8"
) as clean_files:
+ # Files with expected warnings are stored as a set of tuples
+ # where the first element is the file name and the second element
+ # is the number of warnings expected in that file
files_with_expected_warnings = {
- file.strip()
+ FileWarnings(file.strip().split()[0], int(file.strip().split()[1]))
for file in clean_files
if file.strip() and not file.startswith("#")
}