389 lines
12 KiB
Python
389 lines
12 KiB
Python
|
# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
||
|
# For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE
|
||
|
# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt
|
||
|
|
||
|
from __future__ import annotations
|
||
|
|
||
|
from typing import Literal, TypedDict, cast
|
||
|
|
||
|
from pylint.typing import MessageTypesFullName
|
||
|
|
||
|
|
||
|
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
|
||
|
typevar: int
|
||
|
typealias: 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: BadNames | None = None,
|
||
|
by_module: dict[str, ModuleStats] | None = None,
|
||
|
by_msg: dict[str, int] | None = None,
|
||
|
code_type_count: CodeTypeCount | None = None,
|
||
|
dependencies: dict[str, set[str]] | None = None,
|
||
|
duplicated_lines: DuplicatedLines | None = None,
|
||
|
node_count: NodeCount | None = None,
|
||
|
undocumented: UndocumentedNodes | None = 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,
|
||
|
typevar=0,
|
||
|
typealias=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 __repr__(self) -> str:
|
||
|
return str(self)
|
||
|
|
||
|
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 init_single_module(self, module_name: str) -> None:
|
||
|
"""Use through PyLinter.set_current_module so PyLinter.current_name is
|
||
|
consistent.
|
||
|
"""
|
||
|
self.by_module[module_name] = ModuleStats(
|
||
|
convention=0, error=0, fatal=0, info=0, refactor=0, statement=0, warning=0
|
||
|
)
|
||
|
|
||
|
def get_bad_names(
|
||
|
self,
|
||
|
node_name: Literal[
|
||
|
"argument",
|
||
|
"attr",
|
||
|
"class",
|
||
|
"class_attribute",
|
||
|
"class_const",
|
||
|
"const",
|
||
|
"inlinevar",
|
||
|
"function",
|
||
|
"method",
|
||
|
"module",
|
||
|
"variable",
|
||
|
"typevar",
|
||
|
"typealias",
|
||
|
],
|
||
|
) -> 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",
|
||
|
"typevar",
|
||
|
"typealias",
|
||
|
}:
|
||
|
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",
|
||
|
"typevar",
|
||
|
"typealias",
|
||
|
],
|
||
|
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,
|
||
|
typevar=0,
|
||
|
typealias=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: MessageTypesFullName, increase: int
|
||
|
) -> None:
|
||
|
"""Increase the message type count of an individual message type of a
|
||
|
module.
|
||
|
"""
|
||
|
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]) -> 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"]
|
||
|
merged.bad_names["typevar"] += stat.bad_names["typevar"]
|
||
|
merged.bad_names["typealias"] += stat.bad_names["typealias"]
|
||
|
|
||
|
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
|