From 7e7a46b6178f17a154bc95cc448ebe65fc3db848 Mon Sep 17 00:00:00 2001 From: Itai Hay <3392524+itaihay@users.noreply.github.com> Date: Mon, 27 Jan 2025 21:44:38 +0200 Subject: [PATCH 1/3] Add N808 for type variable name convention check --- README.rst | 4 ++++ src/pep8ext_naming.py | 24 ++++++++++++++++++++++ testsuite/N808.py | 47 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 testsuite/N808.py diff --git a/README.rst b/README.rst index b016ef7..362b932 100644 --- a/README.rst +++ b/README.rst @@ -54,6 +54,9 @@ These error codes are emitted: +---------+-----------------------------------------------------------------+ | _`N807` | function name should not start and end with '__' | +---------+-----------------------------------------------------------------+ +| _`N808` | type variable names should use CapWords convention and an | +| | optional suffix '_co' or '_contra' (`type variable names`_) | ++---------+-----------------------------------------------------------------+ | _`N811` | constant imported as non constant (`constants`_) | +---------+-----------------------------------------------------------------+ | _`N812` | lowercase imported as non-lowercase | @@ -80,6 +83,7 @@ These error codes are emitted: .. _function names: https://www.python.org/dev/peps/pep-0008/#function-and-variable-names .. _function arguments: https://www.python.org/dev/peps/pep-0008/#function-and-method-arguments .. _method names: https://www.python.org/dev/peps/pep-0008/#method-names-and-instance-variables +.. _type variable names: https://peps.python.org/pep-0008/#type-variable-names Options ------- diff --git a/src/pep8ext_naming.py b/src/pep8ext_naming.py index aaa5548..79e59f0 100644 --- a/src/pep8ext_naming.py +++ b/src/pep8ext_naming.py @@ -254,6 +254,7 @@ class ClassNameCheck(BaseASTCheck): Classes for internal use have a leading underscore in addition. """ N801 = "class name '{name}' should use CapWords convention" + N808 = "type variable name '{name}' should use CapWords convention and an optional suffix '_co' or '_contra'" N818 = "exception name '{name}' should be named with an Error suffix" @classmethod @@ -275,6 +276,29 @@ def superclass_names(cls, name, parents: Iterable, _names=None): names.update(cls.superclass_names(base.id, parents, names)) return names + def visit_module(self, node, parents: Iterable, ignore=None): + for body in node.body: + try: + if not len(body.targets) == 1: + continue + name = body.targets[0].id + func_name = body.value.func.id + except AttributeError: + continue + + if _ignored(name, ignore) or func_name != "TypeVar": + return + + if not name[:1].isupper(): + yield self.err(body, 'N808', name=name) + elif '_' in name: + underscore_split = name.split('_') + suffix = underscore_split[-1] + if len(underscore_split) > 2 or (suffix != 'co' and suffix != 'contra'): + yield self.err(body, 'N808', name=name) + + + def visit_classdef(self, node, parents: Iterable, ignore=None): name = node.name if _ignored(name, ignore): diff --git a/testsuite/N808.py b/testsuite/N808.py new file mode 100644 index 0000000..61a49d1 --- /dev/null +++ b/testsuite/N808.py @@ -0,0 +1,47 @@ +from typing import TypeVar + +#: Okay +Ok = TypeVar("Ok") + +#: N808 +notok = TypeVar("notok") + +#: N808 +notok_co = TypeVar("notok_co") + +#: Okay(--ignore-names=notok) +notok = TypeVar("notok") + +#: N808:1:1(--ignore-names=*OK) +notok = TypeVar("notok") + +#: Okay +Ok_co = TypeVar("Ok_co", covariant=True) + +#: Okay +Ok_contra = TypeVar("Ok_contra", contravariant=True) + +#: N808 +Ok__contra = TypeVar("Ok__contra", contravariant=True) + +#: Okay +Ok_co = TypeVar("Ok_co") + +#: Okay +Ok_contra = TypeVar("Ok_contra") + +#: Okay +Good = TypeVar("Good") + +#: N808 +__NotGood = TypeVar("__NotGood") + +#: N808 +__NotGood__ = TypeVar("__NotGood__") + +#: N808 +NotGood__ = TypeVar("NotGood__") + +# Make sure other function calls do not get checked +#: Okay +t = str('something') From b353a5c35b08065dd042ae6fa03946977d72ed58 Mon Sep 17 00:00:00 2001 From: Itai Hay <3392524+itaihay@users.noreply.github.com> Date: Tue, 11 Mar 2025 18:22:08 +0200 Subject: [PATCH 2/3] fixup! Add N808 for type variable name convention check --- src/pep8ext_naming.py | 19 +++++++++++++------ testsuite/N808.py | 9 +++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/pep8ext_naming.py b/src/pep8ext_naming.py index 79e59f0..abc8e79 100644 --- a/src/pep8ext_naming.py +++ b/src/pep8ext_naming.py @@ -283,20 +283,27 @@ def visit_module(self, node, parents: Iterable, ignore=None): continue name = body.targets[0].id func_name = body.value.func.id + keywords = {kw.arg: kw.value.value for kw in body.value.keywords} except AttributeError: continue - if _ignored(name, ignore) or func_name != "TypeVar": + if func_name != "TypeVar" or _ignored(name, ignore): return if not name[:1].isupper(): yield self.err(body, 'N808', name=name) - elif '_' in name: - underscore_split = name.split('_') - suffix = underscore_split[-1] - if len(underscore_split) > 2 or (suffix != 'co' and suffix != 'contra'): - yield self.err(body, 'N808', name=name) + parts = name.split('_') + if len(parts) > 2: + yield self.err(body, 'N808', name=name) + + suffix = parts[-1] if len(parts) > 1 else '' + if suffix and suffix != 'co' and suffix != 'contra': + yield self.err(body, 'N808', name=name) + elif keywords.get('covariant') and suffix != 'co': + yield self.err(body, 'N808', name=name) + elif keywords.get('contravariant') and suffix != 'contra': + yield self.err(body, 'N808', name=name) def visit_classdef(self, node, parents: Iterable, ignore=None): diff --git a/testsuite/N808.py b/testsuite/N808.py index 61a49d1..b66a2ed 100644 --- a/testsuite/N808.py +++ b/testsuite/N808.py @@ -24,6 +24,15 @@ #: N808 Ok__contra = TypeVar("Ok__contra", contravariant=True) +#: N808 +Var_contra = TypeVar("Var_contra", covariant=True) + +#: N808 +Var_co = TypeVar("Var_co", contravariant=True) + +#: N808 +Var = TypeVar("Var", covariant=True) + #: Okay Ok_co = TypeVar("Ok_co") From a374c3687b89c9876e876b2586d8826764fa2fe2 Mon Sep 17 00:00:00 2001 From: Itai Hay <3392524+itaihay@users.noreply.github.com> Date: Wed, 12 Mar 2025 17:04:40 +0200 Subject: [PATCH 3/3] fixup! fixup! Add N808 for type variable name convention check --- src/pep8ext_naming.py | 8 ++++++-- testsuite/N808.py | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/pep8ext_naming.py b/src/pep8ext_naming.py index abc8e79..282bf0e 100644 --- a/src/pep8ext_naming.py +++ b/src/pep8ext_naming.py @@ -279,17 +279,21 @@ def superclass_names(cls, name, parents: Iterable, _names=None): def visit_module(self, node, parents: Iterable, ignore=None): for body in node.body: try: - if not len(body.targets) == 1: + if len(body.targets) != 1: continue name = body.targets[0].id func_name = body.value.func.id - keywords = {kw.arg: kw.value.value for kw in body.value.keywords} + args = [a.value for a in body.value.args] + keywords = {kw.arg: kw.value.value for kw in body.value.keywords} except AttributeError: continue if func_name != "TypeVar" or _ignored(name, ignore): return + if len(args) == 0 or args[0] != name: + yield self.err(body, 'N808', name=name) + if not name[:1].isupper(): yield self.err(body, 'N808', name=name) diff --git a/testsuite/N808.py b/testsuite/N808.py index b66a2ed..2c453a0 100644 --- a/testsuite/N808.py +++ b/testsuite/N808.py @@ -51,6 +51,30 @@ #: N808 NotGood__ = TypeVar("NotGood__") +#: Okay +A = TypeVar("A") + +#: Okay +A_contra = TypeVar("A_contra") + +#: N808 +A = TypeVar("B") + +#: N808 +A_contra = TypeVar("B") + +#: N808 +A_contra = TypeVar("B_contra") + +#: N808 +A_contra = TypeVar("A_Contra") + +#: N808 +A_contra = TypeVar() + + # Make sure other function calls do not get checked #: Okay t = str('something') +t = TypeVar +