diff options
author | Daniƫl van Noord <13665637+DanielNoord@users.noreply.github.com> | 2023-02-26 21:47:30 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-02-26 21:47:30 +0100 |
commit | e7ad3e64d36e450a9133c64e804c921d0fd0f204 (patch) | |
tree | 5db8215be4d052ccd7be2d2a5cbafe7ccdb89be5 /pylint | |
parent | 52a2a04a59526ce8344d4d2b7f86bb978177e047 (diff) | |
download | pylint-git-e7ad3e64d36e450a9133c64e804c921d0fd0f204.tar.gz |
Add ``invalid-name`` check for ``TypeAlias`` names (#7116)
Diffstat (limited to 'pylint')
-rw-r--r-- | pylint/checkers/bad_chained_comparison.py | 3 | ||||
-rw-r--r-- | pylint/checkers/base/name_checker/checker.py | 71 | ||||
-rw-r--r-- | pylint/checkers/base/name_checker/naming_style.py | 1 | ||||
-rw-r--r-- | pylint/constants.py | 1 | ||||
-rw-r--r-- | pylint/utils/linterstats.py | 7 |
5 files changed, 61 insertions, 22 deletions
diff --git a/pylint/checkers/bad_chained_comparison.py b/pylint/checkers/bad_chained_comparison.py index fa75c52b2..8c3aeb9cc 100644 --- a/pylint/checkers/bad_chained_comparison.py +++ b/pylint/checkers/bad_chained_comparison.py @@ -24,7 +24,7 @@ class BadChainedComparisonChecker(BaseChecker): name = "bad-chained-comparison" msgs = { - "W3501": ( + "W3601": ( "Suspicious %s-part chained comparison using semantically incompatible operators (%s)", "bad-chained-comparison", "Used when there is a chained comparison where one expression is part " @@ -35,7 +35,6 @@ class BadChainedComparisonChecker(BaseChecker): } def _has_diff_semantic_groups(self, operators: list[str]) -> bool: - # Check if comparison operators are in the same semantic group for semantic_group in (COMPARISON_OP, IDENTITY_OP, MEMBERSHIP_OP): if operators[0] in semantic_group: diff --git a/pylint/checkers/base/name_checker/checker.py b/pylint/checkers/base/name_checker/checker.py index cdbf4a7b5..f1fd8d379 100644 --- a/pylint/checkers/base/name_checker/checker.py +++ b/pylint/checkers/base/name_checker/checker.py @@ -40,7 +40,8 @@ _BadNamesTuple = Tuple[nodes.NodeNG, str, str, interfaces.Confidence] DEFAULT_PATTERNS = { "typevar": re.compile( r"^_{0,2}(?!T[A-Z])(?:[A-Z]+|(?:[A-Z]+[a-z]+)+T?(?<!Type))(?:_co(?:ntra)?)?$" - ) + ), + "typealias": re.compile(r"^_{0,2}(?!T[A-Z]|Type)[A-Z]+[a-z]+(?:[A-Z][a-z]+)*$"), } BUILTIN_PROPERTY = "builtins.property" @@ -400,7 +401,9 @@ class NameChecker(_BasicChecker): "typevar-double-variance", "typevar-name-mismatch", ) - def visit_assignname(self, node: nodes.AssignName) -> None: + def visit_assignname( # pylint: disable=too-many-branches + self, node: nodes.AssignName + ) -> None: """Check module level assigned names.""" frame = node.frame(future=True) assign_type = node.assign_type() @@ -415,25 +418,40 @@ class NameChecker(_BasicChecker): if isinstance(assign_type, nodes.Assign): inferred_assign_type = utils.safe_infer(assign_type.value) - # Check TypeVar's assigned alone or in tuple assignment - if isinstance(node.parent, nodes.Assign) and self._assigns_typevar( - assign_type.value - ): - self._check_name("typevar", assign_type.targets[0].name, node) - elif ( + # Check TypeVar's and TypeAliases assigned alone or in tuple assignment + if isinstance(node.parent, nodes.Assign): + if self._assigns_typevar(assign_type.value): + self._check_name("typevar", assign_type.targets[0].name, node) + return + if self._assigns_typealias(assign_type.value): + self._check_name("typealias", assign_type.targets[0].name, node) + return + + if ( isinstance(node.parent, nodes.Tuple) and isinstance(assign_type.value, nodes.Tuple) # protect against unbalanced tuple unpacking and node.parent.elts.index(node) < len(assign_type.value.elts) - and self._assigns_typevar( - assign_type.value.elts[node.parent.elts.index(node)] - ) ): - self._check_name( - "typevar", - assign_type.targets[0].elts[node.parent.elts.index(node)].name, - node, - ) + assigner = assign_type.value.elts[node.parent.elts.index(node)] + if self._assigns_typevar(assigner): + self._check_name( + "typevar", + assign_type.targets[0] + .elts[node.parent.elts.index(node)] + .name, + node, + ) + return + if self._assigns_typealias(assigner): + self._check_name( + "typealias", + assign_type.targets[0] + .elts[node.parent.elts.index(node)] + .name, + node, + ) + return # Check classes (TypeVar's are classes so they need to be excluded first) elif isinstance(inferred_assign_type, nodes.ClassDef): @@ -450,10 +468,11 @@ class NameChecker(_BasicChecker): ) # Check names defined in AnnAssign nodes - elif isinstance( - assign_type, nodes.AnnAssign - ) and utils.is_assign_name_annotated_with(node, "Final"): - self._check_name("const", node.name, node) + elif isinstance(assign_type, nodes.AnnAssign): + if utils.is_assign_name_annotated_with(node, "Final"): + self._check_name("const", node.name, node) + elif self._assigns_typealias(assign_type.annotation): + self._check_name("typealias", node.name, node) # Check names defined in function scopes elif isinstance(frame, nodes.FunctionDef): @@ -577,6 +596,18 @@ class NameChecker(_BasicChecker): return True return False + @staticmethod + def _assigns_typealias(node: nodes.NodeNG | None) -> bool: + """Check if a node is assigning a TypeAlias.""" + inferred = utils.safe_infer(node) + if isinstance(inferred, nodes.ClassDef): + if inferred.qname() == ".Union": + return True + elif isinstance(inferred, nodes.FunctionDef): + if inferred.qname() == "typing.TypeAlias": + return True + return False + def _check_typevar(self, name: str, node: nodes.AssignName) -> None: """Check for TypeVar lint violations.""" if isinstance(node.parent, nodes.Assign): diff --git a/pylint/checkers/base/name_checker/naming_style.py b/pylint/checkers/base/name_checker/naming_style.py index bfe6f77d3..8ccec4289 100644 --- a/pylint/checkers/base/name_checker/naming_style.py +++ b/pylint/checkers/base/name_checker/naming_style.py @@ -137,6 +137,7 @@ DEFAULT_NAMING_STYLES = { KNOWN_NAME_TYPES = { *KNOWN_NAME_TYPES_WITH_STYLE, "typevar", + "typealias", } diff --git a/pylint/constants.py b/pylint/constants.py index 92a9990d1..ad7fc2256 100644 --- a/pylint/constants.py +++ b/pylint/constants.py @@ -80,6 +80,7 @@ HUMAN_READABLE_TYPES = { "class_const": "class constant", "inlinevar": "inline iteration", "typevar": "type variable", + "typealias": "type alias", } # ignore some messages when emitting useless-suppression: diff --git a/pylint/utils/linterstats.py b/pylint/utils/linterstats.py index 11338850c..f9a6c6c3c 100644 --- a/pylint/utils/linterstats.py +++ b/pylint/utils/linterstats.py @@ -30,6 +30,7 @@ class BadNames(TypedDict): module: int variable: int typevar: int + typealias: int class CodeTypeCount(TypedDict): @@ -107,6 +108,7 @@ class LinterStats: module=0, variable=0, typevar=0, + typealias=0, ) self.by_module: dict[str, ModuleStats] = by_module or {} self.by_msg: dict[str, int] = by_msg or {} @@ -182,6 +184,7 @@ class LinterStats: "module", "variable", "typevar", + "typealias", ], ) -> int: """Get a bad names node count.""" @@ -204,6 +207,7 @@ class LinterStats: "module", "variable", "typevar", + "typealias", }: raise ValueError("Node type not part of the bad_names stat") @@ -221,6 +225,7 @@ class LinterStats: "module", "variable", "typevar", + "typealias", ], node_name, ) @@ -244,6 +249,7 @@ class LinterStats: module=0, variable=0, typevar=0, + typealias=0, ) def get_code_count( @@ -336,6 +342,7 @@ def merge_stats(stats: list[LinterStats]) -> LinterStats: merged.bad_names["module"] += stat.bad_names["module"] merged.bad_names["variable"] += stat.bad_names["variable"] merged.bad_names["typevar"] += stat.bad_names["typevar"] + merged.bad_names["typealias"] += stat.bad_names["typealias"] for mod_key, mod_value in stat.by_module.items(): merged.by_module[mod_key] = mod_value |