linterstats.py 13 KB

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