diff options
author | Daniƫl van Noord <13665637+DanielNoord@users.noreply.github.com> | 2021-10-07 09:40:31 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-10-07 09:40:31 +0200 |
commit | 16c09cb48a3c66f453faccdf380aea6969d61202 (patch) | |
tree | 9cd442a7c64913f9815f6bf140ed39d0e24ac5fd /pylint/utils/linterstats.py | |
parent | d98c29d888fbfd11aa42da593c6cfc6812434ee0 (diff) | |
download | pylint-git-16c09cb48a3c66f453faccdf380aea6969d61202.tar.gz |
Refactor ``LinterStats`` (#5074)
* Refactor ``self.stats`` on linter and checker
This adds a new class ``LinterStats`` which is used to store all
kinds of stats during a run of ``pylint``. Tests have been changed
slightly to be able to use the new class.
Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com>
Diffstat (limited to 'pylint/utils/linterstats.py')
-rw-r--r-- | pylint/utils/linterstats.py | 365 |
1 files changed, 365 insertions, 0 deletions
diff --git a/pylint/utils/linterstats.py b/pylint/utils/linterstats.py new file mode 100644 index 000000000..f46054455 --- /dev/null +++ b/pylint/utils/linterstats.py @@ -0,0 +1,365 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE + +import sys +from typing import Dict, List, Optional, Set, cast + +if sys.version_info >= (3, 8): + from typing import Literal, TypedDict +else: + from typing_extensions import Literal, TypedDict + + +class BadNames(TypedDict): + """TypedDict to store counts of node types with bad names""" + + argument: int + attr: int + klass: int + class_attribute: int + class_const: int + const: int + inlinevar: int + function: int + method: int + module: int + variable: int + + +class CodeTypeCount(TypedDict): + """TypedDict to store counts of lines of code types""" + + code: int + comment: int + docstring: int + empty: int + total: int + + +class DuplicatedLines(TypedDict): + """TypedDict to store counts of lines of duplicated code""" + + nb_duplicated_lines: int + percent_duplicated_lines: float + + +class NodeCount(TypedDict): + """TypedDict to store counts of different types of nodes""" + + function: int + klass: int + method: int + module: int + + +class UndocumentedNodes(TypedDict): + """TypedDict to store counts of undocumented node types""" + + function: int + klass: int + method: int + module: int + + +class ModuleStats(TypedDict): + """TypedDict to store counts of types of messages and statements""" + + convention: int + error: int + fatal: int + info: int + refactor: int + statement: int + warning: int + + +# pylint: disable-next=too-many-instance-attributes +class LinterStats: + """Class used to linter stats""" + + def __init__( + self, + bad_names: Optional[BadNames] = None, + by_module: Optional[Dict[str, ModuleStats]] = None, + by_msg: Optional[Dict[str, int]] = None, + code_type_count: Optional[CodeTypeCount] = None, + dependencies: Optional[Dict[str, Set[str]]] = None, + duplicated_lines: Optional[DuplicatedLines] = None, + node_count: Optional[NodeCount] = None, + undocumented: Optional[UndocumentedNodes] = None, + ) -> None: + self.bad_names = bad_names or BadNames( + argument=0, + attr=0, + klass=0, + class_attribute=0, + class_const=0, + const=0, + inlinevar=0, + function=0, + method=0, + module=0, + variable=0, + ) + self.by_module: Dict[str, ModuleStats] = by_module or {} + self.by_msg: Dict[str, int] = by_msg or {} + self.code_type_count = code_type_count or CodeTypeCount( + code=0, comment=0, docstring=0, empty=0, total=0 + ) + + self.dependencies: Dict[str, Set[str]] = dependencies or {} + self.duplicated_lines = duplicated_lines or DuplicatedLines( + nb_duplicated_lines=0, percent_duplicated_lines=0.0 + ) + self.node_count = node_count or NodeCount( + function=0, klass=0, method=0, module=0 + ) + self.undocumented = undocumented or UndocumentedNodes( + function=0, klass=0, method=0, module=0 + ) + + self.convention = 0 + self.error = 0 + self.fatal = 0 + self.info = 0 + self.refactor = 0 + self.statement = 0 + self.warning = 0 + + self.global_note = 0 + self.nb_duplicated_lines = 0 + self.percent_duplicated_lines = 0.0 + + def __str__(self) -> str: + return f"""{self.bad_names} + {sorted(self.by_module.items())} + {sorted(self.by_msg.items())} + {self.code_type_count} + {sorted(self.dependencies.items())} + {self.duplicated_lines} + {self.undocumented} + {self.convention} + {self.error} + {self.fatal} + {self.info} + {self.refactor} + {self.statement} + {self.warning} + {self.global_note} + {self.nb_duplicated_lines} + {self.percent_duplicated_lines}""" + + def get_bad_names( + self, + node_name: Literal[ + "argument", + "attr", + "class", + "class_attribute", + "class_const", + "const", + "inlinevar", + "function", + "method", + "module", + "variable", + ], + ) -> int: + """Get a bad names node count""" + if node_name == "class": + return self.bad_names.get("klass", 0) + return self.bad_names.get(node_name, 0) + + def increase_bad_name(self, node_name: str, increase: int) -> None: + """Increase a bad names node count""" + if node_name not in { + "argument", + "attr", + "class", + "class_attribute", + "class_const", + "const", + "inlinevar", + "function", + "method", + "module", + "variable", + }: + raise ValueError("Node type not part of the bad_names stat") + + node_name = cast( + Literal[ + "argument", + "attr", + "class", + "class_attribute", + "class_const", + "const", + "inlinevar", + "function", + "method", + "module", + "variable", + ], + node_name, + ) + if node_name == "class": + self.bad_names["klass"] += increase + else: + self.bad_names[node_name] += increase + + def reset_bad_names(self) -> None: + """Resets the bad_names attribute""" + self.bad_names = BadNames( + argument=0, + attr=0, + klass=0, + class_attribute=0, + class_const=0, + const=0, + inlinevar=0, + function=0, + method=0, + module=0, + variable=0, + ) + + def get_code_count( + self, type_name: Literal["code", "comment", "docstring", "empty", "total"] + ) -> int: + """Get a code type count""" + return self.code_type_count.get(type_name, 0) + + def reset_code_count(self) -> None: + """Resets the code_type_count attribute""" + self.code_type_count = CodeTypeCount( + code=0, comment=0, docstring=0, empty=0, total=0 + ) + + def reset_duplicated_lines(self) -> None: + """Resets the duplicated_lines attribute""" + self.duplicated_lines = DuplicatedLines( + nb_duplicated_lines=0, percent_duplicated_lines=0.0 + ) + + def get_node_count( + self, node_name: Literal["function", "class", "method", "module"] + ) -> int: + """Get a node count while handling some extra conditions""" + if node_name == "class": + return self.node_count.get("klass", 0) + return self.node_count.get(node_name, 0) + + def reset_node_count(self) -> None: + """Resets the node count attribute""" + self.node_count = NodeCount(function=0, klass=0, method=0, module=0) + + def get_undocumented( + self, node_name: Literal["function", "class", "method", "module"] + ) -> float: + """Get a undocumented node count""" + if node_name == "class": + return self.undocumented["klass"] + return self.undocumented[node_name] + + def reset_undocumented(self) -> None: + """Resets the undocumented attribute""" + self.undocumented = UndocumentedNodes(function=0, klass=0, method=0, module=0) + + def get_global_message_count(self, type_name: str) -> int: + """Get a global message count""" + return getattr(self, type_name, 0) + + def get_module_message_count(self, modname: str, type_name: str) -> int: + """Get a module message count""" + return getattr(self.by_module[modname], type_name, 0) + + def increase_single_message_count(self, type_name: str, increase: int) -> None: + """Increase the message type count of an individual message type""" + setattr(self, type_name, getattr(self, type_name) + increase) + + def increase_single_module_message_count( + self, + modname: str, + type_name: Literal["convention", "error", "fatal", "info", "refactor"], + increase: int, + ) -> None: + """Increase the message type count of an individual message type of a module""" + self.by_module[modname][type_name] = ( + self.by_module[modname][type_name] + increase + ) + + def reset_message_count(self) -> None: + """Resets the message type count of the stats object""" + self.convention = 0 + self.error = 0 + self.fatal = 0 + self.info = 0 + self.refactor = 0 + self.warning = 0 + + +def merge_stats(stats: List[LinterStats]): + """Used to merge multiple stats objects into a new one when pylint is run in parallel mode""" + merged = LinterStats() + for stat in stats: + merged.bad_names["argument"] += stat.bad_names["argument"] + merged.bad_names["attr"] += stat.bad_names["attr"] + merged.bad_names["klass"] += stat.bad_names["klass"] + merged.bad_names["class_attribute"] += stat.bad_names["class_attribute"] + merged.bad_names["class_const"] += stat.bad_names["class_const"] + merged.bad_names["const"] += stat.bad_names["const"] + merged.bad_names["inlinevar"] += stat.bad_names["inlinevar"] + merged.bad_names["function"] += stat.bad_names["function"] + merged.bad_names["method"] += stat.bad_names["method"] + merged.bad_names["module"] += stat.bad_names["module"] + merged.bad_names["variable"] += stat.bad_names["variable"] + + for mod_key, mod_value in stat.by_module.items(): + merged.by_module[mod_key] = mod_value + + for msg_key, msg_value in stat.by_msg.items(): + try: + merged.by_msg[msg_key] += msg_value + except KeyError: + merged.by_msg[msg_key] = msg_value + + merged.code_type_count["code"] += stat.code_type_count["code"] + merged.code_type_count["comment"] += stat.code_type_count["comment"] + merged.code_type_count["docstring"] += stat.code_type_count["docstring"] + merged.code_type_count["empty"] += stat.code_type_count["empty"] + merged.code_type_count["total"] += stat.code_type_count["total"] + + for dep_key, dep_value in stat.dependencies.items(): + try: + merged.dependencies[dep_key].update(dep_value) + except KeyError: + merged.dependencies[dep_key] = dep_value + + merged.duplicated_lines["nb_duplicated_lines"] += stat.duplicated_lines[ + "nb_duplicated_lines" + ] + merged.duplicated_lines["percent_duplicated_lines"] += stat.duplicated_lines[ + "percent_duplicated_lines" + ] + + merged.node_count["function"] += stat.node_count["function"] + merged.node_count["klass"] += stat.node_count["klass"] + merged.node_count["method"] += stat.node_count["method"] + merged.node_count["module"] += stat.node_count["module"] + + merged.undocumented["function"] += stat.undocumented["function"] + merged.undocumented["klass"] += stat.undocumented["klass"] + merged.undocumented["method"] += stat.undocumented["method"] + merged.undocumented["module"] += stat.undocumented["module"] + + merged.convention += stat.convention + merged.error += stat.error + merged.fatal += stat.fatal + merged.info += stat.info + merged.refactor += stat.refactor + merged.statement += stat.statement + merged.warning += stat.warning + + merged.global_note += stat.global_note + return merged |