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