From 21f4d8efd0a27cc0d72b1c5c913344b30df3bf9f Mon Sep 17 00:00:00 2001 From: Bryan Forbes Date: Fri, 19 Feb 2021 14:17:41 -0600 Subject: [PATCH] Handle positional-only args in class methods (fixes #157) (#158) --- bugbear.py | 2 +- tests/b902_py38.py | 102 ++++++++++++++++++++++++++++++++++++++++++ tests/test_bugbear.py | 22 +++++++++ 3 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 tests/b902_py38.py diff --git a/bugbear.py b/bugbear.py index aeed4b3..18797f2 100644 --- a/bugbear.py +++ b/bugbear.py @@ -487,7 +487,7 @@ def check_for_b902(self, node): expected_first_args = B902.self kind = "instance" - args = node.args.args + args = getattr(node.args, "posonlyargs", []) + node.args.args vararg = node.args.vararg kwarg = node.args.kwarg kwonlyargs = node.args.kwonlyargs diff --git a/tests/b902_py38.py b/tests/b902_py38.py new file mode 100644 index 0000000..7a942e8 --- /dev/null +++ b/tests/b902_py38.py @@ -0,0 +1,102 @@ +def not_a_method(arg1, /): + ... + + +class NoWarnings: + def __init__(self, /): + def not_a_method_either(arg1, /): + ... + + def __new__(cls, /, *args, **kwargs): + ... + + def method(self, arg1, /, *, yeah): + ... + + async def async_method(self, arg1, /, *, yeah): + ... + + @classmethod + def someclassmethod(cls, arg1, with_default=None, /): + ... + + @staticmethod + def not_a_problem(arg1, /): + ... + + +class Warnings: + def __init__(i_am_special, /): + ... + + def almost_a_class_method(cls, arg1, /): + ... + + def almost_a_static_method(): + ... + + @classmethod + def wat(self, i_like_confusing_people, /): + ... + + def i_am_strange(*args, **kwargs): + self = args[0] + + def defaults_anyone(self=None, /): + ... + + def invalid_kwargs_only(**kwargs): + ... + + def invalid_keyword_only(*, self): + ... + + async def async_invalid_keyword_only(*, self): + ... + + +class Meta(type): + def __init__(cls, name, bases, d, /): + ... + + @classmethod + def __prepare__(metacls, name, bases, /): + return {} + + +class OtherMeta(type): + def __init__(self, name, bases, d, /): + ... + + @classmethod + def __prepare__(cls, name, bases, /): + return {} + + @classmethod + def first_arg_mcs_allowed(mcs, value, /): + ... + + +def type_factory(): + return object + + +class CrazyBases(Warnings, type_factory(), metaclass=type): + def __init__(self): + ... + + +class RuntimeError("This is not a base"): + def __init__(self): + ... + + +class ImplicitClassMethods: + def __new__(cls, /, *args, **kwargs): + ... + + def __init_subclass__(cls, /, *args, **kwargs): + ... + + def __class_getitem__(cls, key, /): + ... diff --git a/tests/test_bugbear.py b/tests/test_bugbear.py index d08dc11..ccce9da 100644 --- a/tests/test_bugbear.py +++ b/tests/test_bugbear.py @@ -3,6 +3,7 @@ from pathlib import Path import site import subprocess +import sys import unittest from hypothesis import HealthCheck, given, settings @@ -262,6 +263,27 @@ def test_b902(self): ), ) + @unittest.skipIf(sys.version_info < (3, 8), "requires 3.8+") + def test_b902_py38(self): + filename = Path(__file__).absolute().parent / "b902_py38.py" + bbc = BugBearChecker(filename=str(filename)) + errors = list(bbc.run()) + self.assertEqual( + errors, + self.errors( + B902(29, 17, vars=("'i_am_special'", "instance", "self")), + B902(32, 30, vars=("'cls'", "instance", "self")), + B902(35, 4, vars=("(none)", "instance", "self")), + B902(39, 12, vars=("'self'", "class", "cls")), + B902(42, 22, vars=("*args", "instance", "self")), + B902(48, 30, vars=("**kwargs", "instance", "self")), + B902(51, 32, vars=("*, self", "instance", "self")), + B902(54, 44, vars=("*, self", "instance", "self")), + B902(68, 17, vars=("'self'", "metaclass instance", "cls")), + B902(72, 20, vars=("'cls'", "metaclass class", "metacls")), + ), + ) + def test_b903(self): filename = Path(__file__).absolute().parent / "b903.py" bbc = BugBearChecker(filename=str(filename))