linterstats.py 12 KB

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