Skip to content

Commit

Permalink
Add typing in pylint.reporters (#5004)
Browse files Browse the repository at this point in the history
* Add typing and fix small issue in pylint.reporters

Fix typing error in pylint/checkers/imports.py. Add typing
of report related code outside of pylint.reporters.

* Remove unused argument in pylint.reporters.VNode constructor

* Simplify and specify the typing in reporters nodes

Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
  • Loading branch information
Pierre-Sassoulas and DanielNoord committed Sep 16, 2021
1 parent 14f4db5 commit dc0c7e9
Show file tree
Hide file tree
Showing 11 changed files with 118 additions and 73 deletions.
2 changes: 1 addition & 1 deletion pylint/checkers/__init__.py
Expand Up @@ -61,7 +61,7 @@ def table_lines_from_stats(
columns: Iterable[str],
) -> List[str]:
"""get values listed in <columns> from <stats> and <old_stats>,
and return a formated list of values, designed to be given to a
and return a formatted list of values, designed to be given to a
ureport.Table object
"""
lines: List[str] = []
Expand Down
2 changes: 1 addition & 1 deletion pylint/checkers/imports.py
Expand Up @@ -193,7 +193,7 @@ def _make_graph(
report's section
"""
outputfile = _dependencies_graph(filename, dep_info)
sect.append(Paragraph(f"{gtype}imports graph has been written to {outputfile}"))
sect.append(Paragraph((f"{gtype}imports graph has been written to {outputfile}",)))


# the import checker itself ###################################################
Expand Down
5 changes: 3 additions & 2 deletions pylint/interfaces.py
Expand Up @@ -17,6 +17,7 @@

"""Interfaces for Pylint objects"""
from collections import namedtuple
from typing import Tuple

from astroid import nodes

Expand All @@ -40,7 +41,7 @@ def is_implemented_by(cls, instance):
return implements(instance, cls)


def implements(obj, interface):
def implements(obj: "Interface", interface: Tuple[type, type]) -> bool:
"""Return true if the give object (maybe an instance or class) implements
the interface.
"""
Expand Down Expand Up @@ -101,4 +102,4 @@ def display_reports(self, layout):
"""display results encapsulated in the layout tree"""


__all__ = ("IRawChecker", "IAstroidChecker", "ITokenChecker", "IReporter")
__all__ = ("IRawChecker", "IAstroidChecker", "ITokenChecker", "IReporter", "IChecker")
10 changes: 5 additions & 5 deletions pylint/lint/report_functions.py
Expand Up @@ -5,7 +5,7 @@
from typing import DefaultDict, Dict, List, Tuple, Union

from pylint import checkers, exceptions
from pylint.reporters.ureports import nodes as report_nodes
from pylint.reporters.ureports.nodes import Table
from pylint.typing import CheckerStats


Expand All @@ -19,7 +19,7 @@ def report_total_messages_stats(
lines += checkers.table_lines_from_stats(
stats, previous_stats, ("convention", "refactor", "warning", "error")
)
sect.append(report_nodes.Table(children=lines, cols=4, rheaders=1))
sect.append(Table(children=lines, cols=4, rheaders=1))


def report_messages_stats(
Expand All @@ -41,7 +41,7 @@ def report_messages_stats(
lines = ["message id", "occurrences"]
for value, msg_id in in_order:
lines += [msg_id, str(value)]
sect.append(report_nodes.Table(children=lines, cols=2, rheaders=1))
sect.append(Table(children=lines, cols=2, rheaders=1))


def report_messages_by_module_stats(
Expand All @@ -61,7 +61,7 @@ def report_messages_by_module_stats(
total: int = stats[m_type] # type: ignore
for module in module_stats.keys():
mod_total = module_stats[module][m_type]
percent = 0 if total == 0 else float((mod_total) * 100) / total
percent = 0 if total == 0 else float(mod_total * 100) / total
by_mod[module][m_type] = percent
sorted_result = []
for module, mod_info in by_mod.items():
Expand All @@ -86,4 +86,4 @@ def report_messages_by_module_stats(
lines.append(f"{val:.2f}")
if len(lines) == 5:
raise exceptions.EmptyReportError()
sect.append(report_nodes.Table(children=lines, cols=5, rheaders=1))
sect.append(Table(children=lines, cols=5, rheaders=1))
9 changes: 6 additions & 3 deletions pylint/reporters/__init__.py
Expand Up @@ -21,7 +21,7 @@
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE

"""utilities methods and classes for reporters"""

from typing import TYPE_CHECKING

from pylint import utils
from pylint.reporters.base_reporter import BaseReporter
Expand All @@ -30,10 +30,13 @@
from pylint.reporters.multi_reporter import MultiReporter
from pylint.reporters.reports_handler_mix_in import ReportsHandlerMixIn

if TYPE_CHECKING:
from pylint.lint.pylinter import PyLinter


def initialize(linter):
def initialize(linter: "PyLinter") -> None:
"""initialize linter with reporters in this package"""
utils.register_plugins(linter, __path__[0])
utils.register_plugins(linter, __path__[0]) # type: ignore # Fixed in https://github.com/python/mypy/pull/9454


__all__ = [
Expand Down
4 changes: 2 additions & 2 deletions pylint/reporters/collecting_reporter.py
Expand Up @@ -8,11 +8,11 @@ class CollectingReporter(BaseReporter):

name = "collector"

def __init__(self):
def __init__(self) -> None:
BaseReporter.__init__(self)
self.messages = []

def reset(self):
def reset(self) -> None:
self.messages = []

_display = None
2 changes: 1 addition & 1 deletion pylint/reporters/multi_reporter.py
Expand Up @@ -43,7 +43,7 @@ def __init__(

self.set_output(output)

def __del__(self):
def __del__(self) -> None:
self.close_output_files()

@property
Expand Down
23 changes: 14 additions & 9 deletions pylint/reporters/reports_handler_mix_in.py
Expand Up @@ -2,9 +2,10 @@
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE

import collections
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, Callable, DefaultDict, Dict, List, Tuple

from pylint.exceptions import EmptyReportError
from pylint.interfaces import IChecker
from pylint.reporters.ureports.nodes import Section
from pylint.typing import CheckerStats

Expand All @@ -17,17 +18,21 @@ class ReportsHandlerMixIn:
related methods for the main lint class
"""

def __init__(self):
self._reports = collections.defaultdict(list)
self._reports_state = {}
def __init__(self) -> None:
self._reports: DefaultDict[
IChecker, List[Tuple[str, str, Callable]]
] = collections.defaultdict(list)
self._reports_state: Dict[str, bool] = {}

def report_order(self):
"""Return a list of reports, sorted in the order
in which they must be called.
"""
return list(self._reports)

def register_report(self, reportid, r_title, r_cb, checker):
def register_report(
self, reportid: str, r_title: str, r_cb: Callable, checker: IChecker
) -> None:
"""register a report
reportid is the unique identifier for the report
Expand All @@ -38,17 +43,17 @@ def register_report(self, reportid, r_title, r_cb, checker):
reportid = reportid.upper()
self._reports[checker].append((reportid, r_title, r_cb))

def enable_report(self, reportid):
def enable_report(self, reportid: str) -> None:
"""disable the report of the given id"""
reportid = reportid.upper()
self._reports_state[reportid] = True

def disable_report(self, reportid):
def disable_report(self, reportid: str) -> None:
"""disable the report of the given id"""
reportid = reportid.upper()
self._reports_state[reportid] = False

def report_is_enabled(self, reportid):
def report_is_enabled(self, reportid: str) -> bool:
"""return true if the report associated to the given identifier is
enabled
"""
Expand All @@ -58,7 +63,7 @@ def make_reports( # type: ignore # ReportsHandlerMixIn is always mixed with PyL
self: "PyLinter",
stats: CheckerStats,
old_stats: CheckerStats,
):
) -> Section:
"""render registered reports"""
sect = Section("Report", f"{self.stats['statement']} statements analysed.")
for checker in self.report_order():
Expand Down
26 changes: 18 additions & 8 deletions pylint/reporters/ureports/base_writer.py
Expand Up @@ -18,7 +18,15 @@
import os
import sys
from io import StringIO
from typing import Iterator, TextIO
from typing import TYPE_CHECKING, Iterator, List, TextIO, Union

if TYPE_CHECKING:
from pylint.reporters.ureports.nodes import (
EvaluationSection,
Paragraph,
Section,
Table,
)


class BaseWriter:
Expand All @@ -39,34 +47,36 @@ def format(self, layout, stream: TextIO = sys.stdout, encoding=None) -> None:
layout.accept(self)
self.end_format()

def format_children(self, layout):
def format_children(
self, layout: Union["EvaluationSection", "Paragraph", "Section"]
) -> None:
"""recurse on the layout children and call their accept method
(see the Visitor pattern)
"""
for child in getattr(layout, "children", ()):
child.accept(self)

def writeln(self, string=""):
def writeln(self, string: str = "") -> None:
"""write a line in the output buffer"""
self.write(string + os.linesep)

def write(self, string):
def write(self, string: str) -> None:
"""write a string in the output buffer"""
self.out.write(string)

def begin_format(self):
def begin_format(self) -> None:
"""begin to format a layout"""
self.section = 0

def end_format(self):
def end_format(self) -> None:
"""finished to format a layout"""

def get_table_content(self, table):
def get_table_content(self, table: "Table") -> List[List[str]]:
"""trick to get table content without actually writing it
return an aligned list of lists containing table cells values as string
"""
result = [[]]
result: List[List[str]] = [[]]
cols = table.cols
for cell in self.compute_content(table):
if cols == 0:
Expand Down
64 changes: 37 additions & 27 deletions pylint/reporters/ureports/nodes.py
Expand Up @@ -14,21 +14,21 @@
A micro report is a tree of layout and content objects.
"""
from typing import Optional
from typing import Any, Iterable, Iterator, List, Optional, Union

from pylint.reporters.ureports.text_writer import TextWriter


class VNode:
def __init__(self, nid=None):
self.id = nid
# navigation
self.parent = None
self.children = []
self.visitor_name = self.__class__.__name__.lower()

def __iter__(self):
def __init__(self) -> None:
self.parent: Optional["BaseLayout"] = None
self.children: List["VNode"] = []
self.visitor_name: str = self.__class__.__name__.lower()

def __iter__(self) -> Iterator["VNode"]:
return iter(self.children)

def accept(self, visitor, *args, **kwargs):
def accept(self, visitor: TextWriter, *args: Any, **kwargs: Any) -> None:
func = getattr(visitor, f"visit_{self.visitor_name}")
return func(self, *args, **kwargs)

Expand All @@ -44,8 +44,8 @@ class BaseLayout(VNode):
* children : components in this table (i.e. the table's cells)
"""

def __init__(self, children=(), **kwargs):
super().__init__(**kwargs)
def __init__(self, children: Iterable[Union["Text", str]] = ()) -> None:
super().__init__()
for child in children:
if isinstance(child, VNode):
self.append(child)
Expand All @@ -63,14 +63,14 @@ def insert(self, index: int, child: VNode) -> None:
self.children.insert(index, child)
child.parent = self

def parents(self):
def parents(self) -> List["BaseLayout"]:
"""return the ancestor nodes"""
assert self.parent is not self
if self.parent is None:
return []
return [self.parent] + self.parent.parents()

def add_text(self, text):
def add_text(self, text: str) -> None:
"""shortcut to add text data"""
self.children.append(Text(text))

Expand All @@ -85,11 +85,8 @@ class Text(VNode):
* data : the text value as an encoded or unicode string
"""

def __init__(self, data, escaped=True, **kwargs):
super().__init__(**kwargs)
# if isinstance(data, unicode):
# data = data.encode('ascii')
assert isinstance(data, str), data.__class__
def __init__(self, data: str, escaped: bool = True) -> None:
super().__init__()
self.escaped = escaped
self.data = data

Expand Down Expand Up @@ -117,22 +114,28 @@ class Section(BaseLayout):
as a first paragraph
"""

def __init__(self, title=None, description=None, **kwargs):
super().__init__(**kwargs)
def __init__(
self,
title: Optional[str] = None,
description: Optional[str] = None,
children: Iterable[Union["Text", str]] = (),
) -> None:
super().__init__(children=children)
if description:
self.insert(0, Paragraph([Text(description)]))
if title:
self.insert(0, Title(children=(title,)))
self.report_id: Optional[str] = None
self.report_id: str = "" # Used in ReportHandlerMixin.make_reports


class EvaluationSection(Section):
def __init__(self, message, **kwargs):
super().__init__(**kwargs)
def __init__(
self, message: str, children: Iterable[Union["Text", str]] = ()
) -> None:
super().__init__(children=children)
title = Paragraph()
title.append(Text("-" * len(message)))
self.append(title)

message_body = Paragraph()
message_body.append(Text(message))
self.append(message_body)
Expand Down Expand Up @@ -169,8 +172,15 @@ class Table(BaseLayout):
* title : the table's optional title
"""

def __init__(self, cols, title=None, rheaders=0, cheaders=0, **kwargs):
super().__init__(**kwargs)
def __init__(
self,
cols: int,
title: Optional[str] = None,
rheaders: int = 0,
cheaders: int = 0,
children: Iterable[Union["Text", str]] = (),
) -> None:
super().__init__(children=children)
assert isinstance(cols, int)
self.cols = cols
self.title = title
Expand Down

0 comments on commit dc0c7e9

Please sign in to comment.