Skip to content

Commit 44a3aa2

Browse files
cdce8pPierre-Sassoulas
authored andcommitted
Fix class constant naming
1 parent 8cab76f commit 44a3aa2

11 files changed

+57
-10
lines changed

ChangeLog

+7-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ Release date: Undefined
1919

2020
* Fix issue with PEP 585 syntax and the use of ``collections.abc.Set``
2121

22+
* Fix issue that caused class variables annotated with ``typing.ClassVar`` to be
23+
identified as class constants. Now, class variables annotated with
24+
``typing.Final`` are identified as such.
25+
26+
Closes #4277
27+
2228

2329
What's New in Pylint 2.7.4?
2430
===========================
@@ -31,7 +37,7 @@ Release date: 2021-03-30
3137

3238
* Fix issue with annotated class constants
3339

34-
* Closes #4264
40+
Closes #4264
3541

3642

3743
What's New in Pylint 2.7.3?

pylint/checkers/base.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1994,7 +1994,7 @@ def visit_assignname(self, node):
19941994
if (
19951995
ancestor.name == "Enum"
19961996
and ancestor.root().name == "enum"
1997-
or utils.is_class_var(node)
1997+
or utils.is_assign_name_annotated_with(node, "Final")
19981998
):
19991999
self._check_name("class_const", node.name, node)
20002000
break

pylint/checkers/utils.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -1459,18 +1459,22 @@ def is_attribute_typed_annotation(
14591459
return False
14601460

14611461

1462-
def is_class_var(node: astroid.AssignName) -> bool:
1463-
"""Test if node has `ClassVar` annotation."""
1462+
def is_assign_name_annotated_with(node: astroid.AssignName, typing_name: str) -> bool:
1463+
"""Test if AssignName node has `typing_name` annotation.
1464+
1465+
Especially useful to check for `typing._SpecialForm` instances
1466+
like: `Union`, `Optional`, `Literal`, `ClassVar`, `Final`.
1467+
"""
14641468
if not isinstance(node.parent, astroid.AnnAssign):
14651469
return False
14661470
annotation = node.parent.annotation
14671471
if isinstance(annotation, astroid.Subscript):
14681472
annotation = annotation.value
14691473
if (
14701474
isinstance(annotation, astroid.Name)
1471-
and annotation.name == "ClassVar"
1475+
and annotation.name == typing_name
14721476
or isinstance(annotation, astroid.Attribute)
1473-
and annotation.attrname == "ClassVar"
1477+
and annotation.attrname == typing_name
14741478
):
14751479
return True
14761480
return False

tests/functional/n/name/name_final.py

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# pylint: disable=missing-docstring,too-few-public-methods
2+
"""Test typing.Final"""
3+
import typing
4+
from typing import Final
5+
6+
class Foo:
7+
"""Class with class constants annotated with Final."""
8+
CLASS_CONST: Final[int] = 42
9+
CLASS_CONST2: Final = "const"
10+
variable: Final[str] = "invalid name" # [invalid-name]
11+
CLASS_CONST3: typing.Final
12+
variable2: typing.Final[int] # [invalid-name]
13+
CLASS_CONST4: Final[typing.ClassVar[str]] = "valid"

tests/functional/n/name/name_final.rc

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[testoptions]
2+
min_pyver=3.8
+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
invalid-name:10:4:Foo:"Class constant name ""variable"" doesn't conform to UPPER_CASE naming style"
2+
invalid-name:12:4:Foo:"Class constant name ""variable2"" doesn't conform to UPPER_CASE naming style"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# pylint: disable=missing-docstring,too-few-public-methods
2+
"""Test typing.Final with name style snake_case."""
3+
import typing
4+
from typing import Final
5+
6+
class Foo:
7+
"""Class with class constants annotated with Final."""
8+
CLASS_CONST: Final[int] = 42 # [invalid-name]
9+
CLASS_CONST2: Final = "const" # [invalid-name]
10+
variable: Final[str] = "invalid name"
11+
CLASS_CONST3: typing.Final # [invalid-name]
12+
variable2: typing.Final[int]
13+
CLASS_CONST4: Final[typing.ClassVar[str]] = "invalid name" # [invalid-name]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[testoptions]
2+
min_pyver=3.8
3+
4+
[BASIC]
5+
class-const-naming-style=snake_case
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
invalid-name:8:4:Foo:"Class constant name ""CLASS_CONST"" doesn't conform to snake_case naming style"
2+
invalid-name:9:4:Foo:"Class constant name ""CLASS_CONST2"" doesn't conform to snake_case naming style"
3+
invalid-name:11:4:Foo:"Class constant name ""CLASS_CONST3"" doesn't conform to snake_case naming style"
4+
invalid-name:13:4:Foo:"Class constant name ""CLASS_CONST4"" doesn't conform to snake_case naming style"

tests/functional/n/name/name_styles.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,6 @@ class Bar:
154154
"""Class with class constants annotated with ClassVar."""
155155
CLASS_CONST: ClassVar[int] = 42
156156
CLASS_CONST2: ClassVar = "const"
157-
variable: ClassVar[str] = "invalid name" # [invalid-name]
157+
variable: ClassVar[str] = "invalid name"
158158
CLASS_CONST3: typing.ClassVar
159-
variable2: typing.ClassVar[int] # [invalid-name]
159+
variable2: typing.ClassVar[int]

tests/functional/n/name/name_styles.txt

-2
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,3 @@ invalid-name:110:4:FooClass.PROPERTY_NAME:"Attribute name ""PROPERTY_NAME"" does
1515
invalid-name:115:4:FooClass.ABSTRACT_PROPERTY_NAME:"Attribute name ""ABSTRACT_PROPERTY_NAME"" doesn't conform to snake_case naming style":INFERENCE
1616
invalid-name:120:4:FooClass.PROPERTY_NAME_SETTER:"Attribute name ""PROPERTY_NAME_SETTER"" doesn't conform to snake_case naming style":INFERENCE
1717
invalid-name:151:4:FooEnum:"Class constant name ""bad_enum_name"" doesn't conform to UPPER_CASE naming style"
18-
invalid-name:157:4:Bar:"Class constant name ""variable"" doesn't conform to UPPER_CASE naming style"
19-
invalid-name:159:4:Bar:"Class constant name ""variable2"" doesn't conform to UPPER_CASE naming style"

0 commit comments

Comments
 (0)