11 from __future__
import print_function
22 filename = os.path.join(module_path,
'CMakeLists.txt')
23 with open(filename,
'r')
as f:
26 cpp_file = check_name_camel +
'.cpp'
30 if line.strip() == cpp_file:
33 print(
'Updating %s...' % filename)
34 with open(filename,
'w')
as f:
38 cpp_line = line.strip().endswith(
'.cpp')
39 if (
not file_added)
and (cpp_line
or cpp_found):
41 if (line.strip() > cpp_file)
or (
not cpp_line):
42 f.write(
' ' + cpp_file +
'\n')
50 def write_header(module_path, module, namespace, check_name, check_name_camel):
51 check_name_dashes = module +
'-' + check_name
52 filename = os.path.join(module_path, check_name_camel) +
'.h'
53 print(
'Creating %s...' % filename)
54 with open(filename,
'w')
as f:
55 header_guard = (
'LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_' + module.upper() +
'_'
56 + check_name_camel.upper() +
'_H')
58 f.write(os.path.basename(filename))
59 f.write(
' - clang-tidy ')
60 f.write(
'-' * max(0, 42 - len(os.path.basename(filename))))
61 f.write(
'*- C++ -*-===//')
64 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
65 // See https://llvm.org/LICENSE.txt for license information.
66 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
68 //===----------------------------------------------------------------------===//
70 #ifndef %(header_guard)s
71 #define %(header_guard)s
73 #include "../ClangTidyCheck.h"
77 namespace %(namespace)s {
79 /// FIXME: Write a short description.
81 /// For the user-facing documentation see:
82 /// http://clang.llvm.org/extra/clang-tidy/checks/%(check_name_dashes)s.html
83 class %(check_name)s : public ClangTidyCheck {
85 %(check_name)s(StringRef Name, ClangTidyContext *Context)
86 : ClangTidyCheck(Name, Context) {}
87 void registerMatchers(ast_matchers::MatchFinder *Finder) override;
88 void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
91 } // namespace %(namespace)s
95 #endif // %(header_guard)s
96 """ % {
'header_guard': header_guard,
97 'check_name': check_name_camel,
98 'check_name_dashes': check_name_dashes,
100 'namespace': namespace})
105 filename = os.path.join(module_path, check_name_camel) +
'.cpp'
106 print(
'Creating %s...' % filename)
107 with open(filename,
'w')
as f:
109 f.write(os.path.basename(filename))
110 f.write(
' - clang-tidy ')
111 f.write(
'-' * max(0, 51 - len(os.path.basename(filename))))
115 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
116 // See https://llvm.org/LICENSE.txt for license information.
117 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
119 //===----------------------------------------------------------------------===//
121 #include "%(check_name)s.h"
122 #include "clang/AST/ASTContext.h"
123 #include "clang/ASTMatchers/ASTMatchFinder.h"
125 using namespace clang::ast_matchers;
129 namespace %(namespace)s {
131 void %(check_name)s::registerMatchers(MatchFinder *Finder) {
132 // FIXME: Add matchers.
133 Finder->addMatcher(functionDecl().bind("x"), this);
136 void %(check_name)s::check(const MatchFinder::MatchResult &Result) {
137 // FIXME: Add callback implementation.
138 const auto *MatchedDecl = Result.Nodes.getNodeAs<FunctionDecl>("x");
139 if (MatchedDecl->getName().startswith("awesome_"))
141 diag(MatchedDecl->getLocation(), "function %%0 is insufficiently awesome")
143 diag(MatchedDecl->getLocation(), "insert 'awesome'", DiagnosticIDs::Note)
144 << FixItHint::CreateInsertion(MatchedDecl->getLocation(), "awesome_");
147 } // namespace %(namespace)s
150 """ % {
'check_name': check_name_camel,
152 'namespace': namespace})
157 modulecpp = list(filter(
158 lambda p: p.lower() == module.lower() +
'tidymodule.cpp',
159 os.listdir(module_path)))[0]
160 filename = os.path.join(module_path, modulecpp)
161 with open(filename,
'r')
as f:
162 lines = f.readlines()
164 print(
'Updating %s...' % filename)
165 with open(filename,
'w')
as f:
169 check_fq_name = module +
'-' + check_name
170 check_decl = (
' CheckFactories.registerCheck<' + check_name_camel +
171 '>(\n "' + check_fq_name +
'");\n')
178 match = re.search(
'#include "(.*)"', line)
181 if match.group(1) > check_name_camel:
183 f.write(
'#include "' + check_name_camel +
'.h"\n')
186 f.write(
'#include "' + check_name_camel +
'.h"\n')
189 if line.strip() ==
'}':
193 match = re.search(
'registerCheck<(.*)> *\( *(?:"([^"]*)")?', line)
196 current_check_name = match.group(2)
197 if current_check_name
is None:
202 match = re.search(
' *"([^"]*)"', line)
204 current_check_name = match.group(1)
205 if current_check_name > check_fq_name:
211 except StopIteration:
217 check_name_dashes = module +
'-' + check_name
218 filename = os.path.normpath(os.path.join(module_path,
219 '../../docs/ReleaseNotes.rst'))
220 with open(filename,
'r')
as f:
221 lines = f.readlines()
223 lineMatcher = re.compile(
'New checks')
224 nextSectionMatcher = re.compile(
'New check aliases')
225 checkMatcher = re.compile(
'- New :doc:`(.*)')
227 print(
'Updating %s...' % filename)
228 with open(filename,
'w')
as f:
231 add_note_here =
False
235 match = lineMatcher.match(line)
236 match_next = nextSectionMatcher.match(line)
237 match_check = checkMatcher.match(line)
239 last_check = match_check.group(1)
240 if last_check > check_name_dashes:
251 if line.startswith(
'^^^^'):
255 if header_found
and add_note_here:
256 if not line.startswith(
'^^^^'):
257 f.write(
"""- New :doc:`%s
258 <clang-tidy/checks/%s>` check.
260 FIXME: add release notes.
262 """ % (check_name_dashes, check_name_dashes))
269 def write_test(module_path, module, check_name, test_extension):
270 check_name_dashes = module +
'-' + check_name
271 filename = os.path.normpath(os.path.join(module_path,
'../../test/clang-tidy/checkers',
272 check_name_dashes +
'.' + test_extension))
273 print(
'Creating %s...' % filename)
274 with open(filename,
'w')
as f:
275 f.write(
"""// RUN: %%check_clang_tidy %%s %(check_name_dashes)s %%t
277 // FIXME: Add something that triggers the check here.
279 // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'f' is insufficiently awesome [%(check_name_dashes)s]
281 // FIXME: Verify the applied fix.
282 // * Make the CHECK patterns specific enough and try to make verified lines
283 // unique to avoid incorrect matches.
284 // * Use {{}} for regular expressions.
285 // CHECK-FIXES: {{^}}void awesome_f();{{$}}
287 // FIXME: Add something that doesn't trigger the check here.
289 """ % {
'check_name_dashes': check_name_dashes})
293 if not os.path.isdir(dirname):
295 name = os.path.join(dirname, filename)
296 if (os.path.isfile(name)):
298 caselessname = filename.lower()
299 for file
in os.listdir(dirname):
300 if (file.lower() == caselessname):
301 return os.path.join(dirname, file)
307 docs_dir = os.path.join(clang_tidy_path,
'../docs/clang-tidy/checks')
308 filename = os.path.normpath(os.path.join(docs_dir,
'list.rst'))
310 with open(filename,
'r')
as f:
311 lines = f.readlines()
313 doc_files = list(filter(
lambda s: s.endswith(
'.rst')
and s !=
'list.rst',
314 os.listdir(docs_dir)))
317 def has_auto_fix(check_name):
318 dirname, _, check_name = check_name.partition(
"-")
323 if not os.path.isfile(checkerCode):
326 with open(checkerCode)
as f:
328 if 'FixItHint' in code
or "ReplacementText" in code
or "fixit" in code:
333 def process_doc(doc_file):
334 check_name = doc_file.replace(
'.rst',
'')
336 with open(os.path.join(docs_dir, doc_file),
'r')
as doc:
338 match = re.search(
'.*:orphan:.*', content)
344 match = re.search(
'.*:http-equiv=refresh: \d+;URL=(.*).html.*',
347 return check_name, match
349 def format_link(doc_file):
350 check_name, match = process_doc(doc_file)
351 if not match
and check_name:
352 return ' `%(check)s <%(check)s.html>`_,%(autofix)s\n' % {
354 'autofix': has_auto_fix(check_name)
359 def format_link_alias(doc_file):
360 check_name, match = process_doc(doc_file)
361 if match
and check_name:
362 if match.group(1) ==
'https://clang.llvm.org/docs/analyzer/checkers':
363 title_redirect =
'Clang Static Analyzer'
365 title_redirect = match.group(1)
367 return ' `%(check)s <%(check)s.html>`_, `%(title)s <%(target)s.html>`_,%(autofix)s\n' % {
369 'target': match.group(1),
370 'title': title_redirect,
371 'autofix': has_auto_fix(match.group(1))
375 checks = map(format_link, doc_files)
376 checks_alias = map(format_link_alias, doc_files)
378 print(
'Updating %s...' % filename)
379 with open(filename,
'w')
as f:
382 if line.strip() ==
".. csv-table::":
384 f.write(
' :header: "Name", "Offers fixes"\n\n')
388 f.write(
'.. csv-table:: Aliases..\n')
389 f.write(
' :header: "Name", "Redirect", "Offers fixes"\n\n')
390 f.writelines(checks_alias)
396 check_name_dashes = module +
'-' + check_name
397 filename = os.path.normpath(os.path.join(
398 module_path,
'../../docs/clang-tidy/checks/', check_name_dashes +
'.rst'))
399 print(
'Creating %s...' % filename)
400 with open(filename,
'w')
as f:
401 f.write(
""".. title:: clang-tidy - %(check_name_dashes)s
403 %(check_name_dashes)s
406 FIXME: Describe what patterns does the check detect and why. Give examples.
407 """ % {
'check_name_dashes': check_name_dashes,
408 'underline':
'=' * len(check_name_dashes)})
412 return ''.
join(map(
lambda elem: elem.capitalize(),
413 check_name.split(
'-'))) +
'Check'
417 language_to_extension = {
423 parser = argparse.ArgumentParser()
427 help=
'just update the list of documentation files, then exit')
430 help=
'language to use for new check (defaults to c++)',
431 choices=language_to_extension.keys(),
437 help=
'module directory under which to place the new tidy check (e.g., misc)')
441 help=
'name of new tidy check to add (e.g. foo-do-the-stuff)')
442 args = parser.parse_args()
448 if not args.module
or not args.check:
449 print(
'Module and check must be specified.')
454 check_name = args.check
456 if check_name.startswith(module):
457 print(
'Check name "%s" must not start with the module "%s". Exiting.' % (
460 clang_tidy_path = os.path.dirname(sys.argv[0])
461 module_path = os.path.join(clang_tidy_path, module)
468 namespace = module +
'_check'
472 write_header(module_path, module, namespace, check_name, check_name_camel)
474 adapt_module(module_path, module, check_name, check_name_camel)
476 test_extension = language_to_extension.get(args.language)
477 write_test(module_path, module, check_name, test_extension)
480 print(
'Done. Now it\'s your turn!')
483 if __name__ ==
'__main__':