• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""
2Generates documentation based off the available static analyzers checks
3References Checkers.td to determine what checks exist
4"""
5
6import argparse
7import subprocess
8import json
9import os
10import re
11
12"""Get path of script so files are always in correct directory"""
13__location__ = os.path.realpath(
14        os.path.join(os.getcwd(), os.path.dirname(__file__)))
15
16"""Get dict of checker related info and parse for full check names
17
18Returns:
19  checkers: dict of checker info
20"""
21def get_checkers(checkers_td_directory):
22  p = subprocess.Popen(["llvm-tblgen", "--dump-json", "-I",
23                           checkers_td_directory, checkers_td_directory+"Checkers.td"],
24                           stdout=subprocess.PIPE)
25  table_entries = json.loads(p.communicate()[0])
26  documentable_checkers = []
27  checkers = table_entries["!instanceof"]["Checker"]
28  packages = table_entries["!instanceof"]["Package"]
29
30  for checker_ in checkers:
31    checker = table_entries[checker_]
32    checker_name = checker["CheckerName"]
33    package_ = checker["ParentPackage"]["def"]
34    package = table_entries[package_]
35    package_name = package["PackageName"]
36    checker_package_prefix = package_name
37    parent_package_ = package["ParentPackage"]
38    hidden = (checker["Hidden"] != 0) or (package["Hidden"] != 0)
39
40    while(parent_package_ != None):
41      parent_package = table_entries[parent_package_["def"]]
42      checker_package_prefix = parent_package["PackageName"] + "." + checker_package_prefix
43      hidden = hidden or parent_package["Hidden"] != 0
44      parent_package_ = parent_package["ParentPackage"]
45
46    full_package_name = "clang-analyzer-" + checker_package_prefix + "." + checker_name
47    anchor_url = re.sub("\.", "-", checker_package_prefix + "." + checker_name).lower()
48
49    if(not hidden and "alpha" not in full_package_name.lower()):
50      checker["FullPackageName"] = full_package_name
51      checker["AnchorUrl"] = anchor_url
52      documentable_checkers.append(checker)
53
54  documentable_checkers.sort(key=lambda x: x["FullPackageName"])
55  return documentable_checkers
56
57"""Generate documentation for checker
58
59Args:
60  checker: Checker for which to generate documentation.
61  only_help_text: Generate documentation based off the checker description.
62    Used when there is no other documentation to link to.
63"""
64def generate_documentation(checker, only_help_text=False):
65  with open(os.path.join(__location__, checker["FullPackageName"]+".rst"),"w") as f:
66    f.write(".. title:: clang-tidy - %s\n" % checker["FullPackageName"])
67    if(not only_help_text):
68      f.write(".. meta::\n")
69      f.write("   :http-equiv=refresh: 5;URL=https://clang.llvm.org/docs/analyzer/checkers.html#%s\n" % checker["AnchorUrl"])
70    f.write("\n")
71    f.write("%s\n" % checker["FullPackageName"])
72    f.write("=" * len(checker["FullPackageName"]) + "\n")
73    f.write("\n")
74    if(only_help_text):
75      f.write("%s\n" % checker["HelpText"])
76    else:
77      f.write("The %s check is an alias, please see\n" % checker["FullPackageName"])
78      f.write("`Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#%s>`_\n" % checker["AnchorUrl"])
79      f.write("for more information.\n")
80    f.close()
81
82"""Update list.rst to include the new checks
83
84Args:
85  checkers: dict acquired from get_checkers()
86"""
87def update_documentation_list(checkers):
88  with open(os.path.join(__location__, "list.rst"), "r+") as f:
89    f_text = f.read()
90    header, check_text= f_text.split(".. toctree::\n")
91    checks = check_text.split("\n")
92    for checker in checkers:
93      if(("   %s" % checker["FullPackageName"]) not in checks):
94        checks.append("   %s" % checker["FullPackageName"])
95    checks.sort()
96
97    #Overwrite file with new data
98    f.seek(0)
99    f.write(header)
100    f.write(".. toctree::")
101    for check in checks:
102      f.write("%s\n" % check)
103    f.close()
104
105default_path_monorepo = '../../../../clang/include/clang/StaticAnalyzer/Checkers/'
106default_path_in_tree = '../../../../../include/clang/StaticAnalyzer/Checkers/'
107
108def parse_arguments():
109  """Set up and parse command-line arguments
110  Returns:
111    file_path: Path to Checkers.td"""
112  usage = """Parse Checkers.td to generate documentation for static analyzer checks"""
113  parse = argparse.ArgumentParser(description=usage)
114
115  file_path_help = ("""Path to Checkers directory
116                    defaults to ../../../../clang/include/clang/StaticAnalyzer/Checkers/ if it exists
117                    then to ../../../../../include/clang/StaticAnalyzer/Checkers/""")
118
119  default_path=None
120  if(os.path.exists(default_path_monorepo)):
121    default_path = default_path_monorepo
122  elif(os.path.exists(default_path_in_tree)):
123    default_path = default_path_in_tree
124
125  parse.add_argument("file", type=str, help=file_path_help, nargs='?', default=default_path)
126  args = parse.parse_args()
127
128  if(args.file is None):
129    print("Could not find Checkers directory. Please see -h")
130    exit(1)
131
132  return args.file
133
134
135def main():
136  file_path = parse_arguments()
137  checkers = get_checkers(file_path)
138  for checker in checkers:
139    #No documentation nor alpha documentation
140    if(checker["Documentation"][1] == 0 and checker["Documentation"][0] == 0):
141      generate_documentation(checker, True)
142    else:
143      generate_documentation(checker)
144    print("Generated documentation for: %s" % checker["FullPackageName"])
145  update_documentation_list(checkers)
146
147if __name__ == '__main__':
148  main()
149