Skip to content

[clang-tidy] Add type annotations to add_new_check.py, fix minor bug #106801

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 6, 2024

Conversation

nicovank
Copy link
Contributor

@nicovank nicovank commented Aug 30, 2024

> 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

@llvmbot
Copy link
Member

llvmbot commented Aug 30, 2024

@llvm/pr-subscribers-clang-tools-extra

@llvm/pr-subscribers-clang-tidy

Author: Nicolas van Kempen (nicovank)

Changes
python3 -m mypy --strict clang-tools-extra/clang-tidy/add_new_check.py
Success: no issues found in 1 source file

Full diff: https://github.com/llvm/llvm-project/pull/106801.diff

1 Files Affected:

  • (modified) clang-tools-extra/clang-tidy/add_new_check.py (+49-37)
diff --git a/clang-tools-extra/clang-tidy/add_new_check.py b/clang-tools-extra/clang-tidy/add_new_check.py
index bd69bddcc68256..363eab71a270f1 100755
--- a/clang-tools-extra/clang-tidy/add_new_check.py
+++ b/clang-tools-extra/clang-tidy/add_new_check.py
@@ -8,9 +8,6 @@
 #
 # ===-----------------------------------------------------------------------===#
 
-from __future__ import print_function
-from __future__ import unicode_literals
-
 import argparse
 import io
 import itertools
@@ -18,11 +15,12 @@
 import re
 import sys
 import textwrap
+from typing import Optional, Tuple
 
 
 # Adapts the module's CMakelist file. Returns 'True' if it could add a new
 # entry and 'False' if the entry already existed.
-def adapt_cmake(module_path, check_name_camel):
+def adapt_cmake(module_path: str, check_name_camel: str) -> bool:
     filename = os.path.join(module_path, "CMakeLists.txt")
 
     # The documentation files are encoded using UTF-8, however on Windows the
