clang-tools 6.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_camel.upper() + '_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 release notes entry.
193 def add_release_notes(module_path, module, check_name):
194 check_name_dashes = module + '-' + check_name
195 filename = os.path.normpath(os.path.join(module_path,
196 '../../docs/ReleaseNotes.rst'))
197 with open(filename, 'r') as f:
198 lines = f.readlines()
199
200 print('Updating %s...' % filename)
201 with open(filename, 'wb') as f:
202 note_added = False
203 header_found = False
204
205 for line in lines:
206 if not note_added:
207 match = re.search('Improvements to clang-tidy', line)
208 if match:
209 header_found = True
210 elif header_found:
211 if not line.startswith('----'):
212 f.write("""
213 - New `%s
214 <http://clang.llvm.org/extra/clang-tidy/checks/%s.html>`_ check
215
216 FIXME: add release notes.
217 """ % (check_name_dashes, check_name_dashes))
218 note_added = True
219
220 f.write(line)
221
222
223 # Adds a test for the check.
224 def write_test(module_path, module, check_name):
225 check_name_dashes = module + '-' + check_name
226 filename = os.path.normpath(os.path.join(module_path, '../../test/clang-tidy',
227 check_name_dashes + '.cpp'))
228 print('Creating %s...' % filename)
229 with open(filename, 'wb') as f:
230 f.write("""// RUN: %%check_clang_tidy %%s %(check_name_dashes)s %%t
231
232 // FIXME: Add something that triggers the check here.
233 void f();
234 // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'f' is insufficiently awesome [%(check_name_dashes)s]
235
236 // FIXME: Verify the applied fix.
237 // * Make the CHECK patterns specific enough and try to make verified lines
238 // unique to avoid incorrect matches.
239 // * Use {{}} for regular expressions.
240 // CHECK-FIXES: {{^}}void awesome_f();{{$}}
241
242 // FIXME: Add something that doesn't trigger the check here.
243 void awesome_f2();
244 """ % {'check_name_dashes': check_name_dashes})
245
246
247 # Recreates the list of checks in the docs/clang-tidy/checks directory.
248 def update_checks_list(clang_tidy_path):
249 docs_dir = os.path.join(clang_tidy_path, '../docs/clang-tidy/checks')
250 filename = os.path.normpath(os.path.join(docs_dir, 'list.rst'))
251 with open(filename, 'r') as f:
252 lines = f.readlines()
253 doc_files = filter(lambda s: s.endswith('.rst') and s != 'list.rst',
254 os.listdir(docs_dir))
255 doc_files.sort()
256
257 def format_link(doc_file):
258 check_name = doc_file.replace('.rst', '')
259 with open(os.path.join(docs_dir, doc_file), 'r') as doc:
260 content = doc.read()
261 match = re.search('.*:orphan:.*', content)
262 if match:
263 return ''
264
265 match = re.search('.*:http-equiv=refresh: \d+;URL=(.*).html.*',
266 content)
267 if match:
268 return ' %(check)s (redirects to %(target)s) <%(check)s>\n' % {
269 'check': check_name,
270 'target': match.group(1)
271 }
272 return ' %s\n' % check_name
273
274 checks = map(format_link, doc_files)
275
276 print('Updating %s...' % filename)
277 with open(filename, 'wb') as f:
278 for line in lines:
279 f.write(line)
280 if line.startswith('.. toctree::'):
281 f.writelines(checks)
282 break
283
284
285 # Adds a documentation for the check.
286 def write_docs(module_path, module, check_name):
287 check_name_dashes = module + '-' + check_name
288 filename = os.path.normpath(os.path.join(
289 module_path, '../../docs/clang-tidy/checks/', check_name_dashes + '.rst'))
290 print('Creating %s...' % filename)
291 with open(filename, 'wb') as f:
292 f.write(""".. title:: clang-tidy - %(check_name_dashes)s
293
294 %(check_name_dashes)s
295 %(underline)s
296
297 FIXME: Describe what patterns does the check detect and why. Give examples.
298 """ % {'check_name_dashes': check_name_dashes,
299 'underline': '=' * len(check_name_dashes)})
300
301
302 def main():
303 if len(sys.argv) == 2 and sys.argv[1] == '--update-docs':
304 update_checks_list(os.path.dirname(sys.argv[0]))
305 return
306
307 if len(sys.argv) != 3:
308 print """\
309 Usage: add_new_check.py <module> <check>, e.g.
310 add_new_check.py misc awesome-functions
311
312 Alternatively, run 'add_new_check.py --update-docs' to just update the list of
313 documentation files."""
314
315 return
316
317 module = sys.argv[1]
318 check_name = sys.argv[2]
319
320 if check_name.startswith(module):
321 print 'Check name "%s" must not start with the module "%s". Exiting.' % (
322 check_name, module)
323 return
324 check_name_camel = ''.join(map(lambda elem: elem.capitalize(),
325 check_name.split('-'))) + 'Check'
326 clang_tidy_path = os.path.dirname(sys.argv[0])
327 module_path = os.path.join(clang_tidy_path, module)
328
329 if not adapt_cmake(module_path, check_name_camel):
330 return
331 write_header(module_path, module, check_name, check_name_camel)
332 write_implementation(module_path, module, check_name_camel)
333 adapt_module(module_path, module, check_name, check_name_camel)
334 add_release_notes(module_path, module, check_name)
335 write_test(module_path, module, check_name)
336 write_docs(module_path, module, check_name)
337 update_checks_list(clang_tidy_path)
338 print('Done. Now it\'s your turn!')
339
340
341 if __name__ == '__main__':
342 main()
add_new_check.main
def main()
Definition: add_new_check.py:302
add_new_check.write_header
def write_header(module_path, module, check_name, check_name_camel)
Definition: add_new_check.py:48
add_new_check.write_docs
def write_docs(module_path, module, check_name)
Definition: add_new_check.py:286
add_new_check.write_test
def write_test(module_path, module, check_name)
Definition: add_new_check.py:224
add_new_check.add_release_notes
def add_release_notes(module_path, module, check_name)
Definition: add_new_check.py:193
add_new_check.write_implementation
def write_implementation(module_path, module, check_name_camel)
Definition: add_new_check.py:102
add_new_check.update_checks_list
def update_checks_list(clang_tidy_path)
Definition: add_new_check.py:248
add_new_check.adapt_module
def adapt_module(module_path, module, check_name, check_name_camel)
Definition: add_new_check.py:153
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:19