Skip to content

Commit 180afc0

Browse files
committed
Test generate_analyzer_options_docs.py
1 parent 461d3db commit 180afc0

File tree

3 files changed

+71
-14
lines changed

3 files changed

+71
-14
lines changed

clang/docs/tools/generate_analyzer_options_docs.py

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,46 @@ class TT(Enum):
4242
Token = namedtuple("Token", "kind code")
4343

4444

45-
def report_unexpected(s, pos):
46-
lines = (s[:pos] + "X").split("\n")
47-
lineno, col = (len(lines), len(lines[-1]))
48-
print(
49-
"unexpected character %r in AnalyzerOptions.def at line %d column %d"
50-
% (s[pos], lineno, col),
51-
file=sys.stderr,
52-
)
45+
class ErrorHandler:
46+
def __init__(self):
47+
self.seen_errors = False
48+
49+
# This script uses some heuristical tweaks to modify the documentation
50+
# of some analyzer options. As this code is fragile, we record the use
51+
# of these tweaks and report them if they become obsolete:
52+
self.unused_tweaks = [
53+
"ctu-max-nodes-*",
54+
"accepted values",
55+
"example file content",
56+
]
57+
58+
def record_use_of_tweak(self, tweak_name):
59+
try:
60+
self.unused_tweaks.remove(tweak_name)
61+
except ValueError:
62+
pass
63+
64+
def report_error(self, msg):
65+
print("Error:", msg, file=sys.stderr)
66+
self.seen_errors = True
67+
68+
def report_unexpected_char(self, s, pos):
69+
lines = (s[:pos] + "X").split("\n")
70+
lineno, col = (len(lines), len(lines[-1]))
71+
self.report_error(
72+
"unexpected character %r in AnalyzerOptions.def at line %d column %d"
73+
% (s[pos], lineno, col),
74+
)
75+
76+
def report_unused_tweaks(self):
77+
if not self.unused_tweaks:
78+
return
79+
_is = " is" if len(self.unused_tweaks) == 1 else "s are"
80+
names = ", ".join(self.unused_tweaks)
81+
self.report_error(f"textual tweak{_is} unused in script: {names}")
82+
83+
84+
err_handler = ErrorHandler()
5385

5486

5587
def tokenize(s):
@@ -63,7 +95,7 @@ def tokenize(s):
6395
pos = m.end()
6496
break
6597
else:
66-
report_unexpected(s, pos)
98+
err_handler.report_unexpected_char(s, pos)
6799
pos += 1
68100
return result
69101

@@ -149,10 +181,12 @@ def cmdflag_to_rst_title(cmdflag_tok):
149181

150182

151183
def desc_to_rst_paragraphs(tok):
152-
desc = string_value(tok)
184+
base_desc = string_value(tok)
153185

154186
# Escape a star that would act as inline emphasis within RST.
155-
desc = desc.replace("ctu-max-nodes-*", r"ctu-max-nodes-\*")
187+
desc = base_desc.replace("ctu-max-nodes-*", r"ctu-max-nodes-\*")
188+
if desc != base_desc:
189+
err_handler.record_use_of_tweak("ctu-max-nodes-*")
156190

157191
# Many descriptions end with "Value: <list of accepted values>", which is
158192
# OK for a terse command line printout, but should be prettified for web
@@ -162,8 +196,10 @@ def desc_to_rst_paragraphs(tok):
162196
paragraphs = [desc]
163197
extra = ""
164198
if m := re.search(r"(^|\s)Value:", desc):
199+
err_handler.record_use_of_tweak("accepted values")
165200
paragraphs = [desc[: m.start()], "Accepted values:" + desc[m.end() :]]
166201
elif m := re.search(r"\s*Example file.content:", desc):
202+
err_handler.record_use_of_tweak("example file content")
167203
paragraphs = [desc[: m.start()]]
168204
extra = "Example file content::\n\n " + desc[m.end() :] + "\n\n"
169205

@@ -209,7 +245,7 @@ def macro_call_to_rst_paragraphs(macro_call):
209245
+ defaults_to_rst_paragraph(defaults)
210246
)
211247
except ValueError as ve:
212-
print(ve.args[0], file=sys.stderr)
248+
err_handler.report_error(ve.args[0])
213249
return ""
214250

215251

@@ -227,8 +263,11 @@ def get_option_list(input_file):
227263

228264
p = argparse.ArgumentParser()
229265
p.add_argument("--options-def", help="path to AnalyzerOptions.def")
230-
p.add_argument("--template", help="path of template file")
231-
p.add_argument("--out", help="path of output file")
266+
p.add_argument("--template", help="template file")
267+
p.add_argument("--out", help="output file")
268+
p.add_argument(
269+
"--validate", action="store_true", help="exit with failure on parsing error"
270+
)
232271
opts = p.parse_args()
233272

234273
with open(opts.template, encoding="utf-8") as f:
@@ -238,5 +277,10 @@ def get_option_list(input_file):
238277

239278
rst_output = doc_template.replace(PLACEHOLDER, get_option_list(opts.options_def))
240279

280+
err_handler.report_unused_tweaks()
281+
241282
with open(opts.out, "w", newline="", encoding="utf-8") as f:
242283
f.write(rst_output)
284+
285+
if opts.validate and err_handler.seen_errors:
286+
sys.exit(1)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
The documentation of analyzer options is generated by a script that parses
2+
AnalyzerOptions.def. The following line validates that this script
3+
"understands" everything in its input files:
4+
5+
RUN: %python %src_dir/docs/tools/generate_analyzer_options_docs.py --validate --options-def %src_include_dir/clang/StaticAnalyzer/Core/AnalyzerOptions.def --template %src_dir/docs/analyzer/user-docs/Options.rst.in --out %t.rst
6+
7+
Moreover, verify that the documentation (e.g. this fragment of the
8+
documentation of the "mode" option) can be found in the output file:
9+
10+
RUN: FileCheck --input-file=%t.rst %s
11+
CHECK: Controls the high-level analyzer mode

clang/test/lit.cfg.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@
7070

7171
llvm_config.use_clang()
7272

73+
config.substitutions.append(("%src_dir", config.clang_src_dir))
74+
7375
config.substitutions.append(("%src_include_dir", config.clang_src_dir + "/include"))
7476

7577
config.substitutions.append(("%target_triple", config.target_triple))

0 commit comments

Comments
 (0)