lint usages of functions with @deprecated decorator
Asked Answered
J

2

7

Is there a linter that detects usages of functions that are marked as deprecated with the @deprecated decorator from the deprecated package?

e.g. in

from deprecated import deprecated

def realfun():
    print('Hey there')

@deprecated(reason="this is a test")
def myfun():
    realfun()

if __name__ == "__main__":
    myfun()

I will get a runtime warning when running it as PYTHONWARNINGS="default::DeprecationWarning" python testfile.py but pylint, mypy, and flake8 seem to be (at least how I run them) happy with calling myfun.

Jaquiss answered 29/10, 2020 at 14:51 Comment(1)
This is just a random package that adds warnings. If I were to create a package named hey, my linters would not even know about it. What you need to do: 1) either find how linters suggest resolving deprecation (could be another package or a comment. 2) either create a custom rule, lets say for pylint and add it to your .pylintrc. Check custom checkersRu
O
6

As others have mentioned, you have to write a custom pylint checker. The way this works is you define a class that subclasses pylint's BaseChecker, and define what warnings to emit and when to emit them.

The following code does exactly that. It's a little tailored to the deprecated package's specific keyword arguments, but it should work generally whenever somebody uses a decorator called deprecated to mark a function or class, and will issue a pylint warning W0001 (feel free to change it, for example to an error). It should also provide an informative message.

To use it, add the code to a file called (for example) deprecated_checker.py, and either add the folder containing deprecated_checker.py to the PYTHONPATH or add the source to the pylint/checkers folder. Then you can lint by running pylint with the option --load-plugins=deprecated_checker.

For more info on writing your own checkers, look here.

from astroid.nodes import Call, ClassDef, FunctionDef, Name
from pylint.checkers import BaseChecker
from pylint.interfaces import IAstroidChecker


class DeprecatedChecker(BaseChecker):
    __implements__ = IAstroidChecker

    name = "no-deprecated"
    priority = -1
    msgs = {
        "W0001": (
            "%s %s is deprecated since version %s; reason: %s.",
            "deprecated",
            "Functions that have been marked via annotations as deprecated should not be used.",
        )
    }

    def __init__(self, linter=None):
        super().__init__(linter)

    def visit_decorators(self, node):
        # Check if there are decorators
        if node.nodes is None:
            return

        # Figure out whether its a class or function
        # that is deprecated, and get relevant info
        if isinstance(node.parent, ClassDef):
            parent_type = "Class"
        elif isinstance(node.parent, FunctionDef):
            parent_type = "Function"
        parent_name = node.parent.name

        # Check each decorator to see if its deprecating
        for decorator in node.get_children():
            if isinstance(decorator, Call):
                if decorator.func.name == "deprecated":
                    version = "(not specified)"
                    reason = "not specified"
                    if decorator.keywords is not None:
                        for kw in decorator.keywords:
                            if kw.arg == "version":
                                version = f'"{kw.value.value}"'
                            if kw.arg == "reason":
                                reason = f'"{kw.value.value}"'
                    self.add_message(
                        "deprecated",
                        node=node.parent,
                        args=(parent_type, parent_name, version, reason),
                    )
            elif isinstance(decorator, Name):
                if decorator.name == "deprecated":
                    self.add_message(
                        "deprecated",
                        node=node.parent,
                        args=(
                            parent_type,
                            parent_name,
                            "(not specified)",
                            "not specified",
                        ),
                    )

def register(linter):
    linter.register_checker(DeprecatedChecker(linter))

If you've done all that, then linting the stub file tmp.py

from deprecated import deprecated


@deprecated
def fn_stmt():
    pass

@deprecated(version="0.1.0")
def fn_version():
    pass

@deprecated(reason="I'm mean")
def fn_reason():
    pass

@deprecated(version="0.1.0", reason="I'm mean")
def fn_both():
    pass

@deprecated
class ClassStmt:
    pass

@deprecated(version="0.1.0")
class ClassVersion:
    pass

@deprecated(reason="I'm mean")
class ClassReason:
    pass

@deprecated(version="0.1.0", reason="I'm mean")
class ClassBoth:
    pass

with the command pylint --load-plugins=deprecated_checker --disable=all --enable=deprecated tmp.py will get you

************* Module tmp
tmp.py:5:0: W0001: Function fn_stmt is deprecated since version (not specified); reason: (not specified). (deprecated)
tmp.py:9:0: W0001: Function fn_version is deprecated since version 0.1.0; reason: (not specified). (deprecated)
tmp.py:13:0: W0001: Function fn_reason is deprecated since version (not specified); reason: I'm mean. (deprecated)
tmp.py:17:0: W0001: Function fn_both is deprecated since version 0.1.0; reason: I'm mean. (deprecated)
tmp.py:20:0: W0001: Class ClassStmt is deprecated since version (not specified); reason: (not specified). (deprecated)
tmp.py:24:0: W0001: Class ClassVersion is deprecated since version 0.1.0; reason: (not specified). (deprecated)
tmp.py:28:0: W0001: Class ClassReason is deprecated since version (not specified); reason: I'm mean. (deprecated)
tmp.py:32:0: W0001: Class ClassBoth is deprecated since version 0.1.0; reason: I'm mean. (deprecated)

-------------------------------------------------------------------
Your code has been rated at 5.29/10 (previous run: -2.35/10, +7.65)
Ormiston answered 4/11, 2020 at 16:44 Comment(0)
L
0

You should be looking at How to warn about class (name) deprecation to add your own rule to lint. There is no standard library method for marking deprecations, so it won't be built in to the tools. The closest is a specific class in the warnings library.

Laciniate answered 3/11, 2020 at 22:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.