@@ -57,14 +55,14 @@ def adapt_cmake(module_path, check_name_camel):
 
 # Adds a header for the new check.
 def write_header(
-    module_path,
-    module,
-    namespace,
-    check_name,
-    check_name_camel,
-    description,
-    lang_restrict,
-):
+    module_path: str,
+    module: str,
+    namespace: str,
+    check_name: str,
+    check_name_camel: str,
+    description: str,
+    lang_restrict: str,
+) -> None:
     wrapped_desc = "\n".join(
         textwrap.wrap(
             description, width=80, initial_indent="/// ", subsequent_indent="/// "
@@ -139,7 +137,9 @@ class %(check_name_camel)s : public ClangTidyCheck {
 
 
 # Adds the implementation of the new check.
-def write_implementation(module_path, module, namespace, check_name_camel):
+def write_implementation(
+    module_path: str, module: str, namespace: str, check_name_camel: str
+) -> None:
     filename = os.path.join(module_path, check_name_camel) + ".cpp"
     print("Creating %s..." % filename)
     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):
 
 
 # Returns the source filename that implements the module.
-def get_module_filename(module_path, module):
+def get_module_filename(module_path: str, module: str) -> str:
     modulecpp = list(
         filter(
             lambda p: p.lower() == module.lower() + "tidymodule.cpp",
@@ -198,7 +198,9 @@ def get_module_filename(module_path, module):
 
 
 # Modifies the module to include the new check.
-def adapt_module(module_path, module, check_name, check_name_camel):
+def adapt_module(
+    module_path: str, module: str, check_name: str, check_name_camel: str
+) -> None:
     filename = get_module_filename(module_path, module)
     with io.open(filename, "r", encoding="utf8") as f:
         lines = f.readlines()
@@ -217,10 +219,10 @@ def adapt_module(module_path, module, check_name, check_name_camel):
             + '");\n'
         )
 
-        lines = iter(lines)
+        lines_iter = iter(lines)
         try:
             while True:
-                line = next(lines)
+                line = next(lines_iter)
                 if not header_added:
                     match = re.search('#include "(.*)"', line)
                     if match:
@@ -247,10 +249,11 @@ def adapt_module(module_path, module, check_name, check_name_camel):
                                 # If we didn't find the check name on this line, look on the
                                 # next one.
                                 prev_line = line
-                                line = next(lines)
+                                line = next(lines_iter)
                                 match = re.search(' *"([^"]*)"', line)
                                 if match:
                                     current_check_name = match.group(1)
+                            assert current_check_name
                             if current_check_name > check_fq_name:
                                 check_added = True
                                 f.write(check_decl)
@@ -262,7 +265,9 @@ def adapt_module(module_path, module, check_name, check_name_camel):
 
 
 # Adds a release notes entry.
-def add_release_notes(module_path, module, check_name, description):
+def add_release_notes(
+    module_path: str, module: str, check_name: str, description: str
+) -> None:
     wrapped_desc = "\n".join(
         textwrap.wrap(
             description, width=80, initial_indent="  ", subsequent_indent="  "
@@ -324,7 +329,13 @@ def add_release_notes(module_path, module, check_name, description):
 
 
 # Adds a test for the check.
-def write_test(module_path, module, check_name, test_extension, test_standard):
+def write_test(
+    module_path: str,
+    module: str,
+    check_name: str,
+    test_extension: str,
+    test_standard: str,
+) -> None:
     if test_standard:
         test_standard = f"-std={test_standard}-or-later "
     check_name_dashes = module + "-" + check_name
@@ -362,7 +373,7 @@ def write_test(module_path, module, check_name, test_extension, test_standard):
         )
 
 
-def get_actual_filename(dirname, filename):
+def get_actual_filename(dirname: str, filename: str) -> str:
     if not os.path.isdir(dirname):
         return ""
     name = os.path.join(dirname, filename)
@@ -376,7 +387,7 @@ def get_actual_filename(dirname, filename):
 
 
 # Recreates the list of checks in the docs/clang-tidy/checks directory.
-def update_checks_list(clang_tidy_path):
+def update_checks_list(clang_tidy_path: str) -> None:
     docs_dir = os.path.join(clang_tidy_path, "../docs/clang-tidy/checks")
     filename = os.path.normpath(os.path.join(docs_dir, "list.rst"))
     # Read the content of the current list.rst file
@@ -390,12 +401,12 @@ def update_checks_list(clang_tidy_path):
         for file in filter(
             lambda s: s.endswith(".rst"), os.listdir(os.path.join(docs_dir, subdir))
         ):
-            doc_files.append([subdir, file])
+            doc_files.append((subdir, file))
     doc_files.sort()
 
     # We couldn't find the source file from the check name, so try to find the
     # class name that corresponds to the check in the module file.
-    def filename_from_module(module_name, check_name):
+    def filename_from_module(module_name: str, check_name: str) -> str:
         module_path = os.path.join(clang_tidy_path, module_name)
         if not os.path.isdir(module_path):
             return ""
@@ -433,7 +444,7 @@ def filename_from_module(module_name, check_name):
         return ""
 
     # Examine code looking for a c'tor definition to get the base class name.
-    def get_base_class(code, check_file):
+    def get_base_class(code: str, check_file: str) -> str:
         check_class_name = os.path.splitext(os.path.basename(check_file))[0]
         ctor_pattern = check_class_name + r"\([^:]*\)\s*:\s*([A-Z][A-Za-z0-9]*Check)\("
         matches = re.search(r"\s+" + check_class_name + "::" + ctor_pattern, code)
@@ -452,7 +463,7 @@ def get_base_class(code, check_file):
         return ""
 
     # Some simple heuristics to figure out if a check has an autofix or not.
-    def has_fixits(code):
+    def has_fixits(code: str) -> bool:
         for needle in [
             "FixItHint",
             "ReplacementText",
@@ -464,7 +475,7 @@ def has_fixits(code):
         return False
 
     # Try to figure out of the check supports fixits.
-    def has_auto_fix(check_name):
+    def has_auto_fix(check_name: str) -> str:
         dirname, _, check_name = check_name.partition("-")
 
         check_file = get_actual_filename(
@@ -499,7 +510,7 @@ def has_auto_fix(check_name):
 
         return ""
 
-    def process_doc(doc_file):
+    def process_doc(doc_file: Tuple[str, str]) -> Tuple[str, Optional[re.Match[str]]]:
         check_name = doc_file[0] + "-" + doc_file[1].replace(".rst", "")
 
         with io.open(os.path.join(docs_dir, *doc_file), "r", encoding="utf8") as doc:
@@ -508,13 +519,13 @@ def process_doc(doc_file):
 
             if match:
                 # Orphan page, don't list it.
-                return "", ""
+                return "", None
 
             match = re.search(r".*:http-equiv=refresh: \d+;URL=(.*).html(.*)", content)
             # Is it a redirect?
             return check_name, match
 
-    def format_link(doc_file):
+    def format_link(doc_file: Tuple[str, str]) -> str:
         check_name, match = process_doc(doc_file)
         if not match and check_name and not check_name.startswith("clang-analyzer-"):
             return "   :doc:`%(check_name)s <%(module)s/%(check)s>`,%(autofix)s\n" % {
@@ -526,7 +537,7 @@ def format_link(doc_file):
         else:
             return ""
 
-    def format_link_alias(doc_file):
+    def format_link_alias(doc_file: Tuple[str, str]) -> str:
         check_name, match = process_doc(doc_file)
         if (match or (check_name.startswith("clang-analyzer-"))) and check_name:
             module = doc_file[0]
@@ -543,6 +554,7 @@ def format_link_alias(doc_file):
                 ref_end = "_"
             else:
                 redirect_parts = re.search(r"^\.\./([^/]*)/([^/]*)$", match.group(1))
+                assert redirect_parts
                 title = redirect_parts[1] + "-" + redirect_parts[2]
                 target = redirect_parts[1] + "/" + redirect_parts[2]
                 autofix = has_auto_fix(title)
@@ -599,7 +611,7 @@ def format_link_alias(doc_file):
 
 
 # Adds a documentation for the check.
-def write_docs(module_path, module, check_name):
+def write_docs(module_path: str, module: str, check_name: str) -> None:
     check_name_dashes = module + "-" + check_name
     filename = os.path.normpath(
         os.path.join(
@@ -623,15 +635,15 @@ def write_docs(module_path, module, check_name):
         )
 
 
-def get_camel_name(check_name):
+def get_camel_name(check_name: str) -> str:
     return "".join(map(lambda elem: elem.capitalize(), check_name.split("-")))
 
 
-def get_camel_check_name(check_name):
+def get_camel_check_name(check_name: str) -> str:
     return get_camel_name(check_name) + "Check"
 
 
-def main():
+def main() -> None:
     language_to_extension = {
         "c": "c",
         "c++": "cpp",
@@ -754,7 +766,7 @@ def main():
         language_restrict = (
             f"%(lang)s.{cpp_language_to_requirements.get(args.standard, 'CPlusPlus')}"
         )
-    elif language in ["objc", "objc++"]:
+    else:  # "objc" or "objc++"
         language_restrict = "%(lang)s.ObjC"
 
     write_header(
@@ -769,7 +781,7 @@ def main():
     write_implementation(module_path, module, namespace, check_name_camel)
     adapt_module(module_path, module, check_name, check_name_camel)
     add_release_notes(module_path, module, check_name, description)
-    test_extension = language_to_extension.get(language)
+    test_extension = language_to_extension[language]
     write_test(module_path, module, check_name, test_extension, args.standard)
     write_docs(module_path, module, check_name)
     update_checks_list(clang_tidy_path)

@nicovank nicovank changed the title [NFC][clang-tidy] Add type annotations to add_new_check.py [clang-tidy] Add type annotations to add_new_check.py, fix minor bug Aug 31, 2024
test_extension: str,
test_standard: Optional[str],
) -> None:
test_standard = f"-std={test_standard}-or-later " if test_standard else ""
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the minor bug fix. If test_standard is None, it is wrongly formatted into the string a few lines down.

@nicovank
Copy link
Contributor Author

Thanks for review! Addressed comments.

I have no commit access, if/when this looks good to go you can go ahead and click merge.

Copy link

github-actions bot commented Aug 31, 2024

✅ With the latest revision this PR passed the Python code formatter.

```
> 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
```
@5chmidti 5chmidti merged commit a37f7ae into llvm:main Sep 6, 2024
10 of 11 checks passed
@nicovank nicovank deleted the pr106801 branch September 6, 2024 14:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants