blob: ab0c883db6637dcfc55f3763456b757a1caefd93 [file] [log] [blame]
Daniel Jasper4ecc8b32014-12-09 10:02:51 +00001#!/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
Ben Hamiltonffd2df02018-01-31 21:52:39 +000012import argparse
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000013import os
14import re
15import sys
16
17
18# Adapts the module's CMakelist file. Returns 'True' if it could add a new entry
19# and 'False' if the entry already existed.
20def adapt_cmake(module_path, check_name_camel):
21 filename = os.path.join(module_path, 'CMakeLists.txt')
22 with open(filename, 'r') as f:
Alexander Kornienko3285f1b2015-09-10 13:56:39 +000023 lines = f.readlines()
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000024
25 cpp_file = check_name_camel + '.cpp'
26
27 # Figure out whether this check already exists.
28 for line in lines:
29 if line.strip() == cpp_file:
30 return False
31
Alexander Kornienko3285f1b2015-09-10 13:56:39 +000032 print('Updating %s...' % filename)
Aaron Ballman017bfee2015-10-06 19:11:12 +000033 with open(filename, 'wb') as f:
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000034 cpp_found = False
35 file_added = False
36 for line in lines:
Alexander Kornienko3285f1b2015-09-10 13:56:39 +000037 cpp_line = line.strip().endswith('.cpp')
Alexander Kornienkoc0ebfbe52015-09-04 14:56:57 +000038 if (not file_added) and (cpp_line or cpp_found):
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000039 cpp_found = True
Alexander Kornienkoc0ebfbe52015-09-04 14:56:57 +000040 if (line.strip() > cpp_file) or (not cpp_line):
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000041 f.write(' ' + cpp_file + '\n')
42 file_added = True
Alexander Kornienko3285f1b2015-09-10 13:56:39 +000043 f.write(line)
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000044
45 return True
46
47
48# Adds a header for the new check.
49def write_header(module_path, module, check_name, check_name_camel):
Alexander Kornienko3285f1b2015-09-10 13:56:39 +000050 check_name_dashes = module + '-' + check_name
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000051 filename = os.path.join(module_path, check_name_camel) + '.h'
Alexander Kornienko3285f1b2015-09-10 13:56:39 +000052 print('Creating %s...' % filename)
Aaron Ballman017bfee2015-10-06 19:11:12 +000053 with open(filename, 'wb') as f:
Alexander Kornienko6110cca2016-04-13 08:46:32 +000054 header_guard = ('LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_' + module.upper() + '_'
Alexander Kornienkob43950d2017-11-25 08:49:04 +000055 + check_name_camel.upper() + '_H')
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000056 f.write('//===--- ')
57 f.write(os.path.basename(filename))
58 f.write(' - clang-tidy')
59 f.write('-' * max(0, 43 - len(os.path.basename(filename))))
60 f.write('*- C++ -*-===//')
61 f.write("""
62//
63// The LLVM Compiler Infrastructure
64//
65// This file is distributed under the University of Illinois Open Source
66// License. See LICENSE.TXT for details.
67//
68//===----------------------------------------------------------------------===//
69
70#ifndef %(header_guard)s
71#define %(header_guard)s
72
73#include "../ClangTidy.h"
74
75namespace clang {
76namespace tidy {
Alexander Kornienko821ca472015-12-16 15:05:27 +000077namespace %(module)s {
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000078
Alexander Kornienko3285f1b2015-09-10 13:56:39 +000079/// FIXME: Write a short description.
80///
81/// For the user-facing documentation see:
82/// http://clang.llvm.org/extra/clang-tidy/checks/%(check_name_dashes)s.html
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000083class %(check_name)s : public ClangTidyCheck {
84public:
85 %(check_name)s(StringRef Name, ClangTidyContext *Context)
86 : ClangTidyCheck(Name, Context) {}
87 void registerMatchers(ast_matchers::MatchFinder *Finder) override;
88 void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
89};
90
Alexander Kornienko821ca472015-12-16 15:05:27 +000091} // namespace %(module)s
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000092} // namespace tidy
93} // namespace clang
94
95#endif // %(header_guard)s
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000096""" % {'header_guard': header_guard,
Alexander Kornienko3285f1b2015-09-10 13:56:39 +000097 'check_name': check_name_camel,
Alexander Kornienko821ca472015-12-16 15:05:27 +000098 'check_name_dashes': check_name_dashes,
99 'module': module})
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000100
101
102# Adds the implementation of the new check.
Alexander Kornienko821ca472015-12-16 15:05:27 +0000103def write_implementation(module_path, module, check_name_camel):
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000104 filename = os.path.join(module_path, check_name_camel) + '.cpp'
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000105 print('Creating %s...' % filename)
Aaron Ballman017bfee2015-10-06 19:11:12 +0000106 with open(filename, 'wb') as f:
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000107 f.write('//===--- ')
108 f.write(os.path.basename(filename))
109 f.write(' - clang-tidy')
110 f.write('-' * max(0, 52 - len(os.path.basename(filename))))
111 f.write('-===//')
112 f.write("""
113//
114// The LLVM Compiler Infrastructure
115//
116// This file is distributed under the University of Illinois Open Source
117// License. See LICENSE.TXT for details.
118//
119//===----------------------------------------------------------------------===//
120
121#include "%(check_name)s.h"
122#include "clang/AST/ASTContext.h"
123#include "clang/ASTMatchers/ASTMatchFinder.h"
124
125using namespace clang::ast_matchers;
126
127namespace clang {
128namespace tidy {
Alexander Kornienko821ca472015-12-16 15:05:27 +0000129namespace %(module)s {
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000130
131void %(check_name)s::registerMatchers(MatchFinder *Finder) {
132 // FIXME: Add matchers.
133 Finder->addMatcher(functionDecl().bind("x"), this);
134}
135
136void %(check_name)s::check(const MatchFinder::MatchResult &Result) {
137 // FIXME: Add callback implementation.
Alexander Kornienko423236b2014-12-09 12:43:09 +0000138 const auto *MatchedDecl = Result.Nodes.getNodeAs<FunctionDecl>("x");
139 if (MatchedDecl->getName().startswith("awesome_"))
140 return;
Etienne Bergeron68852292016-05-30 15:42:08 +0000141 diag(MatchedDecl->getLocation(), "function %%0 is insufficiently awesome")
Etienne Bergeron1c51a2d2016-05-30 15:05:10 +0000142 << MatchedDecl
Alexander Kornienko423236b2014-12-09 12:43:09 +0000143 << FixItHint::CreateInsertion(MatchedDecl->getLocation(), "awesome_");
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000144}
145
Alexander Kornienko821ca472015-12-16 15:05:27 +0000146} // namespace %(module)s
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000147} // namespace tidy
148} // namespace clang
Alexander Kornienko821ca472015-12-16 15:05:27 +0000149""" % {'check_name': check_name_camel,
150 'module': module})
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000151
152
153# Modifies the module to include the new check.
154def adapt_module(module_path, module, check_name, check_name_camel):
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000155 modulecpp = filter(lambda p: p.lower() == module.lower() + 'tidymodule.cpp',
156 os.listdir(module_path))[0]
Aaron Ballmanaaa40802015-10-06 13:31:00 +0000157 filename = os.path.join(module_path, modulecpp)
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000158 with open(filename, 'r') as f:
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000159 lines = f.readlines()
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000160
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000161 print('Updating %s...' % filename)
Aaron Ballman017bfee2015-10-06 19:11:12 +0000162 with open(filename, 'wb') as f:
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000163 header_added = False
164 header_found = False
165 check_added = False
166 check_decl = (' CheckFactories.registerCheck<' + check_name_camel +
167 '>(\n "' + module + '-' + check_name + '");\n')
168
169 for line in lines:
170 if not header_added:
171 match = re.search('#include "(.*)"', line)
172 if match:
173 header_found = True
174 if match.group(1) > check_name_camel:
175 header_added = True
176 f.write('#include "' + check_name_camel + '.h"\n')
177 elif header_found:
178 header_added = True
179 f.write('#include "' + check_name_camel + '.h"\n')
180
181 if not check_added:
182 if line.strip() == '}':
183 check_added = True
184 f.write(check_decl)
185 else:
186 match = re.search('registerCheck<(.*)>', line)
187 if match and match.group(1) > check_name_camel:
188 check_added = True
189 f.write(check_decl)
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000190 f.write(line)
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000191
192
Alexander Kornienkoc2dcdd92017-07-12 13:13:41 +0000193# Adds a release notes entry.
194def add_release_notes(module_path, module, check_name):
195 check_name_dashes = module + '-' + check_name
196 filename = os.path.normpath(os.path.join(module_path,
197 '../../docs/ReleaseNotes.rst'))
198 with open(filename, 'r') as f:
199 lines = f.readlines()
200
201 print('Updating %s...' % filename)
202 with open(filename, 'wb') as f:
203 note_added = False
204 header_found = False
205
206 for line in lines:
207 if not note_added:
208 match = re.search('Improvements to clang-tidy', line)
209 if match:
210 header_found = True
211 elif header_found:
212 if not line.startswith('----'):
213 f.write("""
214- New `%s
215 <http://clang.llvm.org/extra/clang-tidy/checks/%s.html>`_ check
216
217 FIXME: add release notes.
218""" % (check_name_dashes, check_name_dashes))
219 note_added = True
220
221 f.write(line)
222
223
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000224# Adds a test for the check.
Ben Hamiltonffd2df02018-01-31 21:52:39 +0000225def write_test(module_path, module, check_name, test_extension):
Alexander Kornienko423236b2014-12-09 12:43:09 +0000226 check_name_dashes = module + '-' + check_name
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000227 filename = os.path.normpath(os.path.join(module_path, '../../test/clang-tidy',
Ben Hamiltonffd2df02018-01-31 21:52:39 +0000228 check_name_dashes + '.' + test_extension))
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000229 print('Creating %s...' % filename)
Aaron Ballman017bfee2015-10-06 19:11:12 +0000230 with open(filename, 'wb') as f:
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000231 f.write("""// RUN: %%check_clang_tidy %%s %(check_name_dashes)s %%t
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000232
Alexander Kornienko423236b2014-12-09 12:43:09 +0000233// FIXME: Add something that triggers the check here.
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000234void f();
Alexander Kornienko423236b2014-12-09 12:43:09 +0000235// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'f' is insufficiently awesome [%(check_name_dashes)s]
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000236
Alexander Kornienko423236b2014-12-09 12:43:09 +0000237// FIXME: Verify the applied fix.
238// * Make the CHECK patterns specific enough and try to make verified lines
239// unique to avoid incorrect matches.
240// * Use {{}} for regular expressions.
241// CHECK-FIXES: {{^}}void awesome_f();{{$}}
242
243// FIXME: Add something that doesn't trigger the check here.
244void awesome_f2();
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000245""" % {'check_name_dashes': check_name_dashes})
246
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000247
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000248# Recreates the list of checks in the docs/clang-tidy/checks directory.
Alexander Kornienko5aebfe22016-01-29 15:54:26 +0000249def update_checks_list(clang_tidy_path):
250 docs_dir = os.path.join(clang_tidy_path, '../docs/clang-tidy/checks')
Aaron Ballmanf5f9bf42016-01-11 16:48:26 +0000251 filename = os.path.normpath(os.path.join(docs_dir, 'list.rst'))
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000252 with open(filename, 'r') as f:
253 lines = f.readlines()
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000254 doc_files = filter(lambda s: s.endswith('.rst') and s != 'list.rst',
255 os.listdir(docs_dir))
Aaron Ballmanf5f9bf42016-01-11 16:48:26 +0000256 doc_files.sort()
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000257
Aaron Ballmanf5f9bf42016-01-11 16:48:26 +0000258 def format_link(doc_file):
259 check_name = doc_file.replace('.rst', '')
260 with open(os.path.join(docs_dir, doc_file), 'r') as doc:
Malcolm Parsonsbcf23662016-12-01 17:24:42 +0000261 content = doc.read()
262 match = re.search('.*:orphan:.*', content)
263 if match:
264 return ''
265
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000266 match = re.search('.*:http-equiv=refresh: \d+;URL=(.*).html.*',
Malcolm Parsonsbcf23662016-12-01 17:24:42 +0000267 content)
Aaron Ballmanf5f9bf42016-01-11 16:48:26 +0000268 if match:
269 return ' %(check)s (redirects to %(target)s) <%(check)s>\n' % {
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000270 'check': check_name,
271 'target': match.group(1)
272 }
Aaron Ballmanf5f9bf42016-01-11 16:48:26 +0000273 return ' %s\n' % check_name
274
275 checks = map(format_link, doc_files)
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000276
277 print('Updating %s...' % filename)
Aaron Ballman017bfee2015-10-06 19:11:12 +0000278 with open(filename, 'wb') as f:
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000279 for line in lines:
280 f.write(line)
281 if line.startswith('.. toctree::'):
282 f.writelines(checks)
283 break
284
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000285
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000286# Adds a documentation for the check.
287def write_docs(module_path, module, check_name):
288 check_name_dashes = module + '-' + check_name
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000289 filename = os.path.normpath(os.path.join(
290 module_path, '../../docs/clang-tidy/checks/', check_name_dashes + '.rst'))
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000291 print('Creating %s...' % filename)
Aaron Ballman017bfee2015-10-06 19:11:12 +0000292 with open(filename, 'wb') as f:
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000293 f.write(""".. title:: clang-tidy - %(check_name_dashes)s
Alexander Kornienko785e5222015-12-22 17:36:49 +0000294
295%(check_name_dashes)s
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000296%(underline)s
297
298FIXME: Describe what patterns does the check detect and why. Give examples.
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000299""" % {'check_name_dashes': check_name_dashes,
300 'underline': '=' * len(check_name_dashes)})
301
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000302
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000303def main():
Ben Hamiltonffd2df02018-01-31 21:52:39 +0000304 language_to_extension = {
305 'c': 'c',
306 'c++': 'cpp',
307 'objc': 'm',
308 'objc++': 'mm',
309 }
310 parser = argparse.ArgumentParser()
311 parser.add_argument(
312 '--update-docs',
313 action='store_true',
314 help='just update the list of documentation files, then exit')
315 parser.add_argument(
316 '--language',
317 help='language to use for new check (defaults to c++)',
318 choices=language_to_extension.keys(),
319 default='c++',
320 metavar='LANG')
321 parser.add_argument(
322 'module',
Alexander Kornienko9ac01b02018-02-28 12:21:38 +0000323 nargs='?',
Ben Hamiltonffd2df02018-01-31 21:52:39 +0000324 help='module directory under which to place the new tidy check (e.g., misc)')
325 parser.add_argument(
326 'check',
Alexander Kornienko9ac01b02018-02-28 12:21:38 +0000327 nargs='?',
Ben Hamiltonffd2df02018-01-31 21:52:39 +0000328 help='name of new tidy check to add (e.g. foo-do-the-stuff)')
329 args = parser.parse_args()
330
331 if args.update_docs:
Alexander Kornienko5aebfe22016-01-29 15:54:26 +0000332 update_checks_list(os.path.dirname(sys.argv[0]))
333 return
334
Alexander Kornienko9ac01b02018-02-28 12:21:38 +0000335 if not args.module or not args.check:
336 print 'Module and check must be specified.'
337 parser.print_usage()
338 return
339
Ben Hamiltonffd2df02018-01-31 21:52:39 +0000340 module = args.module
341 check_name = args.check
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000342
343 if check_name.startswith(module):
344 print 'Check name "%s" must not start with the module "%s". Exiting.' % (
345 check_name, module)
346 return
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000347 check_name_camel = ''.join(map(lambda elem: elem.capitalize(),
348 check_name.split('-'))) + 'Check'
349 clang_tidy_path = os.path.dirname(sys.argv[0])
350 module_path = os.path.join(clang_tidy_path, module)
351
352 if not adapt_cmake(module_path, check_name_camel):
353 return
354 write_header(module_path, module, check_name, check_name_camel)
Alexander Kornienko821ca472015-12-16 15:05:27 +0000355 write_implementation(module_path, module, check_name_camel)
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000356 adapt_module(module_path, module, check_name, check_name_camel)
Alexander Kornienkoc2dcdd92017-07-12 13:13:41 +0000357 add_release_notes(module_path, module, check_name)
Ben Hamiltonffd2df02018-01-31 21:52:39 +0000358 test_extension = language_to_extension.get(args.language)
359 write_test(module_path, module, check_name, test_extension)
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000360 write_docs(module_path, module, check_name)
Alexander Kornienko5aebfe22016-01-29 15:54:26 +0000361 update_checks_list(clang_tidy_path)
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000362 print('Done. Now it\'s your turn!')
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000363
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000364
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000365if __name__ == '__main__':
366 main()