Skip to content

Commit a4aff97

Browse files
authored
Check that a name exists inside enum.Enum class' __members__. (#8905)
Refs #7402
1 parent 07b5c32 commit a4aff97

File tree

5 files changed

+57
-9
lines changed

5 files changed

+57
-9
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix a false positive for ``invalid-name`` when a type-annotated class variable in an ``enum.Enum`` class has no assigned value.
2+
3+
Refs #7402

pylint/checkers/base/name_checker/checker.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -485,15 +485,12 @@ def visit_assignname( # pylint: disable=too-many-branches
485485

486486
# Check names defined in class scopes
487487
elif isinstance(frame, nodes.ClassDef):
488-
if not list(frame.local_attr_ancestors(node.name)):
489-
for ancestor in frame.ancestors():
490-
if utils.is_enum(ancestor) or utils.is_assign_name_annotated_with(
491-
node, "Final"
492-
):
493-
self._check_name("class_const", node.name, node)
494-
break
495-
else:
496-
self._check_name("class_attribute", node.name, node)
488+
if utils.is_enum_member(node) or utils.is_assign_name_annotated_with(
489+
node, "Final"
490+
):
491+
self._check_name("class_const", node.name, node)
492+
else:
493+
self._check_name("class_attribute", node.name, node)
497494

498495
def _recursive_check_names(self, args: list[nodes.AssignName]) -> None:
499496
"""Check names in a possibly recursive list <arg>."""

pylint/checkers/utils.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2266,3 +2266,20 @@ def clear_lru_caches() -> None:
22662266
]
22672267
for lru in caches_holding_node_references:
22682268
lru.cache_clear()
2269+
2270+
2271+
def is_enum_member(node: nodes.AssignName) -> bool:
2272+
"""Return `True` if `node` is an Enum member (is an item of the
2273+
`__members__` container).
2274+
"""
2275+
2276+
frame = node.frame()
2277+
if (
2278+
not isinstance(frame, nodes.ClassDef)
2279+
or not frame.is_subtype_of("enum.Enum")
2280+
or frame.root().qname() == "enum"
2281+
):
2282+
return False
2283+
2284+
enum_member_objects = frame.locals.get("__members__")[0].items
2285+
return node.name in [name_obj.name for value, name_obj in enum_member_objects]
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
""" Tests for invalid-name checker in the context of enums. """
2+
# pylint: disable=too-few-public-methods
3+
4+
5+
from enum import Enum
6+
7+
8+
class Color(Enum):
9+
"""Represents colors as (red, green, blue) tuples."""
10+
11+
YELLOW = 250, 250, 0
12+
KHAKI = 250, 250, 125
13+
MAGENTA = 250, 0, 250
14+
VIOLET = 250, 125, 250
15+
CYAN = 0, 250, 250
16+
aquamarine = 125, 250, 250 # [invalid-name]
17+
18+
red: int
19+
green: int
20+
blue: int
21+
22+
def __init__(self, red: int, green: int, blue: int) -> None:
23+
self.red = red
24+
self.green = green
25+
self.blue = blue
26+
27+
@property
28+
def as_hex(self) -> str:
29+
"""Get hex 'abcdef' representation for a color."""
30+
return f'{self.red:0{2}x}{self.green:0{2}x}{self.blue:0{2}x}'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
invalid-name:16:4:16:14:Color:"Class constant name ""aquamarine"" doesn't conform to UPPER_CASE naming style":HIGH

0 commit comments

Comments
 (0)