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