clang-tools 8.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, 42 - 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, 51 - 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 lineMatcher = re.compile('Improvements to clang-tidy')
204 nextSectionMatcher = re.compile('Improvements to include-fixer')
205 checkerMatcher = re.compile('- New :doc:`(.*)')
206
207 print('Updating %s...' % filename)
208 with open(filename, 'w') as f:
209 note_added = False
210 header_found = False
211 next_header_found = False
212 add_note_here = False
213
214 for line in lines:
215 if not note_added:
216 match = lineMatcher.match(line)
217 match_next = nextSectionMatcher.match(line)
218 match_checker = checkerMatcher.match(line)
219 if match_checker:
220 last_checker = match_checker.group(1)
221 if last_checker > check_name_dashes:
222 add_note_here = True
223
224 if match_next:
225 next_header_found = True
226 add_note_here = True
227
228 if match:
229 header_found = True
230 f.write(line)
231 continue
232
233 if line.startswith('----'):
234 f.write(line)
235 continue
236
237 if header_found and add_note_here:
238 if not line.startswith('----'):
239 f.write("""- New :doc:`%s
240 <clang-tidy/checks/%s>` check.
241
242 FIXME: add release notes.
243
244 """ % (check_name_dashes, check_name_dashes))
245 note_added = True
246
247 f.write(line)
248
249
250 # Adds a test for the check.
251 def write_test(module_path, module, check_name, test_extension):
252 check_name_dashes = module + '-' + check_name
253 filename = os.path.normpath(os.path.join(module_path, '../../test/clang-tidy',
254 check_name_dashes + '.' + test_extension))
255 print('Creating %s...' % filename)
256 with open(filename, 'w') as f:
257 f.write("""// RUN: %%check_clang_tidy %%s %(check_name_dashes)s %%t
258
259 // FIXME: Add something that triggers the check here.
260 void f();
261 // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'f' is insufficiently awesome [%(check_name_dashes)s]
262
263 // FIXME: Verify the applied fix.
264 // * Make the CHECK patterns specific enough and try to make verified lines
265 // unique to avoid incorrect matches.
266 // * Use {{}} for regular expressions.
267 // CHECK-FIXES: {{^}}void awesome_f();{{$}}
268
269 // FIXME: Add something that doesn't trigger the check here.
270 void awesome_f2();
271 """ % {'check_name_dashes': check_name_dashes})
272
273
274 # Recreates the list of checks in the docs/clang-tidy/checks directory.
275 def update_checks_list(clang_tidy_path):
276 docs_dir = os.path.join(clang_tidy_path, '../docs/clang-tidy/checks')
277 filename = os.path.normpath(os.path.join(docs_dir, 'list.rst'))
278 with open(filename, 'r') as f:
279 lines = f.readlines()
280 doc_files = list(filter(lambda s: s.endswith('.rst') and s != 'list.rst',
281 os.listdir(docs_dir)))
282 doc_files.sort()
283
284 def format_link(doc_file):
285 check_name = doc_file.replace('.rst', '')
286 with open(os.path.join(docs_dir, doc_file), 'r') as doc:
287 content = doc.read()
288 match = re.search('.*:orphan:.*', content)
289 if match:
290 return ''
291
292 match = re.search('.*:http-equiv=refresh: \d+;URL=(.*).html.*',
293 content)
294 if match:
295 return ' %(check)s (redirects to %(target)s) <%(check)s>\n' % {
296 'check': check_name,
297 'target': match.group(1)
298 }
299 return ' %s\n' % check_name
300
301 checks = map(format_link, doc_files)
302
303 print('Updating %s...' % filename)
304 with open(filename, 'w') as f:
305 for line in lines:
306 f.write(line)
307 if line.startswith('.. toctree::'):
308 f.writelines(checks)
309 break
310
311
312 # Adds a documentation for the check.
313 def write_docs(module_path, module, check_name):
314 check_name_dashes = module + '-' + check_name
315 filename = os.path.normpath(os.path.join(
316 module_path, '../../docs/clang-tidy/checks/', check_name_dashes + '.rst'))
317 print('Creating %s...' % filename)
318 with open(filename, 'w') as f:
319 f.write(""".. title:: clang-tidy - %(check_name_dashes)s
320
321 %(check_name_dashes)s
322 %(underline)s
323
324 FIXME: Describe what patterns does the check detect and why. Give examples.
325 """ % {'check_name_dashes': check_name_dashes,
326 'underline': '=' * len(check_name_dashes)})
327
328
329 def main():
330 language_to_extension = {
331 'c': 'c',
332 'c++': 'cpp',
333 'objc': 'm',
334 'objc++': 'mm',
335 }
336 parser = argparse.ArgumentParser()
337 parser.add_argument(
338 '--update-docs',
339 action='store_true',
340 help='just update the list of documentation files, then exit')
341 parser.add_argument(
342 '--language',
343 help='language to use for new check (defaults to c++)',
344 choices=language_to_extension.keys(),
345 default='c++',
346 metavar='LANG')
347 parser.add_argument(
348 'module',
349 nargs='?',
350 help='module directory under which to place the new tidy check (e.g., misc)')
351 parser.add_argument(
352 'check',
353 nargs='?',
354 help='name of new tidy check to add (e.g. foo-do-the-stuff)')
355 args = parser.parse_args()
356
357 if args.update_docs:
358 update_checks_list(os.path.dirname(sys.argv[0]))
359 return
360
361 if not args.module or not args.check:
362 print('Module and check must be specified.')
363 parser.print_usage()
364 return
365
366 module = args.module
367 check_name = args.check
368
369 if check_name.startswith(module):
370 print('Check name "%s" must not start with the module "%s". Exiting.' % (
371 check_name, module))
372 return
373 check_name_camel = ''.join(map(lambda elem: elem.capitalize(),
374 check_name.split('-'))) + 'Check'
375 clang_tidy_path = os.path.dirname(sys.argv[0])
376 module_path = os.path.join(clang_tidy_path, module)
377
378 if not adapt_cmake(module_path, check_name_camel):
379 return
380 write_header(module_path, module, check_name, check_name_camel)
381 write_implementation(module_path, module, check_name_camel)
382 adapt_module(module_path, module, check_name, check_name_camel)
383 add_release_notes(module_path, module, check_name)
384 test_extension = language_to_extension.get(args.language)
385 write_test(module_path, module, check_name, test_extension)
386 write_docs(module_path, module, check_name)
387 update_checks_list(clang_tidy_path)
388 print('Done. Now it\'s your turn!')
389
390
391 if __name__ == '__main__':
392 main()
add_new_check.write_test
def write_test(module_path, module, check_name, test_extension)
Definition: add_new_check.py:251
add_new_check.main
def main()
Definition: add_new_check.py:329
add_new_check.write_header
def write_header(module_path, module, check_name, check_name_camel)
Definition: add_new_check.py:50
add_new_check.write_docs
def write_docs(module_path, module, check_name)
Definition: add_new_check.py:313
add_new_check.add_release_notes
def add_release_notes(module_path, module, check_name)
Definition: add_new_check.py:196
add_new_check.write_implementation
def write_implementation(module_path, module, check_name_camel)
Definition: add_new_check.py:104
add_new_check.update_checks_list
def update_checks_list(clang_tidy_path)
Definition: add_new_check.py:275
add_new_check.adapt_module
def adapt_module(module_path, module, check_name, check_name_camel)
Definition: add_new_check.py:155
clang::tidy::cppcoreguidelines::join
static std::string join(ArrayRef< SpecialMemberFunctionsCheck::SpecialMemberFunctionKind > SMFS, llvm::StringRef AndOr)
Definition: SpecialMemberFunctionsCheck.cpp:82
add_new_check.adapt_cmake
def adapt_cmake(module_path, check_name_camel)
Definition: add_new_check.py:21