use-cls-in-classmethod

Enforces using cls as the first argument in a @classmethod.

Message

When using @classmethod, the first argument must be cls.

Valid examples

class foo:
    # classmethod with cls first arg.
    @classmethod
    def cm(cls, a, b, c):
        pass
Show more
class foo:
    # non-classmethod with non-cls first arg.
    def nm(self, a, b, c):
        pass
class foo:
    # staticmethod with non-cls first arg.
    @staticmethod
    def sm(a):
        pass

Invalid examples

class foo:
    # No args at all.
    @classmethod
    def cm():
        pass

Suggested fix

class foo:
    # No args at all.
    @classmethod
    def cm(cls):
        pass
Show more
class foo:
    # Single arg + reference.
    @classmethod
    def cm(a):
        return a

Suggested fix

class foo:
    # Single arg + reference.
    @classmethod
    def cm(cls):
        return cls
class foo:
    # Another "cls" exists: do not autofix.
    @classmethod
    def cm(a):
        cls = 2
class foo:
    # Multiple args + references.
    @classmethod
    async def cm(a, b):
        b = a
        b = a.__name__

Suggested fix

class foo:
    # Multiple args + references.
    @classmethod
    async def cm(cls, b):
        b = cls
        b = cls.__name__
class foo:
    # Do not replace in nested scopes.
    @classmethod
    async def cm(a, b):
        b = a
        b = lambda _: a.__name__
        def g():
            return a.__name__

        # Same-named vars in sub-scopes should not be replaced.
        b = [a for a in [1,2,3]]
        def f(a):
            return a + 1

Suggested fix

class foo:
    # Do not replace in nested scopes.
    @classmethod
    async def cm(cls, b):
        b = cls
        b = lambda _: cls.__name__
        def g():
            return cls.__name__

        # Same-named vars in sub-scopes should not be replaced.
        b = [a for a in [1,2,3]]
        def f(a):
            return a + 1
# Do not replace in surrounding scopes.
a = 1

class foo:
    a = 2

    def im(a):
        a = a

    @classmethod
    def cm(a):
        a[1] = foo.cm(a=a)

Suggested fix

# Do not replace in surrounding scopes.
a = 1

class foo:
    a = 2

    def im(a):
        a = a

    @classmethod
    def cm(cls):
        cls[1] = foo.cm(a=cls)
def another_decorator(x): pass

class foo:
    # Multiple decorators.
    @another_decorator
    @classmethod
    @another_decorator
    async def cm(a, b, c):
        pass

Suggested fix

def another_decorator(x): pass

class foo:
    # Multiple decorators.
    @another_decorator
    @classmethod
    @another_decorator
    async def cm(cls, b, c):
        pass