GNUnet 0.22.0
warningfilter.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""
3Filters and processes warnings generated by Doxygen, which are
4annoyingly inconsistent and verbose, for greater readability.
5
6(Neo)vim commands to go to the file and linenumber listed on a
7line, in the reports this program generates:
8 :exe "let linenumber =" split(getline("."))[1]
9 :exe "edit" fnameescape(split(getline("."))[0]) "|" linenumber
10
11It's easy to put a workflow together to clear up redundant doc
12comments (which generate "multiple @param docs" warnings), using
13simple vim commands to move the cursor and close buffers, Neovim's
14support for the Language Server Protocol or related tooling, and
15the command shown above.
16
17A useful sequence, for rapidly deleting a doc comment from its last
18line, is, in normal mode, `$v%ddd`.
19
20For setting up LSP integration in Neovim, refer to the lsp_config
21plugin.
22
23You may additionally need to generate compile_commands.json in the
24repository root, to allow the language server to find everything.
25This can be done using Bear (found at https://github.com/rizsotto/Bear).
26
27@author: willow <willow@howhill.com>
28"""
29
30import argparse as ap
31import re
32
33# Regular expression construction
34
35def sep_re(field, separator):
36 "Constructs regex for a list"
37 return rf"{field}(?:{separator}{field})*"
38
39# File names and paths
40fileclass = r"[\w-]"
41filename = rf"{fileclass}+"
42# filename = rf"(/{fileclass}+)+\.\w"
43filepath = rf"{sep_re(filename, '/')}\.(?:\w+)"
44main_match = rf"(?P<path>/{filepath}|\[generated\]):(?P<linenumber>\d+): warning:"
45
46# Symbols
47type_name = rf"(?:const )?(?:unsigned (?:long )?|struct |enum )?(?:\w+)(?: \*?const)? \*{{0,3}}"
48var_def = rf"{type_name}\w+(?:\[(?:\‍(\d+/\d+\‍))?\])?"
49func_params = rf"\‍({sep_re(var_def, ', ')}(?:,\.\.\.)?\‍)"
50simple_name = r"\w+"
51func_name = simple_name
52verbose_name = rf"{sep_re(simple_name, ' ')}"
53command_re = "(?:</[^>]+>|\\\w+)"
54macro_params = rf"\‍({sep_re(simple_name, ', ')}(?:,\.\.\.)?\‍)"
55
56
57matches = {
58 "not an input @file": re.compile(rf"{main_match} the name '(?P<name>{filepath}|{simple_name})' supplied as the argument in the \\file statement is not an input file"),
59 "multiple @param docs": re.compile(rf"{main_match} argument '(?P<arg_name>\w+)' from the argument list of ({func_name}) has multiple @param documentation sections"),
60 "undocumented param": re.compile(rf"{main_match} The following parameters? of ({func_name})(?:{func_params}|{macro_params}) (?:is|are) not documented:"),
61 "undocumented param (name)": re.compile(r" parameter '([\w.]+)'"),
62 "explicit link not resolved": re.compile(rf"{main_match} explicit link request to '(\w+(?:\‍(\‍))?)' could not be resolved"),
63 "unknown command": re.compile(rf"{main_match} Found unknown command '(\\\w+)'"),
64 "missing argument": re.compile(rf"{main_match} argument '(\w+)' of command @param is not found in the argument list of ({func_name})(?:{func_params}|{macro_params})"),
65 "eof inside group": re.compile(rf"{main_match} end of file while inside a group"),
66 "eof inside comment": re.compile(rf"{main_match} Reached end of file while still inside a \‍(nested\‍) comment. Nesting level \d+ \‍(probable line reference: (\d+)\‍)"),
67 "eof inside code block": re.compile(rf"{main_match} reached end of file while inside a 'code' block!"),
68 "eof inside code block (line 2)": re.compile(rf"The command that should end the block seems to be missing!"),
69 "title mismatch": re.compile(rf"{main_match} group (?P<group_id>\w+): ignoring title \"(?P<new_title>{verbose_name})\" that does not match old title \"(?P<old_title>{verbose_name})\""),
70 "end of comment expecting command": re.compile(rf"{main_match} end of comment block while expecting command ({command_re})"),
71 "no matching tag": re.compile(rf"{main_match} found </(?P<tag>[^>]+)> tag without matching <(?P=tag)>"),
72 "documented empty return type": re.compile(rf"{main_match} documented empty return type of {func_name}"),
73 "unsupported tag": re.compile(rf"{main_match} Unsupported xml/html tag <(?P<tag>[^>]+)> found"),
74 "expected whitespace after command": re.compile(rf"{main_match} expected whitespace after \\(?P<command>\w+) command"),
75 "illegal command": re.compile(rf"{main_match} Illegal command (?P<illegal_cmd>(?:@|\\)\w+) as part of a \\(?P<command>\w+) command"),
76 "undeclared symbol": re.compile(rf"{main_match} documented symbol '(\w+)' was not declared or defined\."),
77 "nameless member": re.compile(rf"{main_match} member with no name found."),
78 "end of empty list": re.compile(rf"{main_match} End of list marker found without any preceding list items"),
79 "blank": re.compile(rf"^\s*$"),
80# "": re.compile(rf"{main_match} "),
81}
82
83parser_choices = set(matches.keys()) - {"blank",
84 "eof inside code block (line 2)",
85 "undocumented param (name)"}
86
87parser = ap.ArgumentParser()
88parser.add_argument("filename")
89parser.add_argument("--summary", "-s", action="store_true")
90parser.add_argument("--key", "-k", choices=parser_choices, action="append", dest="keys")
91args = parser.parse_args()
92
93sorted_lines = {k:[] for k in matches.keys()}
94unsorted_lines = []
95
96with open(args.filename, "r") as file:
97 for line in file.readlines():
98 for key, value in matches.items():
99 if match := value.match(line):
100 sorted_lines[key].append(match)
101 break
102 else:
103 unsorted_lines.append(line.strip("\n"))
104
105
106processed_lines = {k: [" ".join(g for g in match.groups())
107 for match in matches]
108 for k, matches in sorted_lines.items()}
109
110# Combining multiline warnings
111processed_lines["undocumented param"] = [
112 l1+" "+l2 for l1, l2 in zip(processed_lines["undocumented param"],
113 processed_lines["undocumented param (name)"])
114]
115
116# Removing chaff
117del processed_lines["blank"]
118del processed_lines["eof inside code block (line 2)"]
119del processed_lines["undocumented param (name)"]
120
121# Preparing count dictionary and summarising the results
122counts = {k: len(v) for k, v in processed_lines.items()}
123if args.summary:
124 for k, v in counts.items():
125 print(k+":", v)
126 print("")
127
128if args.keys is not None:
129 for key in args.keys:
130 print(f"{key}: {counts[key]}")
131 for line in processed_lines[key]:
132 print(line)
133 print("")
134