Skip to content

Commit 6d4241a

Browse files
committed
[clang-tidy] Add type annotations to add_new_check.py, fix minor bug
``` > python3 -m mypy --strict clang-tools-extra/clang-tidy/add_new_check.py Success: no issues found in 1 source file ``` Also fix a bug when `--standard` is not provided on the command line: the generated test case has a `None` causing issues: ``` > python3 clang-tools-extra/clang-tidy/add_new_check.py performance XXX Updating clang-tools-extra/clang-tidy/performance/CMakeLists.txt... Creating clang-tools-extra/clang-tidy/performance/XxxCheck.h... Creating clang-tools-extra/clang-tidy/performance/XxxCheck.cpp... Updating clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp... Updating clang-tools-extra/docs/ReleaseNotes.rst... Creating clang-tools-extra/test/clang-tidy/checkers/performance/XXX.cpp... Creating clang-tools-extra/docs/clang-tidy/checks/performance/XXX.rst... Updating clang-tools-extra/docs/clang-tidy/checks/list.rst... Done. Now it's your turn! > head -n 1 clang-tools-extra/test/clang-tidy/checkers/performance/XXX.cpp // RUN: %check_clang_tidy None%s performance-XXX %t ```
1 parent 2f4232d commit 6d4241a

File tree

1 file changed

+50
-39
lines changed

1 file changed

+50
-39
lines changed

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

Lines changed: 50 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,19 @@
88
#
99
# ===-----------------------------------------------------------------------===#
1010

11-
from __future__ import print_function
12-
from __future__ import unicode_literals
13-
1411
import argparse
1512
import io
1613
import itertools
1714
import os
1815
import re
1916
import sys
2017
import textwrap
18+
from typing import Optional, Tuple
2119

2220

2321
# Adapts the module's CMakelist file. Returns 'True' if it could add a new
2422
# entry and 'False' if the entry already existed.
25-
def adapt_cmake(module_path, check_name_camel):
23+
def adapt_cmake(module_path: str, check_name_camel: str) -> bool:
2624
filename = os.path.join(module_path, "CMakeLists.txt")
2725

2826
# The documentation files are encoded using UTF-8, however on Windows the
@@ -57,14 +55,14 @@ def adapt_cmake(module_path, check_name_camel):
5755

