blob: 38c72b7b68679c0795d96d6acf2bd013d3314e28 [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:
22 lines = f.read().split('\n')
23 # .split with separator returns one more element. Ignore it.
24 lines = lines[:-1]
25
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
33 with open(filename, 'w') as f:
34 cpp_found = False
35 file_added = False
36 for line in lines:
Alexander Kornienkoc0ebfbe52015-09-04 14:56:57 +000037 cpp_line = line.endswith('.cpp')
38 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
43 f.write(line + '\n')
44
45 return True
46
47
48# Adds a header for the new check.
49def write_header(module_path, module, check_name, check_name_camel):
50 filename = os.path.join(module_path, check_name_camel) + '.h'
51 with open(filename, 'w') as f:
52 header_guard = ('LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_' + module.upper() +
53 '_' + check_name.upper().replace('-', '_') + '_H')
54 f.write('//===--- ')
55 f.write(os.path.basename(filename))
56 f.write(' - clang-tidy')
57 f.write('-' * max(0, 43 - len(os.path.basename(filename))))
58 f.write('*- C++ -*-===//')
59 f.write("""
60//
61// The LLVM Compiler Infrastructure
62//
63// This file is distributed under the University of Illinois Open Source
64// License. See LICENSE.TXT for details.
65//
66//===----------------------------------------------------------------------===//
67
68#ifndef %(header_guard)s
69#define %(header_guard)s
70
71#include "../ClangTidy.h"
72
73namespace clang {
74namespace tidy {
75
76class %(check_name)s : public ClangTidyCheck {
77public:
78 %(check_name)s(StringRef Name, ClangTidyContext *Context)
79 : ClangTidyCheck(Name, Context) {}
80 void registerMatchers(ast_matchers::MatchFinder *Finder) override;
81 void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
82};
83
84} // namespace tidy
85} // namespace clang
86
87#endif // %(header_guard)s
88
89""" % {'header_guard': header_guard,
90 'check_name': check_name_camel})
91
92
93# Adds the implementation of the new check.
94def write_implementation(module_path, check_name_camel):
95 filename = os.path.join(module_path, check_name_camel) + '.cpp'
96 with open(filename, 'w') as f:
97 f.write('//===--- ')
98 f.write(os.path.basename(filename))
99 f.write(' - clang-tidy')
100 f.write('-' * max(0, 52 - len(os.path.basename(filename))))
101 f.write('-===//')
102 f.write("""
103//
104// The LLVM Compiler Infrastructure
105//
106// This file is distributed under the University of Illinois Open Source
107// License. See LICENSE.TXT for details.
108//
109//===----------------------------------------------------------------------===//
110
111#include "%(check_name)s.h"
112#include "clang/AST/ASTContext.h"
113#include "clang/ASTMatchers/ASTMatchFinder.h"
114
115using namespace clang::ast_matchers;
116
117namespace clang {
118namespace tidy {
119
120void %(check_name)s::registerMatchers(MatchFinder *Finder) {
121 // FIXME: Add matchers.
122 Finder->addMatcher(functionDecl().bind("x"), this);
123}
124
125void %(check_name)s::check(const MatchFinder::MatchResult &Result) {
126 // FIXME: Add callback implementation.
Alexander Kornienko423236b2014-12-09 12:43:09 +0000127 const auto *MatchedDecl = Result.Nodes.getNodeAs<FunctionDecl>("x");
128 if (MatchedDecl->getName().startswith("awesome_"))
129 return;
130 diag(MatchedDecl->getLocation(), "function '%%0' is insufficiently awesome")
131 << MatchedDecl->getName()
132 << FixItHint::CreateInsertion(MatchedDecl->getLocation(), "awesome_");
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000133}
134
135} // namespace tidy
136} // namespace clang
137
138""" % {'check_name': check_name_camel})
139
140
141# Modifies the module to include the new check.
142def adapt_module(module_path, module, check_name, check_name_camel):
143 filename = os.path.join(module_path, module.capitalize() + 'TidyModule.cpp')
144 with open(filename, 'r') as f:
145 lines = f.read().split('\n')
146 # .split with separator returns one more element. Ignore it.
147 lines = lines[:-1]
148
149 with open(filename, 'w') as f:
150 header_added = False
151 header_found = False
152 check_added = False
153 check_decl = (' CheckFactories.registerCheck<' + check_name_camel +
154 '>(\n "' + module + '-' + check_name + '");\n')
155
156 for line in lines:
157 if not header_added:
158 match = re.search('#include "(.*)"', line)
159 if match:
160 header_found = True
161 if match.group(1) > check_name_camel:
162 header_added = True
163 f.write('#include "' + check_name_camel + '.h"\n')
164 elif header_found:
165 header_added = True
166 f.write('#include "' + check_name_camel + '.h"\n')
167
168 if not check_added:
169 if line.strip() == '}':
170 check_added = True
171 f.write(check_decl)
172 else:
173 match = re.search('registerCheck<(.*)>', line)
174 if match and match.group(1) > check_name_camel:
175 check_added = True
176 f.write(check_decl)
177 f.write(line + '\n')
178
179
180# Adds a test for the check.
181def write_test(module_path, module, check_name):
Alexander Kornienko423236b2014-12-09 12:43:09 +0000182 check_name_dashes = module + '-' + check_name
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000183 filename = os.path.join(module_path, '../../test/clang-tidy',
Alexander Kornienko423236b2014-12-09 12:43:09 +0000184 check_name_dashes + '.cpp')
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000185 with open(filename, 'w') as f:
Alexander Kornienko423236b2014-12-09 12:43:09 +0000186 f.write(
Alexander Kornienkoa7970eb2015-08-20 18:11:13 +0000187"""// RUN: %%python %%S/check_clang_tidy.py %%s %(check_name_dashes)s %%t
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000188
Alexander Kornienko423236b2014-12-09 12:43:09 +0000189// FIXME: Add something that triggers the check here.
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000190void f();
Alexander Kornienko423236b2014-12-09 12:43:09 +0000191// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'f' is insufficiently awesome [%(check_name_dashes)s]
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000192
Alexander Kornienko423236b2014-12-09 12:43:09 +0000193// FIXME: Verify the applied fix.
194// * Make the CHECK patterns specific enough and try to make verified lines
195// unique to avoid incorrect matches.
196// * Use {{}} for regular expressions.
197// CHECK-FIXES: {{^}}void awesome_f();{{$}}
198
199// FIXME: Add something that doesn't trigger the check here.
200void awesome_f2();
201""" % {"check_name_dashes" : check_name_dashes})
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000202
203def main():
204 if len(sys.argv) != 3:
205 print 'Usage: add_new_check.py <module> <check>, e.g.\n'
Alexander Kornienko423236b2014-12-09 12:43:09 +0000206 print 'add_new_check.py misc awesome-functions\n'
Daniel Jasper4ecc8b32014-12-09 10:02:51 +0000207 return
208
209 module = sys.argv[1]
210 check_name = sys.argv[2]
211 check_name_camel = ''.join(map(lambda elem: elem.capitalize(),
212 check_name.split('-'))) + 'Check'
213 clang_tidy_path = os.path.dirname(sys.argv[0])
214 module_path = os.path.join(clang_tidy_path, module)
215
216 if not adapt_cmake(module_path, check_name_camel):
217 return
218 write_header(module_path, module, check_name, check_name_camel)
219 write_implementation(module_path, check_name_camel)
220 adapt_module(module_path, module, check_name, check_name_camel)
221 write_test(module_path, module, check_name)
222
223
224if __name__ == '__main__':
225 main()