From 7b6fe1a377e5fa995df9470c6899ef23c01ff012 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Tue, 14 Sep 2021 15:14:19 +0200 Subject: [PATCH] Add typing and fix small issue in pylint.reporters --- pylint/reporters/__init__.py | 9 ++-- pylint/reporters/collecting_reporter.py | 4 +- pylint/reporters/multi_reporter.py | 2 +- pylint/reporters/reports_handler_mix_in.py | 30 +++++++------ pylint/reporters/ureports/base_writer.py | 26 ++++++++---- pylint/reporters/ureports/nodes.py | 49 +++++++++++++++------- pylint/reporters/ureports/text_writer.py | 44 ++++++++++++------- 7 files changed, 108 insertions(+), 56 deletions(-) diff --git a/pylint/reporters/__init__.py b/pylint/reporters/__init__.py index 79b13e083bf..11e5f67bfce 100644 --- a/pylint/reporters/__init__.py +++ b/pylint/reporters/__init__.py @@ -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 @@ -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 __all__ = [ diff --git a/pylint/reporters/collecting_reporter.py b/pylint/reporters/collecting_reporter.py index 309c6040520..145c3c81b3f 100644 --- a/pylint/reporters/collecting_reporter.py +++ b/pylint/reporters/collecting_reporter.py @@ -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 diff --git a/pylint/reporters/multi_reporter.py b/pylint/reporters/multi_reporter.py index a4dbae53b75..38cd0c93230 100644 --- a/pylint/reporters/multi_reporter.py +++ b/pylint/reporters/multi_reporter.py @@ -42,7 +42,7 @@ def __init__( self.set_output(output) - def __del__(self): + def __del__(self) -> None: self.close_output_files() @property diff --git a/pylint/reporters/reports_handler_mix_in.py b/pylint/reporters/reports_handler_mix_in.py index 914556ef482..b141257b3cf 100644 --- a/pylint/reporters/reports_handler_mix_in.py +++ b/pylint/reporters/reports_handler_mix_in.py @@ -2,6 +2,7 @@ # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE import collections +from typing import Any, Callable, DefaultDict, Dict from pylint.exceptions import EmptyReportError from pylint.reporters.ureports.nodes import Section @@ -12,9 +13,9 @@ 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 = collections.defaultdict(list) + self._reports_state: Dict = {} def report_order(self): """Return a list of reports, sorted in the order @@ -22,7 +23,9 @@ def report_order(self): """ 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: Any + ) -> None: """register a report reportid is the unique identifier for the report @@ -33,25 +36,25 @@ 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 """ return self._reports_state.get(reportid, True) - def make_reports(self, stats, old_stats): + def make_reports(self, stats: Dict[str, Any], old_stats: Dict) -> Section: """render registered reports""" - sect = Section("Report", f"{self.stats['statement']} statements analysed.") + sect = Section("Report", f"{self.stats['statement']} statements analysed.") # type: ignore for checker in self.report_order(): for reportid, r_title, r_cb in self._reports[checker]: if not self.report_is_enabled(reportid): @@ -65,13 +68,14 @@ def make_reports(self, stats, old_stats): sect.append(report_sect) return sect - def add_stats(self, **kwargs): + def add_stats(self, **kwargs: Any) -> Dict[str, Any]: """add some stats entries to the statistic dictionary raise an AssertionError if there is a key conflict """ for key, value in kwargs.items(): if key[-1] == "_": key = key[:-1] - assert key not in self.stats - self.stats[key] = value - return self.stats + # self.stats is defined in another class (this is a mixin) + assert key not in self.stats # type: ignore + self.stats[key] = value # type: ignore + return self.stats # type: ignore diff --git a/pylint/reporters/ureports/base_writer.py b/pylint/reporters/ureports/base_writer.py index f0651f98cd4..a7fd167079b 100644 --- a/pylint/reporters/ureports/base_writer.py +++ b/pylint/reporters/ureports/base_writer.py @@ -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: @@ -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: diff --git a/pylint/reporters/ureports/nodes.py b/pylint/reporters/ureports/nodes.py index 3f7842a18e0..a695084a6b1 100644 --- a/pylint/reporters/ureports/nodes.py +++ b/pylint/reporters/ureports/nodes.py @@ -14,30 +14,33 @@ A micro report is a tree of layout and content objects. """ +from typing import Any, List, Optional, Tuple, Union + +from pylint.reporters.ureports.text_writer import TextWriter class VNode: - def __init__(self, nid=None): + def __init__(self, nid: Optional[Any] = None) -> None: self.id = nid # navigation - self.parent = None - self.children = [] - self.visitor_name = self.__class__.__name__.lower() + self.parent: Optional[Any] = None + self.children: List = [] + self.visitor_name: str = self.__class__.__name__.lower() def __iter__(self): return iter(self.children) - def append(self, child): + def append(self, child: Any) -> None: """add a node to children""" self.children.append(child) child.parent = self - def insert(self, index, child): + def insert(self, index: int, child: Union["Paragraph", "Title"]) -> None: """insert a child node""" self.children.insert(index, child) child.parent = self - def accept(self, visitor, *args, **kwargs): + def accept(self, visitor: TextWriter, *args: Any, **kwargs: Any) -> Optional[Any]: func = getattr(visitor, f"visit_{self.visitor_name}") return func(self, *args, **kwargs) @@ -53,7 +56,9 @@ class BaseLayout(VNode): * children : components in this table (i.e. the table's cells) """ - def __init__(self, children=(), **kwargs): + def __init__( + self, children: Union[List["Text"], Tuple[str, ...]] = (), **kwargs: Any + ) -> None: super().__init__(**kwargs) for child in children: if isinstance(child, VNode): @@ -61,19 +66,19 @@ def __init__(self, children=(), **kwargs): else: self.add_text(child) - def append(self, child): + def append(self, child: Any) -> None: """overridden to detect problems easily""" assert child not in self.parents() VNode.append(self, child) - def parents(self): + def parents(self) -> List: """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)) @@ -88,7 +93,7 @@ class Text(VNode): * data : the text value as an encoded or unicode string """ - def __init__(self, data, escaped=True, **kwargs): + def __init__(self, data: str, escaped: bool = True, **kwargs: Any) -> None: super().__init__(**kwargs) # if isinstance(data, unicode): # data = data.encode('ascii') @@ -120,16 +125,23 @@ class Section(BaseLayout): as a first paragraph """ - def __init__(self, title=None, description=None, **kwargs): + def __init__( + self, + title: Optional[str] = None, + description: Optional[str] = None, + **kwargs: Any, + ) -> None: super().__init__(**kwargs) if description: self.insert(0, Paragraph([Text(description)])) if title: self.insert(0, Title(children=(title,))) + # Used in ReportHandlerMixin make_reports + self.report_id = None class EvaluationSection(Section): - def __init__(self, message, **kwargs): + def __init__(self, message: str, **kwargs: Any) -> None: super().__init__(**kwargs) title = Paragraph() title.append(Text("-" * len(message))) @@ -171,7 +183,14 @@ class Table(BaseLayout): * title : the table's optional title """ - def __init__(self, cols, title=None, rheaders=0, cheaders=0, **kwargs): + def __init__( + self, + cols: int, + title: Optional[Any] = None, + rheaders: int = 0, + cheaders: int = 0, + **kwargs: Any, + ) -> None: super().__init__(**kwargs) assert isinstance(cols, int) self.cols = cols diff --git a/pylint/reporters/ureports/text_writer.py b/pylint/reporters/ureports/text_writer.py index a48d73aac78..a6d441e1eb1 100644 --- a/pylint/reporters/ureports/text_writer.py +++ b/pylint/reporters/ureports/text_writer.py @@ -11,7 +11,20 @@ """Text formatting drivers for ureports""" -from pylint.reporters.ureports import BaseWriter +from typing import TYPE_CHECKING, List + +from pylint.reporters.ureports.base_writer import BaseWriter + +if TYPE_CHECKING: + from pylint.reporters.ureports.nodes import ( + EvaluationSection, + Paragraph, + Section, + Table, + Text, + Title, + VerbatimText, + ) TITLE_UNDERLINES = ["", "=", "-", "`", ".", "~", "^"] BULLETS = ["*", "-"] @@ -22,11 +35,11 @@ class TextWriter(BaseWriter): (ReStructured inspiration but not totally handled yet) """ - def begin_format(self): - super().begin_format() + def __init__(self): + super().__init__() self.list_level = 0 - def visit_section(self, layout): + def visit_section(self, layout: "Section") -> None: """display a section as text""" self.section += 1 self.writeln() @@ -34,14 +47,14 @@ def visit_section(self, layout): self.section -= 1 self.writeln() - def visit_evaluationsection(self, layout): + def visit_evaluationsection(self, layout: "EvaluationSection") -> None: """Display an evaluation section as a text.""" self.section += 1 self.format_children(layout) self.section -= 1 self.writeln() - def visit_title(self, layout): + def visit_title(self, layout: "Title") -> None: title = "".join(list(self.compute_content(layout))) self.writeln(title) try: @@ -49,12 +62,12 @@ def visit_title(self, layout): except IndexError: print("FIXME TITLE TOO DEEP. TURNING TITLE INTO TEXT") - def visit_paragraph(self, layout): + def visit_paragraph(self, layout: "Paragraph") -> None: """enter a paragraph""" self.format_children(layout) self.writeln() - def visit_table(self, layout): + def visit_table(self, layout: "Table") -> None: """display a table as text""" table_content = self.get_table_content(layout) # get columns width @@ -65,33 +78,36 @@ def visit_table(self, layout): self.default_table(layout, table_content, cols_width) self.writeln() - def default_table(self, layout, table_content, cols_width): + def default_table( + self, layout: "Table", table_content: List[List[str]], cols_width: List[int] + ) -> None: """format a table""" cols_width = [size + 1 for size in cols_width] format_strings = " ".join(["%%-%ss"] * len(cols_width)) - format_strings = format_strings % tuple(cols_width) - format_strings = format_strings.split(" ") + format_strings %= tuple(cols_width) + table_linesep = "\n+" + "+".join("-" * w for w in cols_width) + "+\n" headsep = "\n+" + "+".join("=" * w for w in cols_width) + "+\n" self.write(table_linesep) + split_strings = format_strings.split(" ") for index, line in enumerate(table_content): self.write("|") for line_index, at_index in enumerate(line): - self.write(format_strings[line_index] % at_index) + self.write(split_strings[line_index] % at_index) self.write("|") if index == 0 and layout.rheaders: self.write(headsep) else: self.write(table_linesep) - def visit_verbatimtext(self, layout): + def visit_verbatimtext(self, layout: "VerbatimText"): """display a verbatim layout as text (so difficult ;)""" self.writeln("::\n") for line in layout.data.splitlines(): self.writeln(" " + line) self.writeln() - def visit_text(self, layout): + def visit_text(self, layout: "Text") -> None: """add some text""" self.write(f"{layout.data}")