From 08dd5919caf9af25dac0919964235540457dd79d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sun, 8 May 2022 10:17:45 +0200 Subject: [PATCH 1/2] Create ``_MessageStateHandler`` and move all private methods --- pylint/lint/message_state_handler.py | 138 +++++++++++++++++++++++++++ pylint/lint/pylinter.py | 118 +---------------------- 2 files changed, 141 insertions(+), 115 deletions(-) create mode 100644 pylint/lint/message_state_handler.py diff --git a/pylint/lint/message_state_handler.py b/pylint/lint/message_state_handler.py new file mode 100644 index 0000000000..8a7edcef0d --- /dev/null +++ b/pylint/lint/message_state_handler.py @@ -0,0 +1,138 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pylint import exceptions +from pylint.constants import MSG_TYPES, MSG_TYPES_LONG +from pylint.message import MessageDefinition +from pylint.typing import ManagedMessage + +if TYPE_CHECKING: + from pylint.lint.pylinter import PyLinter + + +class _MessageStateHandler: + """Class that handles message disabling & enabling and processing of inline pragma's.""" + + def __init__(self, linter: PyLinter) -> None: + self.linter = linter + self._msgs_state: dict[str, bool] = {} + + def _set_one_msg_status( + self, scope: str, msg: MessageDefinition, line: int | None, enable: bool + ) -> None: + """Set the status of an individual message.""" + if scope == "module": + assert isinstance(line, int) # should always be int inside module scope + + self.linter.file_state.set_msg_status(msg, line, enable) + if not enable and msg.symbol != "locally-disabled": + self.linter.add_message( + "locally-disabled", line=line, args=(msg.symbol, msg.msgid) + ) + else: + msgs = self._msgs_state + msgs[msg.msgid] = enable + + def _get_messages_to_set( + self, msgid: str, enable: bool, ignore_unknown: bool = False + ) -> list[MessageDefinition]: + """Do some tests and find the actual messages of which the status should be set.""" + message_definitions: list[MessageDefinition] = [] + if msgid == "all": + for _msgid in MSG_TYPES: + message_definitions.extend( + self._get_messages_to_set(_msgid, enable, ignore_unknown) + ) + return message_definitions + + # msgid is a category? + category_id = msgid.upper() + if category_id not in MSG_TYPES: + category_id_formatted = MSG_TYPES_LONG.get(category_id) + else: + category_id_formatted = category_id + if category_id_formatted is not None: + for _msgid in self.linter.msgs_store._msgs_by_category[ + category_id_formatted + ]: + message_definitions.extend( + self._get_messages_to_set(_msgid, enable, ignore_unknown) + ) + return message_definitions + + # msgid is a checker name? + if msgid.lower() in self.linter._checkers: + for checker in self.linter._checkers[msgid.lower()]: + for _msgid in checker.msgs: + message_definitions.extend( + self._get_messages_to_set(_msgid, enable, ignore_unknown) + ) + return message_definitions + + # msgid is report id? + if msgid.lower().startswith("rp"): + if enable: + self.linter.enable_report(msgid) + else: + self.linter.disable_report(msgid) + return message_definitions + + try: + # msgid is a symbolic or numeric msgid. + message_definitions = self.linter.msgs_store.get_message_definitions(msgid) + except exceptions.UnknownMessageError: + if not ignore_unknown: + raise + return message_definitions + + def _set_msg_status( + self, + msgid: str, + enable: bool, + scope: str = "package", + line: int | None = None, + ignore_unknown: bool = False, + ) -> None: + """Do some tests and then iterate over message definitions to set state.""" + assert scope in {"package", "module"} + + message_definitions = self._get_messages_to_set(msgid, enable, ignore_unknown) + + for message_definition in message_definitions: + self._set_one_msg_status(scope, message_definition, line, enable) + + # sync configuration object + self.linter.config.enable = [] + self.linter.config.disable = [] + for msgid_or_symbol, is_enabled in self._msgs_state.items(): + symbols = [ + m.symbol + for m in self.linter.msgs_store.get_message_definitions(msgid_or_symbol) + ] + if is_enabled: + self.linter.config.enable += symbols + else: + self.linter.config.disable += symbols + + def _register_by_id_managed_msg( + self, msgid_or_symbol: str, line: int | None, is_disabled: bool = True + ) -> None: + """If the msgid is a numeric one, then register it to inform the user + it could furnish instead a symbolic msgid. + """ + if msgid_or_symbol[1:].isdigit(): + try: + symbol = self.linter.msgs_store.message_id_store.get_symbol( + msgid=msgid_or_symbol + ) + except exceptions.UnknownMessageError: + return + managed = ManagedMessage( + self.linter.current_name, msgid_or_symbol, symbol, line, is_disabled + ) + self.linter._by_id_managed_msgs.append(managed) diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index ca609d0ca4..1937c9564d 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -29,13 +29,13 @@ MSG_STATE_SCOPE_CONFIG, MSG_STATE_SCOPE_MODULE, MSG_TYPES, - MSG_TYPES_LONG, MSG_TYPES_STATUS, WarningScope, ) from pylint.lint.base_options import _make_linter_options from pylint.lint.caching import load_results, save_results from pylint.lint.expand_modules import expand_modules +from pylint.lint.message_state_handler import _MessageStateHandler from pylint.lint.parallel import check_parallel from pylint.lint.report_functions import ( report_messages_by_module_stats, @@ -227,6 +227,7 @@ def _load_reporter_by_class(reporter_class: str) -> type[BaseReporter]: # pylint: disable=too-many-instance-attributes,too-many-public-methods class PyLinter( _ArgumentsManager, + _MessageStateHandler, reporters.ReportsHandlerMixIn, checkers.BaseTokenChecker, ): @@ -265,6 +266,7 @@ def __init__( pylintrc: str | None = None, # pylint: disable=unused-argument ) -> None: _ArgumentsManager.__init__(self, prog="pylint") + _MessageStateHandler.__init__(self, self) # Some stuff has to be done before initialization of other ancestors... # messages store / checkers / reporter / astroid manager @@ -320,7 +322,6 @@ def __init__( # Attributes related to messages (states) and their handling self.msgs_store = MessageDefinitionStore() self.msg_status = 0 - self._msgs_state: dict[str, bool] = {} self._by_id_managed_msgs: list[ManagedMessage] = [] reporters.ReportsHandlerMixIn.__init__(self) @@ -1401,119 +1402,6 @@ def add_ignored_message( # Setting the state (disabled/enabled) of messages and registering them - def _set_one_msg_status( - self, scope: str, msg: MessageDefinition, line: int | None, enable: bool - ) -> None: - """Set the status of an individual message.""" - if scope == "module": - assert isinstance(line, int) # should always be int inside module scope - - self.file_state.set_msg_status(msg, line, enable) - if not enable and msg.symbol != "locally-disabled": - self.add_message( - "locally-disabled", line=line, args=(msg.symbol, msg.msgid) - ) - else: - msgs = self._msgs_state - msgs[msg.msgid] = enable - - def _get_messages_to_set( - self, msgid: str, enable: bool, ignore_unknown: bool = False - ) -> list[MessageDefinition]: - """Do some tests and find the actual messages of which the status should be set.""" - message_definitions = [] - if msgid == "all": - for _msgid in MSG_TYPES: - message_definitions.extend( - self._get_messages_to_set(_msgid, enable, ignore_unknown) - ) - return message_definitions - - # msgid is a category? - category_id = msgid.upper() - if category_id not in MSG_TYPES: - category_id_formatted = MSG_TYPES_LONG.get(category_id) - else: - category_id_formatted = category_id - if category_id_formatted is not None: - for _msgid in self.msgs_store._msgs_by_category[category_id_formatted]: - message_definitions.extend( - self._get_messages_to_set(_msgid, enable, ignore_unknown) - ) - return message_definitions - - # msgid is a checker name? - if msgid.lower() in self._checkers: - for checker in self._checkers[msgid.lower()]: - for _msgid in checker.msgs: - message_definitions.extend( - self._get_messages_to_set(_msgid, enable, ignore_unknown) - ) - return message_definitions - - # msgid is report id? - if msgid.lower().startswith("rp"): - if enable: - self.enable_report(msgid) - else: - self.disable_report(msgid) - return message_definitions - - try: - # msgid is a symbolic or numeric msgid. - message_definitions = self.msgs_store.get_message_definitions(msgid) - except exceptions.UnknownMessageError: - if not ignore_unknown: - raise - return message_definitions - - def _set_msg_status( - self, - msgid: str, - enable: bool, - scope: str = "package", - line: int | None = None, - ignore_unknown: bool = False, - ) -> None: - """Do some tests and then iterate over message definitions to set state.""" - assert scope in {"package", "module"} - - message_definitions = self._get_messages_to_set(msgid, enable, ignore_unknown) - - for message_definition in message_definitions: - self._set_one_msg_status(scope, message_definition, line, enable) - - # sync configuration object - self.config.enable = [] - self.config.disable = [] - for msgid_or_symbol, is_enabled in self._msgs_state.items(): - symbols = [ - m.symbol - for m in self.msgs_store.get_message_definitions(msgid_or_symbol) - ] - if is_enabled: - self.config.enable += symbols - else: - self.config.disable += symbols - - def _register_by_id_managed_msg( - self, msgid_or_symbol: str, line: int | None, is_disabled: bool = True - ) -> None: - """If the msgid is a numeric one, then register it to inform the user - it could furnish instead a symbolic msgid. - """ - if msgid_or_symbol[1:].isdigit(): - try: - symbol = self.msgs_store.message_id_store.get_symbol( - msgid=msgid_or_symbol - ) - except exceptions.UnknownMessageError: - return - managed = ManagedMessage( - self.current_name, msgid_or_symbol, symbol, line, is_disabled - ) - self._by_id_managed_msgs.append(managed) - def disable( self, msgid: str, From 3aabe33d019c212aa9b0ca709b78f34b88956d28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sun, 8 May 2022 10:32:05 +0200 Subject: [PATCH 2/2] Move all public methods to ``_MessageStateHandler`` --- pylint/lint/message_state_handler.py | 183 ++++++++++++++++++++++++++- pylint/lint/pylinter.py | 176 +------------------------- 2 files changed, 183 insertions(+), 176 deletions(-) diff --git a/pylint/lint/message_state_handler.py b/pylint/lint/message_state_handler.py index 8a7edcef0d..17f48ca7c8 100644 --- a/pylint/lint/message_state_handler.py +++ b/pylint/lint/message_state_handler.py @@ -4,13 +4,25 @@ from __future__ import annotations +import sys from typing import TYPE_CHECKING -from pylint import exceptions -from pylint.constants import MSG_TYPES, MSG_TYPES_LONG +from pylint import exceptions, interfaces +from pylint.constants import ( + MSG_STATE_CONFIDENCE, + MSG_STATE_SCOPE_CONFIG, + MSG_STATE_SCOPE_MODULE, + MSG_TYPES, + MSG_TYPES_LONG, +) from pylint.message import MessageDefinition from pylint.typing import ManagedMessage +if sys.version_info >= (3, 8): + from typing import Literal +else: + from typing_extensions import Literal + if TYPE_CHECKING: from pylint.lint.pylinter import PyLinter @@ -21,6 +33,15 @@ class _MessageStateHandler: def __init__(self, linter: PyLinter) -> None: self.linter = linter self._msgs_state: dict[str, bool] = {} + self._options_methods = { + "enable": self.enable, + "disable": self.disable, + "disable-next": self.disable_next, + } + self._bw_options_methods = { + "disable-msg": self._options_methods["disable"], + "enable-msg": self._options_methods["enable"], + } def _set_one_msg_status( self, scope: str, msg: MessageDefinition, line: int | None, enable: bool @@ -136,3 +157,161 @@ def _register_by_id_managed_msg( self.linter.current_name, msgid_or_symbol, symbol, line, is_disabled ) self.linter._by_id_managed_msgs.append(managed) + + def disable( + self, + msgid: str, + scope: str = "package", + line: int | None = None, + ignore_unknown: bool = False, + ) -> None: + """Disable a message for a scope.""" + self._set_msg_status( + msgid, enable=False, scope=scope, line=line, ignore_unknown=ignore_unknown + ) + self._register_by_id_managed_msg(msgid, line) + + def disable_next( + self, + msgid: str, + scope: str = "package", + line: int | None = None, + ignore_unknown: bool = False, + ) -> None: + """Disable a message for the next line.""" + if not line: + raise exceptions.NoLineSuppliedError + self._set_msg_status( + msgid, + enable=False, + scope=scope, + line=line + 1, + ignore_unknown=ignore_unknown, + ) + self._register_by_id_managed_msg(msgid, line + 1) + + def enable( + self, + msgid: str, + scope: str = "package", + line: int | None = None, + ignore_unknown: bool = False, + ) -> None: + """Enable a message for a scope.""" + self._set_msg_status( + msgid, enable=True, scope=scope, line=line, ignore_unknown=ignore_unknown + ) + self._register_by_id_managed_msg(msgid, line, is_disabled=False) + + def disable_noerror_messages(self) -> None: + for msgcat, msgids in self.linter.msgs_store._msgs_by_category.items(): + # enable only messages with 'error' severity and above ('fatal') + if msgcat in {"E", "F"}: + for msgid in msgids: + self.enable(msgid) + else: + for msgid in msgids: + self.disable(msgid) + + def list_messages_enabled(self) -> None: + emittable, non_emittable = self.linter.msgs_store.find_emittable_messages() + enabled: list[str] = [] + disabled: list[str] = [] + for message in emittable: + if self.is_message_enabled(message.msgid): + enabled.append(f" {message.symbol} ({message.msgid})") + else: + disabled.append(f" {message.symbol} ({message.msgid})") + print("Enabled messages:") + for msg in enabled: + print(msg) + print("\nDisabled messages:") + for msg in disabled: + print(msg) + print("\nNon-emittable messages with current interpreter:") + for msg_def in non_emittable: + print(f" {msg_def.symbol} ({msg_def.msgid})") + print("") + + def _get_message_state_scope( + self, + msgid: str, + line: int | None = None, + confidence: interfaces.Confidence | None = None, + ) -> Literal[0, 1, 2] | None: + """Returns the scope at which a message was enabled/disabled.""" + if confidence is None: + confidence = interfaces.UNDEFINED + if confidence.name not in self.linter.config.confidence: + return MSG_STATE_CONFIDENCE # type: ignore[return-value] # mypy does not infer Literal correctly + try: + if line in self.linter.file_state._module_msgs_state[msgid]: + return MSG_STATE_SCOPE_MODULE # type: ignore[return-value] + except (KeyError, TypeError): + return MSG_STATE_SCOPE_CONFIG # type: ignore[return-value] + return None + + def _is_one_message_enabled(self, msgid: str, line: int | None) -> bool: + """Checks state of a single message for the current file. + + This function can't be cached as it depends on self.file_state which can + change. + """ + if line is None: + return self._msgs_state.get(msgid, True) + try: + return self.linter.file_state._module_msgs_state[msgid][line] + except KeyError: + # Check if the message's line is after the maximum line existing in ast tree. + # This line won't appear in the ast tree and won't be referred in + # self.file_state._module_msgs_state + # This happens for example with a commented line at the end of a module. + max_line_number = self.linter.file_state.get_effective_max_line_number() + if max_line_number and line > max_line_number: + fallback = True + lines = self.linter.file_state._raw_module_msgs_state.get(msgid, {}) + + # Doesn't consider scopes, as a 'disable' can be in a + # different scope than that of the current line. + closest_lines = reversed( + [ + (message_line, enable) + for message_line, enable in lines.items() + if message_line <= line + ] + ) + _, fallback_iter = next(closest_lines, (None, None)) + if fallback_iter is not None: + fallback = fallback_iter + + return self._msgs_state.get(msgid, fallback) + return self._msgs_state.get(msgid, True) + + def is_message_enabled( + self, + msg_descr: str, + line: int | None = None, + confidence: interfaces.Confidence | None = None, + ) -> bool: + """Return whether this message is enabled for the current file, line and confidence level. + + This function can't be cached right now as the line is the line of + the currently analysed file (self.file_state), if it changes, then the + result for the same msg_descr/line might need to change. + + :param msg_descr: Either the msgid or the symbol for a MessageDefinition + :param line: The line of the currently analysed file + :param confidence: The confidence of the message + """ + if confidence and confidence.name not in self.linter.config.confidence: + return False + try: + msgids = self.linter.msgs_store.message_id_store.get_active_msgids( + msg_descr + ) + except exceptions.UnknownMessageError: + # The linter checks for messages that are not registered + # due to version mismatch, just treat them as message IDs + # for now. + msgids = [msg_descr] + return any(self._is_one_message_enabled(msgid, line) for msgid in msgids) diff --git a/pylint/lint/pylinter.py b/pylint/lint/pylinter.py index 1937c9564d..98d2aac68b 100644 --- a/pylint/lint/pylinter.py +++ b/pylint/lint/pylinter.py @@ -25,9 +25,6 @@ from pylint.config.arguments_manager import _ArgumentsManager from pylint.constants import ( MAIN_CHECKER_NAME, - MSG_STATE_CONFIDENCE, - MSG_STATE_SCOPE_CONFIG, - MSG_STATE_SCOPE_MODULE, MSG_TYPES, MSG_TYPES_STATUS, WarningScope, @@ -68,9 +65,9 @@ ) if sys.version_info >= (3, 8): - from typing import Literal, Protocol + from typing import Protocol else: - from typing_extensions import Literal, Protocol + from typing_extensions import Protocol MANAGER = astroid.MANAGER @@ -306,15 +303,6 @@ def __init__( ("Messages control", "Options controlling analysis messages"), ("Reports", "Options related to output formatting and reporting"), ) - self._options_methods = { - "enable": self.enable, - "disable": self.disable, - "disable-next": self.disable_next, - } - self._bw_options_methods = { - "disable-msg": self._options_methods["disable"], - "enable-msg": self._options_methods["enable"], - } self.fail_on_symbols: list[str] = [] """List of message symbols on which pylint should fail, set by --fail-on.""" self._error_mode = False @@ -500,16 +488,6 @@ def enable_fail_on_messages(self) -> None: def any_fail_on_issues(self) -> bool: return any(x in self.fail_on_symbols for x in self.stats.by_msg.keys()) - def disable_noerror_messages(self) -> None: - for msgcat, msgids in self.msgs_store._msgs_by_category.items(): - # enable only messages with 'error' severity and above ('fatal') - if msgcat in {"E", "F"}: - for msgid in msgids: - self.enable(msgid) - else: - for msgid in msgids: - self.disable(msgid) - def disable_reporters(self) -> None: """Disable all reporters.""" for _reporters in self._reports.values(): @@ -530,26 +508,6 @@ def _parse_error_mode(self) -> None: self.set_option("persistent", False) self.set_option("score", False) - def list_messages_enabled(self) -> None: - emittable, non_emittable = self.msgs_store.find_emittable_messages() - enabled = [] - disabled = [] - for message in emittable: - if self.is_message_enabled(message.msgid): - enabled.append(f" {message.symbol} ({message.msgid})") - else: - disabled.append(f" {message.symbol} ({message.msgid})") - print("Enabled messages:") - for msg in enabled: - print(msg) - print("\nDisabled messages:") - for msg in disabled: - print(msg) - print("\nNon-emittable messages with current interpreter:") - for msg_def in non_emittable: - print(f" {msg_def.symbol} ({msg_def.msgid})") - print("") - # block level option handling ############################################# # see func_block_disable_msg.py test case for expected behaviour @@ -1162,89 +1120,6 @@ def _report_evaluation(self) -> int | None: self.reporter.display_reports(sect) return note - # Adding (ignored) messages to the Message Reporter - - def _get_message_state_scope( - self, - msgid: str, - line: int | None = None, - confidence: interfaces.Confidence | None = None, - ) -> Literal[0, 1, 2] | None: - """Returns the scope at which a message was enabled/disabled.""" - if confidence is None: - confidence = interfaces.UNDEFINED - if confidence.name not in self.config.confidence: - return MSG_STATE_CONFIDENCE # type: ignore[return-value] # mypy does not infer Literal correctly - try: - if line in self.file_state._module_msgs_state[msgid]: - return MSG_STATE_SCOPE_MODULE # type: ignore[return-value] - except (KeyError, TypeError): - return MSG_STATE_SCOPE_CONFIG # type: ignore[return-value] - return None - - def _is_one_message_enabled(self, msgid: str, line: int | None) -> bool: - """Checks state of a single message for the current file. - - This function can't be cached as it depends on self.file_state which can - change. - """ - if line is None: - return self._msgs_state.get(msgid, True) - try: - return self.file_state._module_msgs_state[msgid][line] - except KeyError: - # Check if the message's line is after the maximum line existing in ast tree. - # This line won't appear in the ast tree and won't be referred in - # self.file_state._module_msgs_state - # This happens for example with a commented line at the end of a module. - max_line_number = self.file_state.get_effective_max_line_number() - if max_line_number and line > max_line_number: - fallback = True - lines = self.file_state._raw_module_msgs_state.get(msgid, {}) - - # Doesn't consider scopes, as a 'disable' can be in a - # different scope than that of the current line. - closest_lines = reversed( - [ - (message_line, enable) - for message_line, enable in lines.items() - if message_line <= line - ] - ) - _, fallback_iter = next(closest_lines, (None, None)) - if fallback_iter is not None: - fallback = fallback_iter - - return self._msgs_state.get(msgid, fallback) - return self._msgs_state.get(msgid, True) - - def is_message_enabled( - self, - msg_descr: str, - line: int | None = None, - confidence: interfaces.Confidence | None = None, - ) -> bool: - """Return whether this message is enabled for the current file, line and confidence level. - - This function can't be cached right now as the line is the line of - the currently analysed file (self.file_state), if it changes, then the - result for the same msg_descr/line might need to change. - - :param msg_descr: Either the msgid or the symbol for a MessageDefinition - :param line: The line of the currently analysed file - :param confidence: The confidence of the message - """ - if confidence and confidence.name not in self.config.confidence: - return False - try: - msgids = self.msgs_store.message_id_store.get_active_msgids(msg_descr) - except exceptions.UnknownMessageError: - # The linter checks for messages that are not registered - # due to version mismatch, just treat them as message IDs - # for now. - msgids = [msg_descr] - return any(self._is_one_message_enabled(msgid, line) for msgid in msgids) - def _add_one_message( self, message_definition: MessageDefinition, @@ -1399,50 +1274,3 @@ def add_ignored_message( message_definition.msgid, line, ) - - # Setting the state (disabled/enabled) of messages and registering them - - def disable( - self, - msgid: str, - scope: str = "package", - line: int | None = None, - ignore_unknown: bool = False, - ) -> None: - """Disable a message for a scope.""" - self._set_msg_status( - msgid, enable=False, scope=scope, line=line, ignore_unknown=ignore_unknown - ) - self._register_by_id_managed_msg(msgid, line) - - def disable_next( - self, - msgid: str, - scope: str = "package", - line: int | None = None, - ignore_unknown: bool = False, - ) -> None: - """Disable a message for the next line.""" - if not line: - raise exceptions.NoLineSuppliedError - self._set_msg_status( - msgid, - enable=False, - scope=scope, - line=line + 1, - ignore_unknown=ignore_unknown, - ) - self._register_by_id_managed_msg(msgid, line + 1) - - def enable( - self, - msgid: str, - scope: str = "package", - line: int | None = None, - ignore_unknown: bool = False, - ) -> None: - """Enable a message for a scope.""" - self._set_msg_status( - msgid, enable=True, scope=scope, line=line, ignore_unknown=ignore_unknown - ) - self._register_by_id_managed_msg(msgid, line, is_disabled=False)