Skip to content

Commit 22e56c0

Browse files
DanielNoordcdce8ppre-commit-ci[bot]
authored
Add typing to all calls to self.stats (#4973)
* Add typing to all calls to ``self.stats`` All checkers inherit from a baseclass which has a ``stats`` attribute. This attribute has a fairly unmanageable type, but the current typing includes all variations of the attribute. Other changes not directly related to ``self.stats`` are due to ``mypy``warnings. This incorporate the feedback received in #4954 * Add ``CheckerStatistic`` class to ``pylint/typing`` * Guard `typing.Counter` import Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent cb89612 commit 22e56c0

19 files changed

+154
-63
lines changed

pylint/checkers/__init__.py

+12-5
Original file line numberDiff line numberDiff line change
@@ -46,28 +46,35 @@
4646
4747
"""
4848

49+
from typing import Iterable, List, Union
50+
4951
from pylint.checkers.base_checker import BaseChecker, BaseTokenChecker
5052
from pylint.checkers.deprecated import DeprecatedMixin
5153
from pylint.checkers.mapreduce_checker import MapReduceMixin
54+
from pylint.typing import CheckerStats
5255
from pylint.utils import diff_string, register_plugins
5356

5457

55-
def table_lines_from_stats(stats, old_stats, columns):
58+
def table_lines_from_stats(
59+
stats: CheckerStats,
60+
old_stats: CheckerStats,
61+
columns: Iterable[str],
62+
) -> List[str]:
5663
"""get values listed in <columns> from <stats> and <old_stats>,
5764
and return a formated list of values, designed to be given to a
5865
ureport.Table object
5966
"""
60-
lines = []
67+
lines: List[str] = []
6168
for m_type in columns:
62-
new = stats[m_type]
63-
old = old_stats.get(m_type)
69+
new: Union[int, str] = stats[m_type] # type: ignore
70+
old: Union[int, str, None] = old_stats.get(m_type) # type: ignore
6471
if old is not None:
6572
diff_str = diff_string(old, new)
6673
else:
6774
old, diff_str = "NC", "NC"
6875
new = f"{new:.3f}" if isinstance(new, float) else str(new)
6976
old = f"{old:.3f}" if isinstance(old, float) else str(old)
70-
lines += (m_type.replace("_", " "), new, old, diff_str)
77+
lines.extend((m_type.replace("_", " "), new, old, diff_str))
7178
return lines
7279

7380

pylint/checkers/base.py

+19-12
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
import itertools
6767
import re
6868
import sys
69-
from typing import Any, Iterator, Optional, Pattern
69+
from typing import Any, Dict, Iterator, Optional, Pattern, Union
7070

7171
import astroid
7272
from astroid import nodes
@@ -81,6 +81,7 @@
8181
is_property_setter,
8282
)
8383
from pylint.reporters.ureports import nodes as reporter_nodes
84+
from pylint.typing import CheckerStats
8485

8586

8687
class NamingStyle:
@@ -386,36 +387,42 @@ def _has_abstract_methods(node):
386387
return len(utils.unimplemented_abstract_methods(node)) > 0
387388

388389

389-
def report_by_type_stats(sect, stats, old_stats):
390+
def report_by_type_stats(
391+
sect,
392+
stats: CheckerStats,
393+
old_stats: CheckerStats,
394+
):
390395
"""make a report of
391396
392397
* percentage of different types documented
393398
* percentage of different types with a bad name
394399
"""
395400
# percentage of different types documented and/or with a bad name
396-
nice_stats = {}
401+
nice_stats: Dict[str, Dict[str, str]] = {}
397402
for node_type in ("module", "class", "method", "function"):
398403
try:
399-
total = stats[node_type]
404+
total: int = stats[node_type] # type: ignore
400405
except KeyError as e:
401406
raise exceptions.EmptyReportError() from e
402407
nice_stats[node_type] = {}
403408
if total != 0:
404409
try:
405-
documented = total - stats["undocumented_" + node_type]
410+
undocumented_node: int = stats["undocumented_" + node_type] # type: ignore
411+
documented = total - undocumented_node
406412
percent = (documented * 100.0) / total
407413
nice_stats[node_type]["percent_documented"] = f"{percent:.2f}"
408414
except KeyError:
409415
nice_stats[node_type]["percent_documented"] = "NC"
410416
try:
411-
percent = (stats["badname_" + node_type] * 100.0) / total
417+
badname_node: int = stats["badname_" + node_type] # type: ignore
418+
percent = (badname_node * 100.0) / total
412419
nice_stats[node_type]["percent_badname"] = f"{percent:.2f}"
413420
except KeyError:
414421
nice_stats[node_type]["percent_badname"] = "NC"
415422
lines = ["type", "number", "old number", "difference", "%documented", "%badname"]
416423
for node_type in ("module", "class", "method", "function"):
417424
new = stats[node_type]
418-
old = old_stats.get(node_type, None)
425+
old: Optional[Union[str, int]] = old_stats.get(node_type, None) # type: ignore
419426
if old is not None:
420427
diff_str = lint_utils.diff_string(old, new)
421428
else:
@@ -1082,7 +1089,7 @@ class BasicChecker(_BasicChecker):
10821089

10831090
def __init__(self, linter):
10841091
_BasicChecker.__init__(self, linter)
1085-
self.stats = None
1092+
self.stats: CheckerStats = {}
10861093
self._tryfinallys = None
10871094

10881095
def open(self):
@@ -1159,13 +1166,13 @@ def _check_using_constant_test(self, node, test):
11591166

11601167
def visit_module(self, _: nodes.Module) -> None:
11611168
"""check module name, docstring and required arguments"""
1162-
self.stats["module"] += 1
1169+
self.stats["module"] += 1 # type: ignore
11631170

11641171
def visit_classdef(self, _: nodes.ClassDef) -> None:
11651172
"""check module name, docstring and redefinition
11661173
increment branch counter
11671174
"""
1168-
self.stats["class"] += 1
1175+
self.stats["class"] += 1 # type: ignore
11691176

11701177
@utils.check_messages(
11711178
"pointless-statement", "pointless-string-statement", "expression-not-assigned"
@@ -1304,7 +1311,7 @@ def visit_functiondef(self, node: nodes.FunctionDef) -> None:
13041311
"""check function name, docstring, arguments, redefinition,
13051312
variable names, max locals
13061313
"""
1307-
self.stats["method" if node.is_method() else "function"] += 1
1314+
self.stats["method" if node.is_method() else "function"] += 1 # type: ignore
13081315
self._check_dangerous_default(node)
13091316

13101317
visit_asyncfunctiondef = visit_functiondef
@@ -2040,7 +2047,7 @@ def _raise_name_warning(
20402047
)
20412048

20422049
self.add_message(warning, node=node, args=args, confidence=confidence)
2043-
self.stats["badname_" + node_type] += 1
2050+
self.stats["badname_" + node_type] += 1 # type: ignore
20442051

20452052
def _name_allowed_by_regex(self, name: str) -> bool:
20462053
return name in self.config.good_names or any(

pylint/checkers/base_checker.py

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from pylint.exceptions import InvalidMessageError
2525
from pylint.interfaces import UNDEFINED, IRawChecker, ITokenChecker, implements
2626
from pylint.message.message_definition import MessageDefinition
27+
from pylint.typing import CheckerStats
2728
from pylint.utils import get_rst_section, get_rst_title
2829

2930

@@ -51,6 +52,7 @@ def __init__(self, linter=None):
5152
self.name = self.name.lower()
5253
OptionsProviderMixIn.__init__(self)
5354
self.linter = linter
55+
self.stats: CheckerStats = {}
5456

5557
def __gt__(self, other):
5658
"""Permit to sort a list of Checker by name."""

pylint/checkers/design_analysis.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from pylint.checkers import BaseChecker
3636
from pylint.checkers.utils import check_messages
3737
from pylint.interfaces import IAstroidChecker
38+
from pylint.typing import CheckerStats
3839

3940
MSGS = { # pylint: disable=consider-using-namedtuple-or-dataclass
4041
"R0901": (
@@ -391,7 +392,7 @@ class MisdesignChecker(BaseChecker):
391392

392393
def __init__(self, linter=None):
393394
BaseChecker.__init__(self, linter)
394-
self.stats = None
395+
self.stats: CheckerStats = {}
395396
self._returns = None
396397
self._branches = None
397398
self._stmts = None

pylint/checkers/imports.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
from pylint.interfaces import IAstroidChecker
6969
from pylint.lint import PyLinter
7070
from pylint.reporters.ureports.nodes import Paragraph, VerbatimText, VNode
71+
from pylint.typing import CheckerStats
7172
from pylint.utils import IsortDriver, get_global_option
7273

7374

@@ -423,7 +424,7 @@ def __init__(
423424
self, linter: PyLinter = None
424425
): # pylint: disable=super-init-not-called # See https://github.com/PyCQA/pylint/issues/4941
425426
BaseChecker.__init__(self, linter)
426-
self.stats: Dict[Any, Any] = {}
427+
self.stats: CheckerStats = {}
427428
self.import_graph: collections.defaultdict = collections.defaultdict(set)
428429
self._imports_stack: List[Tuple[Any, Any]] = []
429430
self._first_non_import_node = None
@@ -839,9 +840,8 @@ def _add_imported_module(
839840
self._module_pkg[context_name] = context_name.rsplit(".", 1)[0]
840841

841842
# handle dependencies
842-
importedmodnames = self.stats["dependencies"].setdefault(
843-
importedmodname, set()
844-
)
843+
dependencies_stat: Dict[str, Union[Set]] = self.stats["dependencies"] # type: ignore
844+
importedmodnames = dependencies_stat.setdefault(importedmodname, set())
845845
if context_name not in importedmodnames:
846846
importedmodnames.add(context_name)
847847

pylint/checkers/raw_metrics.py

+11-6
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,32 @@
1515
# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
1616

1717
import tokenize
18-
from typing import Any
18+
from typing import Any, Optional, Union
1919

2020
from pylint.checkers import BaseTokenChecker
2121
from pylint.exceptions import EmptyReportError
2222
from pylint.interfaces import ITokenChecker
2323
from pylint.reporters.ureports.nodes import Table
24+
from pylint.typing import CheckerStats
2425
from pylint.utils import diff_string
2526

2627

27-
def report_raw_stats(sect, stats, old_stats):
28+
def report_raw_stats(
29+
sect,
30+
stats: CheckerStats,
31+
old_stats: CheckerStats,
32+
):
2833
"""calculate percentage of code / doc / comment / empty"""
29-
total_lines = stats["total_lines"]
34+
total_lines: int = stats["total_lines"] # type: ignore
3035
if not total_lines:
3136
raise EmptyReportError()
3237
sect.description = f"{total_lines} lines have been analyzed"
3338
lines = ["type", "number", "%", "previous", "difference"]
3439
for node_type in ("code", "docstring", "comment", "empty"):
3540
key = node_type + "_lines"
36-
total = stats[key]
41+
total: int = stats[key] # type: ignore
3742
percent = float(total * 100) / total_lines
38-
old = old_stats.get(key, None)
43+
old: Optional[Union[int, str]] = old_stats.get(key, None) # type: ignore
3944
if old is not None:
4045
diff_str = diff_string(old, total)
4146
else:
@@ -66,7 +71,7 @@ class RawMetricsChecker(BaseTokenChecker):
6671

6772
def __init__(self, linter):
6873
BaseTokenChecker.__init__(self, linter)
69-
self.stats = None
74+
self.stats: CheckerStats = {}
7075

7176
def open(self):
7277
"""init statistics"""

pylint/checkers/similar.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
from pylint.checkers import BaseChecker, MapReduceMixin, table_lines_from_stats
7474
from pylint.interfaces import IRawChecker
7575
from pylint.reporters.ureports.nodes import Table
76+
from pylint.typing import CheckerStats
7677
from pylint.utils import decoding_stream
7778

7879
DEFAULT_MIN_SIMILARITY_LINE = 4
@@ -721,7 +722,11 @@ def real_lines(self):
721722
}
722723

723724

724-
def report_similarities(sect, stats, old_stats):
725+
def report_similarities(
726+
sect,
727+
stats: CheckerStats,
728+
old_stats: CheckerStats,
729+
):
725730
"""make a layout with some stats about duplication"""
726731
lines = ["", "now", "previous", "difference"]
727732
lines += table_lines_from_stats(
@@ -804,7 +809,7 @@ def __init__(self, linter=None) -> None:
804809
ignore_imports=self.config.ignore_imports,
805810
ignore_signatures=self.config.ignore_signatures,
806811
)
807-
self.stats = None
812+
self.stats: CheckerStats = {}
808813

809814
def set_option(self, optname, value, action=None, optdict=None):
810815
"""method called to set an option (registered in the options list)

pylint/lint/parallel.py

+11-6
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@
33

44
import collections
55
import functools
6+
from typing import TYPE_CHECKING, Dict, List, Union
67

78
from pylint import reporters
89
from pylint.lint.utils import _patch_sys_path
910
from pylint.message import Message
11+
from pylint.typing import CheckerStats
12+
13+
if TYPE_CHECKING:
14+
from typing import Counter # typing.Counter added in Python 3.6.1
1015

1116
try:
1217
import multiprocessing
@@ -30,20 +35,20 @@ def _get_new_args(message):
3035
return (message.msg_id, message.symbol, location, message.msg, message.confidence)
3136

3237

33-
def _merge_stats(stats):
34-
merged = {}
35-
by_msg = collections.Counter()
38+
def _merge_stats(stats: List[CheckerStats]):
39+
merged: CheckerStats = {}
40+
by_msg: "Counter[str]" = collections.Counter()
3641
for stat in stats:
37-
message_stats = stat.pop("by_msg", {})
42+
message_stats: Union["Counter[str]", Dict] = stat.pop("by_msg", {}) # type: ignore
3843
by_msg.update(message_stats)
3944

4045
for key, item in stat.items():
4146
if key not in merged:
4247
merged[key] = item
4348
elif isinstance(item, dict):
44-
merged[key].update(item)
49+
merged[key].update(item) # type: ignore
4550
else:
46-
merged[key] = merged[key] + item
51+
merged[key] = merged[key] + item # type: ignore
4752

4853
merged["by_msg"] = by_msg
4954
return merged

pylint/lint/pylinter.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
)
3232
from pylint.message import MessageDefinitionStore, MessagesHandlerMixIn
3333
from pylint.reporters.ureports import nodes as report_nodes
34+
from pylint.typing import CheckerStats
3435
from pylint.utils import ASTWalker, FileState, utils
3536
from pylint.utils.pragma_parser import (
3637
OPTION_PO,
@@ -502,7 +503,7 @@ def __init__(self, options=(), reporter=None, option_groups=(), pylintrc=None):
502503
self.file_state = FileState()
503504
self.current_name = None
504505
self.current_file = None
505-
self.stats = None
506+
self.stats: CheckerStats = {}
506507
self.fail_on_symbols = []
507508
# init options
508509
self._external_opts = options
@@ -729,8 +730,10 @@ def enable_fail_on_messages(self):
729730
self.fail_on_symbols.append(msg.symbol)
730731

731732
def any_fail_on_issues(self):
732-
return self.stats is not None and any(
733-
x in self.fail_on_symbols for x in self.stats["by_msg"]
733+
return (
734+
self.stats
735+
and self.stats.get("by_msg") is not None
736+
and any(x in self.fail_on_symbols for x in self.stats["by_msg"])
734737
)
735738

736739
def disable_noerror_messages(self):

0 commit comments

Comments
 (0)