forked from OSchip/llvm-project
149 lines
5.3 KiB
Python
149 lines
5.3 KiB
Python
"""
|
|
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()
|