Skip to content

Commit

Permalink
Check that a name exists inside enum.Enum class' __members__. (#8905
Browse files Browse the repository at this point in the history
)

Refs #7402
  • Loading branch information
mbyrnepr2 authored Sep 26, 2023
1 parent 07b5c32 commit a4aff97
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 9 deletions.
3 changes: 3 additions & 0 deletions doc/whatsnew/fragments/7402.false_positive
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix a false positive for ``invalid-name`` when a type-annotated class variable in an ``enum.Enum`` class has no assigned value.

Refs #7402
15 changes: 6 additions & 9 deletions pylint/checkers/base/name_checker/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -485,15 +485,12 @@ def visit_assignname( # pylint: disable=too-many-branches

# Check names defined in class scopes
elif isinstance(frame, nodes.ClassDef):
if not list(frame.local_attr_ancestors(node.name)):
for ancestor in frame.ancestors():
if utils.is_enum(ancestor) or utils.is_assign_name_annotated_with(
node, "Final"
):
self._check_name("class_const", node.name, node)
break
else:
self._check_name("class_attribute", node.name, node)
if utils.is_enum_member(node) or utils.is_assign_name_annotated_with(
node, "Final"
):
self._check_name("class_const", node.name, node)
else:
self._check_name("class_attribute", node.name, node)

def _recursive_check_names(self, args: list[nodes.AssignName]) -> None:
"""Check names in a possibly recursive list <arg>."""
Expand Down
17 changes: 17 additions & 0 deletions pylint/checkers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2266,3 +2266,20 @@ def clear_lru_caches() -> None:
]
for lru in caches_holding_node_references:
lru.cache_clear()


def is_enum_member(node: nodes.AssignName) -> bool:
"""Return `True` if `node` is an Enum member (is an item of the
`__members__` container).
"""

frame = node.frame()
if (
not isinstance(frame, nodes.ClassDef)
or not frame.is_subtype_of("enum.Enum")
or frame.root().qname() == "enum"
):
return False

enum_member_objects = frame.locals.get("__members__")[0].items
return node.name in [name_obj.name for value, name_obj in enum_member_objects]
30 changes: 30 additions & 0 deletions tests/functional/i/invalid/invalid_name/invalid_name_enum.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
""" Tests for invalid-name checker in the context of enums. """
# pylint: disable=too-few-public-methods


from enum import Enum


class Color(Enum):
"""Represents colors as (red, green, blue) tuples."""

YELLOW = 250, 250, 0
KHAKI = 250, 250, 125
MAGENTA = 250, 0, 250
VIOLET = 250, 125, 250
CYAN = 0, 250, 250
aquamarine = 125, 250, 250 # [invalid-name]

red: int
green: int
blue: int

def __init__(self, red: int, green: int, blue: int) -> None:
self.red = red
self.green = green
self.blue = blue

@property
def as_hex(self) -> str:
"""Get hex 'abcdef' representation for a color."""
return f'{self.red:0{2}x}{self.green:0{2}x}{self.blue:0{2}x}'
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
invalid-name:16:4:16:14:Color:"Class constant name ""aquamarine"" doesn't conform to UPPER_CASE naming style":HIGH

0 comments on commit a4aff97

Please sign in to comment.