third_party.pylibs.pylint.src/pylint/message/message_definition.py

118 lines
4.5 KiB
Python
Raw Normal View History

# 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
import sys
2021-10-02 13:52:48 +00:00
from typing import TYPE_CHECKING, List, Optional, Tuple
from astroid import nodes
from pylint.constants import _SCOPE_EXEMPT, MSG_TYPES, WarningScope
from pylint.exceptions import InvalidMessageError
from pylint.utils import normalize_text
2021-10-02 13:52:48 +00:00
if TYPE_CHECKING:
from pylint.checkers import BaseChecker
class MessageDefinition:
def __init__(
self,
2021-10-02 13:52:48 +00:00
checker: "BaseChecker",
msgid: str,
msg: str,
description: str,
symbol: str,
scope: str,
minversion: Optional[Tuple[int, int]] = None,
maxversion: Optional[Tuple[int, int]] = None,
old_names: Optional[List[Tuple[str, str]]] = None,
2021-10-02 13:52:48 +00:00
) -> None:
Refactor file checking for a simpler parallel API (#3016) This change prepares the code for enabling Prospector to take advantage of running PyLint parallel. Iterating files is moved into generator (_iterate_file_descrs) so that parallel checking can use the same implementation (_check_file) just by providing different kind of generator that reads the files from parent process. The refactoring removes code duplication that existed in PyLinter._do_check method; checking module content from stdin had identical implementation to checking content from a source file. Made PyLinter.expand_files a private method. The previous implementation of parallel linting created new PyLinter objects in the worker (child) process causing failure when running under Prospector because Prospector uses a custom PyLinter class (a class inherited from PyLinter) and PyLint naturally just creates PyLinter object. This caused linting to fail because there is options for Prospector's IndentChecker which was not created in the worker process. The new implementation passes the original PyLinter object into workers when the workers are created. See https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods Note that as Windows uses spawn method by default, PyLinter object (and its) members need to be pickleable from now on with the exception being PyLinter.reporter which is not passed to child processes. The performance has remained about the same based on quick tests done with Django project containing about 30 000 lines of code; with the old implementation linting took 26-28 seconds with 8 jobs on quad core i7 and 24-27 seconds with the new implementation.
2019-10-18 09:13:50 +00:00
self.checker_name = checker.name
self.check_msgid(msgid)
self.msgid = msgid
self.symbol = symbol
self.msg = msg
self.description = description
self.scope = scope
self.minversion = minversion
self.maxversion = maxversion
self.old_names: List[Tuple[str, str]] = []
if old_names:
for old_msgid, old_symbol in old_names:
self.check_msgid(old_msgid)
self.old_names.append(
(old_msgid, old_symbol),
)
@staticmethod
def check_msgid(msgid: str) -> None:
if len(msgid) != 5:
raise InvalidMessageError(f"Invalid message id {msgid!r}")
if msgid[0] not in MSG_TYPES:
raise InvalidMessageError(f"Bad message type {msgid[0]} in {msgid!r}")
2021-10-02 13:52:48 +00:00
def __repr__(self) -> str:
return f"MessageDefinition:{self.symbol} ({self.msgid})"
2021-10-02 13:52:48 +00:00
def __str__(self) -> str:
return f"{repr(self)}:\n{self.msg} {self.description}"
def may_be_emitted(self) -> bool:
"""Return True if message may be emitted using the current interpreter."""
if self.minversion is not None and self.minversion > sys.version_info:
return False
if self.maxversion is not None and self.maxversion <= sys.version_info:
return False
return True
def format_help(self, checkerref: bool = False) -> str:
"""Return the help string for the given message id."""
desc = self.description
if checkerref:
desc += f" This message belongs to the {self.checker_name} checker."
title = self.msg
if self.minversion or self.maxversion:
restr = []
if self.minversion:
restr.append(f"< {'.'.join(str(n) for n in self.minversion)}")
if self.maxversion:
restr.append(f">= {'.'.join(str(n) for n in self.maxversion)}")
restriction = " or ".join(restr)
if checkerref:
desc += f" It can't be emitted when using Python {restriction}."
else:
desc += (
f" This message can't be emitted when using Python {restriction}."
)
msg_help = normalize_text(" ".join(desc.split()), indent=" ")
message_id = f"{self.symbol} ({self.msgid})"
if title != "%s":
title = title.splitlines()[0]
return f":{message_id}: *{title.rstrip(' ')}*\n{msg_help}"
return f":{message_id}:\n{msg_help}"
def check_message_definition(
self, line: Optional[int], node: Optional[nodes.NodeNG]
) -> None:
"""Check MessageDefinition for possible errors."""
if self.msgid[0] not in _SCOPE_EXEMPT:
# Fatal messages and reports are special, the node/scope distinction
# does not apply to them.
if self.scope == WarningScope.LINE:
if line is None:
raise InvalidMessageError(
f"Message {self.msgid} must provide line, got None"
)
if node is not None:
raise InvalidMessageError(
f"Message {self.msgid} must only provide line, "
f"got line={line}, node={node}"
)
elif self.scope == WarningScope.NODE:
# Node-based warnings may provide an override line.
if node is None:
raise InvalidMessageError(
f"Message {self.msgid} must provide Node, got None"
)