294 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
			
		
		
	
	
			294 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
#!/usr/bin/env 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
 | 
						|
 | 
						|
 | 
						|
def replaceInFileRegex(fileName, sFrom, sTo):
 | 
						|
  if sFrom == sTo:
 | 
						|
    return
 | 
						|
  txt = None
 | 
						|
  with open(fileName, "r") as f:
 | 
						|
    txt = 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)
 | 
						|
 | 
						|
def replaceInFile(fileName, sFrom, sTo):
 | 
						|
  if sFrom == sTo:
 | 
						|
    return
 | 
						|
  txt = None
 | 
						|
  with open(fileName, "r") as f:
 | 
						|
    txt = f.read()
 | 
						|
 | 
						|
  if sFrom not in txt:
 | 
						|
    return
 | 
						|
 | 
						|
  txt = txt.replace(sFrom, sTo)
 | 
						|
  print("Replacing '%s' -> '%s' in '%s'..." % (sFrom, sTo, fileName))
 | 
						|
  with open(fileName, "w") as f:
 | 
						|
    f.write(txt)
 | 
						|
 | 
						|
 | 
						|
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, 52 - len(os.path.basename(filename))),
 | 
						|
                  '-===//'])
 | 
						|
 | 
						|
 | 
						|
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):
 | 
						|
  lines = None
 | 
						|
  with open(fileName, "r") as f:
 | 
						|
    lines = f.readlines()
 | 
						|
 | 
						|
  not_matching_lines = [l for l in lines if not re.search(pattern, l)]
 | 
						|
  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:
 | 
						|
    f.writelines(not_matching_lines)
 | 
						|
 | 
						|
  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
 | 
						|
 | 
						|
# 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)
 | 
						|
        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)
 | 
						|
 | 
						|
 | 
						|
# 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,
 | 
						|
                                           '../docs/ReleaseNotes.rst'))
 | 
						|
  with open(filename, 'r') as f:
 | 
						|
    lines = f.readlines()
 | 
						|
 | 
						|
  print('Updating %s...' % filename)
 | 
						|
  with open(filename, 'wb') as f:
 | 
						|
    note_added = False
 | 
						|
    header_found = False
 | 
						|
 | 
						|
    for line in lines:
 | 
						|
      if not note_added:
 | 
						|
        match = re.search('Improvements to clang-tidy', line)
 | 
						|
        if match:
 | 
						|
          header_found = True
 | 
						|
        elif header_found:
 | 
						|
          if not line.startswith('----'):
 | 
						|
            f.write("""
 | 
						|
- The '%s' check was renamed to :doc:`%s
 | 
						|
  <clang-tidy/checks/%s>`
 | 
						|
""" % (old_check_name, new_check_name, new_check_name))
 | 
						|
            note_added = True
 | 
						|
 | 
						|
      f.write(line)
 | 
						|
 | 
						|
def main():
 | 
						|
  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,
 | 
						|
                      help='New check name.')
 | 
						|
  parser.add_argument('--check_class_name', type=str,
 | 
						|
                      help='Old name of the class implementing the check.')
 | 
						|
  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
 | 
						|
  else:
 | 
						|
    check_name_camel = (''.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')
 | 
						|
 | 
						|
  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)
 | 
						|
    if not check_found:
 | 
						|
      print("Check name '%s' not found in %s. Exiting." %
 | 
						|
            (check_name_camel, cmake_lists))
 | 
						|
      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'
 | 
						|
    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:
 | 
						|
      # 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)
 | 
						|
 | 
						|
  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)
 | 
						|
 | 
						|
  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()
 |