11from __future__
import print_function
12from __future__
import unicode_literals
23 filename = os.path.join(module_path,
"CMakeLists.txt")
29 with io.open(filename,
"r", encoding=
"utf8")
as f:
32 cpp_file = check_name_camel +
".cpp"
36 if line.strip() == cpp_file:
39 print(
"Updating %s..." % filename)
40 with io.open(filename,
"w", encoding=
"utf8", newline=
"\n")
as f:
44 cpp_line = line.strip().endswith(
".cpp")
45 if (
not file_added)
and (cpp_line
or cpp_found):
47 if (line.strip() > cpp_file)
or (
not cpp_line):
48 f.write(
" " + cpp_file +
"\n")
56def write_header(module_path, module, namespace, check_name, check_name_camel):
57 filename = os.path.join(module_path, check_name_camel) +
".h"
58 print(
"Creating %s..." % filename)
59 with io.open(filename,
"w", encoding=
"utf8", newline=
"\n")
as f:
61 "LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_"
64 + check_name_camel.upper()
68 f.write(os.path.basename(filename))
69 f.write(
" - clang-tidy ")
70 f.write(
"-" * max(0, 42 - len(os.path.basename(filename))))
71 f.write(
"*- C++ -*-===//")
75// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
76// See https://llvm.org/LICENSE.txt for license information.
77// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
79//===----------------------------------------------------------------------===//
81#ifndef %(header_guard)s
82#define %(header_guard)s
84#include "../ClangTidyCheck.h"
86namespace clang::tidy::%(namespace)s {
88/// FIXME: Write a short description.
90/// For the user-facing documentation see:
91/// http://clang.llvm.org/extra/clang-tidy/checks/%(module)s/%(check_name)s.html
92class %(check_name_camel)s : public ClangTidyCheck {
94 %(check_name_camel)s(StringRef Name, ClangTidyContext *Context)
95 : ClangTidyCheck(Name, Context) {}
96 void registerMatchers(ast_matchers::MatchFinder *Finder) override;
97 void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
100} // namespace clang::tidy::%(namespace)s
105 "header_guard": header_guard,
106 "check_name_camel": check_name_camel,
107 "check_name": check_name,
109 "namespace": namespace,
116 filename = os.path.join(module_path, check_name_camel) +
".cpp"
117 print(
"Creating %s..." % filename)
118 with io.open(filename,
"w", encoding=
"utf8", newline=
"\n")
as f:
120 f.write(os.path.basename(filename))
121 f.write(
" - clang-tidy ")
122 f.write(
"-" * max(0, 51 - len(os.path.basename(filename))))
127// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
128// See https://llvm.org/LICENSE.txt for license information.
129// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
131//===----------------------------------------------------------------------===//
133#include "%(check_name)s.h"
134#include "clang/ASTMatchers/ASTMatchFinder.h"
136using namespace clang::ast_matchers;
138namespace clang::tidy::%(namespace)s {
140void %(check_name)s::registerMatchers(MatchFinder *Finder) {
141 // FIXME: Add matchers.
142 Finder->addMatcher(functionDecl().bind("x"), this);
145void %(check_name)s::check(const MatchFinder::MatchResult &Result) {
146 // FIXME: Add callback implementation.
147 const auto *MatchedDecl = Result.Nodes.getNodeAs<FunctionDecl>(
"x");
148 if (!MatchedDecl->getIdentifier() || MatchedDecl->getName().starts_with(
"awesome_"))
150 diag(MatchedDecl->getLocation(),
"function %%0 is insufficiently awesome")
152 << FixItHint::CreateInsertion(MatchedDecl->getLocation(),
"awesome_");
153 diag(MatchedDecl->getLocation(),
"insert 'awesome'", DiagnosticIDs::Note);
156} // namespace clang::tidy::%(namespace)s
158 % {"check_name": check_name_camel,
"module": module,
"namespace": namespace}
166 lambda p: p.lower() == module.lower() +
"tidymodule.cpp",
167 os.listdir(module_path),
170 return os.path.join(module_path, modulecpp)
176 with io.open(filename,
"r", encoding=
"utf8")
as f:
177 lines = f.readlines()
179 print(
"Updating %s..." % filename)
180 with io.open(filename,
"w", encoding=
"utf8", newline=
"\n")
as f:
184 check_fq_name = module +
"-" + check_name
186 " CheckFactories.registerCheck<"
198 match = re.search(
'#include "(.*)"', line)
201 if match.group(1) > check_name_camel:
203 f.write(
'#include "' + check_name_camel +
'.h"\n')
206 f.write(
'#include "' + check_name_camel +
'.h"\n')
209 if line.strip() ==
"}":
214 'registerCheck<(.*)> *\( *(?:"([^"]*)")?', line
218 current_check_name = match.group(2)
219 if current_check_name
is None:
224 match = re.search(
' *"([^"]*)"', line)
226 current_check_name = match.group(1)
227 if current_check_name > check_fq_name:
233 except StopIteration:
239 check_name_dashes = module +
"-" + check_name
240 filename = os.path.normpath(
241 os.path.join(module_path,
"../../docs/ReleaseNotes.rst")
243 with io.open(filename,
"r", encoding=
"utf8")
as f:
244 lines = f.readlines()
246 lineMatcher = re.compile(
"New checks")
247 nextSectionMatcher = re.compile(
"New check aliases")
248 checkMatcher = re.compile(
"- New :doc:`(.*)")
250 print(
"Updating %s..." % filename)
251 with io.open(filename,
"w", encoding=
"utf8", newline=
"\n")
as f:
254 add_note_here =
False
258 match = lineMatcher.match(line)
259 match_next = nextSectionMatcher.match(line)
260 match_check = checkMatcher.match(line)
262 last_check = match_check.group(1)
263 if last_check > check_name_dashes:
274 if line.startswith(
"^^^^"):
278 if header_found
and add_note_here:
279 if not line.startswith(
"^^^^"):
282 <clang-tidy/checks/%s/%s>` check.
284 FIXME: add release notes.
287 % (check_name_dashes, module, check_name)
295def write_test(module_path, module, check_name, test_extension):
296 check_name_dashes = module +
"-" + check_name
297 filename = os.path.normpath(
306 check_name +
"." + test_extension,
309 print(
"Creating %s..." % filename)
310 with io.open(filename,
"w", encoding=
"utf8", newline=
"\n")
as f:
312 """// RUN: %%check_clang_tidy %%s %(check_name_dashes)s %%t
314// FIXME: Add something that triggers the check here.
316// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'f' is insufficiently awesome [%(check_name_dashes)s]
318// FIXME: Verify the applied fix.
319// * Make the CHECK patterns specific enough and try to make verified lines
320// unique to avoid incorrect matches.
321// * Use {{}}
for regular expressions.
322// CHECK-FIXES: {{^}}void awesome_f();{{$}}
324// FIXME: Add something that doesn
't trigger the check here.
327 % {"check_name_dashes": check_name_dashes}
332 if not os.path.isdir(dirname):
334 name = os.path.join(dirname, filename)
335 if os.path.isfile(name):
337 caselessname = filename.lower()
338 for file
in os.listdir(dirname):
339 if file.lower() == caselessname:
340 return os.path.join(dirname, file)
346 docs_dir = os.path.join(clang_tidy_path,
"../docs/clang-tidy/checks")
347 filename = os.path.normpath(os.path.join(docs_dir,
"list.rst"))
349 with io.open(filename,
"r", encoding=
"utf8")
as f:
350 lines = f.readlines()
353 for subdir
in filter(
354 lambda s: os.path.isdir(os.path.join(docs_dir, s)), os.listdir(docs_dir)
357 lambda s: s.endswith(
".rst"), os.listdir(os.path.join(docs_dir, subdir))
359 doc_files.append([subdir, file])
364 def filename_from_module(module_name, check_name):
365 module_path = os.path.join(clang_tidy_path, module_name)
366 if not os.path.isdir(module_path):
369 if not os.path.isfile(module_file):
371 with io.open(module_file,
"r")
as f:
373 full_check_name = module_name +
"-" + check_name
374 name_pos = code.find(
'"' + full_check_name +
'"')
377 stmt_end_pos = code.find(
";", name_pos)
378 if stmt_end_pos == -1:
380 stmt_start_pos = code.rfind(
";", 0, name_pos)
381 if stmt_start_pos == -1:
382 stmt_start_pos = code.rfind(
"{", 0, name_pos)
383 if stmt_start_pos == -1:
385 stmt = code[stmt_start_pos + 1 : stmt_end_pos]
386 matches = re.search(
'registerCheck<([^>:]*)>\(\s*"([^"]*)"\s*\)', stmt)
387 if matches
and matches[2] == full_check_name:
388 class_name = matches[1]
389 if "::" in class_name:
390 parts = class_name.split(
"::")
391 class_name = parts[-1]
392 class_path = os.path.join(
393 clang_tidy_path, module_name,
"..", *parts[0:-1]
396 class_path = os.path.join(clang_tidy_path, module_name)
402 def get_base_class(code, check_file):
403 check_class_name = os.path.splitext(os.path.basename(check_file))[0]
404 ctor_pattern = check_class_name +
"\([^:]*\)\s*:\s*([A-Z][A-Za-z0-9]*Check)\("
405 matches = re.search(
"\s+" + check_class_name +
"::" + ctor_pattern, code)
409 header_file = os.path.splitext(check_file)[0] +
".h"
410 if not os.path.isfile(header_file):
412 with io.open(header_file, encoding=
"utf8")
as f:
414 matches = re.search(
" " + ctor_pattern, code)
416 if matches
and matches[1] !=
"ClangTidyCheck":
421 def has_fixits(code):
426 "TransformerClangTidyCheck",
433 def has_auto_fix(check_name):
434 dirname, _, check_name = check_name.partition(
"-")
437 os.path.join(clang_tidy_path, dirname),
440 if not os.path.isfile(check_file):
443 os.path.join(clang_tidy_path, dirname),
446 if not os.path.isfile(check_file):
448 check_file = filename_from_module(dirname, check_name)
449 if not check_file
or not os.path.isfile(check_file):
452 with io.open(check_file, encoding=
"utf8")
as f:
457 base_class = get_base_class(code, check_file)
459 base_file = os.path.join(clang_tidy_path, dirname, base_class +
".cpp")
460 if os.path.isfile(base_file):
461 with io.open(base_file, encoding=
"utf8")
as f:
468 def process_doc(doc_file):
469 check_name = doc_file[0] +
"-" + doc_file[1].replace(
".rst",
"")
471 with io.open(os.path.join(docs_dir, *doc_file),
"r", encoding=
"utf8")
as doc:
473 match = re.search(
".*:orphan:.*", content)
479 match = re.search(
".*:http-equiv=refresh: \d+;URL=(.*).html(.*)", content)
481 return check_name, match
483 def format_link(doc_file):
484 check_name, match = process_doc(doc_file)
485 if not match
and check_name
and not check_name.startswith(
"clang-analyzer-"):
486 return " :doc:`%(check_name)s <%(module)s/%(check)s>`,%(autofix)s\n" % {
487 "check_name": check_name,
488 "module": doc_file[0],
489 "check": doc_file[1].replace(
".rst",
""),
490 "autofix": has_auto_fix(check_name),
495 def format_link_alias(doc_file):
496 check_name, match = process_doc(doc_file)
497 if (match
or (check_name.startswith(
"clang-analyzer-")))
and check_name:
499 check_file = doc_file[1].replace(
".rst",
"")
500 if not match
or match.group(1) ==
"https://clang.llvm.org/docs/analyzer/checkers":
501 title =
"Clang Static Analyzer " + check_file
503 target =
"" if not match
else match.group(1) +
".html" + match.group(2)
508 redirect_parts = re.search(
"^\.\./([^/]*)/([^/]*)$", match.group(1))
509 title = redirect_parts[1] +
"-" + redirect_parts[2]
510 target = redirect_parts[1] +
"/" + redirect_parts[2]
511 autofix = has_auto_fix(title)
518 " :doc:`%(check_name)s <%(module)s/%(check_file)s>`, %(ref_begin)s`%(title)s <%(target)s>`%(ref_end)s,%(autofix)s\n"
520 "check_name": check_name,
522 "check_file": check_file,
526 "ref_begin" : ref_begin,
532 " :doc:`%(check_name)s <%(module)s/%(check_file)s>`, %(title)s,%(autofix)s\n"
534 "check_name": check_name,
536 "check_file": check_file,
543 checks = map(format_link, doc_files)
544 checks_alias = map(format_link_alias, doc_files)
546 print(
"Updating %s..." % filename)
547 with io.open(filename,
"w", encoding=
"utf8", newline=
"\n")
as f:
550 if line.strip() ==
".. csv-table::":
552 f.write(
' :header: "Name", "Offers fixes"\n\n')
556 f.write(
".. csv-table:: Aliases..\n")
557 f.write(
' :header: "Name", "Redirect", "Offers fixes"\n\n')
558 f.writelines(checks_alias)
564 check_name_dashes = module +
"-" + check_name
565 filename = os.path.normpath(
567 module_path,
"../../docs/clang-tidy/checks/", module, check_name +
".rst"
570 print(
"Creating %s..." % filename)
571 with io.open(filename,
"w", encoding=
"utf8", newline=
"\n")
as f:
573 """.. title:: clang-tidy - %(check_name_dashes)s
578FIXME: Describe what patterns does the check detect and why. Give examples.
581 "check_name_dashes": check_name_dashes,
582 "underline":
"=" * len(check_name_dashes),
588 return "".join(map(
lambda elem: elem.capitalize(), check_name.split(
"-")))
596 language_to_extension = {
602 parser = argparse.ArgumentParser()
606 help=
"just update the list of documentation files, then exit",
610 help=
"language to use for new check (defaults to c++)",
611 choices=language_to_extension.keys(),
618 help=
"module directory under which to place the new tidy check (e.g., misc)",
621 "check", nargs=
"?", help=
"name of new tidy check to add (e.g. foo-do-the-stuff)"
623 args = parser.parse_args()
629 if not args.module
or not args.check:
630 print(
"Module and check must be specified.")
635 check_name = args.check
637 if check_name.startswith(module):
639 'Check name "%s" must not start with the module "%s". Exiting.'
640 % (check_name, module)
643 clang_tidy_path = os.path.dirname(sys.argv[0])
644 module_path = os.path.join(clang_tidy_path, module)
651 namespace = module +
"_check"
655 write_header(module_path, module, namespace, check_name, check_name_camel)
657 adapt_module(module_path, module, check_name, check_name_camel)
659 test_extension = language_to_extension.get(args.language)
660 write_test(module_path, module, check_name, test_extension)
663 print(
"Done. Now it's your turn!")
666if __name__ ==
"__main__":
def adapt_cmake(module_path, check_name_camel)
def update_checks_list(clang_tidy_path)
def get_camel_name(check_name)
def get_camel_check_name(check_name)
def write_implementation(module_path, module, namespace, check_name_camel)
def get_actual_filename(dirname, filename)
def write_header(module_path, module, namespace, check_name, check_name_camel)
def write_test(module_path, module, check_name, test_extension)
def adapt_module(module_path, module, check_name, check_name_camel)
def add_release_notes(module_path, module, check_name)
def write_docs(module_path, module, check_name)
def get_module_filename(module_path, module)