blob: 4d811aa1f2fdcffb6e83d678d2296c158767e51e [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
Jonathan Coe96d81912018-03-24 10:49:17 +000012from __future__ import print_function
13
Ben Hamiltonffd2df02018-01-31 21:52:39 +000014import argparse
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000015import os
16import re
17import sys
18
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000019# Adapts the module's CMakelist file. Returns 'True' if it could add a new entry
20# and 'False' if the entry already existed.
21def adapt_cmake(module_path, check_name_camel):
22 filename = os.path.join(module_path, 'CMakeLists.txt')
23 with open(filename, 'r') as f:
Alexander Kornienko3285f1b2015-09-10 13:56:39 +000024 lines = f.readlines()
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000025
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
Alexander Kornienko3285f1b2015-09-10 13:56:39 +000033 print('Updating %s...' % filename)
Jonathan Coe96d81912018-03-24 10:49:17 +000034 with open(filename, 'w') as f:
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000035 cpp_found = False
36 file_added = False
37 for line in lines:
Alexander Kornienko3285f1b2015-09-10 13:56:39 +000038 cpp_line = line.strip().endswith('.cpp')
Alexander Kornienkoc0ebfbe52015-09-04 14:56:57 +000039 if (not file_added) and (cpp_line or cpp_found):
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000040 cpp_found = True
Alexander Kornienkoc0ebfbe52015-09-04 14:56:57 +000041 if (line.strip() > cpp_file) or (not cpp_line):
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000042 f.write(' ' + cpp_file + '\n')
43 file_added = True
Alexander Kornienko3285f1b2015-09-10 13:56:39 +000044 f.write(line)
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000045
46 return True
47
48
49# Adds a header for the new check.
50def write_header(module_path, module, check_name, check_name_camel):
Alexander Kornienko3285f1b2015-09-10 13:56:39 +000051 check_name_dashes = module + '-' + check_name
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000052 filename = os.path.join(module_path, check_name_camel) + '.h'
Alexander Kornienko3285f1b2015-09-10 13:56:39 +000053 print('Creating %s...' % filename)
Jonathan Coe96d81912018-03-24 10:49:17 +000054 with open(filename, 'w') as f:
Alexander Kornienko6110cca2016-04-13 08:46:32 +000055 header_guard = ('LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_' + module.upper() + '_'
Alexander Kornienkob43950d2017-11-25 08:49:04 +000056 + check_name_camel.upper() + '_H')
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000057 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
76namespace clang {
77namespace tidy {
Alexander Kornienko821ca472015-12-16 15:05:27 +000078namespace %(module)s {
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000079
Alexander Kornienko3285f1b2015-09-10 13:56:39 +000080/// 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
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000084class %(check_name)s : public ClangTidyCheck {
85public:
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
Alexander Kornienko821ca472015-12-16 15:05:27 +000092} // namespace %(module)s
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000093} // namespace tidy
94} // namespace clang
95
96#endif // %(header_guard)s
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000097""" % {'header_guard': header_guard,
Alexander Kornienko3285f1b2015-09-10 13:56:39 +000098 'check_name': check_name_camel,
Alexander Kornienko821ca472015-12-16 15:05:27 +000099 'check_name_dashes': check_name_dashes,
100 'module': module})
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000101
102
103# Adds the implementation of the new check.
Alexander Kornienko821ca472015-12-16 15:05:27 +0000104def write_implementation(module_path, module, check_name_camel):
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000105 filename = os.path.join(module_path, check_name_camel) + '.cpp'
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000106 print('Creating %s...' % filename)
Jonathan Coe96d81912018-03-24 10:49:17 +0000107 with open(filename, 'w') as f:
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000108 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
126using namespace clang::ast_matchers;
127
128namespace clang {
129namespace tidy {
Alexander Kornienko821ca472015-12-16 15:05:27 +0000130namespace %(module)s {
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000131
132void %(check_name)s::registerMatchers(MatchFinder *Finder) {
133 // FIXME: Add matchers.
134 Finder->addMatcher(functionDecl().bind("x"), this);
135}
136
137void %(check_name)s::check(const MatchFinder::MatchResult &Result) {
138 // FIXME: Add callback implementation.
Alexander Kornienko423236b2014-12-09 12:43:09 +0000139 const auto *MatchedDecl = Result.Nodes.getNodeAs<FunctionDecl>("x");
140 if (MatchedDecl->getName().startswith("awesome_"))
141 return;
Etienne Bergeron68852292016-05-30 15:42:08 +0000142 diag(MatchedDecl->getLocation(), "function %%0 is insufficiently awesome")
Etienne Bergeron1c51a2d2016-05-30 15:05:10 +0000143 << MatchedDecl
Alexander Kornienko423236b2014-12-09 12:43:09 +0000144 << FixItHint::CreateInsertion(MatchedDecl->getLocation(), "awesome_");
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000145}
146
Alexander Kornienko821ca472015-12-16 15:05:27 +0000147} // namespace %(module)s
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000148} // namespace tidy
149} // namespace clang
Alexander Kornienko821ca472015-12-16 15:05:27 +0000150""" % {'check_name': check_name_camel,
151 'module': module})
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000152
153
154# Modifies the module to include the new check.
155def adapt_module(module_path, module, check_name, check_name_camel):
Jonathan Coe96d81912018-03-24 10:49:17 +0000156 modulecpp = list(filter(
157 lambda p: p.lower() == module.lower() + 'tidymodule.cpp',
158 os.listdir(module_path)))[0]
Aaron Ballmanaaa40802015-10-06 13:31:00 +0000159 filename = os.path.join(module_path, modulecpp)
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000160 with open(filename, 'r') as f:
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000161 lines = f.readlines()
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000162
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000163 print('Updating %s...' % filename)
Jonathan Coe96d81912018-03-24 10:49:17 +0000164 with open(filename, 'w') as f:
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000165 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)
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000192 f.write(line)
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000193
194
Alexander Kornienkoc2dcdd92017-07-12 13:13:41 +0000195# Adds a release notes entry.
196def 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)
Jonathan Coe96d81912018-03-24 10:49:17 +0000204 with open(filename, 'w') as f:
Alexander Kornienkoc2dcdd92017-07-12 13:13:41 +0000205 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("""
Eugene Zelenko3f9ef8d2018-03-21 17:06:13 +0000216- New :doc:`%s
Alexander Kornienko36ba5ad2018-05-03 15:59:39 +0000217 <clang-tidy/checks/%s>` check.
Alexander Kornienkoc2dcdd92017-07-12 13:13:41 +0000218
219 FIXME: add release notes.
220""" % (check_name_dashes, check_name_dashes))
221 note_added = True
222
223 f.write(line)
224
225
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000226# Adds a test for the check.
Ben Hamiltonffd2df02018-01-31 21:52:39 +0000227def write_test(module_path, module, check_name, test_extension):
Alexander Kornienko423236b2014-12-09 12:43:09 +0000228 check_name_dashes = module + '-' + check_name
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000229 filename = os.path.normpath(os.path.join(module_path, '../../test/clang-tidy',
Ben Hamiltonffd2df02018-01-31 21:52:39 +0000230 check_name_dashes + '.' + test_extension))
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000231 print('Creating %s...' % filename)
Jonathan Coe96d81912018-03-24 10:49:17 +0000232 with open(filename, 'w') as f:
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000233 f.write("""// RUN: %%check_clang_tidy %%s %(check_name_dashes)s %%t
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000234
Alexander Kornienko423236b2014-12-09 12:43:09 +0000235// FIXME: Add something that triggers the check here.
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000236void f();
Alexander Kornienko423236b2014-12-09 12:43:09 +0000237// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'f' is insufficiently awesome [%(check_name_dashes)s]
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000238
Alexander Kornienko423236b2014-12-09 12:43:09 +0000239// 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.
246void awesome_f2();
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000247""" % {'check_name_dashes': check_name_dashes})
248
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000249
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000250# Recreates the list of checks in the docs/clang-tidy/checks directory.
Alexander Kornienko5aebfe22016-01-29 15:54:26 +0000251def update_checks_list(clang_tidy_path):
252 docs_dir = os.path.join(clang_tidy_path, '../docs/clang-tidy/checks')
Aaron Ballmanf5f9bf42016-01-11 16:48:26 +0000253 filename = os.path.normpath(os.path.join(docs_dir, 'list.rst'))
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000254 with open(filename, 'r') as f:
255 lines = f.readlines()
Jonathan Coe96d81912018-03-24 10:49:17 +0000256 doc_files = list(filter(lambda s: s.endswith('.rst') and s != 'list.rst',
257 os.listdir(docs_dir)))
Aaron Ballmanf5f9bf42016-01-11 16:48:26 +0000258 doc_files.sort()
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000259
Aaron Ballmanf5f9bf42016-01-11 16:48:26 +0000260 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:
Malcolm Parsonsbcf23662016-12-01 17:24:42 +0000263 content = doc.read()
264 match = re.search('.*:orphan:.*', content)
265 if match:
266 return ''
267
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000268 match = re.search('.*:http-equiv=refresh: \d+;URL=(.*).html.*',
Malcolm Parsonsbcf23662016-12-01 17:24:42 +0000269 content)
Aaron Ballmanf5f9bf42016-01-11 16:48:26 +0000270 if match:
271 return ' %(check)s (redirects to %(target)s) <%(check)s>\n' % {
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000272 'check': check_name,
273 'target': match.group(1)
274 }
Aaron Ballmanf5f9bf42016-01-11 16:48:26 +0000275 return ' %s\n' % check_name
276
277 checks = map(format_link, doc_files)
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000278
279 print('Updating %s...' % filename)
Jonathan Coe96d81912018-03-24 10:49:17 +0000280 with open(filename, 'w') as f:
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000281 for line in lines:
282 f.write(line)
283 if line.startswith('.. toctree::'):
284 f.writelines(checks)
285 break
286
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000287
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000288# Adds a documentation for the check.
289def write_docs(module_path, module, check_name):
290 check_name_dashes = module + '-' + check_name
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000291 filename = os.path.normpath(os.path.join(
292 module_path, '../../docs/clang-tidy/checks/', check_name_dashes + '.rst'))
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000293 print('Creating %s...' % filename)
Jonathan Coe96d81912018-03-24 10:49:17 +0000294 with open(filename, 'w') as f:
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000295 f.write(""".. title:: clang-tidy - %(check_name_dashes)s
Alexander Kornienko785e5222015-12-22 17:36:49 +0000296
297%(check_name_dashes)s
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000298%(underline)s
299
300FIXME: Describe what patterns does the check detect and why. Give examples.
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000301""" % {'check_name_dashes': check_name_dashes,
302 'underline': '=' * len(check_name_dashes)})
303
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000304
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000305def main():
Ben Hamiltonffd2df02018-01-31 21:52:39 +0000306 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',
Alexander Kornienko9ac01b02018-02-28 12:21:38 +0000325 nargs='?',
Ben Hamiltonffd2df02018-01-31 21:52:39 +0000326 help='module directory under which to place the new tidy check (e.g., misc)')
327 parser.add_argument(
328 'check',
Alexander Kornienko9ac01b02018-02-28 12:21:38 +0000329 nargs='?',
Ben Hamiltonffd2df02018-01-31 21:52:39 +0000330 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:
Alexander Kornienko5aebfe22016-01-29 15:54:26 +0000334 update_checks_list(os.path.dirname(sys.argv[0]))
335 return
336
Alexander Kornienko9ac01b02018-02-28 12:21:38 +0000337 if not args.module or not args.check:
Jonathan Coe96d81912018-03-24 10:49:17 +0000338 print('Module and check must be specified.')
Alexander Kornienko9ac01b02018-02-28 12:21:38 +0000339 parser.print_usage()
340 return
341
Ben Hamiltonffd2df02018-01-31 21:52:39 +0000342 module = args.module
343 check_name = args.check
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000344
345 if check_name.startswith(module):
Jonathan Coe96d81912018-03-24 10:49:17 +0000346 print('Check name "%s" must not start with the module "%s". Exiting.' % (
347 check_name, module))
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000348 return
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000349 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)
Alexander Kornienko821ca472015-12-16 15:05:27 +0000357 write_implementation(module_path, module, check_name_camel)
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000358 adapt_module(module_path, module, check_name, check_name_camel)
Alexander Kornienkoc2dcdd92017-07-12 13:13:41 +0000359 add_release_notes(module_path, module, check_name)
Ben Hamiltonffd2df02018-01-31 21:52:39 +0000360 test_extension = language_to_extension.get(args.language)
361 write_test(module_path, module, check_name, test_extension)
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000362 write_docs(module_path, module, check_name)
Alexander Kornienko5aebfe22016-01-29 15:54:26 +0000363 update_checks_list(clang_tidy_path)
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000364 print('Done. Now it\'s your turn!')
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000365
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000366
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000367if __name__ == '__main__':
368 main()