5856
# Adds a header for the new check.
5957
def write_header(
60-
module_path,
61-
module,
62-
namespace,
63-
check_name,
64-
check_name_camel,
65-
description,
66-
lang_restrict,
67-
):
58+
module_path: str,
59+
module: str,
60+
namespace: str,
61+
check_name: str,
62+
check_name_camel: str,
63+
description: str,
64+
lang_restrict: str,
65+
) -> None:
6866
wrapped_desc = "\n".join(
6967
textwrap.wrap(
7068
description, width=80, initial_indent="/// ", subsequent_indent="/// "
@@ -139,7 +137,9 @@ class %(check_name_camel)s : public ClangTidyCheck {
139137

140138

141139
# Adds the implementation of the new check.
142-
def write_implementation(module_path, module, namespace, check_name_camel):
140+
def write_implementation(
141+
module_path: str, module: str, namespace: str, check_name_camel: str
142+
) -> None:
143143
filename = os.path.join(module_path, check_name_camel) + ".cpp"
144144
print("Creating %s..." % filename)
145145
with io.open(filename, "w", encoding="utf8", newline="\n") as f:
@@ -187,7 +187,7 @@ def write_implementation(module_path, module, namespace, check_name_camel):
187187

188188

189189
# Returns the source filename that implements the module.
190-
def get_module_filename(module_path, module):
190+
def get_module_filename(module_path: str, module: str) -> str:
191191
modulecpp = list(
192192
filter(
193193
lambda p: p.lower() == module.lower() + "tidymodule.cpp",
@@ -198,7 +198,9 @@ def get_module_filename(module_path, module):
198198

199199

200200
# Modifies the module to include the new check.
201-
def adapt_module(module_path, module, check_name, check_name_camel):
201+
def adapt_module(
202+
module_path: str, module: str, check_name: str, check_name_camel: str
203+
) -> None:
202204
filename = get_module_filename(module_path, module)
203205
with io.open(filename, "r", encoding="utf8") as f:
204206
lines = f.readlines()
@@ -217,10 +219,10 @@ def adapt_module(module_path, module, check_name, check_name_camel):
217219
+ '");\n'
218220
)
219221

220-
lines = iter(lines)
222+
lines_iter = iter(lines)
221223
try:
222224
while True:
223-
line = next(lines)
225+
line = next(lines_iter)
224226
if not header_added:
225227
match = re.search('#include "(.*)"', line)
226228
if match:
@@ -247,10 +249,11 @@ def adapt_module(module_path, module, check_name, check_name_camel):
247249
# If we didn't find the check name on this line, look on the
248250
# next one.
249251
prev_line = line
250-
line = next(lines)
252+
line = next(lines_iter)
251253
match = re.search(' *"([^"]*)"', line)
252254
if match:
253255
current_check_name = match.group(1)
256+
assert current_check_name
254257
if current_check_name > check_fq_name:
255258
check_added = True
256259
f.write(check_decl)
@@ -262,7 +265,9 @@ def adapt_module(module_path, module, check_name, check_name_camel):
262265

263266

264267
# Adds a release notes entry.
265-
def add_release_notes(module_path, module, check_name, description):
268+
def add_release_notes(
269+
module_path: str, module: str, check_name: str, description: str
270+
) -> None:
266271
wrapped_desc = "\n".join(
267272
textwrap.wrap(
268273
description, width=80, initial_indent=" ", subsequent_indent=" "
@@ -324,9 +329,14 @@ def add_release_notes(module_path, module, check_name, description):
324329

325330

326331
# Adds a test for the check.
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 "
332+
def write_test(
333+
module_path: str,
334+
module: str,
335+
check_name: str,
336+
test_extension: str,
337+
test_standard: Optional[str],
338+
) -> None:
339+
test_standard = f"-std={test_standard}-or-later " if test_standard else ""
330340
check_name_dashes = module + "-" + check_name
331341
filename = os.path.normpath(
332342
os.path.join(
@@ -362,7 +372,7 @@ def write_test(module_path, module, check_name, test_extension, test_standard):
362372
)
363373

364374

365-
def get_actual_filename(dirname, filename):
375+
def get_actual_filename(dirname: str, filename: str) -> str:
366376
if not os.path.isdir(dirname):
367377
return ""
368378
name = os.path.join(dirname, filename)
@@ -376,7 +386,7 @@ def get_actual_filename(dirname, filename):
376386

377387

378388
# Recreates the list of checks in the docs/clang-tidy/checks directory.
379-
def update_checks_list(clang_tidy_path):
389+
def update_checks_list(clang_tidy_path: str) -> None:
380390
docs_dir = os.path.join(clang_tidy_path, "../docs/clang-tidy/checks")
381391
filename = os.path.normpath(os.path.join(docs_dir, "list.rst"))
382392
# Read the content of the current list.rst file
@@ -390,12 +400,12 @@ def update_checks_list(clang_tidy_path):
390400
for file in filter(
391401
lambda s: s.endswith(".rst"), os.listdir(os.path.join(docs_dir, subdir))
392402
):
393-
doc_files.append([subdir, file])
403+
doc_files.append((subdir, file))
394404
doc_files.sort()
395405

396406
# We couldn't find the source file from the check name, so try to find the
397407
# class name that corresponds to the check in the module file.
398-
def filename_from_module(module_name, check_name):
408+
def filename_from_module(module_name: str, check_name: str) -> str:
399409
module_path = os.path.join(clang_tidy_path, module_name)
400410
if not os.path.isdir(module_path):
401411
return ""
@@ -433,7 +443,7 @@ def filename_from_module(module_name, check_name):
433443
return ""
434444

435445
# Examine code looking for a c'tor definition to get the base class name.
436-
def get_base_class(code, check_file):
446+
def get_base_class(code: str, check_file: str) -> str:
437447
check_class_name = os.path.splitext(os.path.basename(check_file))[0]
438448
ctor_pattern = check_class_name + r"\([^:]*\)\s*:\s*([A-Z][A-Za-z0-9]*Check)\("
439449
matches = re.search(r"\s+" + check_class_name + "::" + ctor_pattern, code)
@@ -452,7 +462,7 @@ def get_base_class(code, check_file):
452462
return ""
453463

454464
# Some simple heuristics to figure out if a check has an autofix or not.
455-
def has_fixits(code):
465+
def has_fixits(code: str) -> bool:
456466
for needle in [
457467
"FixItHint",
458468
"ReplacementText",
@@ -464,7 +474,7 @@ def has_fixits(code):
464474
return False
465475

466476
# Try to figure out of the check supports fixits.
467-
def has_auto_fix(check_name):
477+
def has_auto_fix(check_name: str) -> str:
468478
dirname, _, check_name = check_name.partition("-")
469479

470480
check_file = get_actual_filename(
@@ -499,7 +509,7 @@ def has_auto_fix(check_name):
499509

500510
return ""
501511

502-
def process_doc(doc_file):
512+
def process_doc(doc_file: Tuple[str, str]) -> Tuple[str, Optional[re.Match[str]]]:
503513
check_name = doc_file[0] + "-" + doc_file[1].replace(".rst", "")
504514

505515
with io.open(os.path.join(docs_dir, *doc_file), "r", encoding="utf8") as doc:
@@ -508,13 +518,13 @@ def process_doc(doc_file):
508518

509519
if match:
510520
# Orphan page, don't list it.
511-
return "", ""
521+
return "", None
512522

513523
match = re.search(r".*:http-equiv=refresh: \d+;URL=(.*).html(.*)", content)
514524
# Is it a redirect?
515525
return check_name, match
516526

517-
def format_link(doc_file):
527+
def format_link(doc_file: Tuple[str, str]) -> str:
518528
check_name, match = process_doc(doc_file)
519529
if not match and check_name and not check_name.startswith("clang-analyzer-"):
520530
return " :doc:`%(check_name)s <%(module)s/%(check)s>`,%(autofix)s\n" % {
@@ -526,7 +536,7 @@ def format_link(doc_file):
526536
else:
527537
return ""
528538

529-
def format_link_alias(doc_file):
539+
def format_link_alias(doc_file: Tuple[str, str]) -> str:
530540
check_name, match = process_doc(doc_file)
531541
if (match or (check_name.startswith("clang-analyzer-"))) and check_name:
532542
module = doc_file[0]
@@ -543,6 +553,7 @@ def format_link_alias(doc_file):
543553
ref_end = "_"
544554
else:
545555
redirect_parts = re.search(r"^\.\./([^/]*)/([^/]*)$", match.group(1))
556+
assert redirect_parts
546557
title = redirect_parts[1] + "-" + redirect_parts[2]
547558
target = redirect_parts[1] + "/" + redirect_parts[2]
548559
autofix = has_auto_fix(title)
@@ -599,7 +610,7 @@ def format_link_alias(doc_file):
599610

600611

601612
# Adds a documentation for the check.
602-
def write_docs(module_path, module, check_name):
613+
def write_docs(module_path: str, module: str, check_name: str) -> None:
603614
check_name_dashes = module + "-" + check_name
604615
filename = os.path.normpath(
605616
os.path.join(
@@ -623,15 +634,15 @@ def write_docs(module_path, module, check_name):
623634
)
624635

625636

626-
def get_camel_name(check_name):
637+
def get_camel_name(check_name: str) -> str:
627638
return "".join(map(lambda elem: elem.capitalize(), check_name.split("-")))
628639

629640

630-
def get_camel_check_name(check_name):
641+
def get_camel_check_name(check_name: str) -> str:
631642
return get_camel_name(check_name) + "Check"
632643

633644

634-
def main():
645+
def main() -> None:
635646
language_to_extension = {
636647
"c": "c",
637648
"c++": "cpp",
@@ -754,7 +765,7 @@ def main():
754765
language_restrict = (
755766
f"%(lang)s.{cpp_language_to_requirements.get(args.standard, 'CPlusPlus')}"
756767
)
757-
elif language in ["objc", "objc++"]:
768+
else: # "objc" or "objc++"
758769
language_restrict = "%(lang)s.ObjC"
759770

760771
write_header(
@@ -769,7 +780,7 @@ def main():
769780
write_implementation(module_path, module, namespace, check_name_camel)
770781
adapt_module(module_path, module, check_name, check_name_camel)
771782
add_release_notes(module_path, module, check_name, description)
772-
test_extension = language_to_extension.get(language)
783+
test_extension = language_to_extension[language]
773784
write_test(module_path, module, check_name, test_extension, args.standard)
774785
write_docs(module_path, module, check_name)
775786
update_checks_list(clang_tidy_path)

0 commit comments

Comments
 (0)