Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add deprecated-attribute message #8857

Merged
merged 24 commits into from
Oct 29, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f41828d
Added the message for deprecated attribute.
AmanSal1 Jul 17, 2023
7991753
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 17, 2023
5063ba8
Register message, add one deprecated attribute, adjust logic
jacobtylerwalls Jul 21, 2023
81421f0
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 21, 2023
bad6c98
Address failing tests
jacobtylerwalls Jul 22, 2023
1b31042
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 22, 2023
16ae5ab
Remove old_names
jacobtylerwalls Sep 4, 2023
b9b11d8
Remove Container
jacobtylerwalls Sep 4, 2023
3df5647
Use qname
jacobtylerwalls Oct 28, 2023
beec69c
shared: True
jacobtylerwalls Oct 28, 2023
2a25f5a
Add further 3.12 deprecations
jacobtylerwalls Oct 28, 2023
e2bf5c0
Add news
jacobtylerwalls Oct 28, 2023
2a310c6
Add functional test
jacobtylerwalls Oct 28, 2023
ac275d7
Fix tests
jacobtylerwalls Oct 28, 2023
9b1e4c0
Document message with examples
jacobtylerwalls Oct 28, 2023
d6e06d9
Merge branch 'main' into Attribute_message
jacobtylerwalls Oct 28, 2023
dfeda75
Update bad.py
jacobtylerwalls Oct 28, 2023
9ac41f2
Bump Python to 3.12 on remaining CI jobs
jacobtylerwalls Oct 28, 2023
81a2574
Revert "Bump Python to 3.12 on remaining CI jobs"
jacobtylerwalls Oct 29, 2023
065d0bd
Add further deprecated items
jacobtylerwalls Oct 29, 2023
efe03a4
Update example
jacobtylerwalls Oct 29, 2023
934540b
Add deprecated-attribute to overview table
jacobtylerwalls Oct 29, 2023
c69302c
Upgrade feature.rst with new doc
Pierre-Sassoulas Oct 29, 2023
b8eae93
missing whitespace
Pierre-Sassoulas Oct 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions pylint/checkers/deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,20 @@

import astroid
from astroid import nodes
from astroid.bases import Instance

from pylint.checkers import utils
from pylint.checkers.base_checker import BaseChecker
from pylint.checkers.utils import get_import_name, infer_all, safe_infer
from pylint.interfaces import INFERENCE
from pylint.typing import MessageDefinitionTuple

ACCEPTABLE_NODES = (
astroid.BoundMethod,
astroid.UnboundMethod,
nodes.FunctionDef,
nodes.ClassDef,
astroid.Attribute,
)


Expand All @@ -31,6 +34,15 @@ class DeprecatedMixin(BaseChecker):
A class implementing mixin must define "deprecated-method" Message.
"""

DEPRECATED_ATTRIBUTE_MESSAGE: dict[str, MessageDefinitionTuple] = {
"W4906": (
"Using deprecated attribute %r",
"deprecated-attribute",
"The attribute is marked as deprecated and will be removed in the future.",
{"old_names": [("WXXXX", "old-deprecated-attribute")], "shared": True},
jacobtylerwalls marked this conversation as resolved.
Show resolved Hide resolved
),
}

DEPRECATED_MODULE_MESSAGE: dict[str, MessageDefinitionTuple] = {
"W4901": (
"Deprecated module %r",
Expand Down Expand Up @@ -76,6 +88,11 @@ class DeprecatedMixin(BaseChecker):
),
}

@utils.only_required_for_messages("deprecated-attribute")
def visit_attribute(self, node: astroid.Attribute) -> None:
"""Called when an `astroid.Attribute` node is visited."""
self.check_deprecated_attribute(node)

@utils.only_required_for_messages(
"deprecated-method",
"deprecated-argument",
Expand Down Expand Up @@ -189,6 +206,29 @@ def deprecated_classes(self, module: str) -> Iterable[str]:
# pylint: disable=unused-argument
return ()

def deprecated_attributes(self) -> Container[str]:
"""Callback returning the deprecated attributes.

