clang-tools  7.0.0
add_new_check.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 #
3 #===- add_new_check.py - clang-tidy check generator ----------*- python -*--===#
4 #
5 # The LLVM Compiler Infrastructure
6 #
7 # This file is distributed under the University of Illinois Open Source
8 # License. See LICENSE.TXT for details.
9 #
10 #===------------------------------------------------------------------------===#
11 
12 from __future__ import print_function
13 
14 import argparse
15 import os
16 import re
17 import sys
18 
19 # Adapts the module's CMakelist file. Returns 'True' if it could add a new entry
20 # and 'False' if the entry already existed.
21 def adapt_cmake(module_path, check_name_camel):
22  filename = os.path.join(module_path, 'CMakeLists.txt')
23  with open(filename, 'r') as f:
24  lines = f.readlines()
25 
26  cpp_file = check_name_camel + '.cpp'
27 
28  # Figure out whether this check already exists.
29  for line in lines:
30  if line.strip() == cpp_file:
31  return False
32 
33  print('Updating %s...' % filename)
34  with open(filename, 'w') as f:
35  cpp_found = False
36  file_added = False
37  for line in lines:
38  cpp_line = line.strip().endswith('.cpp')
39  if (not file_added) and (cpp_line or cpp_found):
40  cpp_found = True
41  if (line.strip() > cpp_file) or (not cpp_line):
42  f.write(' ' + cpp_file + '\n')
43  file_added = True
44  f.write(line)
45 
46  return True
47 
48 
49 # Adds a header for the new check.
50 def write_header(module_path, module, check_name, check_name_camel):
51  check_name_dashes = module + '-' + check_name
52  filename = os.path.join(module_path, check_name_camel) + '.h'
53  print('Creating %s...' % filename)
54  with open(filename, 'w') as f:
55  header_guard = ('LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_' + module.upper() + '_'
56  + check_name_camel.upper() + '_H')
57  f.write('//===--- ')
58  f.write(os.path.basename(filename))
59  f.write(' - clang-tidy')
60  f.write('-' * max(0, 43 - len(os.path.basename(filename))))
61  f.write('*- C++ -*-===//')
62  f.write("""
63 //
64 // The LLVM Compiler Infrastructure
65 //
66 // This file is distributed under the University of Illinois Open Source
67 // License. See LICENSE.TXT for details.
68 //
69 //===----------------------------------------------------------------------===//
70 
71 #ifndef %(header_guard)s
72 #define %(header_guard)s
73 
74 #include "../ClangTidy.h"
75 
76 namespace clang {
77 namespace tidy {
78 namespace %(module)s {
79 
80 /// FIXME: Write a short description.
81 ///
82 /// For the user-facing documentation see:
83 /// http://clang.llvm.org/extra/clang-tidy/checks/%(check_name_dashes)s.html
84 class %(check_name)s : public ClangTidyCheck {
85 public:
86  %(check_name)s(StringRef Name, ClangTidyContext *Context)
87  : ClangTidyCheck(Name, Context) {}
88  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
89  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
90 };
91 
92 } // namespace %(module)s
93 } // namespace tidy
94 } // namespace clang
95 
96 #endif // %(header_guard)s
97 """ % {'header_guard': header_guard,
98  'check_name': check_name_camel,
99  'check_name_dashes': check_name_dashes,
100  'module': module})
101 
102 
103 # Adds the implementation of the new check.
104 def write_implementation(module_path, module, check_name_camel):
105  filename = os.path.join(module_path, check_name_camel) + '.cpp'
106  print('Creating %s...' % filename)
107  with open(filename, 'w') as f:
108  f.write('//===--- ')
109  f.write(os.path.basename(filename))
110  f.write(' - clang-tidy')
111  f.write('-' * max(0, 52 - len(os.path.basename(filename))))
112  f.write('-===//')
113  f.write("""
114 //
115 // The LLVM Compiler Infrastructure
116 //
117 // This file is distributed under the University of Illinois Open Source
118 // License. See LICENSE.TXT for details.
119 //
120 //===----------------------------------------------------------------------===//
121 
122 #include "%(check_name)s.h"
123 #include "clang/AST/ASTContext.h"
124 #include "clang/ASTMatchers/ASTMatchFinder.h"
125 
126 using namespace clang::ast_matchers;
127 
128 namespace clang {
129 namespace tidy {
130 namespace %(module)s {
131 
132 void %(check_name)s::registerMatchers(MatchFinder *Finder) {
133  // FIXME: Add matchers.
134  Finder->addMatcher(functionDecl().bind("x"), this);
135 }
136 
137 void %(check_name)s::check(const MatchFinder::MatchResult &Result) {
138  // FIXME: Add callback implementation.
139  const auto *MatchedDecl = Result.Nodes.getNodeAs<FunctionDecl>("x");
140  if (MatchedDecl->getName().startswith("awesome_"))
141  return;
142  diag(MatchedDecl->getLocation(), "function %%0 is insufficiently awesome")
143  << MatchedDecl
144  << FixItHint::CreateInsertion(MatchedDecl->getLocation(), "awesome_");
145 }
146 
147 } // namespace %(module)s
148 } // namespace tidy
149 } // namespace clang
150 """ % {'check_name': check_name_camel,
151  'module': module})
152 
153 
154 # Modifies the module to include the new check.
155 def adapt_module(module_path, module, check_name, check_name_camel):
156  modulecpp = list(filter(
157  lambda p: p.lower() == module.lower() + 'tidymodule.cpp',
158  os.listdir(module_path)))[0]
159  filename = os.path.join(module_path, modulecpp)
160  with open(filename, 'r') as f:
161  lines = f.readlines()
162 
163  print('Updating %s...' % filename)
164  with open(filename, 'w') as f:
165  header_added = False
166  header_found = False
167  check_added = False
168  check_decl = (' CheckFactories.registerCheck<' + check_name_camel +
169  '>(\n "' + module + '-' + check_name + '");\n')
170 
171  for line in lines:
172  if not header_added:
173  match = re.search('#include "(.*)"', line)
174  if match:
175  header_found = True
176  if match.group(1) > check_name_camel:
177  header_added = True
178  f.write('#include "' + check_name_camel + '.h"\n')
179  elif header_found:
180  header_added = True
181  f.write('#include "' + check_name_camel + '.h"\n')
182 
183  if not check_added:
184  if line.strip() == '}':
185  check_added = True
186  f.write(check_decl)
187  else:
188  match = re.search('registerCheck<(.*)>', line)
189  if match and match.group(1) > check_name_camel:
190  check_added = True
191  f.write(check_decl)
192  f.write(line)
193 
194 
195 # Adds a release notes entry.
196 def add_release_notes(module_path, module, check_name):
197  check_name_dashes = module + '-' + check_name
198  filename = os.path.normpath(os.path.join(module_path,
199  '../../docs/ReleaseNotes.rst'))
200  with open(filename, 'r') as f:
201  lines = f.readlines()
202 
203  print('Updating %s...' % filename)
204  with open(filename, 'w') as f:
205  note_added = False
206  header_found = False
207 
208  for line in lines:
209  if not note_added:
210  match = re.search('Improvements to clang-tidy', line)
211  if match:
212  header_found = True
213  elif header_found:
214  if not line.startswith('----'):
215  f.write("""
216 - New :doc:`%s
217  <clang-tidy/checks/%s>` check.
218 
219  FIXME: add release notes.
220 """ % (check_name_dashes, check_name_dashes))
221  note_added = True
222 
223  f.write(line)
224 
225 
226 # Adds a test for the check.
227 def write_test(module_path, module, check_name, test_extension):
228  check_name_dashes = module + '-' + check_name
229  filename = os.path.normpath(os.path.join(module_path, '../../test/clang-tidy',
230  check_name_dashes + '.' + test_extension))
231  print('Creating %s...' % filename)
232  with open(filename, 'w') as f:
233  f.write("""// RUN: %%check_clang_tidy %%s %(check_name_dashes)s %%t
234 
235 // FIXME: Add something that triggers the check here.
236 void f();
237 // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'f' is insufficiently awesome [%(check_name_dashes)s]
238 
239 // FIXME: Verify the applied fix.
240 // * Make the CHECK patterns specific enough and try to make verified lines
241 // unique to avoid incorrect matches.
242 // * Use {{}} for regular expressions.
243 // CHECK-FIXES: {{^}}void awesome_f();{{$}}
244 
245 // FIXME: Add something that doesn't trigger the check here.
246 void awesome_f2();
247 """ % {'check_name_dashes': check_name_dashes})
248 
249 
250 # Recreates the list of checks in the docs/clang-tidy/checks directory.
251 def update_checks_list(clang_tidy_path):
252  docs_dir = os.path.join(clang_tidy_path, '../docs/clang-tidy/checks')
253  filename = os.path.normpath(os.path.join(docs_dir, 'list.rst'))
254  with open(filename, 'r') as f:
255  lines = f.readlines()
256  doc_files = list(filter(lambda s: s.endswith('.rst') and s != 'list.rst',
257  os.listdir(docs_dir)))
258  doc_files.sort()
259 
260  def format_link(doc_file):
261  check_name = doc_file.replace('.rst', '')
262  with open(os.path.join(docs_dir, doc_file), 'r') as doc:
263  content = doc.read()
264  match = re.search('.*:orphan:.*', content)
265  if match:
266  return ''
267 
268  match = re.search('.*:http-equiv=refresh: \d+;URL=(.*).html.*',
269  content)
270  if match:
271  return ' %(check)s (redirects to %(target)s) <%(check)s>\n' % {
272  'check': check_name,
273  'target': match.group(1)
274  }
275  return ' %s\n' % check_name
276 
277  checks = map(format_link, doc_files)
278 
279  print('Updating %s...' % filename)
280  with open(filename, 'w') as f:
281  for line in lines:
282  f.write(line)
283  if line.startswith('.. toctree::'):
284  f.writelines(checks)
285  break
286 
287 
288 # Adds a documentation for the check.
289 def write_docs(module_path, module, check_name):
290  check_name_dashes = module + '-' + check_name
291  filename = os.path.normpath(os.path.join(
292  module_path, '../../docs/clang-tidy/checks/', check_name_dashes + '.rst'))
293  print('Creating %s...' % filename)
294  with open(filename, 'w') as f:
295  f.write(""".. title:: clang-tidy - %(check_name_dashes)s
296 
297 %(check_name_dashes)s
298 %(underline)s
299 
300 FIXME: Describe what patterns does the check detect and why. Give examples.
301 """ % {'check_name_dashes': check_name_dashes,
302  'underline': '=' * len(check_name_dashes)})
303 
304 
305 def main():
306  language_to_extension = {
307  'c': 'c',
308  'c++': 'cpp',
309  'objc': 'm',
310  'objc++': 'mm',
311  }
312  parser = argparse.ArgumentParser()
313  parser.add_argument(
314  '--update-docs',
315  action='store_true',
316  help='just update the list of documentation files, then exit')
317  parser.add_argument(
318  '--language',
319  help='language to use for new check (defaults to c++)',
320  choices=language_to_extension.keys(),
321  default='c++',
322  metavar='LANG')
323  parser.add_argument(
324  'module',
325  nargs='?',
326  help='module directory under which to place the new tidy check (e.g., misc)')
327  parser.add_argument(
328  'check',
329  nargs='?',
330  help='name of new tidy check to add (e.g. foo-do-the-stuff)')
331  args = parser.parse_args()
332 
333  if args.update_docs:
334  update_checks_list(os.path.dirname(sys.argv[0]))
335  return
336 
337  if not args.module or not args.check:
338  print('Module and check must be specified.')
339  parser.print_usage()
340  return
341 
342  module = args.module
343  check_name = args.check
344 
345  if check_name.startswith(module):
346  print('Check name "%s" must not start with the module "%s". Exiting.' % (
347  check_name, module))
348  return
349  check_name_camel = ''.join(map(lambda elem: elem.capitalize(),
350  check_name.split('-'))) + 'Check'
351  clang_tidy_path = os.path.dirname(sys.argv[0])
352  module_path = os.path.join(clang_tidy_path, module)
353 
354  if not adapt_cmake(module_path, check_name_camel):
355  return
356  write_header(module_path, module, check_name, check_name_camel)
357  write_implementation(module_path, module, check_name_camel)
358  adapt_module(module_path, module, check_name, check_name_camel)
359  add_release_notes(module_path, module, check_name)
360  test_extension = language_to_extension.get(args.language)
361  write_test(module_path, module, check_name, test_extension)
362  write_docs(module_path, module, check_name)
363  update_checks_list(clang_tidy_path)
364  print('Done. Now it\'s your turn!')
365 
366 
367 if __name__ == '__main__':
368  main()
def write_test(module_path, module, check_name, test_extension)
def write_header(module_path, module, check_name, check_name_camel)
def write_docs(module_path, module, check_name)
def add_release_notes(module_path, module, check_name)
def write_implementation(module_path, module, check_name_camel)
def update_checks_list(clang_tidy_path)
def adapt_module(module_path, module, check_name, check_name_camel)
static std::string join(ArrayRef< SpecialMemberFunctionsCheck::SpecialMemberFunctionKind > SMFS, llvm::StringRef AndOr)
def adapt_cmake(module_path, check_name_camel)