1""" 2Generates documentation based off the available static analyzers checks 3References Checkers.td to determine what checks exist 4""" 5 6import subprocess 7import json 8import os 9import re 10 11"""Get path of script so files are always in correct directory""" 12__location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) 13 14default_checkers_td_location = "../../../../clang/include/clang/StaticAnalyzer/Checkers/Checkers.td" 15default_checkers_rst_location = "../../../../clang/docs/analyzer/checkers.rst" 16 17"""Get dict of checker related info and parse for full check names 18 19Returns: 20 checkers: dict of checker info 21""" 22def get_checkers(checkers_td, checkers_rst): 23 p = subprocess.Popen( 24 [ 25 "llvm-tblgen", 26 "--dump-json", 27 "-I", 28 os.path.dirname(checkers_td), 29 checkers_td, 30 ], 31 stdout=subprocess.PIPE, 32 ) 33 table_entries = json.loads(p.communicate()[0]) 34 documentable_checkers = [] 35 checkers = table_entries["!instanceof"]["Checker"] 36 37 with open(checkers_rst, "r") as f: 38 checker_rst_text = f.read() 39 40 for checker_ in checkers: 41 checker = table_entries[checker_] 42 checker_name = checker["CheckerName"] 43 package_ = checker["ParentPackage"]["def"] 44 package = table_entries[package_] 45 package_name = package["PackageName"] 46 checker_package_prefix = package_name 47 parent_package_ = package["ParentPackage"] 48 hidden = (checker["Hidden"] != 0) or (package["Hidden"] != 0) 49 50 while parent_package_ is not None: 51 parent_package = table_entries[parent_package_["def"]] 52 checker_package_prefix = ( 53 parent_package["PackageName"] + "." + checker_package_prefix 54 ) 55 hidden = hidden or parent_package["Hidden"] != 0 56 parent_package_ = parent_package["ParentPackage"] 57 58 full_package_name = ( 59 "clang-analyzer-" + checker_package_prefix + "." + checker_name 60 ) 61 anchor_url = re.sub( 62 r"\.", "-", checker_package_prefix + "." + checker_name 63 ).lower() 64 65 if not hidden and "alpha" not in full_package_name.lower(): 66 checker["FullPackageName"] = full_package_name 67 checker["ShortName"] = checker_package_prefix + "." + checker_name 68 checker["AnchorUrl"] = anchor_url 69 checker["Documentation"] = ".. _%s:" % (checker["ShortName"].replace(".","-")) in checker_rst_text 70 documentable_checkers.append(checker) 71 72 documentable_checkers.sort(key=lambda x: x["FullPackageName"]) 73 return documentable_checkers 74 75 76"""Generate documentation for checker 77 78Args: 79 checker: Checker for which to generate documentation. 80 has_documentation: Specify that there is other documentation to link to. 81""" 82def generate_documentation(checker, has_documentation): 83 84 with open( 85 os.path.join(__location__, "clang-analyzer", checker["ShortName"] + ".rst"), "w" 86 ) as f: 87 f.write(".. title:: clang-tidy - %s\n" % checker["FullPackageName"]) 88 if has_documentation: 89 f.write(".. meta::\n") 90 f.write( 91 " :http-equiv=refresh: 5;URL=https://clang.llvm.org/docs/analyzer/checkers.html#%s\n" 92 % checker["AnchorUrl"] 93 ) 94 f.write("\n") 95 f.write("%s\n" % checker["FullPackageName"]) 96 f.write("=" * len(checker["FullPackageName"]) + "\n") 97 help_text = checker["HelpText"].strip() 98 if not help_text.endswith("."): 99 help_text += "." 100 characters = 80 101 for word in help_text.split(" "): 102 if characters+len(word)+1 > 80: 103 characters = len(word) 104 f.write("\n") 105 f.write(word) 106 else: 107 f.write(" ") 108 f.write(word) 109 characters += len(word) + 1 110 f.write("\n\n") 111 if has_documentation: 112 f.write( 113 "The `%s` check is an alias, please see\n" % checker["FullPackageName"] 114 ) 115 f.write( 116 "`Clang Static Analyzer Available Checkers\n<https://clang.llvm.org/docs/analyzer/checkers.html#%s>`_\n" 117 % checker["AnchorUrl"] 118 ) 119 f.write("for more information.\n") 120 else: 121 f.write("The %s check is an alias of\nClang Static Analyzer %s.\n" % (checker["FullPackageName"], checker["ShortName"])); 122 f.close() 123 124 125"""Update list.rst to include the new checks 126 127Args: 128 checkers: dict acquired from get_checkers() 129""" 130def update_documentation_list(checkers): 131 with open(os.path.join(__location__, "list.rst"), "r+") as f: 132 f_text = f.read() 133 check_text = f_text.split(':header: "Name", "Redirect", "Offers fixes"\n')[1] 134 checks = [x for x in check_text.split("\n") if ":header:" not in x and x] 135 old_check_text = "\n".join(checks) 136 checks = [x for x in checks if "clang-analyzer-" not in x] 137 for checker in checkers: 138 if checker["Documentation"]: 139 checks.append(" :doc:`%s <clang-analyzer/%s>`, `Clang Static Analyzer %s <https://clang.llvm.org/docs/analyzer/checkers.html#%s>`_," % (checker["FullPackageName"], 140 checker["ShortName"], checker["ShortName"], checker["AnchorUrl"])) 141 else: 142 checks.append(" :doc:`%s <clang-analyzer/%s>`, Clang Static Analyzer %s," % (checker["FullPackageName"], checker["ShortName"], checker["ShortName"])) 143 144 checks.sort() 145 146 # Overwrite file with new data 147 f.seek(0) 148 f_text = f_text.replace(old_check_text, "\n".join(checks)) 149 f.write(f_text) 150 f.close() 151 152 153def main(): 154 CheckersPath = os.path.join(__location__, default_checkers_td_location) 155 if not os.path.exists(CheckersPath): 156 print("Could not find Checkers.td under %s." % (os.path.abspath(CheckersPath))) 157 exit(1) 158 159 CheckersDoc = os.path.join(__location__, default_checkers_rst_location) 160 if not os.path.exists(CheckersDoc): 161 print("Could not find checkers.rst under %s." % (os.path.abspath(CheckersDoc))) 162 exit(1) 163 164 checkers = get_checkers(CheckersPath, CheckersDoc) 165 for checker in checkers: 166 generate_documentation(checker, checker["Documentation"]) 167 print("Generated documentation for: %s" % (checker["FullPackageName"])) 168 update_documentation_list(checkers) 169 170 171if __name__ == "__main__": 172 main() 173