blob: ebee5049aa23d4bd18a77c5d9a3677d210f1b902 [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)
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000032 with open(filename, 'w') as f:
33 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)
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000052 with open(filename, 'w') as f:
53 header_guard = ('LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_' + module.upper() +
54 '_' + check_name.upper().replace('-', '_') + '_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
74namespace clang {
75namespace tidy {
76
Alexander Kornienko3285f1b2015-09-10 13:56:39 +000077/// FIXME: Write a short description.
78///
79/// For the user-facing documentation see:
80/// http://clang.llvm.org/extra/clang-tidy/checks/%(check_name_dashes)s.html
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000081class %(check_name)s : public ClangTidyCheck {
82public:
83 %(check_name)s(StringRef Name, ClangTidyContext *Context)
84 : ClangTidyCheck(Name, Context) {}
85 void registerMatchers(ast_matchers::MatchFinder *Finder) override;
86 void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
87};
88
89} // namespace tidy
90} // namespace clang
91
92#endif // %(header_guard)s
93
94""" % {'header_guard': header_guard,
Alexander Kornienko3285f1b2015-09-10 13:56:39 +000095 'check_name': check_name_camel,
96 'check_name_dashes': check_name_dashes})
Daniel Jasper4ecc8b32014-12-09 10:02:51 +000097
98
99# Adds the implementation of the new check.
100def write_implementation(module_path, check_name_camel):
101 filename = os.path.join(module_path, check_name_camel) + '.cpp'
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000102 print('Creating %s...' % filename)
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000103 with open(filename, 'w') as f:
104 f.write('//===--- ')
105 f.write(os.path.basename(filename))
106 f.write(' - clang-tidy')
107 f.write('-' * max(0, 52 - len(os.path.basename(filename))))
108 f.write('-===//')
109 f.write("""
110//
111// The LLVM Compiler Infrastructure
112//
113// This file is distributed under the University of Illinois Open Source
114// License. See LICENSE.TXT for details.
115//
116//===----------------------------------------------------------------------===//
117
118#include "%(check_name)s.h"
119#include "clang/AST/ASTContext.h"
120#include "clang/ASTMatchers/ASTMatchFinder.h"
121
122using namespace clang::ast_matchers;
123
124namespace clang {
125namespace tidy {
126
127void %(check_name)s::registerMatchers(MatchFinder *Finder) {
128 // FIXME: Add matchers.
129 Finder->addMatcher(functionDecl().bind("x"), this);
130}
131
132void %(check_name)s::check(const MatchFinder::MatchResult &Result) {
133 // FIXME: Add callback implementation.
Alexander Kornienko423236b2014-12-09 12:43:09 +0000134 const auto *MatchedDecl = Result.Nodes.getNodeAs<FunctionDecl>("x");
135 if (MatchedDecl->getName().startswith("awesome_"))
136 return;
137 diag(MatchedDecl->getLocation(), "function '%%0' is insufficiently awesome")
138 << MatchedDecl->getName()
139 << FixItHint::CreateInsertion(MatchedDecl->getLocation(), "awesome_");
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000140}
141
142} // namespace tidy
143} // namespace clang
144
145""" % {'check_name': check_name_camel})
146
147
148# Modifies the module to include the new check.
149def adapt_module(module_path, module, check_name, check_name_camel):
150 filename = os.path.join(module_path, module.capitalize() + 'TidyModule.cpp')
151 with open(filename, 'r') as f:
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000152 lines = f.readlines()
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000153
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000154 print('Updating %s...' % filename)
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000155 with open(filename, 'w') as f:
156 header_added = False
157 header_found = False
158 check_added = False
159 check_decl = (' CheckFactories.registerCheck<' + check_name_camel +
160 '>(\n "' + module + '-' + check_name + '");\n')
161
162 for line in lines:
163 if not header_added:
164 match = re.search('#include "(.*)"', line)
165 if match:
166 header_found = True
167 if match.group(1) > check_name_camel:
168 header_added = True
169 f.write('#include "' + check_name_camel + '.h"\n')
170 elif header_found:
171 header_added = True
172 f.write('#include "' + check_name_camel + '.h"\n')
173
174 if not check_added:
175 if line.strip() == '}':
176 check_added = True
177 f.write(check_decl)
178 else:
179 match = re.search('registerCheck<(.*)>', line)
180 if match and match.group(1) > check_name_camel:
181 check_added = True
182 f.write(check_decl)
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000183 f.write(line)
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000184
185
186# Adds a test for the check.
187def write_test(module_path, module, check_name):
Alexander Kornienko423236b2014-12-09 12:43:09 +0000188 check_name_dashes = module + '-' + check_name
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000189 filename = os.path.normpath(
190 os.path.join(module_path, '../../test/clang-tidy',
191 check_name_dashes + '.cpp'))
192 print('Creating %s...' % filename)
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000193 with open(filename, 'w') as f:
Alexander Kornienko423236b2014-12-09 12:43:09 +0000194 f.write(
Alexander Kornienkoa7970eb2015-08-20 18:11:13 +0000195"""// RUN: %%python %%S/check_clang_tidy.py %%s %(check_name_dashes)s %%t
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000196
Alexander Kornienko423236b2014-12-09 12:43:09 +0000197// FIXME: Add something that triggers the check here.
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000198void f();
Alexander Kornienko423236b2014-12-09 12:43:09 +0000199// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'f' is insufficiently awesome [%(check_name_dashes)s]
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000200
Alexander Kornienko423236b2014-12-09 12:43:09 +0000201// FIXME: Verify the applied fix.
202// * Make the CHECK patterns specific enough and try to make verified lines
203// unique to avoid incorrect matches.
204// * Use {{}} for regular expressions.
205// CHECK-FIXES: {{^}}void awesome_f();{{$}}
206
207// FIXME: Add something that doesn't trigger the check here.
208void awesome_f2();
209""" % {"check_name_dashes" : check_name_dashes})
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000210
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000211# Recreates the list of checks in the docs/clang-tidy/checks directory.
212def update_checks_list(module_path):
213 filename = os.path.normpath(
214 os.path.join(module_path, '../../docs/clang-tidy/checks/list.rst'))
215 with open(filename, 'r') as f:
216 lines = f.readlines()
217
218 checks = map(lambda s: ' ' + s.replace('.rst', '\n'),
219 filter(lambda s: s.endswith('.rst') and s != 'list.rst',
Alexander Kornienkoeef2c232015-10-01 09:23:20 +0000220 os.listdir(os.path.join(module_path, '../../docs/clang-tidy/checks'))))
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000221 checks.sort()
222
223 print('Updating %s...' % filename)
224 with open(filename, 'w') as f:
225 for line in lines:
226 f.write(line)
227 if line.startswith('.. toctree::'):
228 f.writelines(checks)
229 break
230
231# Adds a documentation for the check.
232def write_docs(module_path, module, check_name):
233 check_name_dashes = module + '-' + check_name
234 filename = os.path.normpath(
235 os.path.join(module_path, '../../docs/clang-tidy/checks/',
236 check_name_dashes + '.rst'))
237 print('Creating %s...' % filename)
238 with open(filename, 'w') as f:
239 f.write(
240"""%(check_name_dashes)s
241%(underline)s
242
243FIXME: Describe what patterns does the check detect and why. Give examples.
244""" % {"check_name_dashes" : check_name_dashes,
245 "underline" : "=" * len(check_name_dashes)})
246
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000247def main():
248 if len(sys.argv) != 3:
249 print 'Usage: add_new_check.py <module> <check>, e.g.\n'
Alexander Kornienko423236b2014-12-09 12:43:09 +0000250 print 'add_new_check.py misc awesome-functions\n'
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000251 return
252
253 module = sys.argv[1]
254 check_name = sys.argv[2]
255 check_name_camel = ''.join(map(lambda elem: elem.capitalize(),
256 check_name.split('-'))) + 'Check'
257 clang_tidy_path = os.path.dirname(sys.argv[0])
258 module_path = os.path.join(clang_tidy_path, module)
259
260 if not adapt_cmake(module_path, check_name_camel):
261 return
262 write_header(module_path, module, check_name, check_name_camel)
263 write_implementation(module_path, check_name_camel)
264 adapt_module(module_path, module, check_name, check_name_camel)
265 write_test(module_path, module, check_name)
Alexander Kornienko3285f1b2015-09-10 13:56:39 +0000266 write_docs(module_path, module, check_name)
267 update_checks_list(module_path)
268 print('Done. Now it\'s your turn!')
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000269
270if __name__ == '__main__':
271 main()