blob: 7032ad9cf8eaacff973b7ab060ecfec6602f5a0f [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
12import os
13import re
14import 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.
19def adapt_cmake(module_path, check_name_camel):
20 filename = os.path.join(module_path, 'CMakeLists.txt')
21 with open(filename, 'r') as f:
Alexander Kornienko3285f1b2015-09-10 13:56:39 +000022 lines = f.readlines()
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000023
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
Alexander Kornienko3285f1b2015-09-10 13:56:39 +000031 print('Updating %s...' % filename)
Aaron Ballman017bfee2015-10-06 19:11:12 +000032 with open(filename, 'wb') as f:
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000033 cpp_found = False
34 file_added = False
35 for line in lines:
Alexander Kornienko3285f1b2015-09-10 13:56:39 +000036 cpp_line = line.strip().endswith('.cpp')
Alexander Kornienkoc0ebfbe52015-09-04 14:56:57 +000037 if (not file_added) and (cpp_line or cpp_found):
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000038 cpp_found = True
Alexander Kornienkoc0ebfbe52015-09-04 14:56:57 +000039 if (line.strip() > cpp_file) or (not cpp_line):
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000040 f.write(' ' + cpp_file + '\n')
41 file_added = True
Alexander Kornienko3285f1b2015-09-10 13:56:39 +000042 f.write(line)
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000043
44 return True
45
46
47# Adds a header for the new check.
48def write_header(module_path, module, check_name, check_name_camel):
Alexander Kornienko3285f1b2015-09-10 13:56:39 +000049 check_name_dashes = module + '-' + check_name
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000050 filename = os.path.join(module_path, check_name_camel) + '.h'
Alexander Kornienko3285f1b2015-09-10 13:56:39 +000051 print('Creating %s...' % filename)
Aaron Ballman017bfee2015-10-06 19:11:12 +000052 with open(filename, 'wb') as f:
Alexander Kornienko6110cca2016-04-13 08:46:32 +000053 header_guard = ('LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_' + module.upper() + '_'
54 + check_name.upper().replace('-', '_') + '_H')
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000055 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
74namespace clang {
75namespace tidy {
Alexander Kornienko821ca472015-12-16 15:05:27 +000076namespace %(module)s {
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000077
Alexander Kornienko3285f1b2015-09-10 13:56:39 +000078/// 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
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000082class %(check_name)s : public ClangTidyCheck {
83public:
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
Alexander Kornienko821ca472015-12-16 15:05:27 +000090} // namespace %(module)s
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000091} // namespace tidy
92} // namespace clang
93
94#endif // %(header_guard)s
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000095""" % {'header_guard': header_guard,
Alexander Kornienko3285f1b2015-09-10 13:56:39 +000096 'check_name': check_name_camel,
Alexander Kornienko821ca472015-12-16 15:05:27 +000097 'check_name_dashes': check_name_dashes,
98 'module': module})
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000099
100
101# Adds the implementation of the new check.
Alexander Kornienko821ca472015-12-16 15:05:27 +0000102def write_implementation(module_path, module, check_name_camel):
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000103 filename = os.path.join(module_path, check_name_camel) + '.cpp'
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000104 print('Creating %s...' % filename)
Aaron Ballman017bfee2015-10-06 19:11:12 +0000105 with open(filename, 'wb') as f:
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000106 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
124using namespace clang::ast_matchers;
125
126namespace clang {
127namespace tidy {
Alexander Kornienko821ca472015-12-16 15:05:27 +0000128namespace %(module)s {
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000129
130void %(check_name)s::registerMatchers(MatchFinder *Finder) {
131 // FIXME: Add matchers.
132 Finder->addMatcher(functionDecl().bind("x"), this);
133}
134
135void %(check_name)s::check(const MatchFinder::MatchResult &Result) {
136 // FIXME: Add callback implementation.
Alexander Kornienko423236b2014-12-09 12:43:09 +0000137 const auto *MatchedDecl = Result.Nodes.getNodeAs<FunctionDecl>("x");
138 if (MatchedDecl->getName().startswith("awesome_"))
139 return;
Etienne Bergeron68852292016-05-30 15:42:08 +0000140 diag(MatchedDecl->getLocation(), "function %%0 is insufficiently awesome")
Etienne Bergeron1c51a2d2016-05-30 15:05:10 +0000141 << MatchedDecl
Alexander Kornienko423236b2014-12-09 12:43:09 +0000142 << FixItHint::CreateInsertion(MatchedDecl->getLocation(), "awesome_");
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000143}
144
Alexander Kornienko821ca472015-12-16 15:05:27 +0000145} // namespace %(module)s
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000146} // namespace tidy
147} // namespace clang
Alexander Kornienko821ca472015-12-16 15:05:27 +0000148""" % {'check_name': check_name_camel,
149 'module': module})
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000150
151
152# Modifies the module to include the new check.
153def adapt_module(module_path, module, check_name, check_name_camel):
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000154 modulecpp = filter(lambda p: p.lower() == module.lower() + 'tidymodule.cpp',
155 os.listdir(module_path))[0]
Aaron Ballmanaaa40802015-10-06 13:31:00 +0000156 filename = os.path.join(module_path, modulecpp)
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000157 with open(filename, 'r') as f:
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000158 lines = f.readlines()
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000159
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000160 print('Updating %s...' % filename)
Aaron Ballman017bfee2015-10-06 19:11:12 +0000161 with open(filename, 'wb') as f:
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000162 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)
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000189 f.write(line)
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000190
191
192# Adds a test for the check.
193def write_test(module_path, module, check_name):
Alexander Kornienko423236b2014-12-09 12:43:09 +0000194 check_name_dashes = module + '-' + check_name
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000195 filename = os.path.normpath(os.path.join(module_path, '../../test/clang-tidy',
196 check_name_dashes + '.cpp'))
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000197 print('Creating %s...' % filename)
Aaron Ballman017bfee2015-10-06 19:11:12 +0000198 with open(filename, 'wb') as f:
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000199 f.write("""// RUN: %%check_clang_tidy %%s %(check_name_dashes)s %%t
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000200
Alexander Kornienko423236b2014-12-09 12:43:09 +0000201// FIXME: Add something that triggers the check here.
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000202void f();
Alexander Kornienko423236b2014-12-09 12:43:09 +0000203// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'f' is insufficiently awesome [%(check_name_dashes)s]
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000204
Alexander Kornienko423236b2014-12-09 12:43:09 +0000205// 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.
212void awesome_f2();
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000213""" % {'check_name_dashes': check_name_dashes})
214
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000215
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000216# Recreates the list of checks in the docs/clang-tidy/checks directory.
Alexander Kornienko5aebfe22016-01-29 15:54:26 +0000217def update_checks_list(clang_tidy_path):
218 docs_dir = os.path.join(clang_tidy_path, '../docs/clang-tidy/checks')
Aaron Ballmanf5f9bf42016-01-11 16:48:26 +0000219 filename = os.path.normpath(os.path.join(docs_dir, 'list.rst'))
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000220 with open(filename, 'r') as f:
221 lines = f.readlines()
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000222 doc_files = filter(lambda s: s.endswith('.rst') and s != 'list.rst',
223 os.listdir(docs_dir))
Aaron Ballmanf5f9bf42016-01-11 16:48:26 +0000224 doc_files.sort()
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000225
Aaron Ballmanf5f9bf42016-01-11 16:48:26 +0000226 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:
Malcolm Parsonsbcf23662016-12-01 17:24:42 +0000229 content = doc.read()
230 match = re.search('.*:orphan:.*', content)
231 if match:
232 return ''
233
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000234 match = re.search('.*:http-equiv=refresh: \d+;URL=(.*).html.*',
Malcolm Parsonsbcf23662016-12-01 17:24:42 +0000235 content)
Aaron Ballmanf5f9bf42016-01-11 16:48:26 +0000236 if match:
237 return ' %(check)s (redirects to %(target)s) <%(check)s>\n' % {
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000238 'check': check_name,
239 'target': match.group(1)
240 }
Aaron Ballmanf5f9bf42016-01-11 16:48:26 +0000241 return ' %s\n' % check_name
242
243 checks = map(format_link, doc_files)
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000244
245 print('Updating %s...' % filename)
Aaron Ballman017bfee2015-10-06 19:11:12 +0000246 with open(filename, 'wb') as f:
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000247 for line in lines:
248 f.write(line)
249 if line.startswith('.. toctree::'):
250 f.writelines(checks)
251 break
252
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000253
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000254# Adds a documentation for the check.
255def write_docs(module_path, module, check_name):
256 check_name_dashes = module + '-' + check_name
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000257 filename = os.path.normpath(os.path.join(
258 module_path, '../../docs/clang-tidy/checks/', check_name_dashes + '.rst'))
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000259 print('Creating %s...' % filename)
Aaron Ballman017bfee2015-10-06 19:11:12 +0000260 with open(filename, 'wb') as f:
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000261 f.write(""".. title:: clang-tidy - %(check_name_dashes)s
Alexander Kornienko785e5222015-12-22 17:36:49 +0000262
263%(check_name_dashes)s
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000264%(underline)s
265
266FIXME: Describe what patterns does the check detect and why. Give examples.
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000267""" % {'check_name_dashes': check_name_dashes,
268 'underline': '=' * len(check_name_dashes)})
269
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000270
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000271def main():
Alexander Kornienko5aebfe22016-01-29 15:54:26 +0000272 if len(sys.argv) == 2 and sys.argv[1] == '--update-docs':
273 update_checks_list(os.path.dirname(sys.argv[0]))
274 return
275
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000276 if len(sys.argv) != 3:
Alexander Kornienko5aebfe22016-01-29 15:54:26 +0000277 print """\
278Usage: add_new_check.py <module> <check>, e.g.
279 add_new_check.py misc awesome-functions
280
281Alternatively, run 'add_new_check.py --update-docs' to just update the list of
282documentation files."""
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000283
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000284 return
285
286 module = sys.argv[1]
287 check_name = sys.argv[2]
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000288
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
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000293 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)
Alexander Kornienko821ca472015-12-16 15:05:27 +0000301 write_implementation(module_path, module, check_name_camel)
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000302 adapt_module(module_path, module, check_name, check_name_camel)
303 write_test(module_path, module, check_name)
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000304 write_docs(module_path, module, check_name)
Alexander Kornienko5aebfe22016-01-29 15:54:26 +0000305 update_checks_list(clang_tidy_path)
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000306 print('Done. Now it\'s your turn!')
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000307
Alexander Kornienko6110cca2016-04-13 08:46:32 +0000308
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000309if __name__ == '__main__':
310 main()