Skip to content

Commit 154d00d

Browse files
authored
Extend support for specifying languages and version in add_new_check.py (#100129)
- Allow specifying a language standard when adding a new check - Simplify the language standards(and or-later) handlnig in check_clang_tidy
1 parent e83ba1e commit 154d00d

File tree

2 files changed

+137
-31
lines changed

2 files changed

+137
-31
lines changed

clang-tools-extra/clang-tidy/add_new_check.py

Lines changed: 102 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@
1313

1414
import argparse
1515
import io
16+
import itertools
1617
import os
1718
import re
1819
import sys
1920
import 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.
2325
def 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.
5759
def 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.
329349
void 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.
339359
void 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!")

clang-tools-extra/test/clang-tidy/check_clang_tidy.py

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -205,9 +205,11 @@ def run_clang_tidy(self):
205205
self.temp_file_name,
206206
]
207207
+ [
208-
"-fix"
209-
if self.export_fixes is None
210-
else "--export-fixes=" + self.export_fixes
208+
(
209+
"-fix"
210+
if self.export_fixes is None
211+
else "--export-fixes=" + self.export_fixes
212+
)
211213
]
212214
+ [
213215
"--checks=-*," + self.check_name,
@@ -299,19 +301,37 @@ def run(self):
299301
self.check_notes(clang_tidy_output)
300302

301303

304+
CPP_STANDARDS = [
305+
"c++98",
306+
"c++11",
307+
("c++14", "c++1y"),
308+
("c++17", "c++1z"),
309+
("c++20", "c++2a"),
310+
("c++23", "c++2b"),
311+
("c++26", "c++2c"),
312+
]
313+
C_STANDARDS = ["c99", ("c11", "c1x"), "c17", ("c23", "c2x"), "c2y"]
314+
315+
302316
def expand_std(std):
303-
if std == "c++98-or-later":
304-
return ["c++98", "c++11", "c++14", "c++17", "c++20", "c++23", "c++2c"]
305-
if std == "c++11-or-later":
306-
return ["c++11", "c++14", "c++17", "c++20", "c++23", "c++2c"]
307-
if std == "c++14-or-later":
308-
return ["c++14", "c++17", "c++20", "c++23", "c++2c"]
309-
if std == "c++17-or-later":
310-
return ["c++17", "c++20", "c++23", "c++2c"]
311-
if std == "c++20-or-later":
312-
return ["c++20", "c++23", "c++2c"]
313-
if std == "c++23-or-later":
314-
return ["c++23", "c++2c"]
317+
split_std, or_later, _ = std.partition("-or-later")
318+
319+
if not or_later:
320+
return [split_std]
321+
322+
for standard_list in (CPP_STANDARDS, C_STANDARDS):
323+
item = next(
324+
(
325+
i
326+
for i, v in enumerate(standard_list)
327+
if (split_std in v if isinstance(v, (list, tuple)) else split_std == v)
328+
),
329+
None,
330+
)
331+
if item is not None:
332+
return [split_std] + [
333+
x if isinstance(x, str) else x[0] for x in standard_list[item + 1 :]
334+
]
315335
return [std]
316336

317337

0 commit comments

Comments
 (0)