173 lines · python
1"""2Generates documentation based off the available static analyzers checks3References Checkers.td to determine what checks exist4"""5 6import subprocess7import json8import os9import re10 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 names18 19Returns:20 checkers: dict of checker info21"""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_name47 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_prefix54 )55 hidden = hidden or parent_package["Hidden"] != 056 parent_package_ = parent_package["ParentPackage"]57 58 full_package_name = (59 "clang-analyzer-" + checker_package_prefix + "." + checker_name60 )61 anchor_url = re.sub(62 r"\.", "-", checker_package_prefix + "." + checker_name63 ).lower()64 65 if not hidden and "alpha" not in full_package_name.lower():66 checker["FullPackageName"] = full_package_name67 checker["ShortName"] = checker_package_prefix + "." + checker_name68 checker["AnchorUrl"] = anchor_url69 checker["Documentation"] = ".. _%s:" % (checker["ShortName"].replace(".","-")) in checker_rst_text70 documentable_checkers.append(checker)71 72 documentable_checkers.sort(key=lambda x: x["FullPackageName"])73 return documentable_checkers74 75 76"""Generate documentation for checker77 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 = 80101 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) + 1110 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 checks126 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 data147 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