diff --git a/pylint/checkers/imports.py b/pylint/checkers/imports.py index b8609e9624..4c75b59283 100644 --- a/pylint/checkers/imports.py +++ b/pylint/checkers/imports.py @@ -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 ################################################### diff --git a/pylint/interfaces.py b/pylint/interfaces.py index cac7e76c36..2e1ca6da5f 100644 --- a/pylint/interfaces.py +++ b/pylint/interfaces.py @@ -17,6 +17,7 @@ """Interfaces for Pylint objects""" from collections import namedtuple +from typing import Tuple from astroid import nodes @@ -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. """ @@ -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") diff --git a/pylint/lint/report_functions.py b/pylint/lint/report_functions.py index 12c4b03352..201b28b7bb 100644 --- a/pylint/lint/report_functions.py +++ b/pylint/lint/report_functions.py @@ -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 @@ -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( @@ -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( @@ -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(): @@ -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)) diff --git a/pylint/reporters/__init__.py b/pylint/reporters/__init__.py index 79b13e083b..39cf5fb0a7 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 # Fixed in https://github.com/python/mypy/pull/9454 __all__ = [ diff --git a/pylint/reporters/collecting_reporter.py b/pylint/reporters/collecting_reporter.py index 309c604052..145c3c81b3 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 245c10f79c..2ac361b381 100644 --- a/pylint/reporters/multi_reporter.py +++ b/pylint/reporters/multi_reporter.py @@ -43,7 +43,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 450d383f51..3dd6bacc63 100644 --- a/pylint/reporters/reports_handler_mix_in.py +++ b/pylint/reporters/reports_handler_mix_in.py @@ -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 @@ -17,9 +18,11 @@ 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 @@ -27,7 +30,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: IChecker + ) -> None: """register a report reportid is the unique identifier for the report @@ -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 """ @@ -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(): @@ -83,6 +88,7 @@ def add_stats( # type: ignore # ReportsHandlerMixIn is always mixed with PyLint for key, value in kwargs.items(): if key[-1] == "_": key = key[:-1] + # self.stats is defined in another class (this is a mixin) assert key not in self.stats self.stats[key] = value return self.stats diff --git a/pylint/reporters/ureports/base_writer.py b/pylint/reporters/ureports/base_writer.py index f0651f98cd..a7fd167079 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 02887612e1..34e411ba4f 100644 --- a/pylint/reporters/ureports/nodes.py +++ b/pylint/reporters/ureports/nodes.py @@ -14,21 +14,23 @@ A micro report is a tree of layout and content objects. """ -from typing import Optional +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["VNode"] = [] + self.visitor_name: str = self.__class__.__name__.lower() def __iter__(self): 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) @@ -44,7 +46,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): @@ -63,14 +67,14 @@ def insert(self, index: int, child: VNode) -> None: self.children.insert(index, child) child.parent = self - def parents(self): + def parents(self) -> List["VNode"]: """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)) @@ -85,7 +89,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') @@ -117,17 +121,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,))) - self.report_id: Optional[str] = None + # Used in ReportHandlerMixin make_reports + self.report_id: str = "" 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))) @@ -169,7 +179,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[str] = 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 a48d73aac7..a00392bfe8 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") -> None: """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}")