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 typing to text reporter #5041

Merged
merged 9 commits into from
Sep 20, 2021
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
2 changes: 1 addition & 1 deletion pylint/reporters/base_reporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class BaseReporter:

extension = ""

def __init__(self, output: TextIO = sys.stdout):
def __init__(self, output: Optional[TextIO] = None):
self.linter: "PyLinter"
self.section = 0
self.out: TextIO = output or sys.stdout
Expand Down
122 changes: 59 additions & 63 deletions pylint/reporters/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,32 @@
import os
import sys
import warnings
from typing import TYPE_CHECKING, Optional
from typing import TYPE_CHECKING, Dict, List, NamedTuple, Optional, Set, TextIO, Tuple

from pylint import utils
from pylint.interfaces import IReporter
from pylint.message import Message
from pylint.reporters import BaseReporter
from pylint.reporters.ureports.text_writer import TextWriter

if TYPE_CHECKING:
from pylint.lint import PyLinter
from pylint.reporters.ureports.nodes import Section


class MessageStyle(NamedTuple):
"""Styling of a message"""

color: Optional[str]
"""The color name (see `ANSI_COLORS` for available values)
or the color number when 256 colors are available
"""
style: Tuple[str, ...]
"""Tuple of style strings (see `ANSI_COLORS` for available values).
"""


ColorMappingDict = Dict[str, MessageStyle]

TITLE_UNDERLINES = ["", "=", "-", "."]

ANSI_PREFIX = "\033["
Expand Down Expand Up @@ -64,64 +79,45 @@
}


def _get_ansi_code(color=None, style=None):
def _get_ansi_code(msg_style: MessageStyle) -> str:
"""return ansi escape code corresponding to color and style

:type color: str or None
:param color:
the color name (see `ANSI_COLORS` for available values)
or the color number when 256 colors are available

:type style: str or None
:param style:
style string (see `ANSI_COLORS` for available values). To get
several style effects at the same time, use a coma as separator.
:param msg_style: the message style

:raise KeyError: if an unexistent color or style identifier is given

:rtype: str
:return: the built escape code
"""
ansi_code = []
if style:
style_attrs = utils._splitstrip(style)
for effect in style_attrs:
ansi_code.append(ANSI_STYLES[effect])
if color:
if color.isdigit():
ansi_code: List[str] = []
for effect in msg_style.style:
ansi_code.append(ANSI_STYLES[effect])
cdce8p marked this conversation as resolved.
Show resolved Hide resolved
if msg_style.color:
if msg_style.color.isdigit():
ansi_code.extend(["38", "5"])
ansi_code.append(color)
ansi_code.append(msg_style.color)
else:
ansi_code.append(ANSI_COLORS[color])
ansi_code.append(ANSI_COLORS[msg_style.color])
if ansi_code:
return ANSI_PREFIX + ";".join(ansi_code) + ANSI_END
return ""


def colorize_ansi(msg, color=None, style=None):
def colorize_ansi(msg: str, msg_style: MessageStyle) -> str:
cdce8p marked this conversation as resolved.
Show resolved Hide resolved
"""colorize message by wrapping it with ansi escape codes

:type msg: str or unicode
:param msg: the message string to colorize

:type color: str or None
:param color:
the color identifier (see `ANSI_COLORS` for available values)

:type style: str or None
:param style:
style string (see `ANSI_COLORS` for available values). To get
several style effects at the same time, use a coma as separator.
:param msg_style: the message style

:raise KeyError: if an unexistent color or style identifier is given

:rtype: str or unicode
:return: the ansi escaped string
"""
# If both color and style are not defined, then leave the text as is
if color is None and style is None:
if msg_style.color is None and len(msg_style.style) == 0:
return msg
escape_code = _get_ansi_code(color, style)
escape_code = _get_ansi_code(msg_style)
# If invalid (or unknown) color, don't wrap msg with ansi codes
if escape_code:
return f"{escape_code}{msg}{ANSI_RESET}"
Expand All @@ -136,15 +132,15 @@ class TextReporter(BaseReporter):
extension = "txt"
line_format = "{path}:{line}:{column}: {msg_id}: {msg} ({symbol})"