Returns:
collections.abc.Container of deprecated attribute names.
"""
return ()

def check_deprecated_attribute(self, node: astroid.Attribute) -> None:
"""Checks if the attribute is deprecated."""
inferred_expr = safe_infer(node.expr)
if not isinstance(inferred_expr, (nodes.ClassDef, Instance, nodes.Module)):
return
attribute_qname = ".".join((inferred_expr.qname(), node.attrname))
for deprecated_name in self.deprecated_attributes():
if attribute_qname == deprecated_name:
self.add_message(
"deprecated-attribute",
node=node,
args=(node.attrname,),
confidence=INFERENCE,
)
jacobtylerwalls marked this conversation as resolved.
Show resolved Hide resolved

def check_deprecated_module(self, node: nodes.Import, mod_path: str | None) -> None:
"""Checks if the module is deprecated."""
for mod_name in self.deprecated_modules():
Expand Down
15 changes: 15 additions & 0 deletions pylint/checkers/stdlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,13 @@
}


DEPRECATED_ATTRIBUTES: DeprecationDict = {
(3, 12, 0): {
"calendar.January",
jacobtylerwalls marked this conversation as resolved.
Show resolved Hide resolved
},
}


def _check_mode_str(mode: Any) -> bool:
# check type
if not isinstance(mode, str):
Expand Down Expand Up @@ -370,6 +377,7 @@ class StdlibChecker(DeprecatedMixin, BaseChecker):
**DeprecatedMixin.DEPRECATED_ARGUMENT_MESSAGE,
**DeprecatedMixin.DEPRECATED_CLASS_MESSAGE,
**DeprecatedMixin.DEPRECATED_DECORATOR_MESSAGE,
**DeprecatedMixin.DEPRECATED_ATTRIBUTE_MESSAGE,
"W1501": (
'"%s" is not a valid mode for open.',
"bad-open-mode",
Expand Down Expand Up @@ -489,6 +497,7 @@ def __init__(self, linter: PyLinter) -> None:
self._deprecated_arguments: dict[str, tuple[tuple[int | None, str], ...]] = {}
self._deprecated_classes: dict[str, set[str]] = {}
self._deprecated_decorators: set[str] = set()
self._deprecated_attributes: set[str] = set()

for since_vers, func_list in DEPRECATED_METHODS[sys.version_info[0]].items():
if since_vers <= sys.version_info:
Expand All @@ -502,6 +511,9 @@ def __init__(self, linter: PyLinter) -> None:
for since_vers, decorator_list in DEPRECATED_DECORATORS.items():
if since_vers <= sys.version_info:
self._deprecated_decorators.update(decorator_list)
for since_vers, attribute_list in DEPRECATED_ATTRIBUTES.items():
if since_vers <= sys.version_info:
self._deprecated_attributes.update(attribute_list)
# Modules are checked by the ImportsChecker, because the list is
# synced with the config argument deprecated-modules

Expand Down Expand Up @@ -868,6 +880,9 @@ def deprecated_classes(self, module: str) -> Iterable[str]:
def deprecated_decorators(self) -> Iterable[str]:
return self._deprecated_decorators

def deprecated_attributes(self) -> Iterable[str]:
return self._deprecated_attributes


def register(linter: PyLinter) -> None:
linter.register_checker(StdlibChecker(linter))
32 changes: 31 additions & 1 deletion tests/checkers/unittest_deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@

from __future__ import annotations

from collections.abc import Container

import astroid

from pylint.checkers import BaseChecker, DeprecatedMixin
from pylint.interfaces import UNDEFINED
from pylint.interfaces import INFERENCE, UNDEFINED
from pylint.testutils import CheckerTestCase, MessageTest


Expand Down Expand Up @@ -52,11 +54,39 @@ def deprecated_arguments(
def deprecated_decorators(self) -> set[str]:
return {".deprecated_decorator"}

def deprecated_attributes(self) -> Container[str]:
return {".DeprecatedClass.deprecated_attribute"}


# pylint: disable-next = too-many-public-methods
class TestDeprecatedChecker(CheckerTestCase):
CHECKER_CLASS = _DeprecatedChecker

def test_deprecated_attribute(self) -> None:
# Tests detecting deprecated attribute
node = astroid.extract_node(
"""
class DeprecatedClass:
deprecated_attribute = 42
jacobtylerwalls marked this conversation as resolved.
Show resolved Hide resolved

