njames93 updated this revision to Diff 246653.
njames93 added a comment.

- Moved shared code into __init__


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D75134/new/

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

Index: clang-tools-extra/clang-tidy/utils/__init__.py
===================================================================
--- /dev/null
+++ 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))),
+                  '-===//'])
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
@@ -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()
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
@@ -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!')
 
 
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to