| """ |
| Generates documentation based off the available static analyzers checks |
| References Checkers.td to determine what checks exist |
| """ |
| |
| import argparse |
| import subprocess |
| import json |
| import os |
| import re |
| |
| """Get path of script so files are always in correct directory""" |
| __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) |
| |
| """Get dict of checker related info and parse for full check names |
| |
| Returns: |
| checkers: dict of checker info |
| """ |
| |
| |
| def get_checkers(checkers_td_directory): |
| p = subprocess.Popen( |
| [ |
| "llvm-tblgen", |
| "--dump-json", |
| "-I", |
| checkers_td_directory, |
| checkers_td_directory + "Checkers.td", |
| ], |
| stdout=subprocess.PIPE, |
| ) |
| table_entries = json.loads(p.communicate()[0]) |
| documentable_checkers = [] |
| checkers = table_entries["!instanceof"]["Checker"] |
| packages = table_entries["!instanceof"]["Package"] |
| |
| for checker_ in checkers: |
| checker = table_entries[checker_] |
| checker_name = checker["CheckerName"] |
| package_ = checker["ParentPackage"]["def"] |
| package = table_entries[package_] |
| package_name = package["PackageName"] |
| checker_package_prefix = package_name |
| parent_package_ = package["ParentPackage"] |
| hidden = (checker["Hidden"] != 0) or (package["Hidden"] != 0) |
| |
| while parent_package_ != None: |
| parent_package = table_entries[parent_package_["def"]] |
| checker_package_prefix = ( |
| parent_package["PackageName"] + "." + checker_package_prefix |
| ) |
| hidden = hidden or parent_package["Hidden"] != 0 |
| parent_package_ = parent_package["ParentPackage"] |
| |
| full_package_name = ( |
| "clang-analyzer-" + checker_package_prefix + "." + checker_name |
| ) |
| anchor_url = re.sub( |
| "\.", "-", checker_package_prefix + "." + checker_name |
| ).lower() |
| |
| if not hidden and "alpha" not in full_package_name.lower(): |
| checker["FullPackageName"] = full_package_name |
| checker["AnchorUrl"] = anchor_url |
| documentable_checkers.append(checker) |
| |
| documentable_checkers.sort(key=lambda x: x["FullPackageName"]) |
| return documentable_checkers |
| |
| |
| """Generate documentation for checker |
| |
| Args: |
| checker: Checker for which to generate documentation. |
| only_help_text: Generate documentation based off the checker description. |
| Used when there is no other documentation to link to. |
| """ |
| |
| |
| def generate_documentation(checker, only_help_text=False): |
| with open( |
| os.path.join(__location__, checker["FullPackageName"] + ".rst"), "w" |
| ) as f: |
| f.write(".. title:: clang-tidy - %s\n" % checker["FullPackageName"]) |
| if not only_help_text: |
| f.write(".. meta::\n") |
| f.write( |
| " :http-equiv=refresh: 5;URL=https://clang.llvm.org/docs/analyzer/checkers.html#%s\n" |
| % checker["AnchorUrl"] |
| ) |
| f.write("\n") |
| f.write("%s\n" % checker["FullPackageName"]) |
| f.write("=" * len(checker["FullPackageName"]) + "\n") |
| f.write("\n") |
| if only_help_text: |
| f.write("%s\n" % checker["HelpText"]) |
| else: |
| f.write( |
| "The %s check is an alias, please see\n" % checker["FullPackageName"] |
| ) |
| f.write( |
| "`Clang Static Analyzer Available Checkers <https://clang.llvm.org/docs/analyzer/checkers.html#%s>`_\n" |
| % checker["AnchorUrl"] |
| ) |
| f.write("for more information.\n") |
| f.close() |
| |
| |
| """Update list.rst to include the new checks |
| |
| Args: |
| checkers: dict acquired from get_checkers() |
| """ |
| |
| |
| def update_documentation_list(checkers): |
| with open(os.path.join(__location__, "list.rst"), "r+") as f: |
| f_text = f.read() |
| header, check_text = f_text.split(".. toctree::\n") |
| checks = check_text.split("\n") |
| for checker in checkers: |
| if (" %s" % checker["FullPackageName"]) not in checks: |
| checks.append(" %s" % checker["FullPackageName"]) |
| checks.sort() |
| |
| # Overwrite file with new data |
| f.seek(0) |
| f.write(header) |
| f.write(".. toctree::") |
| for check in checks: |
| f.write("%s\n" % check) |
| f.close() |
| |
| |
| default_path_monorepo = "../../../../clang/include/clang/StaticAnalyzer/Checkers/" |
| default_path_in_tree = "../../../../../include/clang/StaticAnalyzer/Checkers/" |
| |
| |
| def parse_arguments(): |
| """Set up and parse command-line arguments |
| Returns: |
| file_path: Path to Checkers.td""" |
| usage = """Parse Checkers.td to generate documentation for static analyzer checks""" |
| parse = argparse.ArgumentParser(description=usage) |
| |
| file_path_help = """Path to Checkers directory |
| defaults to ../../../../clang/include/clang/StaticAnalyzer/Checkers/ if it exists |
| then to ../../../../../include/clang/StaticAnalyzer/Checkers/""" |
| |
| default_path = None |
| if os.path.exists(default_path_monorepo): |
| default_path = default_path_monorepo |
| elif os.path.exists(default_path_in_tree): |
| default_path = default_path_in_tree |
| |
| parse.add_argument( |
| "file", type=str, help=file_path_help, nargs="?", default=default_path |
| ) |
| args = parse.parse_args() |
| |
| if args.file is None: |
| print("Could not find Checkers directory. Please see -h") |
| exit(1) |
| |
| return args.file |
| |
| |
| def main(): |
| file_path = parse_arguments() |
| checkers = get_checkers(file_path) |
| for checker in checkers: |
| # No documentation nor alpha documentation |
| if checker["Documentation"][1] == 0 and checker["Documentation"][0] == 0: |
| generate_documentation(checker, True) |
| else: |
| generate_documentation(checker) |
| print("Generated documentation for: %s" % checker["FullPackageName"]) |
| update_documentation_list(checkers) |
| |
| |
| if __name__ == "__main__": |
| main() |