linterstats.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  2. # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
  3. # Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt
  4. from __future__ import annotations
  5. import sys
  6. from typing import cast
  7. from pylint.typing import MessageTypesFullName
  8. if sys.version_info >= (3, 8):
  9. from typing import Literal, TypedDict
  10. else:
  11. from typing_extensions import Literal, TypedDict
  12. class BadNames(TypedDict):
  13. """TypedDict to store counts of node types with bad names."""
  14. argument: int
  15. attr: int
  16. klass: int
  17. class_attribute: int
  18. class_const: int
  19. const: int
  20. inlinevar: int
  21. function: int
  22. method: int
  23. module: int
  24. variable: int
  25. typevar: int
  26. typealias: int
  27. class CodeTypeCount(TypedDict):
  28. """TypedDict to store counts of lines of code types."""
  29. code: int
  30. comment: int
  31. docstring: int
  32. empty: int
  33. total: int
  34. class DuplicatedLines(TypedDict):
  35. """TypedDict to store counts of lines of duplicated code."""
  36. nb_duplicated_lines: int
  37. percent_duplicated_lines: float
  38. class NodeCount(TypedDict):
  39. """TypedDict to store counts of different types of nodes."""
  40. function: int
  41. klass: int
  42. method: int
  43. module: int
  44. class UndocumentedNodes(TypedDict):
  45. """TypedDict to store counts of undocumented node types."""
  46. function: int
  47. klass: int
  48. method: int
  49. module: int
  50. class ModuleStats(TypedDict):
  51. """TypedDict to store counts of types of messages and statements."""
  52. convention: int
  53. error: int
  54. fatal: int
  55. info: int
  56. refactor: int
  57. statement: int
  58. warning: int
  59. # pylint: disable-next=too-many-instance-attributes
  60. class LinterStats:
  61. """Class used to linter stats."""
  62. def __init__(
  63. self,
  64. bad_names: BadNames | None = None,
  65. by_module: dict[str, ModuleStats] | None = None,
  66. by_msg: dict[str, int] | None = None,
  67. code_type_count: CodeTypeCount | None = None,
  68. dependencies: dict[str, set[str]] | None = None,
  69. duplicated_lines: DuplicatedLines | None = None,
  70. node_count: NodeCount | None = None,
  71. undocumented: UndocumentedNodes | None = None,
  72. ) -> None:
  73. self.bad_names = bad_names or BadNames(
  74. argument=0,
  75. attr=0,
  76. klass=0,
  77. class_attribute=0,
  78. class_const=0,
  79. const=0,
  80. inlinevar=0,
  81. function=0,
  82. method=0,
  83. module=0,
  84. variable=0,
  85. typevar=0,
  86. typealias=0,
  87. )
  88. self.by_module: dict[str, ModuleStats] = by_module or {}
  89. self.by_msg: dict[str, int] = by_msg or {}
  90. self.code_type_count = code_type_count or CodeTypeCount(
  91. code=0, comment=0, docstring=0, empty=0, total=0
  92. )
  93. self.dependencies: dict[str, set[str]] = dependencies or {}
  94. self.duplicated_lines = duplicated_lines or DuplicatedLines(
  95. nb_duplicated_lines=0, percent_duplicated_lines=0.0
  96. )
  97. self.node_count = node_count or NodeCount(
  98. function=0, klass=0, method=0, module=0
  99. )
  100. self.undocumented = undocumented or UndocumentedNodes(
  101. function=0, klass=0, method=0, module=0
  102. )
  103. self.convention = 0
  104. self.error = 0
  105. self.fatal = 0
  106. self.info = 0
  107. self.refactor = 0
  108. self.statement = 0
  109. self.warning = 0
  110. self.global_note = 0
  111. self.nb_duplicated_lines = 0
  112. self.percent_duplicated_lines = 0.0
  113. def __repr__(self) -> str:
  114. return str(self)
  115. def __str__(self) -> str:
  116. return f"""{self.bad_names}
  117. {sorted(self.by_module.items())}
  118. {sorted(self.by_msg.items())}
  119. {self.code_type_count}
  120. {sorted(self.dependencies.items())}
  121. {self.duplicated_lines}
  122. {self.undocumented}
  123. {self.convention}
  124. {self.error}
  125. {self.fatal}
  126. {self.info}
  127. {self.refactor}
  128. {self.statement}
  129. {self.warning}
  130. {self.global_note}
  131. {self.nb_duplicated_lines}
  132. {self.percent_duplicated_lines}"""
  133. def init_single_module(self, module_name: str) -> None:
  134. """Use through PyLinter.set_current_module so PyLinter.current_name is
  135. consistent.
  136. """
  137. self.by_module[module_name] = ModuleStats(
  138. convention=0, error=0, fatal=0, info=0, refactor=0, statement=0, warning=0
  139. )
  140. def get_bad_names(
  141. self,
  142. node_name: Literal[
  143. "argument",
  144. "attr",
  145. "class",
  146. "class_attribute",
  147. "class_const",
  148. "const",
  149. "inlinevar",
  150. "function",
  151. "method",
  152. "module",
  153. "variable",
  154. "typevar",
  155. "typealias",
  156. ],
  157. ) -> int:
  158. """Get a bad names node count."""
  159. if node_name == "class":
  160. return self.bad_names.get("klass", 0)
  161. return self.bad_names.get(node_name, 0)
  162. def increase_bad_name(self, node_name: str, increase: int) -> None:
  163. """Increase a bad names node count."""
  164. if node_name not in {
  165. "argument",
  166. "attr",
  167. "class",
  168. "class_attribute",
  169. "class_const",
  170. "const",
  171. "inlinevar",
  172. "function",
  173. "method",
  174. "module",
  175. "variable",
  176. "typevar",
  177. "typealias",
  178. }:
  179. raise ValueError("Node type not part of the bad_names stat")
  180. node_name = cast(
  181. Literal[
  182. "argument",
  183. "attr",
  184. "class",
  185. "class_attribute",
  186. "class_const",
  187. "const",
  188. "inlinevar",
  189. "function",
  190. "method",
  191. "module",
  192. "variable",
  193. "typevar",
  194. "typealias",
  195. ],
  196. node_name,
  197. )
  198. if node_name == "class":
  199. self.bad_names["klass"] += increase
  200. else:
  201. self.bad_names[node_name] += increase
  202. def reset_bad_names(self) -> None:
  203. """Resets the bad_names attribute."""
  204. self.bad_names = BadNames(
  205. argument=0,
  206. attr=0,
  207. klass=0,
  208. class_attribute=0,
  209. class_const=0,
  210. const=0,
  211. inlinevar=0,
  212. function=0,
  213. method=0,
  214. module=0,
  215. variable=0,
  216. typevar=0,
  217. typealias=0,
  218. )
  219. def get_code_count(
  220. self, type_name: Literal["code", "comment", "docstring", "empty", "total"]
  221. ) -> int:
  222. """Get a code type count."""
  223. return self.code_type_count.get(type_name, 0)
  224. def reset_code_count(self) -> None:
  225. """Resets the code_type_count attribute."""
  226. self.code_type_count = CodeTypeCount(
  227. code=0, comment=0, docstring=0, empty=0, total=0
  228. )
  229. def reset_duplicated_lines(self) -> None:
  230. """Resets the duplicated_lines attribute."""
  231. self.duplicated_lines = DuplicatedLines(
  232. nb_duplicated_lines=0, percent_duplicated_lines=0.0
  233. )
  234. def get_node_count(
  235. self, node_name: Literal["function", "class", "method", "module"]
  236. ) -> int:
  237. """Get a node count while handling some extra conditions."""
  238. if node_name == "class":
  239. return self.node_count.get("klass", 0)
  240. return self.node_count.get(node_name, 0)
  241. def reset_node_count(self) -> None:
  242. """Resets the node count attribute."""
  243. self.node_count = NodeCount(function=0, klass=0, method=0, module=0)
  244. def get_undocumented(
  245. self, node_name: Literal["function", "class", "method", "module"]
  246. ) -> float:
  247. """Get a undocumented node count."""
  248. if node_name == "class":
  249. return self.undocumented["klass"]
  250. return self.undocumented[node_name]
  251. def reset_undocumented(self) -> None:
  252. """Resets the undocumented attribute."""
  253. self.undocumented = UndocumentedNodes(function=0, klass=0, method=0, module=0)
  254. def get_global_message_count(self, type_name: str) -> int:
  255. """Get a global message count."""
  256. return getattr(self, type_name, 0)
  257. def get_module_message_count(self, modname: str, type_name: str) -> int:
  258. """Get a module message count."""
  259. return getattr(self.by_module[modname], type_name, 0)
  260. def increase_single_message_count(self, type_name: str, increase: int) -> None:
  261. """Increase the message type count of an individual message type."""
  262. setattr(self, type_name, getattr(self, type_name) + increase)
  263. def increase_single_module_message_count(
  264. self, modname: str, type_name: MessageTypesFullName, increase: int
  265. ) -> None:
  266. """Increase the message type count of an individual message type of a
  267. module.
  268. """
  269. self.by_module[modname][type_name] += increase
  270. def reset_message_count(self) -> None:
  271. """Resets the message type count of the stats object."""
  272. self.convention = 0
  273. self.error = 0
  274. self.fatal = 0
  275. self.info = 0
  276. self.refactor = 0
  277. self.warning = 0
  278. def merge_stats(stats: list[LinterStats]) -> LinterStats:
  279. """Used to merge multiple stats objects into a new one when pylint is run in
  280. parallel mode.
  281. """
  282. merged = LinterStats()
  283. for stat in stats:
  284. merged.bad_names["argument"] += stat.bad_names["argument"]
  285. merged.bad_names["attr"] += stat.bad_names["attr"]
  286. merged.bad_names["klass"] += stat.bad_names["klass"]
  287. merged.bad_names["class_attribute"] += stat.bad_names["class_attribute"]
  288. merged.bad_names["class_const"] += stat.bad_names["class_const"]
  289. merged.bad_names["const"] += stat.bad_names["const"]
  290. merged.bad_names["inlinevar"] += stat.bad_names["inlinevar"]
  291. merged.bad_names["function"] += stat.bad_names["function"]
  292. merged.bad_names["method"] += stat.bad_names["method"]
  293. merged.bad_names["module"] += stat.bad_names["module"]
  294. merged.bad_names["variable"] += stat.bad_names["variable"]
  295. merged.bad_names["typevar"] += stat.bad_names["typevar"]
  296. merged.bad_names["typealias"] += stat.bad_names["typealias"]
  297. for mod_key, mod_value in stat.by_module.items():
  298. merged.by_module[mod_key] = mod_value
  299. for msg_key, msg_value in stat.by_msg.items():
  300. try:
  301. merged.by_msg[msg_key] += msg_value
  302. except KeyError:
  303. merged.by_msg[msg_key] = msg_value
  304. merged.code_type_count["code"] += stat.code_type_count["code"]
  305. merged.code_type_count["comment"] += stat.code_type_count["comment"]
  306. merged.code_type_count["docstring"] += stat.code_type_count["docstring"]
  307. merged.code_type_count["empty"] += stat.code_type_count["empty"]
  308. merged.code_type_count["total"] += stat.code_type_count["total"]
  309. for dep_key, dep_value in stat.dependencies.items():
  310. try:
  311. merged.dependencies[dep_key].update(dep_value)
  312. except KeyError:
  313. merged.dependencies[dep_key] = dep_value
  314. merged.duplicated_lines["nb_duplicated_lines"] += stat.duplicated_lines[
  315. "nb_duplicated_lines"
  316. ]
  317. merged.duplicated_lines["percent_duplicated_lines"] += stat.duplicated_lines[
  318. "percent_duplicated_lines"
  319. ]
  320. merged.node_count["function"] += stat.node_count["function"]
  321. merged.node_count["klass"] += stat.node_count["klass"]
  322. merged.node_count["method"] += stat.node_count["method"]
  323. merged.node_count["module"] += stat.node_count["module"]
  324. merged.undocumented["function"] += stat.undocumented["function"]
  325. merged.undocumented["klass"] += stat.undocumented["klass"]
  326. merged.undocumented["method"] += stat.undocumented["method"]
  327. merged.undocumented["module"] += stat.undocumented["module"]
  328. merged.convention += stat.convention
  329. merged.error += stat.error
  330. merged.fatal += stat.fatal
  331. merged.info += stat.info
  332. merged.refactor += stat.refactor
  333. merged.statement += stat.statement
  334. merged.warning += stat.warning
  335. merged.global_note += stat.global_note
  336. return merged