njames93 created this revision.
njames93 added reviewers: aaron.ballman, Eugene.Zelenko, gribozavr2, alexfh, 
hokein.
Herald added subscribers: cfe-commits, xazax.hun.
Herald added a project: clang.

Factored out some similar code in add_new_check.py and rename_check.py.
Made rename_check.py less error prone by targetting string replacements
Haven't linted the code as that is a can of worms given the 100+ different 
style options currently used in the python files


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D75134

Files:
  clang-tools-extra/clang-tidy/add_new_check.py
  clang-tools-extra/clang-tidy/rename_check.py
  clang-tools-extra/clang-tidy/utils/__init__.py
  clang-tools-extra/clang-tidy/utils/modify_check.py

Index: clang-tools-extra/clang-tidy/utils/modify_check.py
===================================================================
--- /dev/null
+++ 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
Index: clang-tools-extra/clang-tidy/rename_check.py
===================================================================
--- clang-tools-extra/clang-tidy/rename_check.py
+++ 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,13 +21,46 @@
   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):
+  print ("From:{}\nTo:{}".format(sFrom, sTo))
   if sFrom == sTo:
     return
   txt = None
@@ -49,17 +83,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 +148,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 +289,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 +307,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 +373,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__':
Index: clang-tools-extra/clang-tidy/add_new_check.py
===================================================================
--- clang-tools-extra/clang-tidy/add_new_check.py
+++ 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',
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to