# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE # Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt from __future__ import annotations from contextlib import redirect_stdout from io import StringIO import pytest from pytest import CaptureFixture from pylint.checkers import BaseChecker from pylint.exceptions import InvalidMessageError, UnknownMessageError from pylint.lint.pylinter import PyLinter from pylint.message import MessageDefinition from pylint.message.message_definition_store import MessageDefinitionStore from pylint.typing import MessageDefinitionTuple @pytest.mark.parametrize( "messages,expected", [ ( { "W1234": ("message one", "msg-symbol-one", "msg description"), "W4321": ("message two", "msg-symbol-two", "msg description"), }, r"Inconsistent checker part in message id 'W4321' (expected 'x12xx' because we" r" already had ['W1234']).", ), ( { "W1233": ( "message two", "msg-symbol-two", "msg description", {"old_names": [("W1234", "old-symbol")]}, ), "W1234": ("message one", "msg-symbol-one", "msg description"), }, "Message id 'W1234' cannot have both 'msg-symbol-one' and 'old-symbol' as symbolic name.", ), ( { "W1234": ("message one", "msg-symbol-one", "msg description"), "W1235": ( "message two", "msg-symbol-two", "msg description", {"old_names": [("W1234", "old-symbol")]}, ), }, "Message id 'W1234' cannot have both 'msg-symbol-one' and 'old-symbol' as symbolic name.", ), ( { "W1234": ( "message one", "msg-symbol-one", "msg description", {"old_names": [("W1201", "old-symbol-one")]}, ), "W1235": ( "message two", "msg-symbol-two", "msg description", {"old_names": [("W1201", "old-symbol-two")]}, ), }, "Message id 'W1201' cannot have both 'old-symbol-one' and 'old-symbol-two' as symbolic name.", ), ( { "W1234": ("message one", "msg-symbol", "msg description"), "W1235": ("message two", "msg-symbol", "msg description"), }, "Message symbol 'msg-symbol' cannot be used for 'W1234' and 'W1235' at the same time. " "If you're creating an 'old_names' use 'old-msg-symbol' as the old symbol.", ), ( { "W1233": ( "message two", "msg-symbol-two", "msg description", {"old_names": [("W1230", "msg-symbol-one")]}, ), "W1234": ("message one", "msg-symbol-one", "msg description"), }, "Message symbol 'msg-symbol-one' cannot be used for 'W1230' and 'W1234' at the same time." " If you're creating an 'old_names' use 'old-msg-symbol-one' as the old symbol.", ), ( { "W1234": ("message one", "msg-symbol-one", "msg description"), "W1235": ( "message two", "msg-symbol-two", "msg description", {"old_names": [("W1230", "msg-symbol-one")]}, ), }, "Message symbol 'msg-symbol-one' cannot be used for 'W1230' and 'W1234' at the same time. " "If you're creating an 'old_names' use 'old-msg-symbol-one' as the old symbol.", ), ( { "W1234": ( "message one", "msg-symbol-one", "msg description", {"old_names": [("W1230", "old-symbol-one")]}, ), "W1235": ( "message two", "msg-symbol-two", "msg description", {"old_names": [("W1231", "old-symbol-one")]}, ), }, "Message symbol 'old-symbol-one' cannot be used for 'W1230' and 'W1231' at the same time. " "If you're creating an 'old_names' use 'old-old-symbol-one' as the old symbol.", ), ], ) def test_register_error( empty_store: MessageDefinitionStore, messages: dict[str, MessageDefinitionTuple], expected: str, ) -> None: class Checker(BaseChecker): def __init__(self) -> None: super().__init__(PyLinter()) name = "checker" msgs = messages with pytest.raises(InvalidMessageError) as cm: empty_store.register_messages_from_checker(Checker()) assert str(cm.value) == expected def test_register_error_new_id_duplicate_of_new( empty_store: MessageDefinitionStore, ) -> None: class CheckerOne(BaseChecker): def __init__(self) -> None: super().__init__(PyLinter()) name = "checker_one" msgs = {"W1234": ("message one", "msg-symbol-one", "msg description.")} class CheckerTwo(BaseChecker): def __init__(self) -> None: super().__init__(PyLinter()) name = "checker_two" msgs = {"W1234": ("message two", "msg-symbol-two", "another msg description.")} empty_store.register_messages_from_checker(CheckerOne()) test_register_error( empty_store, CheckerTwo.msgs, "Message id 'W1234' cannot have both 'msg-symbol-one' and 'msg-symbol-two' as symbolic name.", ) def test_format_help(capsys: CaptureFixture, store: MessageDefinitionStore) -> None: store.help_message([]) captured = capsys.readouterr() assert captured.out == "" store.help_message(["W1234", "E1234", "C1234"]) captured = capsys.readouterr() assert ( captured.out == """:msg-symbol (W1234): *message* msg description. This message belongs to the achecker checker. :duplicate-keyword-arg (E1234): *Duplicate keyword argument %r in %s call* Used when a function call passes the same keyword argument multiple times. This message belongs to the achecker checker. It can't be emitted when using Python >= 2.6. No such message id or symbol 'C1234'. """ ) def test_get_msg_display_string(store: MessageDefinitionStore) -> None: assert store.get_msg_display_string("W1234") == "'msg-symbol'" assert store.get_msg_display_string("E1234") == "'duplicate-keyword-arg'" def test_check_message_id(store: MessageDefinitionStore) -> None: w1234 = store.get_message_definitions("W1234")[0] w0001 = store.get_message_definitions("W0001")[0] e1234 = store.get_message_definitions("E1234")[0] old_symbol = store.get_message_definitions("old-symbol")[0] assert isinstance(w1234, MessageDefinition) assert isinstance(e1234, MessageDefinition) assert w1234 == w0001 assert w1234 == old_symbol with pytest.raises(UnknownMessageError): store.get_message_definitions("YB12") class TestMessageDefinitionStore: @staticmethod def _compare_messages( desc: str, msg: MessageDefinition, checkerref: bool = False ) -> None: assert desc == msg.format_help(checkerref=checkerref) def test_message_help(self, store: MessageDefinitionStore) -> None: message_definition = store.get_message_definitions("W1234")[0] self._compare_messages( """:msg-symbol (W1234): *message* msg description. This message belongs to the achecker checker.""", message_definition, checkerref=True, ) self._compare_messages( """:msg-symbol (W1234): *message* msg description.""", message_definition, checkerref=False, ) def test_message_help_minmax(self, store: MessageDefinitionStore) -> None: # build the message manually to be python version independent message_definition = store.get_message_definitions("E1234")[0] self._compare_messages( """:duplicate-keyword-arg (E1234): *Duplicate keyword argument %r in %s call* Used when a function call passes the same keyword argument multiple times. This message belongs to the achecker checker. It can't be emitted when using Python >= 2.6.""", message_definition, checkerref=True, ) self._compare_messages( """:duplicate-keyword-arg (E1234): *Duplicate keyword argument %r in %s call* Used when a function call passes the same keyword argument multiple times. This message can't be emitted when using Python >= 2.6.""", message_definition, checkerref=False, ) def test_list_messages(store: MessageDefinitionStore) -> None: output = StringIO() with redirect_stdout(output): store.list_messages() # cursory examination of the output: we're mostly testing it completes assert ":msg-symbol (W1234): *message*" in output.getvalue() def test_renamed_message_register(store: MessageDefinitionStore) -> None: assert store.get_message_definitions("W0001")[0].symbol == "msg-symbol" assert store.get_message_definitions("old-symbol")[0].symbol == "msg-symbol" def test_multiple_child_of_old_name(store: MessageDefinitionStore) -> None: """We can define multiple name with the same old name.""" class FamillyChecker(BaseChecker): def __init__(self) -> None: super().__init__(PyLinter()) name = "famillychecker" msgs = { "W1235": ( "Child 1", "child-one", "Child one description.", {"old_names": [("C1234", "mother")]}, ), "W1236": ( "Child 2", "child-two", "Child two description", {"old_names": [("C1234", "mother")]}, ), } store.register_messages_from_checker(FamillyChecker()) mother = store.get_message_definitions("C1234") child = store.get_message_definitions("W1235") other_child = store.get_message_definitions("W1236") assert len(mother) == 2 assert len(child) == 1 assert len(other_child) == 1 assert child[0] in mother assert other_child[0] in mother