diff options
-rw-r--r-- | ChangeLog | 5 | ||||
-rw-r--r-- | doc/user_guide/options.rst | 6 | ||||
-rw-r--r-- | pylint/checkers/base.py | 19 | ||||
-rw-r--r-- | tests/functional/n/name_preset_snake_case.py | 5 | ||||
-rw-r--r-- | tests/functional/n/name_preset_snake_case.rc | 1 | ||||
-rw-r--r-- | tests/functional/n/name_preset_snake_case.txt | 9 | ||||
-rw-r--r-- | tests/functional/n/name_styles.py | 7 | ||||
-rw-r--r-- | tests/functional/n/name_styles.txt | 35 |
8 files changed, 64 insertions, 23 deletions
@@ -37,6 +37,11 @@ Release date: TBA Closes #3167 +* Add ``--class-const-naming-style`` for Enum constants and class variables annotated + with ``typing.ClassVar`` + + Closes #4181 + What's New in Pylint 2.7.2? =========================== diff --git a/doc/user_guide/options.rst b/doc/user_guide/options.rst index dc464812a..93833d009 100644 --- a/doc/user_guide/options.rst +++ b/doc/user_guide/options.rst @@ -35,6 +35,8 @@ name is found in, and not the type of object assigned. +--------------------+---------------------------------------------------------------------------------------------------+ | ``class-attribute``| Attributes defined in class bodies. | +--------------------+---------------------------------------------------------------------------------------------------+ +| ``class-const`` | Enum constants and class variables annotated with ``ClassVar`` | ++--------------------+---------------------------------------------------------------------------------------------------+ | ``inlinevar`` | Loop variables in list comprehensions and generator expressions. | +--------------------+---------------------------------------------------------------------------------------------------+ @@ -76,6 +78,8 @@ Following options are exposed: .. option:: --class-attribute-naming-style=<style> +.. option:: --class-const-naming-style=<style> + .. option:: --inlinevar-naming-style=<style> @@ -110,6 +114,8 @@ expression will lead to an instance of ``invalid-name``. .. option:: --class-attribute-rgx=<regex> +.. option:: --class-const-rgx=<regex> + .. option:: --inlinevar-rgx=<regex> Multiple naming styles for custom regular expressions diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py index 384abb471..33aa8a037 100644 --- a/pylint/checkers/base.py +++ b/pylint/checkers/base.py @@ -104,6 +104,7 @@ class NamingStyle: "argument": cls.DEFAULT_NAME_RGX, "variable": cls.DEFAULT_NAME_RGX, "class_attribute": cls.CLASS_ATTRIBUTE_RGX, + "class_const": cls.CONST_NAME_RGX, "inlinevar": cls.COMP_VAR_RGX, }[name_type] @@ -1654,6 +1655,7 @@ KNOWN_NAME_TYPES = { "argument", "variable", "class_attribute", + "class_const", "inlinevar", } @@ -1667,6 +1669,7 @@ HUMAN_READABLE_TYPES = { "argument": "argument", "variable": "variable", "class_attribute": "class attribute", + "class_const": "class constant", "inlinevar": "inline iteration", } @@ -1680,6 +1683,7 @@ DEFAULT_NAMING_STYLES = { "argument": "snake_case", "variable": "snake_case", "class_attribute": "any", + "class_const": "UPPER_CASE", "inlinevar": "any", } @@ -1846,6 +1850,7 @@ class NameChecker(_BasicChecker): badname_inlinevar=0, badname_argument=0, badname_class_attribute=0, + badname_class_const=0, ) for group in self.config.name_group: for name_type in group.split(":"): @@ -1983,8 +1988,18 @@ class NameChecker(_BasicChecker): elif isinstance(frame, astroid.ClassDef): if not list(frame.local_attr_ancestors(node.name)): for ancestor in frame.ancestors(): - if ancestor.name == "Enum" and ancestor.root().name == "enum": - self._check_name("const", node.name, node) + if ( # pylint: disable=too-many-boolean-expressions + ancestor.name == "Enum" + and ancestor.root().name == "enum" + or isinstance(node.parent, astroid.AnnAssign) + and ( + isinstance(node.parent.annotation, astroid.Subscript) + and node.parent.annotation.value.name == "ClassVar" + or isinstance(node.parent.annotation, astroid.Name) + and node.parent.annotation.name == "ClassVar" + ) + ): + self._check_name("class_const", node.name, node) break else: self._check_name("class_attribute", node.name, node) diff --git a/tests/functional/n/name_preset_snake_case.py b/tests/functional/n/name_preset_snake_case.py index 892d34081..75350497e 100644 --- a/tests/functional/n/name_preset_snake_case.py +++ b/tests/functional/n/name_preset_snake_case.py @@ -1,5 +1,6 @@ # pylint: disable=missing-docstring,too-few-public-methods from enum import Enum +from typing import ClassVar __version__ = "1.0" SOME_CONSTANT = 42 # [invalid-name] @@ -28,3 +29,7 @@ def sayHello(): # [invalid-name] class FooEnum(Enum): # [invalid-name] const_with_snake_case = 42 another_const = 43 + + +class Bar: # [invalid-name] + const_with_snake_case: ClassVar[int] = 42 diff --git a/tests/functional/n/name_preset_snake_case.rc b/tests/functional/n/name_preset_snake_case.rc index 0424cdcfb..2b04af5d4 100644 --- a/tests/functional/n/name_preset_snake_case.rc +++ b/tests/functional/n/name_preset_snake_case.rc @@ -7,6 +7,7 @@ module-naming-style=snake_case method-naming-style=snake_case variable-naming-style=snake_case class-attribute-naming-style=snake_case +class-const-naming-style=snake_case inlinevar-naming-style=snake_case class-naming-style=snake_case diff --git a/tests/functional/n/name_preset_snake_case.txt b/tests/functional/n/name_preset_snake_case.txt index ffcdd9a66..c189797e9 100644 --- a/tests/functional/n/name_preset_snake_case.txt +++ b/tests/functional/n/name_preset_snake_case.txt @@ -1,4 +1,5 @@ -invalid-name:5:0::"Constant name ""SOME_CONSTANT"" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]*|__.*__)$' pattern)" -invalid-name:12:0:MyClass:"Class name ""MyClass"" doesn't conform to snake_case naming style ('[^\\W\\dA-Z][^\\WA-Z]+$' pattern)" -invalid-name:24:0:sayHello:"Function name ""sayHello"" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]{2,}|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)" -invalid-name:28:0:FooEnum:"Class name ""FooEnum"" doesn't conform to snake_case naming style ('[^\\W\\dA-Z][^\\WA-Z]+$' pattern)" +invalid-name:6:0::"Constant name ""SOME_CONSTANT"" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]*|__.*__)$' pattern)" +invalid-name:13:0:MyClass:"Class name ""MyClass"" doesn't conform to snake_case naming style ('[^\\W\\dA-Z][^\\WA-Z]+$' pattern)" +invalid-name:25:0:sayHello:"Function name ""sayHello"" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]{2,}|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)" +invalid-name:29:0:FooEnum:"Class name ""FooEnum"" doesn't conform to snake_case naming style ('[^\\W\\dA-Z][^\\WA-Z]+$' pattern)" +invalid-name:34:0:Bar:"Class name ""Bar"" doesn't conform to snake_case naming style ('[^\\W\\dA-Z][^\\WA-Z]+$' pattern)" diff --git a/tests/functional/n/name_styles.py b/tests/functional/n/name_styles.py index ca3572fe4..394c82826 100644 --- a/tests/functional/n/name_styles.py +++ b/tests/functional/n/name_styles.py @@ -4,6 +4,7 @@ from __future__ import print_function import abc import collections from enum import Enum +from typing import ClassVar GOOD_CONST_NAME = '' bad_const_name = 0 # [invalid-name] @@ -147,3 +148,9 @@ class FooEnum(Enum): """A test case for enum names.""" GOOD_ENUM_NAME = 1 bad_enum_name = 2 # [invalid-name] + +class Bar: + """Class with class constants annotated with ClassVar.""" + CLASS_CONST: ClassVar[int] = 42 + CLASS_CONST2: ClassVar = "const" + variable: ClassVar[str] = "invalid name" # [invalid-name] diff --git a/tests/functional/n/name_styles.txt b/tests/functional/n/name_styles.txt index 0785ec65c..ef665c0de 100644 --- a/tests/functional/n/name_styles.txt +++ b/tests/functional/n/name_styles.txt @@ -1,17 +1,18 @@ -invalid-name:9:0::"Constant name ""bad_const_name"" doesn't conform to UPPER_CASE naming style" -invalid-name:12:0:BADFUNCTION_name:"Function name ""BADFUNCTION_name"" doesn't conform to snake_case naming style" -invalid-name:14:4:BADFUNCTION_name:"Variable name ""BAD_LOCAL_VAR"" doesn't conform to snake_case naming style" -invalid-name:18:0:func_bad_argname:"Argument name ""NOT_GOOD"" doesn't conform to snake_case naming style" -invalid-name:28:0:bad_class_name:"Class name ""bad_class_name"" doesn't conform to PascalCase naming style" -invalid-name:39:8:CorrectClassName.__init__:"Attribute name ""_Bad_AtTR_name"" doesn't conform to snake_case naming style" -invalid-name:40:8:CorrectClassName.__init__:"Attribute name ""Bad_PUBLIC_name"" doesn't conform to snake_case naming style" -invalid-name:45:4:CorrectClassName.BadMethodName:"Method name ""BadMethodName"" doesn't conform to snake_case naming style":INFERENCE -invalid-name:51:4:CorrectClassName.__DunDER_IS_not_free_for_all__:"Method name ""__DunDER_IS_not_free_for_all__"" doesn't conform to snake_case naming style":INFERENCE -invalid-name:81:0::"Class name ""BAD_NAME_FOR_CLASS"" doesn't conform to PascalCase naming style" -invalid-name:82:0::"Class name ""NEXT_BAD_NAME_FOR_CLASS"" doesn't conform to PascalCase naming style" -invalid-name:89:0::"Class name ""NOT_CORRECT"" doesn't conform to PascalCase naming style" -invalid-name:95:4:test_globals:"Constant name ""AlsoCorrect"" doesn't conform to UPPER_CASE naming style" -invalid-name:108:4:FooClass.PROPERTY_NAME:"Attribute name ""PROPERTY_NAME"" doesn't conform to snake_case naming style":INFERENCE -invalid-name:113:4:FooClass.ABSTRACT_PROPERTY_NAME:"Attribute name ""ABSTRACT_PROPERTY_NAME"" doesn't conform to snake_case naming style":INFERENCE -invalid-name:118:4:FooClass.PROPERTY_NAME_SETTER:"Attribute name ""PROPERTY_NAME_SETTER"" doesn't conform to snake_case naming style":INFERENCE -invalid-name:149:4:FooEnum:"Constant name ""bad_enum_name"" doesn't conform to UPPER_CASE naming style" +invalid-name:10:0::"Constant name ""bad_const_name"" doesn't conform to UPPER_CASE naming style" +invalid-name:13:0:BADFUNCTION_name:"Function name ""BADFUNCTION_name"" doesn't conform to snake_case naming style" +invalid-name:15:4:BADFUNCTION_name:"Variable name ""BAD_LOCAL_VAR"" doesn't conform to snake_case naming style" +invalid-name:19:0:func_bad_argname:"Argument name ""NOT_GOOD"" doesn't conform to snake_case naming style" +invalid-name:29:0:bad_class_name:"Class name ""bad_class_name"" doesn't conform to PascalCase naming style" +invalid-name:40:8:CorrectClassName.__init__:"Attribute name ""_Bad_AtTR_name"" doesn't conform to snake_case naming style" +invalid-name:41:8:CorrectClassName.__init__:"Attribute name ""Bad_PUBLIC_name"" doesn't conform to snake_case naming style" +invalid-name:46:4:CorrectClassName.BadMethodName:"Method name ""BadMethodName"" doesn't conform to snake_case naming style":INFERENCE +invalid-name:52:4:CorrectClassName.__DunDER_IS_not_free_for_all__:"Method name ""__DunDER_IS_not_free_for_all__"" doesn't conform to snake_case naming style":INFERENCE +invalid-name:82:0::"Class name ""BAD_NAME_FOR_CLASS"" doesn't conform to PascalCase naming style" +invalid-name:83:0::"Class name ""NEXT_BAD_NAME_FOR_CLASS"" doesn't conform to PascalCase naming style" +invalid-name:90:0::"Class name ""NOT_CORRECT"" doesn't conform to PascalCase naming style" +invalid-name:96:4:test_globals:"Constant name ""AlsoCorrect"" doesn't conform to UPPER_CASE naming style" +invalid-name:109:4:FooClass.PROPERTY_NAME:"Attribute name ""PROPERTY_NAME"" doesn't conform to snake_case naming style":INFERENCE +invalid-name:114:4:FooClass.ABSTRACT_PROPERTY_NAME:"Attribute name ""ABSTRACT_PROPERTY_NAME"" doesn't conform to snake_case naming style":INFERENCE +invalid-name:119:4:FooClass.PROPERTY_NAME_SETTER:"Attribute name ""PROPERTY_NAME_SETTER"" doesn't conform to snake_case naming style":INFERENCE +invalid-name:150:4:FooEnum:"Class constant name ""bad_enum_name"" doesn't conform to UPPER_CASE naming style" +invalid-name:156:4:Bar:"Class constant name ""variable"" doesn't conform to UPPER_CASE naming style" |