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 @@ -1,12 +1,12 @@ #!/usr/bin/env python # -#===- add_new_check.py - clang-tidy check generator ----------*- python -*--===# +# ===- add_new_check.py - clang-tidy check generator ---------*- python -*--===# # # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. # See https://llvm.org/LICENSE.txt for license information. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception # -#===------------------------------------------------------------------------===# +# ===-----------------------------------------------------------------------===# from __future__ import print_function @@ -14,35 +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 +import utils # Adds a header for the new check. @@ -51,13 +23,8 @@ filename = os.path.join(module_path, check_name_camel) + '.h' print('Creating %s...' % filename) with open(filename, 'w') as f: - header_guard = ('LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_' + module.upper() + '_' - + check_name_camel.upper() + '_H') - f.write('//===--- ') - f.write(os.path.basename(filename)) - f.write(' - clang-tidy ') - f.write('-' * max(0, 42 - len(os.path.basename(filename)))) - f.write('*- C++ -*-===//') + header_guard = utils.get_header_guard(module, check_name_camel) + f.write(utils.generate_comment_line_header(filename)) f.write(""" // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. @@ -100,15 +67,11 @@ # Adds the implementation of the new check. -def write_implementation(module_path, module, namespace, check_name_camel): +def writeImplementation(module_path, module, namespace, check_name_camel): filename = os.path.join(module_path, check_name_camel) + '.cpp' print('Creating %s...' % filename) with open(filename, 'w') as f: - f.write('//===--- ') - f.write(os.path.basename(filename)) - f.write(' - clang-tidy ') - f.write('-' * max(0, 51 - len(os.path.basename(filename)))) - f.write('-===//') + f.write(utils.generate_comment_line_source(filename)) f.write(""" // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. @@ -153,10 +116,10 @@ # Modifies the module to include the new check. def adapt_module(module_path, module, check_name, check_name_camel): - modulecpp = list(filter( + module_cpp = list(filter( lambda p: p.lower() == module.lower() + 'tidymodule.cpp', os.listdir(module_path)))[0] - filename = os.path.join(module_path, modulecpp) + filename = os.path.join(module_path, module_cpp) with open(filename, 'r') as f: lines = f.readlines() @@ -166,8 +129,8 @@ header_found = False check_added = False check_fq_name = module + '-' + check_name - check_decl = (' CheckFactories.registerCheck<' + check_name_camel + - '>(\n "' + check_fq_name + '");\n') + check_decl = (' CheckFactories.registerCheck<' + check_name_camel + + '>(\n "' + check_fq_name + '");\n') lines = iter(lines) try: @@ -189,7 +152,7 @@ check_added = True f.write(check_decl) else: - match = re.search('registerCheck<(.*)> *\( *(?:"([^"]*)")?', line) + match = re.search(r'registerCheck<(.*)> *\( *(?:"([^"]*)")?', line) prev_line = None if match: current_check_name = match.group(2) @@ -219,29 +182,27 @@ with open(filename, 'r') as f: lines = f.readlines() - lineMatcher = re.compile('New checks') - nextSectionMatcher = re.compile('New check aliases') - checkMatcher = re.compile('- New :doc:`(.*)') + line_matcher = re.compile('New checks') + next_selection_matcher = re.compile('New check aliases') + check_matcher = re.compile('- New :doc:`(.*)') print('Updating %s...' % filename) with open(filename, 'w') as f: note_added = False header_found = False - next_header_found = False add_note_here = False for line in lines: if not note_added: - match = lineMatcher.match(line) - match_next = nextSectionMatcher.match(line) - match_check = checkMatcher.match(line) + match = line_matcher.match(line) + match_next = next_selection_matcher.match(line) + match_check = check_matcher.match(line) if match_check: last_check = match_check.group(1) if last_check > check_name_dashes: add_note_here = True if match_next: - next_header_found = True add_note_here = True if match: @@ -269,15 +230,18 @@ # Adds a test for the check. def write_test(module_path, module, check_name, test_extension): check_name_dashes = module + '-' + check_name - filename = os.path.normpath(os.path.join(module_path, '../../test/clang-tidy/checkers', - check_name_dashes + '.' + test_extension)) + filename = os.path.normpath(os.path.join(module_path, + '../../test/clang-tidy/checkers', + check_name_dashes + + '.' + test_extension)) print('Creating %s...' % filename) with open(filename, 'w') as f: f.write("""// RUN: %%check_clang_tidy %%s %(check_name_dashes)s %%t // FIXME: Add something that triggers the check here. void f(); -// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'f' is insufficiently awesome [%(check_name_dashes)s] +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'f' is insufficiently \ +awesome [%(check_name_dashes)s] // FIXME: Verify the applied fix. // * Make the CHECK patterns specific enough and try to make verified lines @@ -290,94 +254,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 +271,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', @@ -408,10 +279,6 @@ 'objc++': 'mm', } parser = argparse.ArgumentParser() - parser.add_argument( - '--update-docs', - action='store_true', - help='just update the list of documentation files, then exit') parser.add_argument( '--language', help='language to use for new check (defaults to c++)', @@ -421,25 +288,22 @@ parser.add_argument( 'module', nargs='?', - help='module directory under which to place the new tidy check (e.g., misc)') + help='module directory under which to ' + 'place the new tidy check (e.g., misc)') parser.add_argument( 'check', nargs='?', help='name of new tidy check to add (e.g. foo-do-the-stuff)') args = parser.parse_args() - if args.update_docs: - update_checks_list(os.path.dirname(sys.argv[0])) - return - if not args.module or not args.check: - print('Module and check must be specified.') + print('module and check must be specified.') parser.print_usage() return module = args.module check_name = args.check - check_name_camel = get_camel_name(check_name) + check_name_camel = utils.get_camel_name(check_name) if check_name.startswith(module): print('Check name "%s" must not start with the module "%s". Exiting.' % ( check_name, module)) @@ -447,23 +311,24 @@ clang_tidy_path = os.path.dirname(sys.argv[0]) module_path = os.path.join(clang_tidy_path, module) - if not adapt_cmake(module_path, check_name_camel): + if not utils.adapt_cmake(module_path, check_name_camel): return - # Map module names to namespace names that don't conflict with widely used top-level namespaces. + # Map module names to namespace names that don't conflict with widely used + # top-level namespaces. if module == 'llvm': - namespace = module + '_check' + Namespace = module + '_check' else: - namespace = module + Namespace = module - write_header(module_path, module, namespace, check_name, check_name_camel) - write_implementation(module_path, module, namespace, check_name_camel) + write_header(module_path, module, Namespace, check_name, check_name_camel) + writeImplementation(module_path, module, Namespace, check_name_camel) adapt_module(module_path, module, check_name, check_name_camel) add_release_notes(module_path, module, check_name) test_extension = language_to_extension.get(args.language) write_test(module_path, module, check_name, test_extension) write_docs(module_path, module, check_name) - update_checks_list(clang_tidy_path) + utils.update_checks_list(clang_tidy_path) print('Done. Now it\'s your turn!') 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 @@ -1,132 +1,183 @@ #!/usr/bin/env python # -#===- rename_check.py - clang-tidy check renamer -------------*- python -*--===# +# ===- rename_check.py - clang-tidy check renamer ------------*- python -*--===# # # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. # See https://llvm.org/LICENSE.txt for license information. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception # -#===------------------------------------------------------------------------===# +# ===-----------------------------------------------------------------------===# import argparse import glob import os import re +import utils -def replaceInFileRegex(fileName, sFrom, sTo): - if sFrom == sTo: +def replace_in_file_regex(filename, s_from, s_to): + if s_from == s_to: return - txt = None - with open(fileName, "r") as f: - txt = f.read() + code = None + with open(filename, "r") as f: + code = f.read() - txt = re.sub(sFrom, sTo, txt) - print("Replacing '%s' -> '%s' in '%s'..." % (sFrom, sTo, fileName)) - with open(fileName, "w") as f: - f.write(txt) + # hack that prevents using nonlocal to get the return from the callback + replacements = {'Any': False} -def replaceInFile(fileName, sFrom, sTo): - if sFrom == sTo: + def callback(match): + replacements['Any'] = True + act_to = match.expand(s_to) + print("Replacing '%s' -> '%s' in '%s'..." % (match.group(), + act_to, + filename)) + return act_to + + code = re.sub(s_from, callback, code) + + if replacements['Any']: + with open(filename, "w") as f: + f.write(code) + + +def replace_word_in_file(filename, s_from, s_to): + if s_from == s_to: return - txt = None - with open(fileName, "r") as f: - txt = f.read() + code = None + with open(filename, "r") as f: + code = f.read() + + replacements = {'Any': False} - if sFrom not in txt: + def callback(match): + replacements['Any'] = True + return match.expand(r'\1%s\2' % s_to) + + code = re.sub(r'(^|\s|{0}){1}($|\s|{0})'.format(r'[":;\(\)<>=\.,/\\`\`\[\]]', + re.escape(s_from)), + callback, code) + + if replacements['Any']: + print("Replacing '%s' -> '%s' in '%s'..." % (s_from, s_to, filename)) + with open(filename, "w") as f: + f.write(code) + + +def replace_in_file(filename, s_from, s_to): + if s_from == s_to: return + code = None + with open(filename, "r") as f: + code = f.read() + + if s_from not in code: + return + + code = code.replace(s_from, s_to) + print("Replacing '%s' -> '%s' in '%s'..." % (s_from, s_to, filename)) + with open(filename, "w") as f: + f.write(code) + + +def replace_header_comment(new_header): + with open(new_header, "r") as f: + code = f.read() + + code = re.sub(r'//===---[ -][^-.].+', + utils.generate_comment_line_header(new_header), + code) + + with open(new_header, "w") as f: + f.write(code) + + +def replace_source_comment(new_source): + with open(new_source, "r") as f: + code = f.read() + + code = re.sub(r'//===---[ -][^-.].+', + utils.generate_comment_line_source(new_source), + code) - txt = txt.replace(sFrom, sTo) - print("Replacing '%s' -> '%s' in '%s'..." % (sFrom, sTo, fileName)) - with open(fileName, "w") as f: - f.write(txt) + with open(new_source, "w") as f: + f.write(code) -def generateCommentLineHeader(filename): - return ''.join(['//===--- ', - os.path.basename(filename), - ' - clang-tidy ', - '-' * max(0, 42 - len(os.path.basename(filename))), - '*- C++ -*-===//']) +def replace_header_guard(new_header, new_module, new_check_name_camel): + with open(new_header, "r") as f: + code = f.read() + new_guard = utils.get_header_guard(new_module, new_check_name_camel) -def generateCommentLineSource(filename): - return ''.join(['//===--- ', - os.path.basename(filename), - ' - clang-tidy', - '-' * max(0, 52 - len(os.path.basename(filename))), - '-===//']) + code = re.sub(r'#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY.+', + r'#ifndef %s' % new_guard, code) + code = re.sub(r'#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY.+', + r'#define %s' % new_guard, code) + code = re.sub(r'#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY.+', + r'#endif // %s' % new_guard, code) + with open(new_header, "w") as f: + f.write(code) -def fileRename(fileName, sFrom, sTo): - if sFrom not in fileName or sFrom == sTo: - return fileName - newFileName = fileName.replace(sFrom, sTo) - print("Renaming '%s' -> '%s'..." % (fileName, newFileName)) - os.rename(fileName, newFileName) - return newFileName -def deleteMatchingLines(fileName, pattern): +def file_rename(filename, s_from, s_to): + name_part = os.path.split(filename)[1] + if not name_part.startswith(s_from) or s_from == s_to: + return filename + new_filename = filename.replace(s_from, s_to) + print("Renaming '%s' -> '%s'..." % (filename, new_filename)) + os.rename(filename, new_filename) + return new_filename + + +def delete_matching_lines(filename, Pattern): lines = None - with open(fileName, "r") as f: + with open(filename, "r") as f: lines = f.readlines() - not_matching_lines = [l for l in lines if not re.search(pattern, l)] + not_matching_lines = [line for line in lines if not re.search(Pattern, line)] if len(not_matching_lines) == len(lines): return False - print("Removing lines matching '%s' in '%s'..." % (pattern, fileName)) - print(' ' + ' '.join([l for l in lines if re.search(pattern, l)])) - with open(fileName, "w") as f: + print("Removing lines matching '%s' in '%s'..." % (Pattern, filename)) + print(' ' + ' '.join([line for line in lines if re.search(Pattern, line)])) + with open(filename, "w") as f: f.writelines(not_matching_lines) return True -def getListOfFiles(clang_tidy_path): + +def get_clang_tidy_files(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, '*')) - files += glob.glob(os.path.join(clang_tidy_path, '..', 'test', - 'clang-tidy', '*')) - files += glob.glob(os.path.join(clang_tidy_path, '..', 'docs', - '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() - cpp_file = check_name_camel + '.cpp' +def get_doc_files(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)] - # 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, '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 get_test_files(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)] + + +def get_unit_test_files(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)] - return True # Modifies the module to include the new check. def adapt_module(module_path, module, check_name, check_name_camel): - modulecpp = filter(lambda p: p.lower() == module.lower() + 'tidymodule.cpp', - os.listdir(module_path))[0] - filename = os.path.join(module_path, modulecpp) + module_cpp = filter(lambda p: p.lower() == module.lower() + 'tidymodule.cpp', + os.listdir(module_path))[0] + filename = os.path.join(module_path, module_cpp) with open(filename, 'r') as f: lines = f.readlines() @@ -135,8 +186,8 @@ header_added = False header_found = False check_added = False - check_decl = (' CheckFactories.registerCheck<' + check_name_camel + - '>(\n "' + check_name + '");\n') + check_decl = (' CheckFactories.registerCheck<' + check_name_camel + + '>(\n "' + check_name + '");\n') for line in lines: if not header_added: @@ -169,29 +220,27 @@ with open(filename, 'r') as f: lines = f.readlines() - lineMatcher = re.compile('Renamed checks') - nextSectionMatcher = re.compile('Improvements to include-fixer') - checkMatcher = re.compile('- The \'(.*)') + line_matcher = re.compile('Renamed checks') + next_selection_matcher = re.compile('Improvements to include-fixer') + check_matcher = re.compile('- The \'(.*)') print('Updating %s...' % filename) with open(filename, 'wb') as f: note_added = False header_found = False - next_header_found = False add_note_here = False for line in lines: if not note_added: - match = lineMatcher.match(line) - match_next = nextSectionMatcher.match(line) - match_check = checkMatcher.match(line) + match = line_matcher.match(line) + match_next = next_selection_matcher.match(line) + match_check = check_matcher.match(line) if match_check: last_check = match_check.group(1) if last_check > old_check_name: add_note_here = True if match_next: - next_header_found = True add_note_here = True if match: @@ -213,6 +262,7 @@ f.write(line) + def main(): parser = argparse.ArgumentParser(description='Rename clang-tidy check.') parser.add_argument('old_check_name', type=str, @@ -229,64 +279,81 @@ check_name_camel = args.check_class_name else: check_name_camel = (''.join(map(lambda elem: elem.capitalize(), - args.old_check_name.split('-')[1:])) + - 'Check') + args.old_check_name.split('-')[1:])) + + 'Check') new_check_name_camel = (''.join(map(lambda elem: elem.capitalize(), - args.new_check_name.split('-')[1:])) + - 'Check') + args.new_check_name.split('-')[1:])) + + 'Check') 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) if (args.old_check_name != args.new_check_name): # Remove the check from the old module. - cmake_lists = os.path.join(old_module_path, 'CMakeLists.txt') - check_found = deleteMatchingLines(cmake_lists, '\\b' + check_name_camel) + c_make_lists = os.path.join(old_module_path, 'CMakeLists.txt') + check_found = delete_matching_lines(c_make_lists, '\\b' + check_name_camel) if not check_found: print("Check name '%s' not found in %s. Exiting." % - (check_name_camel, cmake_lists)) + (check_name_camel, c_make_lists)) return 1 - modulecpp = filter( + module_cpp = filter( lambda p: p.lower() == old_module.lower() + 'tidymodule.cpp', os.listdir(old_module_path))[0] - 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, - 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, - new_module + '::' + new_check_name_camel) - replaceInFile(filename, old_module + '/' + check_name_camel, - new_module + '/' + new_check_name_camel) - replaceInFile(filename, check_name_camel, new_check_name_camel) + delete_matching_lines(os.path.join(old_module_path, module_cpp), + '\\b' + check_name_camel + '|\\b' + + args.old_check_name) + + old_header = os.path.join(old_module_path, check_name_camel + ".h") + old_source = os.path.join(old_module_path, check_name_camel + ".cpp") + if os.path.isfile(old_header) and os.path.isfile(old_source): + new_header = os.path.join(new_module_path, new_check_name_camel + ".h") + new_source = os.path.join(new_module_path, new_check_name_camel + ".cpp") + os.rename(old_header, new_header) + os.rename(old_source, new_source) + replace_header_comment(new_header) + replace_source_comment(new_source) + replace_header_guard(new_header, new_module, new_check_name_camel) + else: + print("Check implementation Files not found:\nHeader: {}\nSource: {}" + .format(old_header, old_source)) + + doc_folder = os.path.join(clang_tidy_path, '..', 'docs', + 'clang-tidy', 'checks') + + old_doc_file = os.path.join(doc_folder, args.old_check_name + ".rst") + new_doc_file = os.path.join(doc_folder, args.new_check_name + ".rst") + os.rename(old_doc_file, new_doc_file) + replace_in_file(new_doc_file, + args.old_check_name + '\n' + + '=' * len(args.old_check_name) + '\n', + args.new_check_name + '\n' + + '=' * len(args.new_check_name) + '\n') + + for filename in get_doc_files(clang_tidy_path): + replace_word_in_file(filename, args.old_check_name, args.new_check_name) + + for filename in get_clang_tidy_files(clang_tidy_path): + replace_word_in_file(filename, args.old_check_name, args.new_check_name) + replace_word_in_file(filename, old_module + '::' + check_name_camel, + new_module + '::' + new_check_name_camel) + replace_word_in_file(filename, old_module + '/' + check_name_camel, + new_module + '/' + new_check_name_camel) + replace_word_in_file(filename, check_name_camel, new_check_name_camel) + + for filename in get_test_files(clang_tidy_path): + replace_word_in_file(filename, args.old_check_name, args.new_check_name) + + for filename in get_unit_test_files(clang_tidy_path): + replace_word_in_file(filename, args.old_check_name, args.new_check_name) + replace_word_in_file(filename, old_module + '::' + check_name_camel, + new_module + '::' + new_check_name_camel) + replace_word_in_file(filename, old_module + '/' + check_name_camel, + new_module + '/' + new_check_name_camel) + replace_word_in_file(filename, check_name_camel, new_check_name_camel) if old_module != new_module or new_module == 'llvm': if new_module == 'llvm': @@ -297,21 +364,20 @@ os.path.join(old_module_path, new_check_name_camel + '*')) for filename in check_implementation_files: # Move check implementation to the directory of the new module. - filename = fileRename(filename, old_module_path, new_module_path) - replaceInFileRegex(filename, 'namespace ' + old_module + '[^ \n]*', - 'namespace ' + new_namespace) + filename = file_rename(filename, old_module_path, new_module_path) + replace_in_file_regex(filename, 'namespace ' + old_module + '[^ \n]*', + 'namespace ' + new_namespace) if (args.old_check_name == args.new_check_name): return # Add check to the new module. - adapt_cmake(new_module_path, new_check_name_camel) + utils.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') + utils.update_checks_list(clang_tidy_path) add_release_notes(clang_tidy_path, args.old_check_name, args.new_check_name) + if __name__ == '__main__': 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 --- /dev/null +++ b/clang-tools-extra/clang-tidy/utils/__init__.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python +# +# ===- __init__.py - clang-tidy check generator utils --------*- python -*--===# +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ===-----------------------------------------------------------------------===# + +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: + lies = 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_autofix(check_name): + dir_name, _, check_name = check_name.partition("-") + + checker_code = os.path.join(dir_name, get_camel_name(check_name)) + ".cpp" + + if not os.path.isfile(checker_code): + return "" + + with open(checker_code) 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. + 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(r'.*: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_autofix(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_autofix(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 lies: + 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 + + +def get_header_guard(module, check_name_camel): + return '_'.join(['LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY', + module.upper(), + check_name_camel.upper(), + 'H']) + + +def generate_comment_line_header(filename): + return ''.join(['//===--- ', + os.path.basename(filename), + ' - clang-tidy ', + '-' * max(0, 42 - len(os.path.basename(filename))), + '*- C++ -*-===//']) + + +def generate_comment_line_source(filename): + return ''.join(['//===--- ', + os.path.basename(filename), + ' - clang-tidy ', + '-' * max(0, 51 - len(os.path.basename(filename))), + '-===//'])