blob: 873a2bde3243d47d6ad953c5d5335d0cb2a9c231 [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:
37 if not file_added and (line.endswith('.cpp') or cpp_found):
38 cpp_found = True
39 if line.strip() > cpp_file:
40 f.write(' ' + cpp_file + '\n')
41 file_added = True
42 f.write(line + '\n')
43
44 return True
45
46
47# Adds a header for the new check.
48def write_header(module_path, module, check_name, check_name_camel):
49 filename = os.path.join(module_path, check_name_camel) + '.h'
50 with open(filename, 'w') as f:
51 header_guard = ('LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_' + module.upper() +
52 '_' + check_name.upper().replace('-', '_') + '_H')
53 f.write('//===--- ')
54 f.write(os.path.basename(filename))
55 f.write(' - clang-tidy')
56 f.write('-' * max(0, 43 - len(os.path.basename(filename))))
57 f.write('*- C++ -*-===//')
58 f.write("""
59//
60// The LLVM Compiler Infrastructure
61//
62// This file is distributed under the University of Illinois Open Source
63// License. See LICENSE.TXT for details.
64//
65//===----------------------------------------------------------------------===//
66
67#ifndef %(header_guard)s
68#define %(header_guard)s
69
70#include "../ClangTidy.h"
71
72namespace clang {
73namespace tidy {
74
75class %(check_name)s : public ClangTidyCheck {
76public:
77 %(check_name)s(StringRef Name, ClangTidyContext *Context)
78 : ClangTidyCheck(Name, Context) {}
79 void registerMatchers(ast_matchers::MatchFinder *Finder) override;
80 void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
81};
82
83} // namespace tidy
84} // namespace clang
85
86#endif // %(header_guard)s
87
88""" % {'header_guard': header_guard,
89 'check_name': check_name_camel})
90
91
92# Adds the implementation of the new check.
93def write_implementation(module_path, check_name_camel):
94 filename = os.path.join(module_path, check_name_camel) + '.cpp'
95 with open(filename, 'w') as f:
96 f.write('//===--- ')
97 f.write(os.path.basename(filename))
98 f.write(' - clang-tidy')
99 f.write('-' * max(0, 52 - len(os.path.basename(filename))))
100 f.write('-===//')
101 f.write("""
102//
103// The LLVM Compiler Infrastructure
104//
105// This file is distributed under the University of Illinois Open Source
106// License. See LICENSE.TXT for details.
107//
108//===----------------------------------------------------------------------===//
109
110#include "%(check_name)s.h"
111#include "clang/AST/ASTContext.h"
112#include "clang/ASTMatchers/ASTMatchFinder.h"
113
114using namespace clang::ast_matchers;
115
116namespace clang {
117namespace tidy {
118
119void %(check_name)s::registerMatchers(MatchFinder *Finder) {
120 // FIXME: Add matchers.
121 Finder->addMatcher(functionDecl().bind("x"), this);
122}
123
124void %(check_name)s::check(const MatchFinder::MatchResult &Result) {
125 // FIXME: Add callback implementation.
126 const FunctionDecl *MatchedDecl = Result.Nodes.getNodeAs<FunctionDecl>("x");
127 diag(MatchedDecl->getLocation(), "Dummy warning");
128}
129
130} // namespace tidy
131} // namespace clang
132
133""" % {'check_name': check_name_camel})
134
135
136# Modifies the module to include the new check.
137def adapt_module(module_path, module, check_name, check_name_camel):
138 filename = os.path.join(module_path, module.capitalize() + 'TidyModule.cpp')
139 with open(filename, 'r') as f:
140 lines = f.read().split('\n')
141 # .split with separator returns one more element. Ignore it.
142 lines = lines[:-1]
143
144 with open(filename, 'w') as f:
145 header_added = False
146 header_found = False
147 check_added = False
148 check_decl = (' CheckFactories.registerCheck<' + check_name_camel +
149 '>(\n "' + module + '-' + check_name + '");\n')
150
151 for line in lines:
152 if not header_added:
153 match = re.search('#include "(.*)"', line)
154 if match:
155 header_found = True
156 if match.group(1) > check_name_camel:
157 header_added = True
158 f.write('#include "' + check_name_camel + '.h"\n')
159 elif header_found:
160 header_added = True
161 f.write('#include "' + check_name_camel + '.h"\n')
162
163 if not check_added:
164 if line.strip() == '}':
165 check_added = True
166 f.write(check_decl)
167 else:
168 match = re.search('registerCheck<(.*)>', line)
169 if match and match.group(1) > check_name_camel:
170 check_added = True
171 f.write(check_decl)
172 f.write(line + '\n')
173
174
175# Adds a test for the check.
176def write_test(module_path, module, check_name):
177 filename = os.path.join(module_path, '../../test/clang-tidy',
178 module + '-' + check_name + '.cpp')
179 with open(filename, 'w') as f:
180 f.write('// RUN: $(dirname %s)/check_clang_tidy.sh %s ' + module + '-' +
181 check_name + ' %t\n')
182 f.write("""// REQUIRES: shell
183
184// FIXME: Add something that trigger the check here
185void f();
186// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: Dummy warning
187
188// FIXME: Add something that doesn't trigger the check here
189int i;
190// CHECK-MESSAGES-NOT: warning:""")
191
192def main():
193 if len(sys.argv) != 3:
194 print 'Usage: add_new_check.py <module> <check>, e.g.\n'
195 print 'add_new_check.py misc else-after-return\n'
196 return
197
198 module = sys.argv[1]
199 check_name = sys.argv[2]
200 check_name_camel = ''.join(map(lambda elem: elem.capitalize(),
201 check_name.split('-'))) + 'Check'
202 clang_tidy_path = os.path.dirname(sys.argv[0])
203 module_path = os.path.join(clang_tidy_path, module)
204
205 if not adapt_cmake(module_path, check_name_camel):
206 return
207 write_header(module_path, module, check_name, check_name_camel)
208 write_implementation(module_path, check_name_camel)
209 adapt_module(module_path, module, check_name, check_name_camel)
210 write_test(module_path, module, check_name)
211
212
213if __name__ == '__main__':
214 main()