obj = DeprecatedClass()
obj.deprecated_attribute
"""
)
with self.assertAddsMessages(
MessageTest(
msg_id="deprecated-attribute",
args=("deprecated_attribute",),
node=node,
confidence=INFERENCE,
line=6,
col_offset=0,
end_line=6,
end_col_offset=24,
)
):
self.checker.visit_attribute(node)

def test_deprecated_function(self) -> None:
# Tests detecting deprecated function
node = astroid.extract_node(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class "StdlibChecker" as pylint.checkers.stdlib.StdlibChecker #44BB99 {
msgs : dict[str, MessageDefinitionTuple]
name : str
deprecated_arguments(method: str) -> tuple[tuple[int | None, str], ...]
deprecated_attributes() -> Iterable[str]
deprecated_classes(module: str) -> Iterable[str]
deprecated_decorators() -> Iterable[str]
deprecated_methods() -> set[str]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ charset="utf-8"
"custom_colors.CheckerCollector" [color="red", fontcolor="black", label=<{CheckerCollector|checker1<br ALIGN="LEFT"/>checker2<br ALIGN="LEFT"/>checker3<br ALIGN="LEFT"/>|}>, shape="record", style="filled"];
"pylint.extensions.check_elif.ElseifUsedChecker" [color="#44BB88", fontcolor="black", label=<{ElseifUsedChecker|msgs : dict<br ALIGN="LEFT"/>name : str<br ALIGN="LEFT"/>|leave_module(_: nodes.Module): None<br ALIGN="LEFT"/>process_tokens(tokens: list[TokenInfo]): None<br ALIGN="LEFT"/>visit_if(node: nodes.If): None<br ALIGN="LEFT"/>}>, shape="record", style="filled"];
"pylint.checkers.exceptions.ExceptionsChecker" [color="yellow", fontcolor="black", label=<{ExceptionsChecker|msgs : dict<br ALIGN="LEFT"/>name : str<br ALIGN="LEFT"/>options : tuple<br ALIGN="LEFT"/>|open(): None<br ALIGN="LEFT"/>visit_binop(node: nodes.BinOp): None<br ALIGN="LEFT"/>visit_compare(node: nodes.Compare): None<br ALIGN="LEFT"/>visit_raise(node: nodes.Raise): None<br ALIGN="LEFT"/>visit_try(node: nodes.Try): None<br ALIGN="LEFT"/>}>, shape="record", style="filled"];
"pylint.checkers.stdlib.StdlibChecker" [color="yellow", fontcolor="black", label=<{StdlibChecker|msgs : dict[str, MessageDefinitionTuple]<br ALIGN="LEFT"/>name : str<br ALIGN="LEFT"/>|deprecated_arguments(method: str): tuple[tuple[int \| None, str], ...]<br ALIGN="LEFT"/>deprecated_classes(module: str): Iterable[str]<br ALIGN="LEFT"/>deprecated_decorators(): Iterable[str]<br ALIGN="LEFT"/>deprecated_methods(): set[str]<br ALIGN="LEFT"/>visit_boolop(node: nodes.BoolOp): None<br ALIGN="LEFT"/>visit_call(node: nodes.Call): None<br ALIGN="LEFT"/>visit_functiondef(node: nodes.FunctionDef): None<br ALIGN="LEFT"/>visit_if(node: nodes.If): None<br ALIGN="LEFT"/>visit_ifexp(node: nodes.IfExp): None<br ALIGN="LEFT"/>visit_unaryop(node: nodes.UnaryOp): None<br ALIGN="LEFT"/>}>, shape="record", style="filled"];
"pylint.checkers.stdlib.StdlibChecker" [color="yellow", fontcolor="black", label=<{StdlibChecker|msgs : dict[str, MessageDefinitionTuple]<br ALIGN="LEFT"/>name : str<br ALIGN="LEFT"/>|deprecated_arguments(method: str): tuple[tuple[int \| None, str], ...]<br ALIGN="LEFT"/>deprecated_attributes(): Iterable[str]<br ALIGN="LEFT"/>deprecated_classes(module: str): Iterable[str]<br ALIGN="LEFT"/>deprecated_decorators(): Iterable[str]<br ALIGN="LEFT"/>deprecated_methods(): set[str]<br ALIGN="LEFT"/>visit_boolop(node: nodes.BoolOp): None<br ALIGN="LEFT"/>visit_call(node: nodes.Call): None<br ALIGN="LEFT"/>visit_functiondef(node: nodes.FunctionDef): None<br ALIGN="LEFT"/>visit_if(node: nodes.If): None<br ALIGN="LEFT"/>visit_ifexp(node: nodes.IfExp): None<br ALIGN="LEFT"/>visit_unaryop(node: nodes.UnaryOp): None<br ALIGN="LEFT"/>}>, shape="record", style="filled"];
"pylint.checkers.exceptions.ExceptionsChecker" -> "custom_colors.CheckerCollector" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="checker1", style="solid"];
"pylint.checkers.stdlib.StdlibChecker" -> "custom_colors.CheckerCollector" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="checker3", style="solid"];
"pylint.extensions.check_elif.ElseifUsedChecker" -> "custom_colors.CheckerCollector" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="checker2", style="solid"];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class "StdlibChecker" as pylint.checkers.stdlib.StdlibChecker #yellow {
msgs : dict[str, MessageDefinitionTuple]
name : str
deprecated_arguments(method: str) -> tuple[tuple[int | None, str], ...]
deprecated_attributes() -> Iterable[str]
deprecated_classes(module: str) -> Iterable[str]
deprecated_decorators() -> Iterable[str]
deprecated_methods() -> set[str]
Expand Down
Loading