20from typing
import Optional, Tuple, Match
25def adapt_cmake(module_path: str, check_name_camel: str) -> bool:
26 filename = os.path.join(module_path,
"CMakeLists.txt")
32 with io.open(filename,
"r", encoding=
"utf8")
as f:
35 cpp_file = check_name_camel +
".cpp"
39 if line.strip() == cpp_file:
42 print(
"Updating %s..." % filename)
43 with io.open(filename,
"w", encoding=
"utf8", newline=
"\n")
as f:
47 cpp_line = line.strip().endswith(
".cpp")
48 if (
not file_added)
and (cpp_line
or cpp_found):
50 if (line.strip() > cpp_file)
or (
not cpp_line):
51 f.write(
" " + cpp_file +
"\n")
64 check_name_camel: str,
68 wrapped_desc =
"\n".join(
70 description, width=80, initial_indent=
"/// ", subsequent_indent=
"/// "
74 override_supported =
"""
75 bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
78 lang_restrict % {"lang":
"LangOpts"}
81 override_supported =
""
82 filename = os.path.join(module_path, check_name_camel) +
".h"
83 print(
"Creating %s..." % filename)
84 with io.open(filename,
"w", encoding=
"utf8", newline=
"\n")
as f:
86 "LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_"
89 + check_name_camel.upper()
93 f.write(os.path.basename(filename))
94 f.write(
" - clang-tidy ")
95 f.write(
"-" * max(0, 42 - len(os.path.basename(filename))))
96 f.write(
"*- C++ -*-===//")
100// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
101// See https://llvm.org/LICENSE.txt for license information.
102// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
104//===----------------------------------------------------------------------===//
106#ifndef %(header_guard)s
107#define %(header_guard)s
109#include "../ClangTidyCheck.h"
111namespace clang::tidy::%(namespace)s {
115/// For the user-facing documentation see:
116/// http://clang.llvm.org/extra/clang-tidy/checks/%(module)s/%(check_name)s.html
117class %(check_name_camel)s : public ClangTidyCheck {
119 %(check_name_camel)s(StringRef Name, ClangTidyContext *Context)
120 : ClangTidyCheck(Name, Context) {}
121 void registerMatchers(ast_matchers::MatchFinder *Finder) override;
122 void check(const ast_matchers::MatchFinder::MatchResult &Result) override;%(override_supported)s
125} // namespace clang::tidy::%(namespace)s
130 "header_guard": header_guard,
131 "check_name_camel": check_name_camel,
132 "check_name": check_name,
134 "namespace": namespace,
135 "description": wrapped_desc,
136 "override_supported": override_supported,
143 module_path: str, module: str, namespace: str, check_name_camel: str
145 filename = os.path.join(module_path, check_name_camel) +
".cpp"
146 print(
"Creating %s..." % filename)
147 with io.open(filename,
"w", encoding=
"utf8", newline=
"\n")
as f:
149 f.write(os.path.basename(filename))
150 f.write(
" - clang-tidy ")
151 f.write(
"-" * max(0, 51 - len(os.path.basename(filename))))
156// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
157// See https://llvm.org/LICENSE.txt for license information.
158// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
160//===----------------------------------------------------------------------===//
162#include "%(check_name)s.h"
163#include "clang/ASTMatchers/ASTMatchFinder.h"
165using namespace clang::ast_matchers;
167namespace clang::tidy::%(namespace)s {
169void %(check_name)s::registerMatchers(MatchFinder *Finder) {
170 // FIXME: Add matchers.
171 Finder->addMatcher(functionDecl().bind("x"), this);
174void %(check_name)s::check(const MatchFinder::MatchResult &Result) {
175 // FIXME: Add callback implementation.
176 const auto *MatchedDecl = Result.Nodes.getNodeAs<FunctionDecl>(
"x");
177 if (!MatchedDecl->getIdentifier() || MatchedDecl->getName().starts_with(
"awesome_"))
179 diag(MatchedDecl->getLocation(),
"function %%0 is insufficiently awesome")
181 << FixItHint::CreateInsertion(MatchedDecl->getLocation(),
"awesome_");
182 diag(MatchedDecl->getLocation(),
"insert 'awesome'", DiagnosticIDs::Note);
185} // namespace clang::tidy::%(namespace)s
187 % {"check_name": check_name_camel,
"module": module,
"namespace": namespace}
195 lambda p: p.lower() == module.lower() +
"tidymodule.cpp",
196 os.listdir(module_path),
199 return os.path.join(module_path, modulecpp)
204 module_path: str, module: str, check_name: str, check_name_camel: str
207 with io.open(filename,
"r", encoding=
"utf8")
as f:
208 lines = f.readlines()
210 print(
"Updating %s..." % filename)
211 with io.open(filename,
"w", encoding=
"utf8", newline=
"\n")
as f:
215 check_fq_name = module +
"-" + check_name
217 " CheckFactories.registerCheck<"
224 lines_iter = iter(lines)
227 line = next(lines_iter)
229 match = re.search(
'#include "(.*)"', line)
232 if match.group(1) > check_name_camel:
234 f.write(
'#include "' + check_name_camel +
'.h"\n')
237 f.write(
'#include "' + check_name_camel +
'.h"\n')
240 if line.strip() ==
"}":
245 r'registerCheck<(.*)> *\( *(?:"([^"]*)")?', line
249 current_check_name = match.group(2)
250 if current_check_name
is None:
254 line = next(lines_iter)
255 match = re.search(
' *"([^"]*)"', line)
257 current_check_name = match.group(1)
258 assert current_check_name
259 if current_check_name > check_fq_name:
265 except StopIteration:
271 module_path: str, module: str, check_name: str, description: str
273 wrapped_desc =
"\n".join(
275 description, width=80, initial_indent=
" ", subsequent_indent=
" "
278 check_name_dashes = module +
"-" + check_name
279 filename = os.path.normpath(
280 os.path.join(module_path,
"../../docs/ReleaseNotes.rst")
282 with io.open(filename,
"r", encoding=
"utf8")
as f:
283 lines = f.readlines()
285 lineMatcher = re.compile(
"New checks")
286 nextSectionMatcher = re.compile(
"New check aliases")
287 checkMatcher = re.compile(
"- New :doc:`(.*)")
289 print(
"Updating %s..." % filename)
290 with io.open(filename,
"w", encoding=
"utf8", newline=
"\n")
as f:
293 add_note_here =
False
297 match = lineMatcher.match(line)
298 match_next = nextSectionMatcher.match(line)
299 match_check = checkMatcher.match(line)
301 last_check = match_check.group(1)
302 if last_check > check_name_dashes:
313 if line.startswith(
"^^^^"):
317 if header_found
and add_note_here:
318 if not line.startswith(
"^^^^"):
321 <clang-tidy/checks/%s/%s>` check.
326 % (check_name_dashes, module, check_name, wrapped_desc)
339 test_standard: Optional[str],
341 test_standard = f
"-std={test_standard}-or-later " if test_standard
else ""
342 check_name_dashes = module +
"-" + check_name
343 filename = os.path.normpath(
352 check_name +
"." + test_extension,
355 print(
"Creating %s..." % filename)
356 with io.open(filename,
"w", encoding=
"utf8", newline=
"\n")
as f:
358 """// RUN: %%check_clang_tidy %(standard)s%%s %(check_name_dashes)s %%t
360// FIXME: Add something that triggers the check here.
362// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'f' is insufficiently awesome [%(check_name_dashes)s]
364// FIXME: Verify the applied fix.
365// * Make the CHECK patterns specific enough and try to make verified lines
366// unique to avoid incorrect matches.
367// * Use {{}}
for regular expressions.
368// CHECK-FIXES: {{^}}void awesome_f();{{$}}
370// FIXME: Add something that doesn
't trigger the check here.
373 % {"check_name_dashes": check_name_dashes,
"standard": test_standard}
378 if not os.path.isdir(dirname):
380 name = os.path.join(dirname, filename)
381 if os.path.isfile(name):
383 caselessname = filename.lower()
384 for file
in os.listdir(dirname):
385 if file.lower() == caselessname:
386 return os.path.join(dirname, file)
392 docs_dir = os.path.join(clang_tidy_path,
"../docs/clang-tidy/checks")
393 filename = os.path.normpath(os.path.join(docs_dir,
"list.rst"))
395 with io.open(filename,
"r", encoding=
"utf8")
as f:
396 lines = f.readlines()
399 for subdir
in filter(
400 lambda s: os.path.isdir(os.path.join(docs_dir, s)), os.listdir(docs_dir)
403 lambda s: s.endswith(
".rst"), os.listdir(os.path.join(docs_dir, subdir))
405 doc_files.append((subdir, file))
410 def filename_from_module(module_name: str, check_name: str) -> str:
411 module_path = os.path.join(clang_tidy_path, module_name)
412 if not os.path.isdir(module_path):
415 if not os.path.isfile(module_file):
417 with io.open(module_file,
"r")
as f:
419 full_check_name = module_name +
"-" + check_name
420 name_pos = code.find(
'"' + full_check_name +
'"')
423 stmt_end_pos = code.find(
";", name_pos)
424 if stmt_end_pos == -1:
426 stmt_start_pos = code.rfind(
";", 0, name_pos)
427 if stmt_start_pos == -1:
428 stmt_start_pos = code.rfind(
"{", 0, name_pos)
429 if stmt_start_pos == -1:
431 stmt = code[stmt_start_pos + 1 : stmt_end_pos]
432 matches = re.search(
r'registerCheck<([^>:]*)>\(\s*"([^"]*)"\s*\)', stmt)
433 if matches
and matches[2] == full_check_name:
434 class_name = matches[1]
435 if "::" in class_name:
436 parts = class_name.split(
"::")
437 class_name = parts[-1]
438 class_path = os.path.join(
439 clang_tidy_path, module_name,
"..", *parts[0:-1]
442 class_path = os.path.join(clang_tidy_path, module_name)
448 def get_base_class(code: str, check_file: str) -> str:
449 check_class_name = os.path.splitext(os.path.basename(check_file))[0]
450 ctor_pattern = check_class_name +
r"\([^:]*\)\s*:\s*([A-Z][A-Za-z0-9]*Check)\("
451 matches = re.search(
r"\s+" + check_class_name +
"::" + ctor_pattern, code)
455 header_file = os.path.splitext(check_file)[0] +
".h"
456 if not os.path.isfile(header_file):
458 with io.open(header_file, encoding=
"utf8")
as f:
460 matches = re.search(
" " + ctor_pattern, code)
462 if matches
and matches[1] !=
"ClangTidyCheck":
467 def has_fixits(code: str) -> bool:
472 "TransformerClangTidyCheck",
479 def has_auto_fix(check_name: str) -> str:
480 dirname, _, check_name = check_name.partition(
"-")
483 os.path.join(clang_tidy_path, dirname),
486 if not os.path.isfile(check_file):
489 os.path.join(clang_tidy_path, dirname),
492 if not os.path.isfile(check_file):
494 check_file = filename_from_module(dirname, check_name)
495 if not check_file
or not os.path.isfile(check_file):
498 with io.open(check_file, encoding=
"utf8")
as f:
503 base_class = get_base_class(code, check_file)
505 base_file = os.path.join(clang_tidy_path, dirname, base_class +
".cpp")
506 if os.path.isfile(base_file):
507 with io.open(base_file, encoding=
"utf8")
as f:
514 def process_doc(doc_file: Tuple[str, str]) -> Tuple[str, Optional[Match[str]]]:
515 check_name = doc_file[0] +
"-" + doc_file[1].replace(
".rst",
"")
517 with io.open(os.path.join(docs_dir, *doc_file),
"r", encoding=
"utf8")
as doc:
519 match = re.search(
".*:orphan:.*", content)
525 match = re.search(
r".*:http-equiv=refresh: \d+;URL=(.*).html(.*)", content)
527 return check_name, match
529 def format_link(doc_file: Tuple[str, str]) -> str:
530 check_name, match = process_doc(doc_file)
531 if not match
and check_name
and not check_name.startswith(
"clang-analyzer-"):
532 return " :doc:`%(check_name)s <%(module)s/%(check)s>`,%(autofix)s\n" % {
533 "check_name": check_name,
534 "module": doc_file[0],
535 "check": doc_file[1].replace(
".rst",
""),
536 "autofix": has_auto_fix(check_name),
541 def format_link_alias(doc_file: Tuple[str, str]) -> str:
542 check_name, match = process_doc(doc_file)
543 if (match
or (check_name.startswith(
"clang-analyzer-")))
and check_name:
545 check_file = doc_file[1].replace(
".rst",
"")
548 or match.group(1) ==
"https://clang.llvm.org/docs/analyzer/checkers"
550 title =
"Clang Static Analyzer " + check_file
552 target =
"" if not match
else match.group(1) +
".html" + match.group(2)
557 redirect_parts = re.search(
r"^\.\./([^/]*)/([^/]*)$", match.group(1))
558 assert redirect_parts
559 title = redirect_parts[1] +
"-" + redirect_parts[2]
560 target = redirect_parts[1] +
"/" + redirect_parts[2]
561 autofix = has_auto_fix(title)
568 " :doc:`%(check_name)s <%(module)s/%(check_file)s>`, %(ref_begin)s`%(title)s <%(target)s>`%(ref_end)s,%(autofix)s\n"
570 "check_name": check_name,
572 "check_file": check_file,
576 "ref_begin": ref_begin,
583 " :doc:`%(check_name)s <%(module)s/%(check_file)s>`, %(title)s,%(autofix)s\n"
585 "check_name": check_name,
587 "check_file": check_file,
595 checks = map(format_link, doc_files)
596 checks_alias = map(format_link_alias, doc_files)
598 print(
"Updating %s..." % filename)
599 with io.open(filename,
"w", encoding=
"utf8", newline=
"\n")
as f:
602 if line.strip() ==
".. csv-table::":
604 f.write(
' :header: "Name", "Offers fixes"\n\n')
607 f.write(
"\nCheck aliases\n-------------\n\n")
608 f.write(
".. csv-table::\n")
609 f.write(
' :header: "Name", "Redirect", "Offers fixes"\n\n')
610 f.writelines(checks_alias)
615def write_docs(module_path: str, module: str, check_name: str) ->
None:
616 check_name_dashes = module +
"-" + check_name
617 filename = os.path.normpath(
619 module_path,
"../../docs/clang-tidy/checks/", module, check_name +
".rst"
622 print(
"Creating %s..." % filename)
623 with io.open(filename,
"w", encoding=
"utf8", newline=
"\n")
as f:
625 """.. title:: clang-tidy - %(check_name_dashes)s
630FIXME: Describe what patterns does the check detect and why. Give examples.
633 "check_name_dashes": check_name_dashes,
634 "underline":
"=" * len(check_name_dashes),
640 return "".join(map(
lambda elem: elem.capitalize(), check_name.split(
"-")))
648 language_to_extension = {
654 cpp_language_to_requirements = {
655 "c++98":
"CPlusPlus",
656 "c++11":
"CPlusPlus11",
657 "c++14":
"CPlusPlus14",
658 "c++17":
"CPlusPlus17",
659 "c++20":
"CPlusPlus20",
660 "c++23":
"CPlusPlus23",
661 "c++26":
"CPlusPlus26",
663 c_language_to_requirements = {
670 parser = argparse.ArgumentParser()
674 help=
"just update the list of documentation files, then exit",
678 help=
"language to use for new check (defaults to c++)",
679 choices=language_to_extension.keys(),
686 help=
"short description of what the check does",
687 default=
"FIXME: Write a short description",
692 help=
"Specify a specific version of the language",
695 cpp_language_to_requirements.keys(), c_language_to_requirements.keys()
703 help=
"module directory under which to place the new tidy check (e.g., misc)",
706 "check", nargs=
"?", help=
"name of new tidy check to add (e.g. foo-do-the-stuff)"
708 args = parser.parse_args()
714 if not args.module
or not args.check:
715 print(
"Module and check must be specified.")
720 check_name = args.check
722 if check_name.startswith(module):
724 'Check name "%s" must not start with the module "%s". Exiting.'
725 % (check_name, module)
728 clang_tidy_path = os.path.dirname(sys.argv[0])
729 module_path = os.path.join(clang_tidy_path, module)
736 namespace = module +
"_check"
740 description = args.description
741 if not description.endswith(
"."):
744 language = args.language
747 if args.standard
in cpp_language_to_requirements:
748 if language
and language !=
"c++":
749 raise ValueError(
"C++ standard chosen when language is not C++")
751 elif args.standard
in c_language_to_requirements:
752 if language
and language !=
"c":
753 raise ValueError(
"C standard chosen when language is not C")
759 language_restrict =
None
762 language_restrict =
"!%(lang)s.CPlusPlus"
763 extra = c_language_to_requirements.get(args.standard,
None)
765 language_restrict += f
" && %(lang)s.{extra}"
766 elif language ==
"c++":
767 language_restrict = (
768 f
"%(lang)s.{cpp_language_to_requirements.get(args.standard, 'CPlusPlus')}"
770 elif language
in [
"objc",
"objc++"]:
771 language_restrict =
"%(lang)s.ObjC"
773 raise ValueError(f
"Unsupported language '{language}' was specified")
785 adapt_module(module_path, module, check_name, check_name_camel)
787 test_extension = language_to_extension[language]
788 write_test(module_path, module, check_name, test_extension, args.standard)
791 print(
"Done. Now it's your turn!")
794if __name__ ==
"__main__":
None write_header(str module_path, str module, str namespace, str check_name, str check_name_camel, str description, str lang_restrict)
str get_module_filename(str module_path, str module)
None update_checks_list(str clang_tidy_path)
None adapt_module(str module_path, str module, str check_name, str check_name_camel)
str get_camel_name(str check_name)
None write_docs(str module_path, str module, str check_name)
None write_implementation(str module_path, str module, str namespace, str check_name_camel)
str get_actual_filename(str dirname, str filename)
None write_test(str module_path, str module, str check_name, str test_extension, Optional[str] test_standard)
bool adapt_cmake(str module_path, str check_name_camel)
str get_camel_check_name(str check_name)
None add_release_notes(str module_path, str module, str check_name, str description)