GNUnet  0.20.0
warningfilter.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 """
3 Filters and processes warnings generated by Doxygen, which are
4 annoyingly inconsistent and verbose, for greater readability.
5 
6 (Neo)vim commands to go to the file and linenumber listed on a
7 line, in the reports this program generates:
8  :exe "let linenumber =" split(getline("."))[1]
9  :exe "edit" fnameescape(split(getline("."))[0]) "|" linenumber
10 
11 It's easy to put a workflow together to clear up redundant doc
12 comments (which generate "multiple @param docs" warnings), using
13 simple vim commands to move the cursor and close buffers, Neovim's
14 support for the Language Server Protocol or related tooling, and
15 the command shown above.
16 
17 A useful sequence, for rapidly deleting a doc comment from its last
18 line, is, in normal mode, `$v%ddd`.
19 
20 For setting up LSP integration in Neovim, refer to the lsp_config
21 plugin.
22 
23 You may additionally need to generate compile_commands.json in the
24 repository root, to allow the language server to find everything.
25 This can be done using Bear (found at https://github.com/rizsotto/Bear).
26 
27 @author: willow <willow@howhill.com>
28 """
29 
30 import argparse as ap
31 import re
32 
33 # Regular expression construction
34 
35 def sep_re(field, separator):
36  "Constructs regex for a list"
37  return rf"{field}(?:{separator}{field})*"
38 
39 # File names and paths
40 fileclass = r"[\w-]"
41 filename = rf"{fileclass}+"
42 # filename = rf"(/{fileclass}+)+\.\w"
43 filepath = rf"{sep_re(filename, '/')}\.(?:\w+)"
44 main_match = rf"(?P<path>/{filepath}|\[generated\]):(?P<linenumber>\d+): warning:"
45 
46 # Symbols
47 type_name = rf"(?:const )?(?:unsigned (?:long )?|struct |enum )?(?:\w+)(?: \*?const)? \*{{0,3}}"
48 var_def = rf"{type_name}\w+(?:\[(?:\‍(\d+/\d+\‍))?\])?"
49 func_params = rf"\‍({sep_re(var_def, ', ')}(?:,\.\.\.)?\‍)"
50 simple_name = r"\w+"
51 func_name = simple_name
52 verbose_name = rf"{sep_re(simple_name, ' ')}"
53 command_re = "(?:</[^>]+>|\\\w+)"
54 macro_params = rf"\‍({sep_re(simple_name, ', ')}(?:,\.\.\.)?\‍)"
55 
56 
57 matches = {
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 
83 parser_choices = set(matches.keys()) - {"blank",
84  "eof inside code block (line 2)",
85  "undocumented param (name)"}
86 
87 parser = ap.ArgumentParser()
88 parser.add_argument("filename")
89 parser.add_argument("--summary", "-s", action="store_true")
90 parser.add_argument("--key", "-k", choices=parser_choices, action="append", dest="keys")
91 args = parser.parse_args()
92 
93 sorted_lines = {k:[] for k in matches.keys()}
94 unsorted_lines = []
95 
96 with 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 
106 processed_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
111 processed_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
117 del processed_lines["blank"]
118 del processed_lines["eof inside code block (line 2)"]
119 del processed_lines["undocumented param (name)"]
120 
121 # Preparing count dictionary and summarising the results
122 counts = {k: len(v) for k, v in processed_lines.items()}
123 if args.summary:
124  for k, v in counts.items():
125  print(k+":", v)
126  print("")
127 
128 if 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 
uint16_t len
length of data (which is always a uint32_t, but presumably this can be used to specify that fewer byt...
def sep_re(field, separator)