From 9d199f194c4733d83d0f2314b783a89510a1ce55 Mon Sep 17 00:00:00 2001 From: Daniel Patrick Date: Wed, 19 Jan 2022 21:06:43 +0000 Subject: [PATCH 1/2] Ignore TypedDict attribute casing --- src/pep8ext_naming.py | 31 +++++++++++++++++++------------ testsuite/N815_py38.py | 12 ++++++++++++ 2 files changed, 31 insertions(+), 12 deletions(-) create mode 100644 testsuite/N815_py38.py diff --git a/src/pep8ext_naming.py b/src/pep8ext_naming.py index 44c1207..4fa128a 100644 --- a/src/pep8ext_naming.py +++ b/src/pep8ext_naming.py @@ -202,6 +202,7 @@ def visit_tree(self, node): def visit_node(self, node): if isinstance(node, ast.ClassDef): self.tag_class_functions(node) + self.tag_class_superclasses(node) elif isinstance(node, FUNC_NODES): self.find_global_defs(node) @@ -264,6 +265,11 @@ def set_function_nodes_types(self, nodes, ismetaclass, late_decoration): node.function_type = self.decorator_to_type[name] break + def tag_class_superclasses(self, cls_node): + cls_node.superclasses = self.superclass_names( + cls_node.name, self.parents + ) + @classmethod def find_decorator_name(cls, d): if isinstance(d, ast.Name): @@ -286,16 +292,6 @@ def find_global_defs(func_def_node): nodes_to_check.extend(iter_child_nodes(node)) func_def_node.global_names = global_names - -class ClassNameCheck(BaseASTCheck): - """ - Almost without exception, class names use the CapWords convention. - - Classes for internal use have a leading underscore in addition. - """ - N801 = "class name '{name}' should use CapWords convention" - N818 = "exception name '{name}' should be named with an Error suffix" - @classmethod def get_classdef(cls, name, parents): for parent in parents: @@ -315,6 +311,16 @@ def superclass_names(cls, name, parents, _names=None): names.update(cls.superclass_names(base.id, parents, names)) return names + +class ClassNameCheck(BaseASTCheck): + """ + Almost without exception, class names use the CapWords convention. + + Classes for internal use have a leading underscore in addition. + """ + N801 = "class name '{name}' should use CapWords convention" + N818 = "exception name '{name}' should be named with an Error suffix" + def visit_classdef(self, node, parents, ignore=None): name = node.name if _ignored(name, ignore): @@ -322,8 +328,7 @@ def visit_classdef(self, node, parents, ignore=None): name = name.strip('_') if not name[:1].isupper() or '_' in name: yield self.err(node, 'N801', name=name) - superclasses = self.superclass_names(name, parents) - if "Exception" in superclasses and not name.endswith("Error"): + if "Exception" in node.superclasses and not name.endswith("Error"): yield self.err(node, 'N818', name=name) @@ -448,6 +453,8 @@ class VariablesCheck(BaseASTCheck): def _find_errors(self, assignment_target, parents, ignore): for parent_func in reversed(parents): if isinstance(parent_func, ast.ClassDef): + if "TypedDict" in parent_func.superclasses: + return checker = self.class_variable_check break if isinstance(parent_func, FUNC_NODES): diff --git a/testsuite/N815_py38.py b/testsuite/N815_py38.py new file mode 100644 index 0000000..a3610c5 --- /dev/null +++ b/testsuite/N815_py38.py @@ -0,0 +1,12 @@ +# python_version >= '3.8' +#: Okay +class MyDict(TypedDict): + mixedCase: str +class MyOtherDict(MyDict): + more_Mixed_Case: str +#: N815 +class TypedDict: + mixedCase: str +#: N815 +class TypedDict: + more_Mixed_Case: str From 884f3c40880fcc4c4e3f83e0006f7ec4113213fe Mon Sep 17 00:00:00 2001 From: Daniel Patrick Date: Thu, 20 Jan 2022 17:57:52 +0000 Subject: [PATCH 2/2] Implement new check, N819: TypedDict variable naming --- README.rst | 5 ++++- src/pep8ext_naming.py | 13 ++++++++++--- testsuite/N815.py | 6 ++++++ testsuite/N815_py38.py | 12 ------------ testsuite/N819_py36.py | 18 ++++++++++++++++++ 5 files changed, 38 insertions(+), 16 deletions(-) delete mode 100644 testsuite/N815_py38.py create mode 100644 testsuite/N819_py36.py diff --git a/README.rst b/README.rst index e116126..f1f144f 100644 --- a/README.rst +++ b/README.rst @@ -73,6 +73,9 @@ These error codes are emitted: +---------+-----------------------------------------------------------------+ | _`N818` | error suffix in exception names (`exceptions`_) | +---------+-----------------------------------------------------------------+ +| _`N819` | mixedCase variable in TypedDict subclass | +| | (distinct from `N815`_ for selective enforcement) | ++---------+-----------------------------------------------------------------+ .. _class names: https://www.python.org/dev/peps/pep-0008/#class-names .. _constants: https://www.python.org/dev/peps/pep-0008/#constants @@ -88,7 +91,7 @@ The following flake8 options are added: --ignore-names Ignore errors for specific names or glob patterns. - Currently, this option can only be used for N802, N803, N804, N805, N806, N815, and N816 errors. + Currently, this option can only be used for N802, N803, N804, N805, N806, N815, N816 and N819 errors. Default: ``setUp,tearDown,setUpClass,tearDownClass,asyncSetUp,asyncTearDown,setUpTestData,failureException,longMessage,maxDiff``. diff --git a/src/pep8ext_naming.py b/src/pep8ext_naming.py index 4fa128a..96d9b47 100644 --- a/src/pep8ext_naming.py +++ b/src/pep8ext_naming.py @@ -169,7 +169,7 @@ def add_options(cls, parser): help='List of method decorators pep8-naming plugin ' 'should consider staticmethods (Defaults to ' '%default)') - parser.extend_default_ignore(['N818']) + parser.extend_default_ignore(['N818', 'N819']) @classmethod def parse_options(cls, options): @@ -449,13 +449,15 @@ class VariablesCheck(BaseASTCheck): N806 = "variable '{name}' in function should be lowercase" N815 = "variable '{name}' in class scope should not be mixedCase" N816 = "variable '{name}' in global scope should not be mixedCase" + N819 = "variable '{name}' in TypedDict should not be mixedCase" def _find_errors(self, assignment_target, parents, ignore): for parent_func in reversed(parents): if isinstance(parent_func, ast.ClassDef): if "TypedDict" in parent_func.superclasses: - return - checker = self.class_variable_check + checker = self.typeddict_variable_check + else: + checker = self.class_variable_check break if isinstance(parent_func, FUNC_NODES): checker = partial(self.function_variable_check, parent_func) @@ -544,6 +546,11 @@ def function_variable_check(func, var_name): return None return 'N806' + @staticmethod + def typeddict_variable_check(name): + if is_mixed_case(name): + return 'N819' + def _extract_names(assignment_target): """Yield assignment_target ids.""" diff --git a/testsuite/N815.py b/testsuite/N815.py index 10afd9c..d0ed02b 100644 --- a/testsuite/N815.py +++ b/testsuite/N815.py @@ -25,3 +25,9 @@ class C: #: Okay(--ignore-names=*Case) class C: mixed_Case = 0 +#: N815 +class TypedDict: + mixedCase = 0 +#: N815 +class TypedDict: + mixed_Case = 0 diff --git a/testsuite/N815_py38.py b/testsuite/N815_py38.py deleted file mode 100644 index a3610c5..0000000 --- a/testsuite/N815_py38.py +++ /dev/null @@ -1,12 +0,0 @@ -# python_version >= '3.8' -#: Okay -class MyDict(TypedDict): - mixedCase: str -class MyOtherDict(MyDict): - more_Mixed_Case: str -#: N815 -class TypedDict: - mixedCase: str -#: N815 -class TypedDict: - more_Mixed_Case: str diff --git a/testsuite/N819_py36.py b/testsuite/N819_py36.py new file mode 100644 index 0000000..40184a0 --- /dev/null +++ b/testsuite/N819_py36.py @@ -0,0 +1,18 @@ +# python_version >= '3.6' +#: Okay +class MyDict(TypedDict): + snake_case: str +#: N819 +class MyDict(TypedDict): + mixedCase: str +#: N819 +class MyDict(TypedDict): + snake_case: str +class MyOtherDict(MyDict): + more_Mixed_Case: str +#: Okay(--ignore-names=mixedCase) +class MyDict(TypedDict): + mixedCase: str +#: Okay(--ignore-names=*Case) +class MyDict(TypedDict): + mixedCase: str