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,51 +14,18 @@ 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.modify_check # Adds a header for the new check. -def write_header(module_path, module, namespace, check_name, check_name_camel): - check_name_dashes = module + '-' + check_name - 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++ -*-===//') - f.write(""" +def writeHeader(ModulePath, Module, Namespace, CheckName, CheckNameCamel): + CheckNameDashes = Module + '-' + CheckName + Filename = os.path.join(ModulePath, CheckNameCamel) + '.h' + print('Creating %s...' % Filename) + with open(Filename, 'w') as F: + HeaderGuard = utils.modify_check.getHeaderGuard(Module, CheckNameCamel) + F.write(utils.modify_check.generateCommentLineHeader(Filename)) + F.write(""" // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -92,24 +59,20 @@ } // namespace clang #endif // %(header_guard)s -""" % {'header_guard': header_guard, - 'check_name': check_name_camel, - 'check_name_dashes': check_name_dashes, - 'module': module, - 'namespace': namespace}) +""" % {'header_guard': HeaderGuard, + 'check_name': CheckNameCamel, + 'check_name_dashes': CheckNameDashes, + 'module': Module, + 'namespace': Namespace}) # Adds the implementation of the new check. -def write_implementation(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(""" +def writeImplementation(ModulePath, Module, Namespace, CheckNameCamel): + Filename = os.path.join(ModulePath, CheckNameCamel) + '.cpp' + print('Creating %s...' % Filename) + with open(Filename, 'w') as F: + F.write(utils.modify_check.generateCommentLineSource(Filename)) + F.write(""" // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -146,134 +109,134 @@ } // namespace %(namespace)s } // namespace tidy } // namespace clang -""" % {'check_name': check_name_camel, - 'module': module, - 'namespace': namespace}) +""" % {'check_name': CheckNameCamel, + '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( - lambda p: p.lower() == module.lower() + 'tidymodule.cpp', - os.listdir(module_path)))[0] - filename = os.path.join(module_path, modulecpp) - with open(filename, 'r') as f: - lines = f.readlines() - - print('Updating %s...' % filename) - with open(filename, 'w') as f: - header_added = False - header_found = False - check_added = False - check_fq_name = module + '-' + check_name - check_decl = (' CheckFactories.registerCheck<' + check_name_camel + - '>(\n "' + check_fq_name + '");\n') - - lines = iter(lines) +def adaptModule(ModulePath, Module, CheckName, CheckNameCamel): + ModuleCpp = list(filter( + lambda p: p.lower() == Module.lower() + 'tidymodule.cpp', + os.listdir(ModulePath)))[0] + Filename = os.path.join(ModulePath, ModuleCpp) + with open(Filename, 'r') as F: + Lines = F.readlines() + + print('Updating %s...' % Filename) + with open(Filename, 'w') as F: + HeaderAdded = False + HeaderFound = False + CheckAdded = False + CheckFqName = Module + '-' + CheckName + CHeckDecl = (' CheckFactories.registerCheck<' + CheckNameCamel + + '>(\n "' + CheckFqName + '");\n') + + Lines = iter(Lines) try: while True: - line = lines.next() - if not header_added: - match = re.search('#include "(.*)"', line) - if match: - header_found = True - if match.group(1) > check_name_camel: - header_added = True - f.write('#include "' + check_name_camel + '.h"\n') - elif header_found: - header_added = True - f.write('#include "' + check_name_camel + '.h"\n') - - if not check_added: - if line.strip() == '}': - check_added = True - f.write(check_decl) + Line = Lines.next() + if not HeaderAdded: + Match = re.search('#include "(.*)"', Line) + if Match: + HeaderFound = True + if Match.group(1) > CheckNameCamel: + HeaderAdded = True + F.write('#include "' + CheckNameCamel + '.h"\n') + elif HeaderFound: + HeaderAdded = True + F.write('#include "' + CheckNameCamel + '.h"\n') + + if not CheckAdded: + if Line.strip() == '}': + CheckAdded = True + F.write(CHeckDecl) else: - match = re.search('registerCheck<(.*)> *\( *(?:"([^"]*)")?', line) - prev_line = None - if match: - current_check_name = match.group(2) - if current_check_name is None: + Match = re.search(r'registerCheck<(.*)> *\( *(?:"([^"]*)")?', Line) + PrevLine = None + if Match: + CurrentCheckName = Match.group(2) + if CurrentCheckName is None: # If we didn't find the check name on this line, look on the # next one. - prev_line = line - line = lines.next() - match = re.search(' *"([^"]*)"', line) - if match: - current_check_name = match.group(1) - if current_check_name > check_fq_name: - check_added = True - f.write(check_decl) - if prev_line: - f.write(prev_line) - f.write(line) + PrevLine = Line + Line = Lines.next() + Match = re.search(' *"([^"]*)"', Line) + if Match: + CurrentCheckName = Match.group(1) + if CurrentCheckName > CheckFqName: + CheckAdded = True + F.write(CHeckDecl) + if PrevLine: + F.write(PrevLine) + F.write(Line) except StopIteration: pass # Adds a release notes entry. -def add_release_notes(module_path, module, check_name): - check_name_dashes = module + '-' + check_name - filename = os.path.normpath(os.path.join(module_path, +def addReleaseNotes(ModulePath, Module, CheckName): + CheckNameDashes = Module + '-' + CheckName + Filename = os.path.normpath(os.path.join(ModulePath, '../../docs/ReleaseNotes.rst')) - 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:`(.*)') - - 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) - 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: - header_found = True - f.write(line) + 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:`(.*)') + + print('Updating %s...' % Filename) + with open(Filename, 'w') as F: + NoteAdded = False + HeaderFound = False + AddNoteHere = False + + for Line in Lines: + if not NoteAdded: + Match = LineMatcher.match(Line) + MatchNext = NextSectionMatcher.match(Line) + MatchCheck = CheckMatcher.match(Line) + if MatchCheck: + LastCheck = MatchCheck.group(1) + if LastCheck > CheckNameDashes: + AddNoteHere = True + + if MatchNext: + AddNoteHere = True + + if Match: + HeaderFound = True + F.write(Line) continue - if line.startswith('^^^^'): - f.write(line) + if Line.startswith('^^^^'): + F.write(Line) continue - if header_found and add_note_here: - if not line.startswith('^^^^'): - f.write("""- New :doc:`%s + if HeaderFound and AddNoteHere: + if not Line.startswith('^^^^'): + F.write("""- New :doc:`%s ` check. FIXME: add release notes. -""" % (check_name_dashes, check_name_dashes)) - note_added = True +""" % (CheckNameDashes, CheckNameDashes)) + NoteAdded = True - f.write(line) + F.write(Line) # 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)) - print('Creating %s...' % filename) - with open(filename, 'w') as f: - f.write("""// RUN: %%check_clang_tidy %%s %(check_name_dashes)s %%t +def writeTest(ModulePath, Module, CheckName, TestExtension): + CheckNameDashes = Module + '-' + CheckName + Filename = os.path.normpath(os.path.join(ModulePath, + '../../test/clang-tidy/checkers', + CheckNameDashes + + '.' + TestExtension)) + 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(); @@ -287,183 +250,84 @@ // FIXME: Add something that doesn't trigger the check here. void awesome_f2(); -""" % {'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 +""" % {'check_name_dashes': CheckNameDashes}) # Adds a documentation for the check. -def write_docs(module_path, module, check_name): - check_name_dashes = module + '-' + check_name - filename = os.path.normpath(os.path.join( - module_path, '../../docs/clang-tidy/checks/', check_name_dashes + '.rst')) - print('Creating %s...' % filename) - with open(filename, 'w') as f: - f.write(""".. title:: clang-tidy - %(check_name_dashes)s +def writeDocs(ModulePath, Module, CheckName): + CheckNameDashes = Module + '-' + CheckName + Filename = os.path.normpath(os.path.join( + ModulePath, '../../docs/clang-tidy/checks/', CheckNameDashes + '.rst')) + print('Creating %s...' % Filename) + with open(Filename, 'w') as F: + F.write(""".. title:: clang-tidy - %(check_name_dashes)s %(check_name_dashes)s %(underline)s FIXME: Describe what patterns does the check detect and why. Give examples. -""" % {'check_name_dashes': check_name_dashes, - 'underline': '=' * len(check_name_dashes)}) - - -def get_camel_name(check_name): - return ''.join(map(lambda elem: elem.capitalize(), - check_name.split('-'))) + 'Check' +""" % {'check_name_dashes': CheckNameDashes, + 'underline': '=' * len(CheckNameDashes)}) def main(): - language_to_extension = { + LanguageToExtension = { 'c': 'c', 'c++': 'cpp', 'objc': 'm', '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( + Parser = argparse.ArgumentParser() + Parser.add_argument( '--language', help='language to use for new check (defaults to c++)', - choices=language_to_extension.keys(), + choices=LanguageToExtension.keys(), default='c++', metavar='LANG') - parser.add_argument( + Parser.add_argument( 'module', nargs='?', - help='module directory under which to place the new tidy check (e.g., misc)') - parser.add_argument( + 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 + Args = Parser.parse_args() - if not args.module or not args.check: + if not Args.module or not Args.check: print('Module and check must be specified.') - parser.print_usage() + Parser.print_usage() return - module = args.module - check_name = args.check - check_name_camel = get_camel_name(check_name) - if check_name.startswith(module): + Module = Args.module + CheckName = Args.check + CheckNameCamel = utils.modify_check.getCamelName(CheckName) + if CheckName.startswith(Module): print('Check name "%s" must not start with the module "%s". Exiting.' % ( - check_name, module)) + CheckName, Module)) return - clang_tidy_path = os.path.dirname(sys.argv[0]) - module_path = os.path.join(clang_tidy_path, module) + ClangTidyPath = os.path.dirname(sys.argv[0]) + ModulePath = os.path.join(ClangTidyPath, Module) - if not adapt_cmake(module_path, check_name_camel): + if not utils.modify_check.adaptCmake(ModulePath, CheckNameCamel): return - # Map module names to namespace names that don't conflict with widely used top-level namespaces. - if module == 'llvm': - namespace = module + '_check' + # Map module names to namespace names that don't conflict with widely used + # top-level namespaces. + if Module == 'llvm': + Namespace = Module + '_check' else: - namespace = module - - write_header(module_path, module, namespace, check_name, check_name_camel) - write_implementation(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) + Namespace = Module + + writeHeader(ModulePath, Module, Namespace, CheckName, CheckNameCamel) + writeImplementation(ModulePath, Module, Namespace, CheckNameCamel) + adaptModule(ModulePath, Module, CheckName, CheckNameCamel) + addReleaseNotes(ModulePath, Module, CheckName) + TestExtension = LanguageToExtension.get(Args.language) + writeTest(ModulePath, Module, CheckName, TestExtension) + writeDocs(ModulePath, Module, CheckName) + utils.modify_check.updateChecksList(ClangTidyPath) 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,317 +1,378 @@ #!/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.modify_check -def replaceInFileRegex(fileName, sFrom, sTo): - if sFrom == sTo: +def replaceInFileRegex(Filename, SFrom, STo): + if SFrom == STo: 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 + ActTo = Match.expand(STo) + print("Replacing '%s' -> '%s' in '%s'..." % (Match.group(), ActTo, Filename)) + return ActTo + + Code = re.sub(SFrom, callback, Code) + + if Replacements['Any']: + with open(Filename, "w") as F: + F.write(Code) + + +def replaceWordInFile(Filename, SFrom, STo): + if SFrom == STo: + return + Code = None + with open(Filename, "r") as F: + Code = F.read() + + Replacements = {'Any': False} + + def callback(Match): + Replacements['Any'] = True + return Match.expand(r'\1%s\2' % STo) + + Code = re.sub(r'(^|\s|{0}){1}($|\s|{0})'.format(r'[":;\(\)<>=\.,/\\`\`\[\]]', + re.escape(SFrom)), + callback, Code) + + if Replacements['Any']: + print("Replacing '%s' -> '%s' in '%s'..." % (SFrom, STo, Filename)) + with open(Filename, "w") as F: + F.write(Code) + + +def replaceInFile(Filename, SFrom, STo): + if SFrom == STo: return - txt = None - with open(fileName, "r") as f: - txt = f.read() + Code = None + with open(Filename, "r") as F: + Code = F.read() - if sFrom not in txt: + if SFrom not in Code: return - txt = txt.replace(sFrom, sTo) - print("Replacing '%s' -> '%s' in '%s'..." % (sFrom, sTo, fileName)) - with open(fileName, "w") as f: - f.write(txt) + Code = Code.replace(SFrom, STo) + print("Replacing '%s' -> '%s' in '%s'..." % (SFrom, STo, Filename)) + with open(Filename, "w") as F: + F.write(Code) + + +def replaceHeaderComment(NewHeader): + with open(NewHeader, "r") as F: + Code = F.read() + + Code = re.sub(r'//===---[ -][^-.].+', + utils.modify_check.generateCommentLineHeader(NewHeader), Code) + + with open(NewHeader, "w") as F: + F.write(Code) + +def replaceSourceComment(NewSource): + with open(NewSource, "r") as F: + Code = F.read() -def generateCommentLineHeader(filename): - return ''.join(['//===--- ', - os.path.basename(filename), - ' - clang-tidy ', - '-' * max(0, 42 - len(os.path.basename(filename))), - '*- C++ -*-===//']) + Code = re.sub(r'//===---[ -][^-.].+', + utils.modify_check.generateCommentLineSource(NewSource), Code) + with open(NewSource, "w") as F: + F.write(Code) -def generateCommentLineSource(filename): - return ''.join(['//===--- ', - os.path.basename(filename), - ' - clang-tidy', - '-' * max(0, 52 - len(os.path.basename(filename))), - '-===//']) +def replaceHeaderGuard(NewHeader, NewModule, NewCheckNameCamel): + with open(NewHeader, "r") as F: + Code = F.read() -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 + NewGuard = utils.modify_check.getHeaderGuard(NewModule, NewCheckNameCamel) -def deleteMatchingLines(fileName, pattern): - lines = None - with open(fileName, "r") as f: - lines = f.readlines() + Code = re.sub(r'#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY.+', + r'#ifndef %s' % NewGuard, Code) + Code = re.sub(r'#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY.+', + r'#define %s' % NewGuard, Code) + Code = re.sub(r'#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY.+', + r'#endif // %s' % NewGuard, Code) - not_matching_lines = [l for l in lines if not re.search(pattern, l)] - if len(not_matching_lines) == len(lines): + with open(NewHeader, "w") as F: + F.write(Code) + + +def fileRename(Filename, 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)) + os.rename(Filename, NewFileName) + return NewFileName + + +def deleteMatchingLines(Filename, Pattern): + Lines = None + with open(Filename, "r") as F: + Lines = F.readlines() + + NotMatchingLines = [Line for Line in Lines if not re.search(Pattern, Line)] + if len(NotMatchingLines) == 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: - f.writelines(not_matching_lines) + 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(NotMatchingLines) return True -def getListOfFiles(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' - - # 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) - return True +def getClangTidyFiles(ClangTidyPath): + Files = glob.glob(os.path.join(ClangTidyPath, '*')) + 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)] + + +def getDocFiles(ClangTidyPath): + Files = glob.glob(os.path.join(ClangTidyPath, '..', 'docs', + 'clang-tidy', 'checks', '*')) + return [Filename for Filename in Files if os.path.isfile(Filename)] + + +def getTestFiles(ClangTidyPath): + Files = glob.glob(os.path.join(ClangTidyPath, '..', 'test', + 'clang-tidy', 'checkers', '*')) + return [Filename for Filename in Files if os.path.isfile(Filename)] + + +def getUnitTestFiles(ClangTidyPath): + Files = glob.glob(os.path.join(ClangTidyPath, '..', '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): - modulecpp = filter(lambda p: p.lower() == module.lower() + 'tidymodule.cpp', - os.listdir(module_path))[0] - filename = os.path.join(module_path, modulecpp) - with open(filename, 'r') as f: - lines = f.readlines() - - print('Updating %s...' % filename) - with open(filename, 'wb') as f: - header_added = False - header_found = False - check_added = False - check_decl = (' CheckFactories.registerCheck<' + check_name_camel + - '>(\n "' + check_name + '");\n') - - for line in lines: - if not header_added: - match = re.search('#include "(.*)"', line) - if match: - header_found = True - if match.group(1) > check_name_camel: - header_added = True - f.write('#include "' + check_name_camel + '.h"\n') - elif header_found: - header_added = True - f.write('#include "' + check_name_camel + '.h"\n') - - if not check_added: - if line.strip() == '}': - check_added = True - f.write(check_decl) +def adaptModule(ModulePath, Module, CheckName, CheckNameCamel): + ModuleCpp = filter(lambda p: p.lower() == Module.lower() + 'tidymodule.cpp', + os.listdir(ModulePath))[0] + Filename = os.path.join(ModulePath, ModuleCpp) + with open(Filename, 'r') as F: + Lines = F.readlines() + + print('Updating %s...' % Filename) + with open(Filename, 'wb') as F: + HeaderAdded = False + HeaderFound = False + CheckAdded = False + CheckDecl = (' CheckFactories.registerCheck<' + CheckNameCamel + + '>(\n "' + CheckName + '");\n') + + for Line in Lines: + if not HeaderAdded: + Match = re.search('#include "(.*)"', Line) + if Match: + HeaderFound = True + if Match.group(1) > CheckNameCamel: + HeaderAdded = True + F.write('#include "' + CheckNameCamel + '.h"\n') + elif HeaderFound: + HeaderAdded = True + F.write('#include "' + CheckNameCamel + '.h"\n') + + if not CheckAdded: + if Line.strip() == '}': + CheckAdded = True + F.write(CheckDecl) else: - match = re.search('registerCheck<(.*)>', line) - if match and match.group(1) > check_name_camel: - check_added = True - f.write(check_decl) - f.write(line) + Match = re.search('registerCheck<(.*)>', Line) + if Match and Match.group(1) > CheckNameCamel: + CheckAdded = True + F.write(CheckDecl) + F.write(Line) # Adds a release notes entry. -def add_release_notes(clang_tidy_path, old_check_name, new_check_name): - filename = os.path.normpath(os.path.join(clang_tidy_path, +def addReleaseNotes(ClangTidyPath, OldCheckName, NewCheckName): + Filename = os.path.normpath(os.path.join(ClangTidyPath, '../docs/ReleaseNotes.rst')) - 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 \'(.*)') - - 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) - 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: - header_found = True - f.write(line) + with open(Filename, 'r') as F: + Lines = F.readlines() + + LineMatcher = re.compile('Renamed checks') + NextSelectionMatcher = re.compile('Improvements to include-fixer') + CheckMatcher = re.compile('- The \'(.*)') + + print('Updating %s...' % Filename) + with open(Filename, 'wb') as F: + NoteAdded = False + HeaderFound = False + AddNoteHere = False + + for Line in Lines: + if not NoteAdded: + Match = LineMatcher.match(Line) + MatchNext = NextSelectionMatcher.match(Line) + MatchCheck = CheckMatcher.match(Line) + if MatchCheck: + LastCheck = MatchCheck.group(1) + if LastCheck > OldCheckName: + AddNoteHere = True + + if MatchNext: + AddNoteHere = True + + if Match: + HeaderFound = True + F.write(Line) continue - if line.startswith('^^^^'): - f.write(line) + if Line.startswith('^^^^'): + F.write(Line) continue - if header_found and add_note_here: - if not line.startswith('^^^^'): - f.write("""- The '%s' check was renamed to :doc:`%s + if HeaderFound and AddNoteHere: + if not Line.startswith('^^^^'): + F.write("""- The '%s' check was renamed to :doc:`%s ` -""" % (old_check_name, new_check_name, new_check_name)) - note_added = True +""" % (OldCheckName, NewCheckName, NewCheckName)) + NoteAdded = True + + F.write(Line) - f.write(line) def main(): - parser = argparse.ArgumentParser(description='Rename clang-tidy check.') - parser.add_argument('old_check_name', type=str, + Parser = argparse.ArgumentParser(description='Rename clang-tidy check.') + Parser.add_argument('old_check_name', type=str, help='Old check name.') - parser.add_argument('new_check_name', type=str, + Parser.add_argument('new_check_name', type=str, help='New check name.') - parser.add_argument('--check_class_name', type=str, + Parser.add_argument('--check_class_name', type=str, help='Old name of the class implementing the check.') - args = parser.parse_args() + Args = Parser.parse_args() - old_module = args.old_check_name.split('-')[0] - new_module = args.new_check_name.split('-')[0] - if args.check_class_name: - check_name_camel = args.check_class_name + OldModule = Args.old_check_name.split('-')[0] + NewModule = Args.new_check_name.split('-')[0] + if Args.check_class_name: + CheckNameCamel = Args.check_class_name else: - check_name_camel = (''.join(map(lambda elem: elem.capitalize(), - args.old_check_name.split('-')[1:])) + - 'Check') + CheckNameCamel = (''.join(map(lambda elem: elem.capitalize(), + Args.old_check_name.split('-')[1:])) + + 'Check') - new_check_name_camel = (''.join(map(lambda elem: elem.capitalize(), - args.new_check_name.split('-')[1:])) + - 'Check') + NewCheckNameCamel = (''.join(map(lambda elem: elem.capitalize(), + Args.new_check_name.split('-')[1:])) + + 'Check') - clang_tidy_path = os.path.dirname(__file__) + ClangTidyPath = 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() + OldModulePath = os.path.join(ClangTidyPath, OldModule) + NewModulePath = os.path.join(ClangTidyPath, NewModule) - 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): + 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) - if not check_found: + CMakeLists = os.path.join(OldModulePath, 'CMakeLists.txt') + CheckFound = deleteMatchingLines(CMakeLists, '\\b' + CheckNameCamel) + if not CheckFound: print("Check name '%s' not found in %s. Exiting." % - (check_name_camel, cmake_lists)) + (CheckNameCamel, CMakeLists)) return 1 - modulecpp = 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) - - if old_module != new_module or new_module == 'llvm': - if new_module == 'llvm': - new_namespace = new_module + '_check' + ModuleCpp = filter( + lambda p: p.lower() == OldModule.lower() + 'tidymodule.cpp', + os.listdir(OldModulePath))[0] + deleteMatchingLines(os.path.join(OldModulePath, ModuleCpp), + '\\b' + CheckNameCamel + '|\\b' + Args.old_check_name) + + ImplHeader = os.path.join(OldModulePath, CheckNameCamel + ".h") + ImplSource = os.path.join(OldModulePath, CheckNameCamel + ".cpp") + if os.path.isfile(ImplHeader) and os.path.isfile(ImplSource): + NewHeader = os.path.join(NewModulePath, NewCheckNameCamel + ".h") + NewSource = os.path.join(NewModulePath, NewCheckNameCamel + ".cpp") + os.rename(ImplHeader, NewHeader) + os.rename(ImplSource, NewSource) + replaceHeaderComment(NewHeader) + replaceSourceComment(NewSource) + replaceHeaderGuard(NewHeader, NewModule, NewCheckNameCamel) + else: + print("Check implementation Files not found:\nHeader: {}\nSource: {}" + .format(ImplHeader, ImplSource)) + + DocFolder = os.path.join(ClangTidyPath, '..', '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') + + for Filename in getDocFiles(ClangTidyPath): + replaceWordInFile(Filename, Args.old_check_name, Args.new_check_name) + + for Filename in getClangTidyFiles(ClangTidyPath): + replaceWordInFile(Filename, Args.old_check_name, Args.new_check_name) + replaceWordInFile(Filename, OldModule + '::' + CheckNameCamel, + NewModule + '::' + NewCheckNameCamel) + replaceWordInFile(Filename, OldModule + '/' + CheckNameCamel, + NewModule + '/' + NewCheckNameCamel) + replaceWordInFile(Filename, CheckNameCamel, NewCheckNameCamel) + + for Filename in getTestFiles(ClangTidyPath): + replaceWordInFile(Filename, Args.old_check_name, Args.new_check_name) + + for Filename in getUnitTestFiles(ClangTidyPath): + replaceWordInFile(Filename, Args.old_check_name, Args.new_check_name) + replaceWordInFile(Filename, OldModule + '::' + CheckNameCamel, + NewModule + '::' + NewCheckNameCamel) + replaceWordInFile(Filename, OldModule + '/' + CheckNameCamel, + NewModule + '/' + NewCheckNameCamel) + replaceWordInFile(Filename, CheckNameCamel, NewCheckNameCamel) + + if OldModule != NewModule or NewModule == 'llvm': + if NewModule == 'llvm': + NewNamespace = NewModule + '_check' else: - new_namespace = new_module - check_implementation_files = glob.glob( - os.path.join(old_module_path, new_check_name_camel + '*')) - for filename in check_implementation_files: + NewNamespace = NewModule + CheckImplementationFiles = glob.glob( + os.path.join(OldModulePath, NewCheckNameCamel + '*')) + for Filename in CheckImplementationFiles: # 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 = fileRename(Filename, OldModulePath, NewModulePath) + replaceInFileRegex(Filename, 'namespace ' + OldModule + '[^ \n]*', + 'namespace ' + NewNamespace) - if (args.old_check_name == args.new_check_name): + 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) - adapt_module(new_module_path, new_module, args.new_check_name, - new_check_name_camel) + utils.modify_check.adaptCmake(NewModulePath, NewCheckNameCamel) + adaptModule(NewModulePath, NewModule, Args.new_check_name, + NewCheckNameCamel) + utils.modify_check.updateChecksList(ClangTidyPath) + addReleaseNotes(ClangTidyPath, Args.old_check_name, Args.new_check_name) - os.system(os.path.join(clang_tidy_path, 'add_new_check.py') - + ' --update-docs') - 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 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,158 @@ +#!/usr/bin/env python +# +# ===- modify_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 +# +# ===-----------------------------------------------------------------------===# + +import os +import re + + +def getCamelName(CheckName): + return ''.join(map(lambda elem: elem.capitalize(), + CheckName.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 adaptCmake(module_path, CheckNameCamel): + Filename = os.path.join(module_path, 'CMakeLists.txt') + with open(Filename, 'r') as F: + Lines = F.readlines() + + CppFile = CheckNameCamel + '.cpp' + + # Figure out whether this check already exists. + for Line in Lines: + if Line.strip() == CppFile: + return False + + print('Updating %s...' % Filename) + with open(Filename, 'w') as F: + CppFound = False + FileAdded = False + for Line in Lines: + CppLine = Line.strip().endswith('.cpp') + if (not FileAdded) and (CppLine or CppFound): + CppFound = True + if (Line.strip() > CppFile) or (not CppLine): + F.write(' ' + CppFile + '\n') + FileAdded = True + F.write(Line) + + return True + + +# Recreates the list of checks in the docs/clang-tidy/checks directory. +def updateChecksList(clang_tidy_path): + DocsDir = os.path.join(clang_tidy_path, '../docs/clang-tidy/checks') + Filename = os.path.normpath(os.path.join(DocsDir, 'list.rst')) + # Read the content of the current list.rst file + with open(Filename, 'r') as F: + Lines = F.readlines() + # Get all existing docs + DocFiles = list( + filter(lambda s: s.endswith('.rst') and s != 'list.rst', + os.listdir(DocsDir))) + DocFiles.sort() + + def hasAutoFix(CheckName): + DirName, _, CheckName = CheckName.partition("-") + + CheckerCode = os.path.join(DirName, getCamelName(CheckName)) + ".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. + return ' "Yes"' + return "" + + def processDoc(DocFile): + CheckName = DocFile.replace('.rst', '') + + with open(os.path.join(DocsDir, DocFile), '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 CheckName, Match + + def formatLink(DocFile): + CheckName, Match = processDoc(DocFile) + if not Match and CheckName: + return ' `%(check)s <%(check)s.html>`_,%(autofix)s\n' % { + 'check': CheckName, + 'autofix': hasAutoFix(CheckName) + } + else: + return '' + + def formatLinkAlias(DocFile): + CheckName, Match = processDoc(DocFile) + if Match and CheckName: + if Match.group(1) == 'https://clang.llvm.org/docs/analyzer/checkers': + TitleRedirect = 'Clang Static Analyzer' + else: + TitleRedirect = Match.group(1) + # The checker is just a redirect. + return ' `%(check)s <%(check)s.html>`_, `%(title)s <%(target)s.html>`_,%(autofix)s\n' % { + 'check': CheckName, + 'target': Match.group(1), + 'title': TitleRedirect, + 'autofix': hasAutoFix(Match.group(1)) + } + return '' + + Checks = map(formatLink, DocFiles) + ChecksAlias = map(formatLinkAlias, DocFiles) + + 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(ChecksAlias) + break + + +def getHeaderGuard(Module, CheckNameCamel): + return '_'.join(['LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY', + Module.upper(), + CheckNameCamel.upper(), + 'H']) + + +def generateCommentLineHeader(Filename): + return ''.join(['//===--- ', + os.path.basename(Filename), + ' - clang-tidy ', + '-' * max(0, 42 - len(os.path.basename(Filename))), + '*- C++ -*-===//']) + + +def generateCommentLineSource(Filename): + return ''.join(['//===--- ', + os.path.basename(Filename), + ' - clang-tidy ', + '-' * max(0, 51 - len(os.path.basename(Filename))), + '-===//'])