1313
1414import argparse
1515import io
16+ import itertools
1617import os
1718import re
1819import sys
1920import textwrap
2021
22+
2123# Adapts the module's CMakelist file. Returns 'True' if it could add a new
2224# entry and 'False' if the entry already existed.
2325def adapt_cmake (module_path , check_name_camel ):
@@ -55,13 +57,28 @@ def adapt_cmake(module_path, check_name_camel):
5557
5658# Adds a header for the new check.
5759def write_header (
58- module_path , module , namespace , check_name , check_name_camel , description
60+ module_path ,
61+ module ,
62+ namespace ,
63+ check_name ,
64+ check_name_camel ,
65+ description ,
66+ lang_restrict ,
5967):
6068 wrapped_desc = "\n " .join (
6169 textwrap .wrap (
6270 description , width = 80 , initial_indent = "/// " , subsequent_indent = "/// "
6371 )
6472 )
73+ if lang_restrict :
74+ override_supported = """
75+ bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
76+ return %s;
77+ }""" % (
78+ lang_restrict % {"lang" : "LangOpts" }
79+ )
80+ else :
81+ override_supported = ""
6582 filename = os .path .join (module_path , check_name_camel ) + ".h"
6683 print ("Creating %s..." % filename )
6784 with io .open (filename , "w" , encoding = "utf8" , newline = "\n " ) as f :
@@ -102,7 +119,7 @@ class %(check_name_camel)s : public ClangTidyCheck {
102119 %(check_name_camel)s(StringRef Name, ClangTidyContext *Context)
103120 : ClangTidyCheck(Name, Context) {}
104121 void registerMatchers(ast_matchers::MatchFinder *Finder) override;
105- void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
122+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;%(override_supported)s
106123};
107124
108125} // namespace clang::tidy::%(namespace)s
@@ -116,6 +133,7 @@ class %(check_name_camel)s : public ClangTidyCheck {
116133 "module" : module ,
117134 "namespace" : namespace ,
118135 "description" : wrapped_desc ,
136+ "override_supported" : override_supported ,
119137 }
120138 )
121139
@@ -306,7 +324,9 @@ def add_release_notes(module_path, module, check_name, description):
306324
307325
308326# Adds a test for the check.
309- def write_test (module_path , module , check_name , test_extension ):
327+ def write_test (module_path , module , check_name , test_extension , test_standard ):
328+ if test_standard :
329+ test_standard = f"-std={ test_standard } -or-later "
310330 check_name_dashes = module + "-" + check_name
311331 filename = os .path .normpath (
312332 os .path .join (
@@ -323,7 +343,7 @@ def write_test(module_path, module, check_name, test_extension):
323343 print ("Creating %s..." % filename )
324344 with io .open (filename , "w" , encoding = "utf8" , newline = "\n " ) as f :
325345 f .write (
326- """// RUN: %%check_clang_tidy %%s %(check_name_dashes)s %%t
346+ """// RUN: %%check_clang_tidy %(standard)s% %s %(check_name_dashes)s %%t
327347
328348// FIXME: Add something that triggers the check here.
329349void f();
@@ -338,7 +358,7 @@ def write_test(module_path, module, check_name, test_extension):
338358// FIXME: Add something that doesn't trigger the check here.
339359void awesome_f2();
340360"""
341- % {"check_name_dashes" : check_name_dashes }
361+ % {"check_name_dashes" : check_name_dashes , "standard" : test_standard }
342362 )
343363
344364
@@ -511,7 +531,10 @@ def format_link_alias(doc_file):
511531 if (match or (check_name .startswith ("clang-analyzer-" ))) and check_name :
512532 module = doc_file [0 ]
513533 check_file = doc_file [1 ].replace (".rst" , "" )
514- if not match or match .group (1 ) == "https://clang.llvm.org/docs/analyzer/checkers" :
534+ if (
535+ not match
536+ or match .group (1 ) == "https://clang.llvm.org/docs/analyzer/checkers"
537+ ):
515538 title = "Clang Static Analyzer " + check_file
516539 # Preserve the anchor in checkers.html from group 2.
517540 target = "" if not match else match .group (1 ) + ".html" + match .group (2 )
@@ -529,29 +552,31 @@ def format_link_alias(doc_file):
529552 if target :
530553 # The checker is just a redirect.
531554 return (
532- " :doc:`%(check_name)s <%(module)s/%(check_file)s>`, %(ref_begin)s`%(title)s <%(target)s>`%(ref_end)s,%(autofix)s\n "
555+ " :doc:`%(check_name)s <%(module)s/%(check_file)s>`, %(ref_begin)s`%(title)s <%(target)s>`%(ref_end)s,%(autofix)s\n "
533556 % {
534557 "check_name" : check_name ,
535558 "module" : module ,
536559 "check_file" : check_file ,
537560 "target" : target ,
538561 "title" : title ,
539562 "autofix" : autofix ,
540- "ref_begin" : ref_begin ,
541- "ref_end" : ref_end
542- })
563+ "ref_begin" : ref_begin ,
564+ "ref_end" : ref_end ,
565+ }
566+ )
543567 else :
544568 # The checker is just a alias without redirect.
545569 return (
546- " :doc:`%(check_name)s <%(module)s/%(check_file)s>`, %(title)s,%(autofix)s\n "
570+ " :doc:`%(check_name)s <%(module)s/%(check_file)s>`, %(title)s,%(autofix)s\n "
547571 % {
548572 "check_name" : check_name ,
549573 "module" : module ,
550574 "check_file" : check_file ,
551575 "target" : target ,
552576 "title" : title ,
553577 "autofix" : autofix ,
554- })
578+ }
579+ )
555580 return ""
556581
557582 checks = map (format_link , doc_files )
@@ -613,6 +638,22 @@ def main():
613638 "objc" : "m" ,
614639 "objc++" : "mm" ,
615640 }
641+ cpp_language_to_requirements = {
642+ "c++98" : "CPlusPlus" ,
643+ "c++11" : "CPlusPlus11" ,
644+ "c++14" : "CPlusPlus14" ,
645+ "c++17" : "CPlusPlus17" ,
646+ "c++20" : "CPlusPlus20" ,
647+ "c++23" : "CPlusPlus23" ,
648+ "c++26" : "CPlusPlus26" ,
649+ }
650+ c_language_to_requirements = {
651+ "c99" : None ,
652+ "c11" : "C11" ,
653+ "c17" : "C17" ,
654+ "c23" : "C23" ,
655+ "c27" : "C2Y" ,
656+ }
616657 parser = argparse .ArgumentParser ()
617658 parser .add_argument (
618659 "--update-docs" ,
@@ -623,7 +664,7 @@ def main():
623664 "--language" ,
624665 help = "language to use for new check (defaults to c++)" ,
625666 choices = language_to_extension .keys (),
626- default = "c++" ,
667+ default = None ,
627668 metavar = "LANG" ,
628669 )
629670 parser .add_argument (
@@ -633,6 +674,16 @@ def main():
633674 default = "FIXME: Write a short description" ,
634675 type = str ,
635676 )
677+ parser .add_argument (
678+ "--standard" ,
679+ help = "Specify a specific version of the language" ,
680+ choices = list (
681+ itertools .chain (
682+ cpp_language_to_requirements .keys (), c_language_to_requirements .keys ()
683+ )
684+ ),
685+ default = None ,
686+ )
636687 parser .add_argument (
637688 "module" ,
638689 nargs = "?" ,
@@ -677,14 +728,49 @@ def main():
677728 if not description .endswith ("." ):
678729 description += "."
679730
731+ language = args .language
732+
733+ if args .standard :
734+ if args .standard in cpp_language_to_requirements :
735+ if language and language != "c++" :
736+ raise ValueError ("C++ standard chosen when language is not C++" )
737+ language = "c++"
738+ elif args .standard in c_language_to_requirements :
739+ if language and language != "c" :
740+ raise ValueError ("C standard chosen when language is not C" )
741+ language = "c"
742+
743+ if not language :
744+ language = "c++"
745+
746+ language_restrict = None
747+
748+ if language == "c" :
749+ language_restrict = "!%(lang)s.CPlusPlus"
750+ extra = c_language_to_requirements .get (args .standard , None )
751+ if extra :
752+ language_restrict += f" && %(lang)s.{ extra } "
753+ elif language == "c++" :
754+ language_restrict = (
755+ f"%(lang)s.{ cpp_language_to_requirements .get (args .standard , 'CPlusPlus' )} "
756+ )
757+ elif language in ["objc" , "objc++" ]:
758+ language_restrict = "%(lang)s.ObjC"
759+
680760 write_header (
681- module_path , module , namespace , check_name , check_name_camel , description
761+ module_path ,
762+ module ,
763+ namespace ,
764+ check_name ,
765+ check_name_camel ,
766+ description ,
767+ language_restrict ,
682768 )
683769 write_implementation (module_path , module , namespace , check_name_camel )
684770 adapt_module (module_path , module , check_name , check_name_camel )
685771 add_release_notes (module_path , module , check_name , description )
686- test_extension = language_to_extension .get (args . language )
687- write_test (module_path , module , check_name , test_extension )
772+ test_extension = language_to_extension .get (language )
773+ write_test (module_path , module , check_name , test_extension , args . standard )
688774 write_docs (module_path , module , check_name )
689775 update_checks_list (clang_tidy_path )
690776 print ("Done. Now it's your turn!" )
0 commit comments