Skip to content

Commit

Permalink
Create a reusable function to check Enum membership.
Browse files Browse the repository at this point in the history
  • Loading branch information
mbyrnepr2 committed Aug 2, 2023
1 parent ee883ee commit 8984106
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 16 deletions.
21 changes: 5 additions & 16 deletions pylint/checkers/base/name_checker/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from typing import TYPE_CHECKING, Tuple

import astroid
from astroid import nodes, util
from astroid import nodes

from pylint import constants, interfaces
from pylint.checkers import utils
Expand Down Expand Up @@ -486,21 +486,10 @@ 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():
is_enum_member = False
if utils.is_enum(ancestor):
enum_members = next(frame.igetattr("__members__"))
if isinstance(enum_members, util.UninferableBase):
continue
is_enum_member = any(
node.name == name_node.name
for _, name_node in enum_members.items
)
if is_enum_member or utils.is_assign_name_annotated_with(
node, "Final"
):
self._check_name("class_const", node.name, node)
break
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)

Expand Down
19 changes: 19 additions & 0 deletions pylint/checkers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2246,3 +2246,22 @@ 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"):
return False

enum_member_objects = next(frame.igetattr("__members__"))
try:
return node.name in [
name_obj.name for value, name_obj in enum_member_objects.items
]
except TypeError:

Check warning on line 2265 in pylint/checkers/utils.py

View check run for this annotation

Codecov / codecov/patch

pylint/checkers/utils.py#L2265

Added line #L2265 was not covered by tests
# It is possible `enum_member_objects` is the `UninferableBase` object.
return False

Check warning on line 2267 in pylint/checkers/utils.py

View check run for this annotation

Codecov / codecov/patch

pylint/checkers/utils.py#L2267

Added line #L2267 was not covered by tests
21 changes: 21 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,21 @@

""" Tests for invalid-name checker in the context of enums. """
# pylint: disable=too-few-public-methods


from enum import Enum


class JustARegularClass:
""" No `invalid-name` by default for class attributes
"""
apple = 42
orange = 24


class EnumClass(Enum):
""" Members of a subclass of `enum.Enum` are expected to be UPPERCASE.
"""
apple = 42 # [invalid-name]
orange = 24 # [invalid-name]
PEAR = 1
2 changes: 2 additions & 0 deletions tests/functional/i/invalid/invalid_name/invalid_name_enum.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
invalid-name:19:4:19:9:EnumClass:"Class constant name ""apple"" doesn't conform to UPPER_CASE naming style":HIGH
invalid-name:20:4:20:10:EnumClass:"Class constant name ""orange"" doesn't conform to UPPER_CASE naming style":HIGH

0 comments on commit 8984106

Please sign in to comment.