263 module_path: str, module: str, check_name: str, description: str
265 wrapped_desc =
"\n".join(
267 description, width=80, initial_indent=
" ", subsequent_indent=
" "
270 check_name_dashes = module +
"-" + check_name
271 filename = os.path.normpath(
272 os.path.join(module_path,
"../../docs/ReleaseNotes.rst")
274 with io.open(filename,
"r", encoding=
"utf8")
as f:
275 lines = f.readlines()
277 lineMatcher = re.compile(
"New checks")
278 nextSectionMatcher = re.compile(
"New check aliases")
279 checkMatcher = re.compile(
"- New :doc:`(.*)")
281 print(
"Updating %s..." % filename)
282 with io.open(filename,
"w", encoding=
"utf8", newline=
"\n")
as f:
285 add_note_here =
False
289 match = lineMatcher.match(line)
290 match_next = nextSectionMatcher.match(line)
291 match_check = checkMatcher.match(line)
293 last_check = match_check.group(1)
294 if last_check > check_name_dashes:
305 if line.startswith(
"^^^^"):
309 if header_found
and add_note_here:
310 if not line.startswith(
"^^^^"):
313 <clang-tidy/checks/%s/%s>` check.
318 % (check_name_dashes, module, check_name, wrapped_desc)
385 docs_dir = os.path.join(clang_tidy_path,
"../docs/clang-tidy/checks")
386 filename = os.path.normpath(os.path.join(docs_dir,
"list.rst"))
388 with io.open(filename,
"r", encoding=
"utf8")
as f:
389 lines = f.readlines()
392 for subdir
in filter(
393 lambda s: os.path.isdir(os.path.join(docs_dir, s)), os.listdir(docs_dir)
396 lambda s: s.endswith(
".rst"), os.listdir(os.path.join(docs_dir, subdir))
398 doc_files.append((subdir, file))
403 def filename_from_module(module_name: str, check_name: str) -> str:
404 module_path = os.path.join(clang_tidy_path, module_name)
405 if not os.path.isdir(module_path):
408 if not os.path.isfile(module_file):
410 with io.open(module_file,
"r")
as f:
412 full_check_name = module_name +
"-" + check_name
413 name_pos = code.find(
'"' + full_check_name +
'"')
416 stmt_end_pos = code.find(
";", name_pos)
417 if stmt_end_pos == -1:
419 stmt_start_pos = code.rfind(
";", 0, name_pos)
420 if stmt_start_pos == -1:
421 stmt_start_pos = code.rfind(
"{", 0, name_pos)
422 if stmt_start_pos == -1:
424 stmt = code[stmt_start_pos + 1 : stmt_end_pos]
425 matches = re.search(
r'registerCheck<([^>:]*)>\(\s*"([^"]*)"\s*\)', stmt)
426 if matches
and matches[2] == full_check_name:
427 class_name = matches[1]
428 if "::" in class_name:
429 parts = class_name.split(
"::")
430 class_name = parts[-1]
431 class_path = os.path.join(
432 clang_tidy_path, module_name,
"..", *parts[0:-1]
435 class_path = os.path.join(clang_tidy_path, module_name)
441 def get_base_class(code: str, check_file: str) -> str:
442 check_class_name = os.path.splitext(os.path.basename(check_file))[0]
443 ctor_pattern = check_class_name +
r"\([^:]*\)\s*:\s*([A-Z][A-Za-z0-9]*Check)\("
444 matches = re.search(
r"\s+" + check_class_name +
"::" + ctor_pattern, code)
448 header_file = os.path.splitext(check_file)[0] +
".h"
449 if not os.path.isfile(header_file):
451 with io.open(header_file, encoding=
"utf8")
as f:
453 matches = re.search(
" " + ctor_pattern, code)
455 if matches
and matches[1] !=
"ClangTidyCheck":
460 def has_fixits(code: str) -> bool:
465 "TransformerClangTidyCheck",
472 def has_auto_fix(check_name: str) -> str:
473 dirname, _, check_name = check_name.partition(
"-")
476 os.path.join(clang_tidy_path, dirname),
479 if not os.path.isfile(check_file):
482 os.path.join(clang_tidy_path, dirname),
485 if not os.path.isfile(check_file):
487 check_file = filename_from_module(dirname, check_name)
488 if not check_file
or not os.path.isfile(check_file):
491 with io.open(check_file, encoding=
"utf8")
as f:
496 base_class = get_base_class(code, check_file)
498 base_file = os.path.join(clang_tidy_path, dirname, base_class +
".cpp")
499 if os.path.isfile(base_file):
500 with io.open(base_file, encoding=
"utf8")
as f:
507 def process_doc(doc_file: Tuple[str, str]) -> Tuple[str, Optional[Match[str]]]:
508 check_name = doc_file[0] +
"-" + doc_file[1].replace(
".rst",
"")
510 with io.open(os.path.join(docs_dir, *doc_file),
"r", encoding=
"utf8")
as doc:
512 match = re.search(
".*:orphan:.*", content)
518 match = re.search(
r".*:http-equiv=refresh: \d+;URL=(.*).html(.*)", content)
520 return check_name, match
522 def format_link(doc_file: Tuple[str, str]) -> str:
523 check_name, match = process_doc(doc_file)
524 if not match
and check_name
and not check_name.startswith(
"clang-analyzer-"):
525 return " :doc:`%(check_name)s <%(module)s/%(check)s>`,%(autofix)s\n" % {
526 "check_name": check_name,
527 "module": doc_file[0],
528 "check": doc_file[1].replace(
".rst",
""),
529 "autofix": has_auto_fix(check_name),
534 def format_link_alias(doc_file: Tuple[str, str]) -> str:
535 check_name, match = process_doc(doc_file)
536 if (match
or (check_name.startswith(
"clang-analyzer-")))
and check_name:
538 check_file = doc_file[1].replace(
".rst",
"")
541 or match.group(1) ==
"https://clang.llvm.org/docs/analyzer/checkers"
543 title =
"Clang Static Analyzer " + check_file
545 target =
"" if not match
else match.group(1) +
".html" + match.group(2)
550 redirect_parts = re.search(
r"^\.\./([^/]*)/([^/]*)$", match.group(1))
551 assert redirect_parts
552 title = redirect_parts[1] +
"-" + redirect_parts[2]
553 target = redirect_parts[1] +
"/" + redirect_parts[2]
554 autofix = has_auto_fix(title)
561 " :doc:`%(check_name)s <%(module)s/%(check_file)s>`, %(ref_begin)s`%(title)s <%(target)s>`%(ref_end)s,%(autofix)s\n"
563 "check_name": check_name,
565 "check_file": check_file,
569 "ref_begin": ref_begin,
576 " :doc:`%(check_name)s <%(module)s/%(check_file)s>`, %(title)s,%(autofix)s\n"
578 "check_name": check_name,
580 "check_file": check_file,
588 checks = map(format_link, doc_files)
589 checks_alias = map(format_link_alias, doc_files)
591 print(
"Updating %s..." % filename)
592 with io.open(filename,
"w", encoding=
"utf8", newline=
"\n")
as f:
595 if line.strip() ==
".. csv-table::":
597 f.write(
' :header: "Name", "Offers fixes"\n\n')
600 f.write(
"\nCheck aliases\n-------------\n\n")
601 f.write(
".. csv-table::\n")
602 f.write(
' :header: "Name", "Redirect", "Offers fixes"\n\n')
603 f.writelines(checks_alias)
641 language_to_extension = {
647 cpp_language_to_requirements = {
648 "c++98":
"CPlusPlus",
649 "c++11":
"CPlusPlus11",
650 "c++14":
"CPlusPlus14",
651 "c++17":
"CPlusPlus17",
652 "c++20":
"CPlusPlus20",
653 "c++23":
"CPlusPlus23",
654 "c++26":
"CPlusPlus26",
656 c_language_to_requirements = {
663 parser = argparse.ArgumentParser()
667 help=
"just update the list of documentation files, then exit",
671 help=
"language to use for new check (defaults to c++)",
672 choices=language_to_extension.keys(),
679 help=
"short description of what the check does",
680 default=
"FIXME: Write a short description",
685 help=
"Specify a specific version of the language",
688 cpp_language_to_requirements.keys(), c_language_to_requirements.keys()
696 help=
"module directory under which to place the new tidy check (e.g., misc)",
699 "check", nargs=
"?", help=
"name of new tidy check to add (e.g. foo-do-the-stuff)"
701 args = parser.parse_args()
707 if not args.module
or not args.check:
708 print(
"Module and check must be specified.")
713 check_name = args.check
715 if check_name.startswith(module):
717 'Check name "%s" must not start with the module "%s". Exiting.'
718 % (check_name, module)
721 clang_tidy_path = os.path.dirname(sys.argv[0])
722 module_path = os.path.join(clang_tidy_path, module)
729 namespace = module +
"_check"
733 description = args.description
734 if not description.endswith(
"."):
737 language = args.language
740 if args.standard
in cpp_language_to_requirements:
741 if language
and language !=
"c++":
742 raise ValueError(
"C++ standard chosen when language is not C++")
744 elif args.standard
in c_language_to_requirements:
745 if language
and language !=
"c":
746 raise ValueError(
"C standard chosen when language is not C")
752 language_restrict =
None
755 language_restrict =
"!%(lang)s.CPlusPlus"
756 extra = c_language_to_requirements.get(args.standard,
None)
758 language_restrict += f
" && %(lang)s.{extra}"
759 elif language ==
"c++":
760 language_restrict = (
761 f
"%(lang)s.{cpp_language_to_requirements.get(args.standard, 'CPlusPlus')}"
763 elif language
in [
"objc",
"objc++"]:
764 language_restrict =
"%(lang)s.ObjC"
766 raise ValueError(f
"Unsupported language '{language}' was specified")
778 adapt_module(module_path, module, check_name, check_name_camel)
780 test_extension = language_to_extension[language]
781 write_test(module_path, module, check_name, test_extension, args.standard)
784 print(
"Done. Now it's your turn!")