From 97ca70d1d730023fad549cc84d96afd6ffc2f2c1 Mon Sep 17 00:00:00 2001 From: Alan Su Date: Mon, 18 May 2026 20:49:07 -0400 Subject: [PATCH 1/4] Add pascal and uppercase naming convention suggestions --- .../checkers/invalid_name_checker.py | 40 +++++++++++++-- .../test_invalid_name_checker.py | 49 ++++++++++++++----- 2 files changed, 74 insertions(+), 15 deletions(-) diff --git a/packages/python-ta/src/python_ta/checkers/invalid_name_checker.py b/packages/python-ta/src/python_ta/checkers/invalid_name_checker.py index 90b4e5722..07ba62f34 100644 --- a/packages/python-ta/src/python_ta/checkers/invalid_name_checker.py +++ b/packages/python-ta/src/python_ta/checkers/invalid_name_checker.py @@ -73,7 +73,7 @@ def _is_in_pascal_case(name: str) -> bool: def _is_in_upper_case_with_underscores(name: str) -> bool: """Returns whether `name` is in UPPER_CASE_WITH_UNDERSCORES. - `name` is in `UPPER_CASE_WITH_UNDERSCORES if: + `name` is in `UPPER_CASE_WITH_UNDERSCORES` if: - each word is in uppercase, and - words are separated by an underscore. """ @@ -82,6 +82,33 @@ def _is_in_upper_case_with_underscores(name: str) -> bool: return re.match(pattern, name) is not None +def _parse_name(name: str) -> tuple[str, list[str], str]: + """Extracts the prefix, words, and suffix from `name`.""" + name_match = re.match(r"(_*)(.*?)(_*)$", name) + if not name_match: + return "", [name], "" + prefix, core, suffix = name_match.groups() + prefix = "_" if prefix else "" + core = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", core) + core = re.sub(r"([A-Z])([A-Z][a-z])", r"\1_\2", core) + + return prefix, [word for word in core.split("_") if word], suffix + + +def _to_pascal_case(name: str) -> str: + """Returns a PascalCase version of `name`.""" + prefix, words, _ = _parse_name(name) + + return prefix + "".join(word.capitalize() for word in words) + + +def _to_upper_case_with_underscores(name: str) -> str: + """Returns an UPPER_CASE_WITH_UNDERSCORES version of `name`.""" + prefix, words, suffix = _parse_name(name) + + return prefix + "_".join(word.upper() for word in words) + suffix + + def _is_bad_name(name: str) -> str: """Returns a string detailing why `name` is a bad name. @@ -144,6 +171,7 @@ def _check_const_name(node_type: str, name: str) -> list[str]: error_msgs = [] if not _is_in_upper_case_with_underscores(name): + suggested_name = _to_upper_case_with_underscores(name) msg = ( f'{node_type.capitalize()} name "{name}" should be in UPPER_CASE_WITH_UNDERSCORES format. ' f"Constants should be all-uppercase words with each word separated by an " @@ -151,6 +179,7 @@ def _check_const_name(node_type: str, name: str) -> list[str]: ) if node_type == "class constant": msg += " A double leading underscore invokes Python's name-mangling rules." + msg += f' Consider renaming to "{suggested_name}".' error_msgs.append(msg) return error_msgs @@ -164,11 +193,12 @@ class names. error_msgs = [] if not _is_in_pascal_case(name): + suggested_name = _to_pascal_case(name) error_msgs.append( f'Class name "{name}" should be in PascalCase format. Class names should have the ' f"first letter of each word capitalized with no separation between each " f"word. A single leading underscore can be used to denote a private " - f"class." + f'class. Consider renaming to "{suggested_name}".' ) return error_msgs @@ -238,10 +268,11 @@ def _check_typevar_name(_node_type: str, name: str) -> list[str]: error_msgs = [] if not _is_in_pascal_case(name): + suggested_name = _to_pascal_case(name) error_msgs.append( f'Type variable name "{name}" should be in PascalCase format. Type variable ' f"names should have the first letter of each word capitalized with no separation " - f"between each word." + f'between each word. Consider renaming to "{suggested_name}".' ) return error_msgs @@ -255,10 +286,11 @@ def _check_type_alias_name(_node_type: str, name: str) -> list[str]: error_msgs = [] if not _is_in_pascal_case(name): + suggested_name = _to_pascal_case(name) error_msgs.append( f'Type alias name "{name}" should be in PascalCase format. Type alias names should ' f"have the first letter of each word capitalized with no separation " - f"between each word." + f'between each word. Consider renaming to "{suggested_name}".' ) return error_msgs diff --git a/packages/python-ta/tests/test_custom_checkers/test_invalid_name_checker.py b/packages/python-ta/tests/test_custom_checkers/test_invalid_name_checker.py index cc231e001..6ae26abf2 100644 --- a/packages/python-ta/tests/test_custom_checkers/test_invalid_name_checker.py +++ b/packages/python-ta/tests/test_custom_checkers/test_invalid_name_checker.py @@ -10,7 +10,11 @@ from astroid import nodes import python_ta -from python_ta.checkers.invalid_name_checker import InvalidNameChecker +from python_ta.checkers.invalid_name_checker import ( + InvalidNameChecker, + _to_pascal_case, + _to_upper_case_with_underscores, +) class TestInvalidNameChecker(pylint.testutils.CheckerTestCase): @@ -70,7 +74,8 @@ def test_const_name_violation(self) -> None: msg = ( f'Constant name "{name}" should be in UPPER_CASE_WITH_UNDERSCORES format. Constants ' f"should be all-uppercase words with each word separated by an underscore. A " - f"single leading underscore can be used to denote a private constant." + f"single leading underscore can be used to denote a private constant. " + f'Consider renaming to "CONST_NOT_UPPER".' ) with self.assertAddsMessages( @@ -94,7 +99,8 @@ def test_const_name_annotated_violation(self) -> None: msg = ( f'Constant name "{name}" should be in UPPER_CASE_WITH_UNDERSCORES format. Constants ' f"should be all-uppercase words with each word separated by an underscore. A " - f"single leading underscore can be used to denote a private constant." + f"single leading underscore can be used to denote a private constant. " + f'Consider renaming to "CONST_NOT_UPPER".' ) with self.assertAddsMessages( @@ -139,7 +145,8 @@ class notPascalcase: msg = ( f'Class name "{name}" should be in PascalCase format. Class names should have the ' f"first letter of each word capitalized with no separation between each word. A " - f"single leading underscore can be used to denote a private class." + f"single leading underscore can be used to denote a private class. " + f'Consider renaming to "NotPascalcase".' ) with self.assertAddsMessages( @@ -164,7 +171,8 @@ class MyClass: msg = ( f'Class name "{name}" should be in PascalCase format. Class names should have the ' f"first letter of each word capitalized with no separation between each word. A " - f"single leading underscore can be used to denote a private class." + f"single leading underscore can be used to denote a private class. " + f'Consider renaming to "SnakeCase".' ) with self.assertAddsMessages( @@ -548,7 +556,8 @@ class BadClass: f'Class constant name "{name}" should be in UPPER_CASE_WITH_UNDERSCORES format. ' f"Constants should be all-uppercase words with each word separated by an " f"underscore. A single leading underscore can be used to denote a private " - f"constant. A double leading underscore invokes Python's name-mangling rules." + f"constant. A double leading underscore invokes Python's name-mangling rules. " + f'Consider renaming to "OOGA_BOOGA".' ) with self.assertAddsMessages( @@ -587,7 +596,7 @@ def test_typevar_name_violation(self) -> None: msg = ( f'Type variable name "{name}" should be in PascalCase format. Type variable names ' f"should have the first letter of each word capitalized with no separation between " - f"each word." + f'each word. Consider renaming to "TypeVar".' ) with self.assertAddsMessages( @@ -611,7 +620,7 @@ def test_typevar_name_tuple_violation(self) -> None: msg = ( f'Type variable name "{name}" should be in PascalCase format. Type variable names ' f"should have the first letter of each word capitalized with no separation between " - f"each word." + f'each word. Consider renaming to "TypeVar".' ) with self.assertAddsMessages( @@ -649,7 +658,7 @@ def test_typealias_name_violation(self) -> None: msg = ( f'Type alias name "{name}" should be in PascalCase format. Type alias names should ' f"have the first letter of each word capitalized with no separation between each " - f"word." + f'word. Consider renaming to "NotPascal".' ) with self.assertAddsMessages( @@ -675,7 +684,7 @@ def test_typealias_name_union_violation(self) -> None: msg = ( f'Type alias name "{name}" should be in PascalCase format. Type alias names should ' f"have the first letter of each word capitalized with no separation between each " - f"word." + f'word. Consider renaming to "NotPascal".' ) with self.assertAddsMessages( @@ -701,7 +710,7 @@ def test_typealias_name_tuple_violation(self) -> None: msg = ( f'Type alias name "{name}" should be in PascalCase format. Type alias names should ' f"have the first letter of each word capitalized with no separation between each " - f"word." + f'word. Consider renaming to "NotPascal".' ) with self.assertAddsMessages( @@ -888,3 +897,21 @@ def test_module_name_no_snippet() -> None: snippet = reporter.messages[file_fixture][0].snippet assert snippet == "" + + +class TestNamingConventionHelpers(unittest.TestCase): + def test_to_pascal_case(self) -> None: + """Test that names are correctly converted to PascalCase.""" + self.assertEqual(_to_pascal_case("snake_case"), "SnakeCase") + self.assertEqual(_to_pascal_case("PascalCase"), "PascalCase") + self.assertEqual(_to_pascal_case("_UPPER_CASE_NAME"), "_UpperCaseName") + self.assertEqual(_to_pascal_case("__varName_here_"), "_VarNameHere") + self.assertEqual(_to_pascal_case("parseJSONText"), "ParseJsonText") + + def test_to_uppercase_with_underscores(self) -> None: + """Test that names are correctly converted to UPPERCASE_WITH_UNDERSCORES.""" + self.assertEqual(_to_upper_case_with_underscores("snake_case"), "SNAKE_CASE") + self.assertEqual(_to_upper_case_with_underscores("PascalCase"), "PASCAL_CASE") + self.assertEqual(_to_upper_case_with_underscores("_UPPER_CASE_NAME"), "_UPPER_CASE_NAME") + self.assertEqual(_to_upper_case_with_underscores("__varName_here_"), "_VAR_NAME_HERE_") + self.assertEqual(_to_upper_case_with_underscores("parseJSONText"), "PARSE_JSON_TEXT") From 10537c4e4778fc75f79825d6bcdf30c741cd67ce Mon Sep 17 00:00:00 2001 From: Alan Su Date: Mon, 18 May 2026 20:52:13 -0400 Subject: [PATCH 2/4] Modify changelog with naming convention suggested fixes --- packages/python-ta/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/python-ta/CHANGELOG.md b/packages/python-ta/CHANGELOG.md index b7cfcca9d..517d19137 100644 --- a/packages/python-ta/CHANGELOG.md +++ b/packages/python-ta/CHANGELOG.md @@ -44,6 +44,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Configured CI to skip the `no-commit-to-branch` prek hook, which incorrectly fails when merging PRs into master - Added tests for `unnecessary_indexing_checker.py` to improve coverage for `_iterable_if_range` function - Added tests for `invalid_name_checker.py` to improve coverage +- Added suggested `invalid_name_checker.py` fixes in messages ## [2.12.1] - 2026-02-10 From a49bda932b438243ffb9b7f7610987dd42ed3685 Mon Sep 17 00:00:00 2001 From: Alan Su Date: Mon, 18 May 2026 21:00:30 -0400 Subject: [PATCH 3/4] Modify pascal and uppercase fixes to match snake case --- packages/python-ta/CHANGELOG.md | 1 - .../checkers/invalid_name_checker.py | 16 +++++++-------- .../test_invalid_name_checker.py | 20 +++++++++---------- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/packages/python-ta/CHANGELOG.md b/packages/python-ta/CHANGELOG.md index 517d19137..b7cfcca9d 100644 --- a/packages/python-ta/CHANGELOG.md +++ b/packages/python-ta/CHANGELOG.md @@ -44,7 +44,6 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Configured CI to skip the `no-commit-to-branch` prek hook, which incorrectly fails when merging PRs into master - Added tests for `unnecessary_indexing_checker.py` to improve coverage for `_iterable_if_range` function - Added tests for `invalid_name_checker.py` to improve coverage -- Added suggested `invalid_name_checker.py` fixes in messages ## [2.12.1] - 2026-02-10 diff --git a/packages/python-ta/src/python_ta/checkers/invalid_name_checker.py b/packages/python-ta/src/python_ta/checkers/invalid_name_checker.py index 07ba62f34..91b893098 100644 --- a/packages/python-ta/src/python_ta/checkers/invalid_name_checker.py +++ b/packages/python-ta/src/python_ta/checkers/invalid_name_checker.py @@ -165,7 +165,7 @@ def _check_module_name(_node_type: str, name: str) -> list[str]: def _check_const_name(node_type: str, name: str) -> list[str]: """Returns a list of strings, each detailing how `name` violates Python naming conventions for - constant and class constant names. + constant and class constant names and provides a suggested correction. Returns an empty list if `name` is a valid (global or class) constant name.""" error_msgs = [] @@ -179,7 +179,7 @@ def _check_const_name(node_type: str, name: str) -> list[str]: ) if node_type == "class constant": msg += " A double leading underscore invokes Python's name-mangling rules." - msg += f' Consider renaming to "{suggested_name}".' + msg += f' Suggested fix: "{suggested_name}".' error_msgs.append(msg) return error_msgs @@ -187,7 +187,7 @@ def _check_const_name(node_type: str, name: str) -> list[str]: def _check_class_name(_node_type: str, name: str) -> list[str]: """Returns a list of strings, each detailing how `name` violates Python naming conventions for - class names. + class names and provides a suggested correction. Returns an empty list if `name` is a valid class name.""" error_msgs = [] @@ -198,7 +198,7 @@ class names. f'Class name "{name}" should be in PascalCase format. Class names should have the ' f"first letter of each word capitalized with no separation between each " f"word. A single leading underscore can be used to denote a private " - f'class. Consider renaming to "{suggested_name}".' + f'class. Suggested fix: "{suggested_name}".' ) return error_msgs @@ -262,7 +262,7 @@ def _check_argument_name(_node_type: str, name: str) -> list[str]: def _check_typevar_name(_node_type: str, name: str) -> list[str]: """Returns a list of strings, each detailing how `name` violates Python naming conventions for - type variable names. + type variable names and provides a suggested correction. Returns an empty list if `name` is a valid type variable name.""" error_msgs = [] @@ -272,7 +272,7 @@ def _check_typevar_name(_node_type: str, name: str) -> list[str]: error_msgs.append( f'Type variable name "{name}" should be in PascalCase format. Type variable ' f"names should have the first letter of each word capitalized with no separation " - f'between each word. Consider renaming to "{suggested_name}".' + f'between each word. Suggested fix: "{suggested_name}".' ) return error_msgs @@ -280,7 +280,7 @@ def _check_typevar_name(_node_type: str, name: str) -> list[str]: def _check_type_alias_name(_node_type: str, name: str) -> list[str]: """Returns a list of strings, each detailing how `name` violates Python naming conventions for - type alias names. + type alias names and provides a suggested correction. Returns an empty list if `name` is a valid type alias name.""" error_msgs = [] @@ -290,7 +290,7 @@ def _check_type_alias_name(_node_type: str, name: str) -> list[str]: error_msgs.append( f'Type alias name "{name}" should be in PascalCase format. Type alias names should ' f"have the first letter of each word capitalized with no separation " - f'between each word. Consider renaming to "{suggested_name}".' + f'between each word. Suggested fix: "{suggested_name}".' ) return error_msgs diff --git a/packages/python-ta/tests/test_custom_checkers/test_invalid_name_checker.py b/packages/python-ta/tests/test_custom_checkers/test_invalid_name_checker.py index 6ae26abf2..075494d4b 100644 --- a/packages/python-ta/tests/test_custom_checkers/test_invalid_name_checker.py +++ b/packages/python-ta/tests/test_custom_checkers/test_invalid_name_checker.py @@ -75,7 +75,7 @@ def test_const_name_violation(self) -> None: f'Constant name "{name}" should be in UPPER_CASE_WITH_UNDERSCORES format. Constants ' f"should be all-uppercase words with each word separated by an underscore. A " f"single leading underscore can be used to denote a private constant. " - f'Consider renaming to "CONST_NOT_UPPER".' + f'Suggested fix: "CONST_NOT_UPPER".' ) with self.assertAddsMessages( @@ -100,7 +100,7 @@ def test_const_name_annotated_violation(self) -> None: f'Constant name "{name}" should be in UPPER_CASE_WITH_UNDERSCORES format. Constants ' f"should be all-uppercase words with each word separated by an underscore. A " f"single leading underscore can be used to denote a private constant. " - f'Consider renaming to "CONST_NOT_UPPER".' + f'Suggested fix: "CONST_NOT_UPPER".' ) with self.assertAddsMessages( @@ -146,7 +146,7 @@ class notPascalcase: f'Class name "{name}" should be in PascalCase format. Class names should have the ' f"first letter of each word capitalized with no separation between each word. A " f"single leading underscore can be used to denote a private class. " - f'Consider renaming to "NotPascalcase".' + f'Suggested fix: "NotPascalcase".' ) with self.assertAddsMessages( @@ -172,7 +172,7 @@ class MyClass: f'Class name "{name}" should be in PascalCase format. Class names should have the ' f"first letter of each word capitalized with no separation between each word. A " f"single leading underscore can be used to denote a private class. " - f'Consider renaming to "SnakeCase".' + f'Suggested fix: "SnakeCase".' ) with self.assertAddsMessages( @@ -557,7 +557,7 @@ class BadClass: f"Constants should be all-uppercase words with each word separated by an " f"underscore. A single leading underscore can be used to denote a private " f"constant. A double leading underscore invokes Python's name-mangling rules. " - f'Consider renaming to "OOGA_BOOGA".' + f'Suggested fix: "OOGA_BOOGA".' ) with self.assertAddsMessages( @@ -596,7 +596,7 @@ def test_typevar_name_violation(self) -> None: msg = ( f'Type variable name "{name}" should be in PascalCase format. Type variable names ' f"should have the first letter of each word capitalized with no separation between " - f'each word. Consider renaming to "TypeVar".' + f'each word. Suggested fix: "TypeVar".' ) with self.assertAddsMessages( @@ -620,7 +620,7 @@ def test_typevar_name_tuple_violation(self) -> None: msg = ( f'Type variable name "{name}" should be in PascalCase format. Type variable names ' f"should have the first letter of each word capitalized with no separation between " - f'each word. Consider renaming to "TypeVar".' + f'each word. Suggested fix: "TypeVar".' ) with self.assertAddsMessages( @@ -658,7 +658,7 @@ def test_typealias_name_violation(self) -> None: msg = ( f'Type alias name "{name}" should be in PascalCase format. Type alias names should ' f"have the first letter of each word capitalized with no separation between each " - f'word. Consider renaming to "NotPascal".' + f'word. Suggested fix: "NotPascal".' ) with self.assertAddsMessages( @@ -684,7 +684,7 @@ def test_typealias_name_union_violation(self) -> None: msg = ( f'Type alias name "{name}" should be in PascalCase format. Type alias names should ' f"have the first letter of each word capitalized with no separation between each " - f'word. Consider renaming to "NotPascal".' + f'word. Suggested fix: "NotPascal".' ) with self.assertAddsMessages( @@ -710,7 +710,7 @@ def test_typealias_name_tuple_violation(self) -> None: msg = ( f'Type alias name "{name}" should be in PascalCase format. Type alias names should ' f"have the first letter of each word capitalized with no separation between each " - f'word. Consider renaming to "NotPascal".' + f'word. Suggested fix: "NotPascal".' ) with self.assertAddsMessages( From 394ad2a3623d55a0744962311f8c6a384970b326 Mon Sep 17 00:00:00 2001 From: Alan Su Date: Wed, 20 May 2026 21:37:55 -0400 Subject: [PATCH 4/4] Update invalid name checker suggested fixes and pascal case handling --- packages/python-ta/CHANGELOG.md | 1 + .../checkers/invalid_name_checker.py | 61 +++++++++------ .../test_invalid_name_checker.py | 75 ++++++++++--------- 3 files changed, 79 insertions(+), 58 deletions(-) diff --git a/packages/python-ta/CHANGELOG.md b/packages/python-ta/CHANGELOG.md index b7cfcca9d..3a2c2443a 100644 --- a/packages/python-ta/CHANGELOG.md +++ b/packages/python-ta/CHANGELOG.md @@ -16,6 +16,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Extended `snapshot` to distinguish between nonlocal variables and local variables within a stack frame. - Make `watchdog` an optional dependency; users can opt in with `pip install python-ta[watchdog]`. This affects runs of `python_ta.check_all` with the `watch` config option set to `True`. - Added `LSPReporter`, a new reporter that outputs lint diagnostics in LSP 3.17-compliant JSON format. +- Added suggested fixes for pascal and uppercase names in `invalid_name_checker.py` ### 💫 New checkers diff --git a/packages/python-ta/src/python_ta/checkers/invalid_name_checker.py b/packages/python-ta/src/python_ta/checkers/invalid_name_checker.py index 91b893098..f7032b4ea 100644 --- a/packages/python-ta/src/python_ta/checkers/invalid_name_checker.py +++ b/packages/python-ta/src/python_ta/checkers/invalid_name_checker.py @@ -82,29 +82,35 @@ def _is_in_upper_case_with_underscores(name: str) -> bool: return re.match(pattern, name) is not None -def _parse_name(name: str) -> tuple[str, list[str], str]: +def _parse_name(name: str) -> tuple[str, list[str] | None, str]: """Extracts the prefix, words, and suffix from `name`.""" name_match = re.match(r"(_*)(.*?)(_*)$", name) if not name_match: - return "", [name], "" + return "", None, "" prefix, core, suffix = name_match.groups() prefix = "_" if prefix else "" + if core and core[0].isdigit(): + return "", None, "" core = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", core) core = re.sub(r"([A-Z])([A-Z][a-z])", r"\1_\2", core) return prefix, [word for word in core.split("_") if word], suffix -def _to_pascal_case(name: str) -> str: +def _to_pascal_case(name: str) -> str | None: """Returns a PascalCase version of `name`.""" prefix, words, _ = _parse_name(name) + if words is None: + return None - return prefix + "".join(word.capitalize() for word in words) + return prefix + "".join(word[0].upper() + word[1:] for word in words) -def _to_upper_case_with_underscores(name: str) -> str: +def _to_upper_case_with_underscores(name: str) -> str | None: """Returns an UPPER_CASE_WITH_UNDERSCORES version of `name`.""" prefix, words, suffix = _parse_name(name) + if words is None: + return None return prefix + "_".join(word.upper() for word in words) + suffix @@ -172,14 +178,15 @@ def _check_const_name(node_type: str, name: str) -> list[str]: if not _is_in_upper_case_with_underscores(name): suggested_name = _to_upper_case_with_underscores(name) - msg = ( - f'{node_type.capitalize()} name "{name}" should be in UPPER_CASE_WITH_UNDERSCORES format. ' - f"Constants should be all-uppercase words with each word separated by an " - f"underscore. A single leading underscore can be used to denote a private constant." + msg = f'{node_type.capitalize()} name "{name}" should be in UPPER_CASE_WITH_UNDERSCORES format. ' + if suggested_name: + msg += f'Suggested fix: "{suggested_name}". ' + msg += ( + "Constants should be all-uppercase words with each word separated by an " + "underscore. A single leading underscore can be used to denote a private constant." ) if node_type == "class constant": msg += " A double leading underscore invokes Python's name-mangling rules." - msg += f' Suggested fix: "{suggested_name}".' error_msgs.append(msg) return error_msgs @@ -194,12 +201,14 @@ class names and provides a suggested correction. if not _is_in_pascal_case(name): suggested_name = _to_pascal_case(name) - error_msgs.append( - f'Class name "{name}" should be in PascalCase format. Class names should have the ' - f"first letter of each word capitalized with no separation between each " - f"word. A single leading underscore can be used to denote a private " - f'class. Suggested fix: "{suggested_name}".' + msg = f'Class name "{name}" should be in PascalCase format. ' + if suggested_name: + msg += f'Suggested fix: "{suggested_name}". ' + msg += ( + "Class names should have the first letter of each word capitalized with no separation " + "between each word. A single leading underscore can be used to denote a private class." ) + error_msgs.append(msg) return error_msgs @@ -269,11 +278,14 @@ def _check_typevar_name(_node_type: str, name: str) -> list[str]: if not _is_in_pascal_case(name): suggested_name = _to_pascal_case(name) - error_msgs.append( - f'Type variable name "{name}" should be in PascalCase format. Type variable ' - f"names should have the first letter of each word capitalized with no separation " - f'between each word. Suggested fix: "{suggested_name}".' + msg = f'Type variable name "{name}" should be in PascalCase format. ' + if suggested_name: + msg += f'Suggested fix: "{suggested_name}". ' + msg += ( + "Type variable names should have the first letter of each word " + "capitalized with no separation between each word." ) + error_msgs.append(msg) return error_msgs @@ -287,11 +299,14 @@ def _check_type_alias_name(_node_type: str, name: str) -> list[str]: if not _is_in_pascal_case(name): suggested_name = _to_pascal_case(name) - error_msgs.append( - f'Type alias name "{name}" should be in PascalCase format. Type alias names should ' - f"have the first letter of each word capitalized with no separation " - f'between each word. Suggested fix: "{suggested_name}".' + msg = f'Type alias name "{name}" should be in PascalCase format. ' + if suggested_name: + msg += f'Suggested fix: "{suggested_name}". ' + msg += ( + "Type alias names should have the first letter of each word " + "capitalized with no separation between each word." ) + error_msgs.append(msg) return error_msgs diff --git a/packages/python-ta/tests/test_custom_checkers/test_invalid_name_checker.py b/packages/python-ta/tests/test_custom_checkers/test_invalid_name_checker.py index 075494d4b..15fb90e30 100644 --- a/packages/python-ta/tests/test_custom_checkers/test_invalid_name_checker.py +++ b/packages/python-ta/tests/test_custom_checkers/test_invalid_name_checker.py @@ -72,10 +72,10 @@ def test_const_name_violation(self) -> None: assignname_node, *_ = mod.nodes_of_class(nodes.AssignName) name = assignname_node.name msg = ( - f'Constant name "{name}" should be in UPPER_CASE_WITH_UNDERSCORES format. Constants ' - f"should be all-uppercase words with each word separated by an underscore. A " - f"single leading underscore can be used to denote a private constant. " - f'Suggested fix: "CONST_NOT_UPPER".' + f'Constant name "{name}" should be in UPPER_CASE_WITH_UNDERSCORES format. ' + f'Suggested fix: "CONST_NOT_UPPER". ' + f"Constants should be all-uppercase words with each word separated by an underscore. A " + f"single leading underscore can be used to denote a private constant." ) with self.assertAddsMessages( @@ -97,10 +97,10 @@ def test_const_name_annotated_violation(self) -> None: assignname_node, *_ = mod.nodes_of_class(nodes.AssignName) name = assignname_node.name msg = ( - f'Constant name "{name}" should be in UPPER_CASE_WITH_UNDERSCORES format. Constants ' - f"should be all-uppercase words with each word separated by an underscore. A " - f"single leading underscore can be used to denote a private constant. " - f'Suggested fix: "CONST_NOT_UPPER".' + f'Constant name "{name}" should be in UPPER_CASE_WITH_UNDERSCORES format. ' + f'Suggested fix: "CONST_NOT_UPPER". ' + f"Constants should be all-uppercase words with each word separated by an underscore. A " + f"single leading underscore can be used to denote a private constant." ) with self.assertAddsMessages( @@ -143,10 +143,10 @@ class notPascalcase: classdef_node, *_ = mod.nodes_of_class(nodes.ClassDef) name = classdef_node.name msg = ( - f'Class name "{name}" should be in PascalCase format. Class names should have the ' - f"first letter of each word capitalized with no separation between each word. A " - f"single leading underscore can be used to denote a private class. " - f'Suggested fix: "NotPascalcase".' + f'Class name "{name}" should be in PascalCase format. ' + f'Suggested fix: "NotPascalcase". ' + f"Class names should have the first letter of each word capitalized with no separation " + f"between each word. A single leading underscore can be used to denote a private class." ) with self.assertAddsMessages( @@ -169,10 +169,10 @@ class MyClass: assignname_node, *_ = mod.nodes_of_class(nodes.AssignName) name = assignname_node.name msg = ( - f'Class name "{name}" should be in PascalCase format. Class names should have the ' - f"first letter of each word capitalized with no separation between each word. A " - f"single leading underscore can be used to denote a private class. " - f'Suggested fix: "SnakeCase".' + f'Class name "{name}" should be in PascalCase format. ' + f'Suggested fix: "SnakeCase". ' + f"Class names should have the first letter of each word capitalized with no separation " + f"between each word. A single leading underscore can be used to denote a private class." ) with self.assertAddsMessages( @@ -554,10 +554,10 @@ class BadClass: name = assignname_node.name msg = ( f'Class constant name "{name}" should be in UPPER_CASE_WITH_UNDERSCORES format. ' + f'Suggested fix: "OOGA_BOOGA". ' f"Constants should be all-uppercase words with each word separated by an " f"underscore. A single leading underscore can be used to denote a private " - f"constant. A double leading underscore invokes Python's name-mangling rules. " - f'Suggested fix: "OOGA_BOOGA".' + f"constant. A double leading underscore invokes Python's name-mangling rules." ) with self.assertAddsMessages( @@ -594,9 +594,10 @@ def test_typevar_name_violation(self) -> None: assignname_node, *_ = mod.nodes_of_class(nodes.AssignName) name = assignname_node.name msg = ( - f'Type variable name "{name}" should be in PascalCase format. Type variable names ' - f"should have the first letter of each word capitalized with no separation between " - f'each word. Suggested fix: "TypeVar".' + f'Type variable name "{name}" should be in PascalCase format. ' + f'Suggested fix: "TypeVar". ' + f"Type variable names should have the first letter of each word capitalized with " + f"no separation between each word." ) with self.assertAddsMessages( @@ -618,9 +619,10 @@ def test_typevar_name_tuple_violation(self) -> None: _, assignname_node, *_ = mod.nodes_of_class(nodes.AssignName) name = assignname_node.name msg = ( - f'Type variable name "{name}" should be in PascalCase format. Type variable names ' - f"should have the first letter of each word capitalized with no separation between " - f'each word. Suggested fix: "TypeVar".' + f'Type variable name "{name}" should be in PascalCase format. ' + f'Suggested fix: "TypeVar". ' + f"Type variable names should have the first letter of each word capitalized with " + f"no separation between each word." ) with self.assertAddsMessages( @@ -656,9 +658,10 @@ def test_typealias_name_violation(self) -> None: assignname_node, *_ = mod.nodes_of_class(nodes.AssignName) name = assignname_node.name msg = ( - f'Type alias name "{name}" should be in PascalCase format. Type alias names should ' - f"have the first letter of each word capitalized with no separation between each " - f'word. Suggested fix: "NotPascal".' + f'Type alias name "{name}" should be in PascalCase format. ' + f'Suggested fix: "NotPascal". ' + f"Type alias names should have the first letter of each word capitalized with " + f"no separation between each word." ) with self.assertAddsMessages( @@ -682,9 +685,10 @@ def test_typealias_name_union_violation(self) -> None: assignname_node, *_ = mod.nodes_of_class(nodes.AssignName) name = assignname_node.name msg = ( - f'Type alias name "{name}" should be in PascalCase format. Type alias names should ' - f"have the first letter of each word capitalized with no separation between each " - f'word. Suggested fix: "NotPascal".' + f'Type alias name "{name}" should be in PascalCase format. ' + f'Suggested fix: "NotPascal". ' + f"Type alias names should have the first letter of each word capitalized with " + f"no separation between each word." ) with self.assertAddsMessages( @@ -708,9 +712,10 @@ def test_typealias_name_tuple_violation(self) -> None: _, assignname_node, *_ = mod.nodes_of_class(nodes.AssignName) name = assignname_node.name msg = ( - f'Type alias name "{name}" should be in PascalCase format. Type alias names should ' - f"have the first letter of each word capitalized with no separation between each " - f'word. Suggested fix: "NotPascal".' + f'Type alias name "{name}" should be in PascalCase format. ' + f'Suggested fix: "NotPascal". ' + f"Type alias names should have the first letter of each word capitalized with " + f"no separation between each word." ) with self.assertAddsMessages( @@ -904,9 +909,9 @@ def test_to_pascal_case(self) -> None: """Test that names are correctly converted to PascalCase.""" self.assertEqual(_to_pascal_case("snake_case"), "SnakeCase") self.assertEqual(_to_pascal_case("PascalCase"), "PascalCase") - self.assertEqual(_to_pascal_case("_UPPER_CASE_NAME"), "_UpperCaseName") + self.assertEqual(_to_pascal_case("_UPPER_CASE_NAME"), "_UPPERCASENAME") self.assertEqual(_to_pascal_case("__varName_here_"), "_VarNameHere") - self.assertEqual(_to_pascal_case("parseJSONText"), "ParseJsonText") + self.assertEqual(_to_pascal_case("parseJSONText"), "ParseJSONText") def test_to_uppercase_with_underscores(self) -> None: """Test that names are correctly converted to UPPERCASE_WITH_UNDERSCORES."""