def __init__(self, output=None):
def __init__(self, output: Optional[TextIO] = None) -> None:
BaseReporter.__init__(self, output)
self._modules = set()
self._modules: Set[str] = set()
self._template = self.line_format

def on_set_current_module(self, module: str, filepath: Optional[str]) -> None:
self._template = str(self.linter.config.msg_template or self._template)

def write_message(self, msg):
def write_message(self, msg: Message) -> None:
"""Convenience method to write a formatted message with class default template"""
self.writeln(msg.format(self._template))

Expand Down Expand Up @@ -174,7 +170,7 @@ class ParseableTextReporter(TextReporter):
name = "parseable"
line_format = "{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}"

def __init__(self, output=None):
def __init__(self, output: Optional[TextIO] = None) -> None:
warnings.warn(
f"{self.name} output format is deprecated. This is equivalent to --msg-template={self.line_format}",
DeprecationWarning,
Expand All @@ -193,17 +189,21 @@ class ColorizedTextReporter(TextReporter):
"""Simple TextReporter that colorizes text output"""

name = "colorized"
COLOR_MAPPING = {
"I": ("green", None),
"C": (None, "bold"),
"R": ("magenta", "bold, italic"),
"W": ("magenta", None),
"E": ("red", "bold"),
"F": ("red", "bold, underline"),
"S": ("yellow", "inverse"), # S stands for module Separator
COLOR_MAPPING: ColorMappingDict = {
"I": MessageStyle("green", ()),
cdce8p marked this conversation as resolved.
Show resolved Hide resolved
"C": MessageStyle(None, ("bold",)),
"R": MessageStyle("magenta", ("bold", "italic")),
"W": MessageStyle("magenta", ()),
cdce8p marked this conversation as resolved.
Show resolved Hide resolved
"E": MessageStyle("red", ("bold",)),
"F": MessageStyle("red", ("bold", "underline")),
"S": MessageStyle("yellow", ("inverse",)), # S stands for module Separator
}

def __init__(self, output=None, color_mapping=None):
def __init__(
self,
output: Optional[TextIO] = None,
color_mapping: Optional[ColorMappingDict] = None,
cdce8p marked this conversation as resolved.
Show resolved Hide resolved
) -> None:
TextReporter.__init__(self, output)
self.color_mapping = color_mapping or dict(ColorizedTextReporter.COLOR_MAPPING)
cdce8p marked this conversation as resolved.
Show resolved Hide resolved
ansi_terms = ["xterm-16color", "xterm-256color"]
Expand All @@ -214,41 +214,37 @@ def __init__(self, output=None, color_mapping=None):

self.out = colorama.AnsiToWin32(self.out)

def _get_decoration(self, msg_id):
"""Returns the tuple color, style associated with msg_id as defined
in self.color_mapping
"""
try:
return self.color_mapping[msg_id[0]]
except KeyError:
return None, None
def _get_decoration(self, msg_id: str) -> MessageStyle:
"""Returns the message style as defined in self.color_mapping"""
style = self.color_mapping.get(msg_id[0])
if style:
return style
return MessageStyle(None, ())

def handle_message(self, msg: Message) -> None:
"""manage message of different types, and colorize output
using ansi escape codes
"""
if msg.module not in self._modules:
color, style = self._get_decoration("S")
msg_style = self._get_decoration("S")
if msg.module:
modsep = colorize_ansi(
f"************* Module {msg.module}", color, style
)
modsep = colorize_ansi(f"************* Module {msg.module}", msg_style)
else:
modsep = colorize_ansi(f"************* {msg.module}", color, style)
modsep = colorize_ansi(f"************* {msg.module}", msg_style)
self.writeln(modsep)
self._modules.add(msg.module)
color, style = self._get_decoration(msg.C)
msg_style = self._get_decoration(msg.C)

msg = msg._replace(
**{
attr: colorize_ansi(getattr(msg, attr), color, style)
attr: colorize_ansi(getattr(msg, attr), msg_style)
for attr in ("msg", "symbol", "category", "C")
}
)
self.write_message(msg)


def register(linter):
def register(linter: "PyLinter") -> None:
"""Register the reporter classes with the linter."""
linter.register_reporter(TextReporter)
linter.register_reporter(ParseableTextReporter)
Expand Down