summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/exts/pylint_messages.py82
-rw-r--r--examples/deprecation_checker.py10
-rw-r--r--pylint/checkers/base_checker.py4
-rw-r--r--pylint/checkers/deprecated.py37
-rw-r--r--pylint/checkers/imports.py3
-rw-r--r--pylint/checkers/stdlib.py8
-rw-r--r--pylint/message/message_definition.py3
-rw-r--r--pylint/typing.py1
-rw-r--r--tests/message/unittest_message_definition.py2
-rw-r--r--tests/message/unittest_message_id_store.py2
-rw-r--r--tests/pyreverse/functional/class_diagrams/colorized_output/colorized.puml2
11 files changed, 115 insertions, 39 deletions
diff --git a/doc/exts/pylint_messages.py b/doc/exts/pylint_messages.py
index 3196b0b2c..bb882ba2b 100644
--- a/doc/exts/pylint_messages.py
+++ b/doc/exts/pylint_messages.py
@@ -7,7 +7,7 @@
import os
from collections import defaultdict
from inspect import getmodule
-from itertools import chain
+from itertools import chain, groupby
from pathlib import Path
from typing import DefaultDict, Dict, List, NamedTuple, Optional, Tuple
@@ -43,6 +43,7 @@ class MessageData(NamedTuple):
related_links: str
checker_module_name: str
checker_module_path: str
+ shared: bool = False
MessagesDict = Dict[str, List[MessageData]]
@@ -169,6 +170,7 @@ def _get_all_messages(
((checker, msg) for msg in checker.messages)
for checker in linter.get_checkers()
)
+
for checker, message in checker_message_mapping:
good_code, bad_code, details, related = _get_message_data(
_get_message_data_path(message)
@@ -191,15 +193,21 @@ def _get_all_messages(
related,
checker_module.__name__,
checker_module.__file__,
+ message.shared,
)
msg_type = MSG_TYPES_DOC[message.msgid[0]]
messages_dict[msg_type].append(message_data)
if message.old_names:
for old_name in message.old_names:
category = MSG_TYPES_DOC[old_name[0][0]]
- old_messages[category][(old_name[1], old_name[0])].append(
- (message.symbol, msg_type)
- )
+ # We check if the message is already in old_messages so
+ # we don't duplicate shared messages.
+ if (message.symbol, msg_type) not in old_messages[category][
+ (old_name[1], old_name[0])
+ ]:
+ old_messages[category][(old_name[1], old_name[0])].append(
+ (message.symbol, msg_type)
+ )
return messages_dict, old_messages
@@ -234,19 +242,26 @@ def _write_message_page(messages_dict: MessagesDict) -> None:
if not category_dir.exists():
category_dir.mkdir(parents=True, exist_ok=True)
for message in messages:
+ if message.shared:
+ continue
if not _message_needs_update(message, category):
continue
_write_single_message_page(category_dir, message)
+ for _, shared_messages in groupby(
+ sorted(
+ (message for message in messages if message.shared), key=lambda m: m.id
+ ),
+ key=lambda m: m.id,
+ ):
+ shared_messages_list = list(shared_messages)
+ if len(shared_messages_list) > 1:
+ _write_single_shared_message_page(category_dir, shared_messages_list)
+ else:
+ _write_single_message_page(category_dir, shared_messages_list[0])
-def _write_single_message_page(category_dir: Path, message: MessageData) -> None:
- checker_module_rel_path = os.path.relpath(
- message.checker_module_path, PYLINT_BASE_PATH
- )
- messages_file = os.path.join(category_dir, f"{message.name}.rst")
- with open(messages_file, "w", encoding="utf-8") as stream:
- stream.write(
- f""".. _{message.name}:
+def _generate_single_message_body(message: MessageData) -> str:
+ body = f""".. _{message.name}:
{get_rst_title(f"{message.name} / {message.id}", "=")}
**Message emitted:**
@@ -262,19 +277,42 @@ def _write_single_message_page(category_dir: Path, message: MessageData) -> None
{message.details}
{message.related_links}
"""
- )
- if message.checker_module_name.startswith("pylint.extensions."):
- stream.write(
- f"""
+ if message.checker_module_name.startswith("pylint.extensions."):
+ body += f"""
.. note::
This message is emitted by the optional :ref:`'{message.checker}'<{message.checker_module_name}>` checker which requires the ``{message.checker_module_name}``
plugin to be loaded.
"""
- )
- checker_url = (
- f"https://github.com/PyCQA/pylint/blob/main/{checker_module_rel_path}"
+ return body
+
+
+def _generate_checker_url(message: MessageData) -> str:
+ checker_module_rel_path = os.path.relpath(
+ message.checker_module_path, PYLINT_BASE_PATH
+ )
+ return f"https://github.com/PyCQA/pylint/blob/main/{checker_module_rel_path}"
+
+
+def _write_single_shared_message_page(
+ category_dir: Path, messages: List[MessageData]
+) -> None:
+ message = messages[0]
+ with open(category_dir / f"{message.name}.rst", "w", encoding="utf-8") as stream:
+ stream.write(_generate_single_message_body(message))
+ checker_urls = ", ".join(
+ [
+ f"`{message.checker} <{_generate_checker_url(message)}>`__"
+ for message in messages
+ ]
)
+ stream.write(f"Created by the {checker_urls} checkers.")
+
+
+def _write_single_message_page(category_dir: Path, message: MessageData) -> None:
+ with open(category_dir / f"{message.name}.rst", "w", encoding="utf-8") as stream:
+ stream.write(_generate_single_message_body(message))
+ checker_url = _generate_checker_url(message)
stream.write(f"Created by the `{message.checker} <{checker_url}>`__ checker.")
@@ -309,7 +347,11 @@ Pylint can emit the following messages:
"refactor",
"information",
):
- messages = sorted(messages_dict[category], key=lambda item: item.name)
+ # We need to remove all duplicated shared messages
+ messages = sorted(
+ {msg.id: msg for msg in messages_dict[category]}.values(),
+ key=lambda item: item.name,
+ )
old_messages = sorted(old_messages_dict[category], key=lambda item: item[0])
messages_string = "".join(
f" {category}/{message.name}\n" for message in messages
diff --git a/examples/deprecation_checker.py b/examples/deprecation_checker.py
index 52244edf6..6fb21eedd 100644
--- a/examples/deprecation_checker.py
+++ b/examples/deprecation_checker.py
@@ -51,14 +51,20 @@ if TYPE_CHECKING:
class DeprecationChecker(DeprecatedMixin, BaseChecker):
"""Class implementing deprecation checker."""
- # DeprecationMixin class is Mixin class implementing logic for searching deprecated methods and functions.
- # The list of deprecated methods/functions is defined by implementing class via deprecated_methods callback.
+ # DeprecatedMixin class is Mixin class implementing logic for searching deprecated methods and functions.
+ # The list of deprecated methods/functions is defined by the implementing class via deprecated_methods callback.
# DeprecatedMixin class is overriding attributes of BaseChecker hence must be specified *before* BaseChecker
# in list of base classes.
# The name defines a custom section of the config for this checker.
name = "deprecated"
+ # Register messages emitted by the checker.
+ msgs = {
+ **DeprecatedMixin.DEPRECATED_METHOD_MESSAGE,
+ **DeprecatedMixin.DEPRECATED_ARGUMENT_MESSAGE,
+ }
+
def deprecated_methods(self) -> set[str]:
"""Callback method called by DeprecatedMixin for every method/function found in the code.
diff --git a/pylint/checkers/base_checker.py b/pylint/checkers/base_checker.py
index a2dfe7b17..37ae236d2 100644
--- a/pylint/checkers/base_checker.py
+++ b/pylint/checkers/base_checker.py
@@ -178,6 +178,10 @@ class BaseChecker(_ArgumentsProvider):
checker_id = None
existing_ids = []
for message in self.messages:
+ # Id's for shared messages such as the 'deprecated-*' messages
+ # can be inconsistent with their checker id.
+ if message.shared:
+ continue
if checker_id is not None and checker_id != message.msgid[1:3]:
error_msg = "Inconsistent checker part in message id "
error_msg += f"'{message.msgid}' (expected 'x{checker_id}xx' "
diff --git a/pylint/checkers/deprecated.py b/pylint/checkers/deprecated.py
index ea33ff6bc..78d73586b 100644
--- a/pylint/checkers/deprecated.py
+++ b/pylint/checkers/deprecated.py
@@ -31,31 +31,48 @@ class DeprecatedMixin(BaseChecker):
A class implementing mixin must define "deprecated-method" Message.
"""
- msgs: dict[str, MessageDefinitionTuple] = {
- "W1505": (
+ DEPRECATED_MODULE_MESSAGE: dict[str, MessageDefinitionTuple] = {
+ "W4901": (
+ "Deprecated module %r",
+ "deprecated-module",
+ "A module marked as deprecated is imported.",
+ {"old_names": [("W0402", "old-deprecated-module")], "shared": True},
+ ),
+ }
+
+ DEPRECATED_METHOD_MESSAGE: dict[str, MessageDefinitionTuple] = {
+ "W4902": (
"Using deprecated method %s()",
"deprecated-method",
"The method is marked as deprecated and will be removed in the future.",
+ {"old_names": [("W1505", "old-deprecated-method")], "shared": True},
),
- "W1511": (
+ }
+
+ DEPRECATED_ARGUMENT_MESSAGE: dict[str, MessageDefinitionTuple] = {
+ "W4903": (
"Using deprecated argument %s of method %s()",
"deprecated-argument",
"The argument is marked as deprecated and will be removed in the future.",
+ {"old_names": [("W1511", "old-deprecated-argument")], "shared": True},
),
- "W0402": (
- "Deprecated module %r",
- "deprecated-module",
- "A module marked as deprecated is imported.",
- ),
- "W1512": (
+ }
+
+ DEPRECATED_CLASS_MESSAGE: dict[str, MessageDefinitionTuple] = {
+ "W4904": (
"Using deprecated class %s of module %s",
"deprecated-class",
"The class is marked as deprecated and will be removed in the future.",
+ {"old_names": [("W1512", "old-deprecated-class")], "shared": True},
),
- "W1513": (
+ }
+
+ DEPRECATED_DECORATOR_MESSAGE: dict[str, MessageDefinitionTuple] = {
+ "W4905": (
"Using deprecated decorator %s()",
"deprecated-decorator",
"The decorator is marked as deprecated and will be removed in the future.",
+ {"old_names": [("W1513", "old-deprecated-decorator")], "shared": True},
),
}
diff --git a/pylint/checkers/imports.py b/pylint/checkers/imports.py
index 0119c9bcf..c4015e685 100644
--- a/pylint/checkers/imports.py
+++ b/pylint/checkers/imports.py
@@ -197,7 +197,6 @@ def _make_graph(
# the import checker itself ###################################################
MSGS: dict[str, MessageDefinitionTuple] = {
- **{k: v for k, v in DeprecatedMixin.msgs.items() if k[1:3] == "04"},
"E0401": (
"Unable to import %s",
"import-error",
@@ -302,7 +301,7 @@ class ImportsChecker(DeprecatedMixin, BaseChecker):
"""
name = "imports"
- msgs = MSGS
+ msgs = {**DeprecatedMixin.DEPRECATED_MODULE_MESSAGE, **MSGS}
default_deprecated_modules = ()
options = (
diff --git a/pylint/checkers/stdlib.py b/pylint/checkers/stdlib.py
index 8aa5fc8f6..2e6f8c182 100644
--- a/pylint/checkers/stdlib.py
+++ b/pylint/checkers/stdlib.py
@@ -15,6 +15,7 @@ from astroid import nodes
from pylint import interfaces
from pylint.checkers import BaseChecker, DeprecatedMixin, utils
+from pylint.typing import MessageDefinitionTuple
if TYPE_CHECKING:
from pylint.lint import PyLinter
@@ -332,8 +333,11 @@ def _check_mode_str(mode):
class StdlibChecker(DeprecatedMixin, BaseChecker):
name = "stdlib"
- msgs = {
- **{k: v for k, v in DeprecatedMixin.msgs.items() if k[1:3] == "15"},
+ msgs: dict[str, MessageDefinitionTuple] = {
+ **DeprecatedMixin.DEPRECATED_METHOD_MESSAGE,
+ **DeprecatedMixin.DEPRECATED_ARGUMENT_MESSAGE,
+ **DeprecatedMixin.DEPRECATED_CLASS_MESSAGE,
+ **DeprecatedMixin.DEPRECATED_DECORATOR_MESSAGE,
"W1501": (
'"%s" is not a valid mode for open.',
"bad-open-mode",
diff --git a/pylint/message/message_definition.py b/pylint/message/message_definition.py
index 714ce7efa..3b403b008 100644
--- a/pylint/message/message_definition.py
+++ b/pylint/message/message_definition.py
@@ -18,6 +18,7 @@ if TYPE_CHECKING:
class MessageDefinition:
+ # pylint: disable-next=too-many-arguments
def __init__(
self,
checker: BaseChecker,
@@ -29,6 +30,7 @@ class MessageDefinition:
minversion: tuple[int, int] | None = None,
maxversion: tuple[int, int] | None = None,
old_names: list[tuple[str, str]] | None = None,
+ shared: bool = False,
) -> None:
self.checker_name = checker.name
self.check_msgid(msgid)
@@ -39,6 +41,7 @@ class MessageDefinition:
self.scope = scope
self.minversion = minversion
self.maxversion = maxversion
+ self.shared = shared
self.old_names: list[tuple[str, str]] = []
if old_names:
for old_msgid, old_symbol in old_names:
diff --git a/pylint/typing.py b/pylint/typing.py
index 7ceb33f91..224e0bd6b 100644
--- a/pylint/typing.py
+++ b/pylint/typing.py
@@ -123,6 +123,7 @@ class ExtraMessageOptions(TypedDict, total=False):
old_names: list[tuple[str, str]]
maxversion: tuple[int, int]
minversion: tuple[int, int]
+ shared: bool
MessageDefinitionTuple = Union[
diff --git a/tests/message/unittest_message_definition.py b/tests/message/unittest_message_definition.py
index 2a3fd7a6a..d42a249e3 100644
--- a/tests/message/unittest_message_definition.py
+++ b/tests/message/unittest_message_definition.py
@@ -64,7 +64,6 @@ class TestMessagesDefinition:
@staticmethod
def get_message_definition() -> MessageDefinition:
- kwargs = {"minversion": None, "maxversion": None}
return MessageDefinition(
FalseChecker(),
"W1234",
@@ -72,7 +71,6 @@ class TestMessagesDefinition:
"description",
"msg-symbol",
WarningScope.NODE,
- **kwargs,
)
def test_may_be_emitted(self) -> None:
diff --git a/tests/message/unittest_message_id_store.py b/tests/message/unittest_message_id_store.py
index f300b4f19..d2f0e5ca1 100644
--- a/tests/message/unittest_message_id_store.py
+++ b/tests/message/unittest_message_id_store.py
@@ -131,6 +131,8 @@ def test_exclusivity_of_msgids() -> None:
}
for msgid, definition in runner.linter.msgs_store._messages_definitions.items():
+ if definition.shared:
+ continue
if msgid[1:3] in checker_id_pairs:
assert (
definition.checker_name in checker_id_pairs[msgid[1:3]]
diff --git a/tests/pyreverse/functional/class_diagrams/colorized_output/colorized.puml b/tests/pyreverse/functional/class_diagrams/colorized_output/colorized.puml
index 6a4e356ab..7e8b127c7 100644
--- a/tests/pyreverse/functional/class_diagrams/colorized_output/colorized.puml
+++ b/tests/pyreverse/functional/class_diagrams/colorized_output/colorized.puml
@@ -23,7 +23,7 @@ class "ExceptionsChecker" as pylint.checkers.exceptions.ExceptionsChecker #aquam
visit_tryexcept(node: nodes.TryExcept) -> None
}
class "StdlibChecker" as pylint.checkers.stdlib.StdlibChecker #aquamarine {
- msgs
+ msgs : dict[str, MessageDefinitionTuple]
name : str
deprecated_arguments(method: str)
deprecated_classes(module: str)