diff --git a/clang-tools-extra/clang-tidy/add_new_check.py b/clang-tools-extra/clang-tidy/add_new_check.py --- a/clang-tools-extra/clang-tidy/add_new_check.py +++ b/clang-tools-extra/clang-tidy/add_new_check.py @@ -14,36 +14,7 @@ import os import re import sys - -# Adapts the module's CMakelist file. Returns 'True' if it could add a new entry -# and 'False' if the entry already existed. -def adapt_cmake(module_path, check_name_camel): - filename = os.path.join(module_path, 'CMakeLists.txt') - with open(filename, 'r') as f: - lines = f.readlines() - - cpp_file = check_name_camel + '.cpp' - - # Figure out whether this check already exists. - for line in lines: - if line.strip() == cpp_file: - return False - - print('Updating %s...' % filename) - with open(filename, 'w') as f: - cpp_found = False - file_added = False - for line in lines: - cpp_line = line.strip().endswith('.cpp') - if (not file_added) and (cpp_line or cpp_found): - cpp_found = True - if (line.strip() > cpp_file) or (not cpp_line): - f.write(' ' + cpp_file + '\n') - file_added = True - f.write(line) - - return True - +from utils.modify_check import * # Adds a header for the new check. def write_header(module_path, module, namespace, check_name, check_name_camel): @@ -150,7 +121,6 @@ 'module': module, 'namespace': namespace}) - # Modifies the module to include the new check. def adapt_module(module_path, module, check_name, check_name_camel): modulecpp = list(filter( @@ -290,94 +260,6 @@ """ % {'check_name_dashes': check_name_dashes}) -# Recreates the list of checks in the docs/clang-tidy/checks directory. -def update_checks_list(clang_tidy_path): - docs_dir = os.path.join(clang_tidy_path, '../docs/clang-tidy/checks') - filename = os.path.normpath(os.path.join(docs_dir, 'list.rst')) - # Read the content of the current list.rst file - with open(filename, 'r') as f: - lines = f.readlines() - # Get all existing docs - doc_files = list(filter(lambda s: s.endswith('.rst') and s != 'list.rst', - os.listdir(docs_dir))) - doc_files.sort() - - def has_auto_fix(check_name): - dirname, _, check_name = check_name.partition("-") - - checkerCode = os.path.join(dirname, get_camel_name(check_name)) + ".cpp" - - if not os.path.isfile(checkerCode): - return "" - - with open(checkerCode) as f: - code = f.read() - if 'FixItHint' in code or "ReplacementText" in code or "fixit" in code: - # Some simple heuristics to figure out if a checker has an autofix or not. - return ' "Yes"' - return "" - - def process_doc(doc_file): - check_name = doc_file.replace('.rst', '') - - with open(os.path.join(docs_dir, doc_file), 'r') as doc: - content = doc.read() - match = re.search('.*:orphan:.*', content) - - if match: - # Orphan page, don't list it. - return '', '' - - match = re.search('.*:http-equiv=refresh: \d+;URL=(.*).html.*', - content) - # Is it a redirect? - return check_name, match - - def format_link(doc_file): - check_name, match = process_doc(doc_file) - if not match and check_name: - return ' `%(check)s <%(check)s.html>`_,%(autofix)s\n' % { - 'check': check_name, - 'autofix': has_auto_fix(check_name) - } - else: - return '' - - def format_link_alias(doc_file): - check_name, match = process_doc(doc_file) - if match and check_name: - if match.group(1) == 'https://clang.llvm.org/docs/analyzer/checkers': - title_redirect = 'Clang Static Analyzer' - else: - title_redirect = match.group(1) - # The checker is just a redirect. - return ' `%(check)s <%(check)s.html>`_, `%(title)s <%(target)s.html>`_,%(autofix)s\n' % { - 'check': check_name, - 'target': match.group(1), - 'title': title_redirect, - 'autofix': has_auto_fix(match.group(1)) - } - return '' - - checks = map(format_link, doc_files) - checks_alias = map(format_link_alias, doc_files) - - print('Updating %s...' % filename) - with open(filename, 'w') as f: - for line in lines: - f.write(line) - if line.strip() == ".. csv-table::": - # We dump the checkers - f.write(' :header: "Name", "Offers fixes"\n\n') - f.writelines(checks) - # and the aliases - f.write('\n\n') - f.write('.. csv-table:: Aliases..\n') - f.write(' :header: "Name", "Redirect", "Offers fixes"\n\n') - f.writelines(checks_alias) - break - - # Adds a documentation for the check. def write_docs(module_path, module, check_name): check_name_dashes = module + '-' + check_name @@ -395,11 +277,6 @@ 'underline': '=' * len(check_name_dashes)}) -def get_camel_name(check_name): - return ''.join(map(lambda elem: elem.capitalize(), - check_name.split('-'))) + 'Check' - - def main(): language_to_extension = { 'c': 'c', diff --git a/clang-tools-extra/clang-tidy/rename_check.py b/clang-tools-extra/clang-tidy/rename_check.py --- a/clang-tools-extra/clang-tidy/rename_check.py +++ b/clang-tools-extra/clang-tidy/rename_check.py @@ -12,6 +12,7 @@ import glob import os import re +from utils.modify_check import * def replaceInFileRegex(fileName, sFrom, sTo): @@ -20,11 +21,43 @@ txt = None with open(fileName, "r") as f: txt = f.read() + + # hack that prevents using nonlocal to get the return from the callback + Replacements = {'Any' : False } - txt = re.sub(sFrom, sTo, txt) - print("Replacing '%s' -> '%s' in '%s'..." % (sFrom, sTo, fileName)) - with open(fileName, "w") as f: - f.write(txt) + def callback(match): + Replacements['Any'] = True + actFrom = match.group() + actTo = match.expand(sTo) + print("Replacing '%s' -> '%s' in '%s'..." % (actFrom, actTo, fileName)) + return actTo + + txt = re.sub(sFrom, callback, txt) + + + if Replacements['Any']: + with open(fileName, "w") as f: + f.write(txt) + +def replaceWordInFile(fileName, sFrom, sTo): + if sFrom == sTo: + return + txt = None + with open(fileName, "r") as f: + txt = f.read() + + Replacements = {'Any' : False } + + def callback(match): + Replacements['Any'] = True + return match.expand(r'\1%s\2' % sTo) + + txt = re.sub(r'(^|\s|{0}){1}($|\s|{0})'.format(r'[":;\(\)<>=\.,/\\`\`\[\]]', re.escape(sFrom)), callback, txt) + + if Replacements['Any']: + print("Replacing '%s' -> '%s' in '%s'..." % (sFrom, sTo, fileName)) + with open(fileName, "w") as f: + f.write(txt) def replaceInFile(fileName, sFrom, sTo): if sFrom == sTo: @@ -49,17 +82,38 @@ '-' * max(0, 42 - len(os.path.basename(filename))), '*- C++ -*-===//']) +def replaceHeaderComment(newHeader): + txt = open(newHeader, "r").read() + txt = re.sub(r'//===---[ -][^-.].+', + generateCommentLineHeader(newHeader), txt) + open(newHeader, "w").write(txt) def generateCommentLineSource(filename): return ''.join(['//===--- ', os.path.basename(filename), - ' - clang-tidy', - '-' * max(0, 52 - len(os.path.basename(filename))), + ' - clang-tidy ', + '-' * max(0, 51 - len(os.path.basename(filename))), '-===//']) +def replaceSourceComment(newSource): + txt = open(newSource, "r").read() + txt = re.sub(r'//===---[ -][^-.].+', + generateCommentLineSource(newSource), txt) + open(newSource, "w").write(txt) + +def replaceHeaderGuard(newHeader, new_module, new_check_name_camel): + txt = open(newHeader, "r").read() + newGuard = '_'.join(['LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY',new_module.upper(), new_check_name_camel.upper(), 'H']) + + txt = re.sub(r'#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY.+', r'#ifndef %s' % newGuard, txt) + txt = re.sub(r'#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY.+', r'#define %s' % newGuard, txt) + txt = re.sub(r'#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY.+', r'#endif // %s' % newGuard, txt) + + open(newHeader, "w").write(txt) def fileRename(fileName, sFrom, sTo): - if sFrom not in fileName or sFrom == sTo: + namePart = os.path.split(fileName)[1] + if not namePart.startswith(sFrom) or sFrom == sTo: return fileName newFileName = fileName.replace(sFrom, sTo) print("Renaming '%s' -> '%s'..." % (fileName, newFileName)) @@ -93,34 +147,30 @@ 'clang-tidy', 'checks', '*')) return [filename for filename in files if os.path.isfile(filename)] -# Adapts the module's CMakelist file. Returns 'True' if it could add a new entry -# and 'False' if the entry already existed. -def adapt_cmake(module_path, check_name_camel): - filename = os.path.join(module_path, 'CMakeLists.txt') - with open(filename, 'r') as f: - lines = f.readlines() +def getHeaderFile(clang_tidy_path, module_name, check_name): + return os.path.join - cpp_file = check_name_camel + '.cpp' +def getClangTidyFiles(clang_tidy_path): + files = glob.glob(os.path.join(clang_tidy_path, '*')) + for dirname in files: + if os.path.isdir(dirname): + files += glob.glob(os.path.join(dirname, '*')) + return [filename for filename in files if os.path.isfile(filename)] - # Figure out whether this check already exists. - for line in lines: - if line.strip() == cpp_file: - return False +def getDocFiles(clang_tidy_path): + files = glob.glob(os.path.join(clang_tidy_path, '..', 'docs', + 'clang-tidy', 'checks', '*')) + return [filename for filename in files if os.path.isfile(filename)] - print('Updating %s...' % filename) - with open(filename, 'wb') as f: - cpp_found = False - file_added = False - for line in lines: - cpp_line = line.strip().endswith('.cpp') - if (not file_added) and (cpp_line or cpp_found): - cpp_found = True - if (line.strip() > cpp_file) or (not cpp_line): - f.write(' ' + cpp_file + '\n') - file_added = True - f.write(line) +def getTestFiles(clang_tidy_path): + files = glob.glob(os.path.join(clang_tidy_path, '..', 'test', + 'clang-tidy', 'checkers', '*')) + return [filename for filename in files if os.path.isfile(filename)] - return True +def getUnitTestFiles(clang_tidy_path): + files = glob.glob(os.path.join(clang_tidy_path, '..', 'unittests', + 'clang-tidy', '*')) + return [filename for filename in files if os.path.isfile(filename)] # Modifies the module to include the new check. def adapt_module(module_path, module, check_name, check_name_camel): @@ -238,13 +288,6 @@ clang_tidy_path = os.path.dirname(__file__) - header_guard_variants = [ - (args.old_check_name.replace('-', '_')).upper() + '_CHECK', - (old_module + '_' + check_name_camel).upper(), - (old_module + '_' + new_check_name_camel).upper(), - args.old_check_name.replace('-', '_').upper()] - header_guard_new = (new_module + '_' + new_check_name_camel).upper() - old_module_path = os.path.join(clang_tidy_path, old_module) new_module_path = os.path.join(clang_tidy_path, new_module) @@ -263,30 +306,51 @@ deleteMatchingLines(os.path.join(old_module_path, modulecpp), '\\b' + check_name_camel + '|\\b' + args.old_check_name) - for filename in getListOfFiles(clang_tidy_path): - originalName = filename - filename = fileRename(filename, args.old_check_name, - args.new_check_name) - filename = fileRename(filename, check_name_camel, new_check_name_camel) - replaceInFile(filename, generateCommentLineHeader(originalName), - generateCommentLineHeader(filename)) - replaceInFile(filename, generateCommentLineSource(originalName), - generateCommentLineSource(filename)) - for header_guard in header_guard_variants: - replaceInFile(filename, header_guard, header_guard_new) - - if args.new_check_name + '.rst' in filename: - replaceInFile( - filename, + ImplHeader = os.path.join(old_module_path, check_name_camel + ".h") + ImplSource = os.path.join(old_module_path, check_name_camel + ".cpp") + if os.path.isfile(ImplHeader) and os.path.isfile(ImplSource): + NewHeader = os.path.join(new_module_path, new_check_name_camel + ".h") + NewSource = os.path.join(new_module_path, new_check_name_camel + ".cpp") + os.rename(ImplHeader, NewHeader) + os.rename(ImplSource, NewSource) + replaceHeaderComment(NewHeader) + replaceSourceComment(NewSource) + replaceHeaderGuard(NewHeader, new_module, new_check_name_camel) + else: + print("Check implementation files not found:\nHeader: {}\nSource: {}".format(ImplHeader, ImplSource)) + + DocFolder = os.path.join(clang_tidy_path, '..', 'docs', + 'clang-tidy', 'checks'); + + DocFile = os.path.join(DocFolder, args.old_check_name + ".rst") + NewDocFile = os.path.join(DocFolder, args.new_check_name + ".rst") + os.rename(DocFile, NewDocFile) + replaceInFile( + NewDocFile, args.old_check_name + '\n' + '=' * len(args.old_check_name) + '\n', args.new_check_name + '\n' + '=' * len(args.new_check_name) + '\n') - replaceInFile(filename, args.old_check_name, args.new_check_name) - replaceInFile(filename, old_module + '::' + check_name_camel, + for filename in getDocFiles(clang_tidy_path): + replaceWordInFile(filename, args.old_check_name, args.new_check_name) + + for filename in getClangTidyFiles(clang_tidy_path): + replaceWordInFile(filename, args.old_check_name, args.new_check_name) + replaceWordInFile(filename, old_module + '::' + check_name_camel, new_module + '::' + new_check_name_camel) - replaceInFile(filename, old_module + '/' + check_name_camel, + replaceWordInFile(filename, old_module + '/' + check_name_camel, new_module + '/' + new_check_name_camel) - replaceInFile(filename, check_name_camel, new_check_name_camel) + replaceWordInFile(filename, check_name_camel, new_check_name_camel) + + for filename in getTestFiles(clang_tidy_path): + replaceWordInFile(filename, args.old_check_name, args.new_check_name) + + for filename in getUnitTestFiles(clang_tidy_path): + replaceWordInFile(filename, args.old_check_name, args.new_check_name) + replaceWordInFile(filename, old_module + '::' + check_name_camel, + new_module + '::' + new_check_name_camel) + replaceWordInFile(filename, old_module + '/' + check_name_camel, + new_module + '/' + new_check_name_camel) + replaceWordInFile(filename, check_name_camel, new_check_name_camel) if old_module != new_module or new_module == 'llvm': if new_module == 'llvm': @@ -308,9 +372,7 @@ adapt_cmake(new_module_path, new_check_name_camel) adapt_module(new_module_path, new_module, args.new_check_name, new_check_name_camel) - - os.system(os.path.join(clang_tidy_path, 'add_new_check.py') - + ' --update-docs') + update_checks_list(clang_tidy_path) add_release_notes(clang_tidy_path, args.old_check_name, args.new_check_name) if __name__ == '__main__': diff --git a/clang-tools-extra/clang-tidy/utils/__init__.py b/clang-tools-extra/clang-tidy/utils/__init__.py new file mode 100644 diff --git a/clang-tools-extra/clang-tidy/utils/modify_check.py b/clang-tools-extra/clang-tidy/utils/modify_check.py new file mode 100755 --- /dev/null +++ b/clang-tools-extra/clang-tidy/utils/modify_check.py @@ -0,0 +1,122 @@ +import os +import re + +def get_camel_name(check_name): + return ''.join(map(lambda elem: elem.capitalize(), + check_name.split('-'))) + 'Check' + +# Adapts the module's CMakelist file. Returns 'True' if it could add a new entry +# and 'False' if the entry already existed. +def adapt_cmake(module_path, check_name_camel): + filename = os.path.join(module_path, 'CMakeLists.txt') + with open(filename, 'r') as f: + lines = f.readlines() + + cpp_file = check_name_camel + '.cpp' + + # Figure out whether this check already exists. + for line in lines: + if line.strip() == cpp_file: + return False + + print('Updating %s...' % filename) + with open(filename, 'w') as f: + cpp_found = False + file_added = False + for line in lines: + cpp_line = line.strip().endswith('.cpp') + if (not file_added) and (cpp_line or cpp_found): + cpp_found = True + if (line.strip() > cpp_file) or (not cpp_line): + f.write(' ' + cpp_file + '\n') + file_added = True + f.write(line) + + return True + +# Recreates the list of checks in the docs/clang-tidy/checks directory. +def update_checks_list(clang_tidy_path): + docs_dir = os.path.join(clang_tidy_path, '../docs/clang-tidy/checks') + filename = os.path.normpath(os.path.join(docs_dir, 'list.rst')) + # Read the content of the current list.rst file + with open(filename, 'r') as f: + lines = f.readlines() + # Get all existing docs + doc_files = list(filter(lambda s: s.endswith('.rst') and s != 'list.rst', + os.listdir(docs_dir))) + doc_files.sort() + + def has_auto_fix(check_name): + dirname, _, check_name = check_name.partition("-") + + checkerCode = os.path.join(dirname, get_camel_name(check_name)) + ".cpp" + + if not os.path.isfile(checkerCode): + return "" + + with open(checkerCode) as f: + code = f.read() + if 'FixItHint' in code or "ReplacementText" in code or "fixit" in code: + # Some simple heuristics to figure out if a checker has an autofix or not. + return ' "Yes"' + return "" + + def process_doc(doc_file): + check_name = doc_file.replace('.rst', '') + + with open(os.path.join(docs_dir, doc_file), 'r') as doc: + content = doc.read() + match = re.search('.*:orphan:.*', content) + + if match: + # Orphan page, don't list it. + return '', '' + + match = re.search('.*:http-equiv=refresh: \d+;URL=(.*).html.*', + content) + # Is it a redirect? + return check_name, match + + def format_link(doc_file): + check_name, match = process_doc(doc_file) + if not match and check_name: + return ' `%(check)s <%(check)s.html>`_,%(autofix)s\n' % { + 'check': check_name, + 'autofix': has_auto_fix(check_name) + } + else: + return '' + + def format_link_alias(doc_file): + check_name, match = process_doc(doc_file) + if match and check_name: + if match.group(1) == 'https://clang.llvm.org/docs/analyzer/checkers': + title_redirect = 'Clang Static Analyzer' + else: + title_redirect = match.group(1) + # The checker is just a redirect. + return ' `%(check)s <%(check)s.html>`_, `%(title)s <%(target)s.html>`_,%(autofix)s\n' % { + 'check': check_name, + 'target': match.group(1), + 'title': title_redirect, + 'autofix': has_auto_fix(match.group(1)) + } + return '' + + checks = map(format_link, doc_files) + checks_alias = map(format_link_alias, doc_files) + + print('Updating %s...' % filename) + with open(filename, 'w') as f: + for line in lines: + f.write(line) + if line.strip() == ".. csv-table::": + # We dump the checkers + f.write(' :header: "Name", "Offers fixes"\n\n') + f.writelines(checks) + # and the aliases + f.write('\n\n') + f.write('.. csv-table:: Aliases..\n') + f.write(' :header: "Name", "Redirect", "Offers fixes"\n\n') + f.writelines(checks_alias) + break