Coverage for C:\Repos\ekr-pylint\pylint\utils\linterstats.py: 39%

170 statements  

« prev     ^ index     » next       coverage.py v6.4, created at 2022-05-24 10:21 -0500

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 

5from __future__ import annotations 

6 

7import sys 

8from typing import cast 

9 

10from pylint.typing import MessageTypesFullName 

11 

12if sys.version_info >= (3, 8): 

13 from typing import Literal, TypedDict 

14else: 

15 from typing_extensions import Literal, TypedDict 

16 

17 

18class BadNames(TypedDict): 

19 """TypedDict to store counts of node types with bad names.""" 

20 

21 argument: int 

22 attr: int 

23 klass: int 

24 class_attribute: int 

25 class_const: int 

26 const: int 

27 inlinevar: int 

28 function: int 

29 method: int 

30 module: int 

31 variable: int 

32 typevar: int 

33 

34 

35class CodeTypeCount(TypedDict): 

36 """TypedDict to store counts of lines of code types.""" 

37 

38 code: int 

39 comment: int 

40 docstring: int 

41 empty: int 

42 total: int 

43 

44 

45class DuplicatedLines(TypedDict): 

46 """TypedDict to store counts of lines of duplicated code.""" 

47 

48 nb_duplicated_lines: int 

49 percent_duplicated_lines: float 

50 

51 

52class NodeCount(TypedDict): 

53 """TypedDict to store counts of different types of nodes.""" 

54 

55 function: int 

56 klass: int 

57 method: int 

58 module: int 

59 

60 

61class UndocumentedNodes(TypedDict): 

62 """TypedDict to store counts of undocumented node types.""" 

63 

64 function: int 

65 klass: int 

66 method: int 

67 module: int 

68 

69 

70class ModuleStats(TypedDict): 

71 """TypedDict to store counts of types of messages and statements.""" 

72 

73 convention: int 

74 error: int 

75 fatal: int 

76 info: int 

77 refactor: int 

78 statement: int 

79 warning: int 

80 

81 

82# pylint: disable-next=too-many-instance-attributes 

83class LinterStats: 

84 """Class used to linter stats.""" 

85 

86 def __init__( 

87 self, 

88 bad_names: BadNames | None = None, 

89 by_module: dict[str, ModuleStats] | None = None, 

90 by_msg: dict[str, int] | None = None, 

91 code_type_count: CodeTypeCount | None = None, 

92 dependencies: dict[str, set[str]] | None = None, 

93 duplicated_lines: DuplicatedLines | None = None, 

94 node_count: NodeCount | None = None, 

95 undocumented: UndocumentedNodes | None = None, 

96 ) -> None: 

97 self.bad_names = bad_names or BadNames( 

98 argument=0, 

99 attr=0, 

100 klass=0, 

101 class_attribute=0, 

102 class_const=0, 

103 const=0, 

104 inlinevar=0, 

105 function=0, 

106 method=0, 

107 module=0, 

108 variable=0, 

109 typevar=0, 

110 ) 

111 self.by_module: dict[str, ModuleStats] = by_module or {} 

112 self.by_msg: dict[str, int] = by_msg or {} 

113 self.code_type_count = code_type_count or CodeTypeCount( 

114 code=0, comment=0, docstring=0, empty=0, total=0 

115 ) 

116 

117 self.dependencies: dict[str, set[str]] = dependencies or {} 

118 self.duplicated_lines = duplicated_lines or DuplicatedLines( 

119 nb_duplicated_lines=0, percent_duplicated_lines=0.0 

120 ) 

121 self.node_count = node_count or NodeCount( 

122 function=0, klass=0, method=0, module=0 

123 ) 

124 self.undocumented = undocumented or UndocumentedNodes( 

125 function=0, klass=0, method=0, module=0 

126 ) 

127 

128 self.convention = 0 

129 self.error = 0 

130 self.fatal = 0 

131 self.info = 0 

132 self.refactor = 0 

133 self.statement = 0 

134 self.warning = 0 

135 

136 self.global_note = 0 

137 self.nb_duplicated_lines = 0 

138 self.percent_duplicated_lines = 0.0 

139 

140 def __repr__(self) -> str: 

141 return str(self) 

142 

143 def __str__(self) -> str: 

144 return f"""{self.bad_names} 

145 {sorted(self.by_module.items())} 

146 {sorted(self.by_msg.items())} 

147 {self.code_type_count} 

148 {sorted(self.dependencies.items())} 

149 {self.duplicated_lines} 

150 {self.undocumented} 

151 {self.convention} 

152 {self.error} 

153 {self.fatal} 

154 {self.info} 

155 {self.refactor} 

156 {self.statement} 

157 {self.warning} 

158 {self.global_note} 

159 {self.nb_duplicated_lines} 

160 {self.percent_duplicated_lines}""" 

161 

162 def init_single_module(self, module_name: str) -> None: 

163 """Use through PyLinter.set_current_module so PyLinter.current_name is consistent.""" 

164 self.by_module[module_name] = ModuleStats( 

165 convention=0, error=0, fatal=0, info=0, refactor=0, statement=0, warning=0 

166 ) 

167 

168 def get_bad_names( 

169 self, 

170 node_name: Literal[ 

171 "argument", 

172 "attr", 

173 "class", 

174 "class_attribute", 

175 "class_const", 

176 "const", 

177 "inlinevar", 

178 "function", 

179 "method", 

180 "module", 

181 "variable", 

182 "typevar", 

183 ], 

184 ) -> int: 

185 """Get a bad names node count.""" 

186 if node_name == "class": 

187 return self.bad_names.get("klass", 0) 

188 return self.bad_names.get(node_name, 0) 

189 

190 def increase_bad_name(self, node_name: str, increase: int) -> None: 

191 """Increase a bad names node count.""" 

192 if node_name not in { 

193 "argument", 

194 "attr", 

195 "class", 

196 "class_attribute", 

197 "class_const", 

198 "const", 

199 "inlinevar", 

200 "function", 

201 "method", 

202 "module", 

203 "variable", 

204 "typevar", 

205 }: 

206 raise ValueError("Node type not part of the bad_names stat") 

207 

208 node_name = cast( 

209 Literal[ 

210 "argument", 

211 "attr", 

212 "class", 

213 "class_attribute", 

214 "class_const", 

215 "const", 

216 "inlinevar", 

217 "function", 

218 "method", 

219 "module", 

220 "variable", 

221 "typevar", 

222 ], 

223 node_name, 

224 ) 

225 if node_name == "class": 

226 self.bad_names["klass"] += increase 

227 else: 

228 self.bad_names[node_name] += increase 

229 

230 def reset_bad_names(self) -> None: 

231 """Resets the bad_names attribute.""" 

232 self.bad_names = BadNames( 

233 argument=0, 

234 attr=0, 

235 klass=0, 

236 class_attribute=0, 

237 class_const=0, 

238 const=0, 

239 inlinevar=0, 

240 function=0, 

241 method=0, 

242 module=0, 

243 variable=0, 

244 typevar=0, 

245 ) 

246 

247 def get_code_count( 

248 self, type_name: Literal["code", "comment", "docstring", "empty", "total"] 

249 ) -> int: 

250 """Get a code type count.""" 

251 return self.code_type_count.get(type_name, 0) 

252 

253 def reset_code_count(self) -> None: 

254 """Resets the code_type_count attribute.""" 

255 self.code_type_count = CodeTypeCount( 

256 code=0, comment=0, docstring=0, empty=0, total=0 

257 ) 

258 

259 def reset_duplicated_lines(self) -> None: 

260 """Resets the duplicated_lines attribute.""" 

261 self.duplicated_lines = DuplicatedLines( 

262 nb_duplicated_lines=0, percent_duplicated_lines=0.0 

263 ) 

264 

265 def get_node_count( 

266 self, node_name: Literal["function", "class", "method", "module"] 

267 ) -> int: 

268 """Get a node count while handling some extra conditions.""" 

269 if node_name == "class": 

270 return self.node_count.get("klass", 0) 

271 return self.node_count.get(node_name, 0) 

272 

273 def reset_node_count(self) -> None: 

274 """Resets the node count attribute.""" 

275 self.node_count = NodeCount(function=0, klass=0, method=0, module=0) 

276 

277 def get_undocumented( 

278 self, node_name: Literal["function", "class", "method", "module"] 

279 ) -> float: 

280 """Get a undocumented node count.""" 

281 if node_name == "class": 

282 return self.undocumented["klass"] 

283 return self.undocumented[node_name] 

284 

285 def reset_undocumented(self) -> None: 

286 """Resets the undocumented attribute.""" 

287 self.undocumented = UndocumentedNodes(function=0, klass=0, method=0, module=0) 

288 

289 def get_global_message_count(self, type_name: str) -> int: 

290 """Get a global message count.""" 

291 return getattr(self, type_name, 0) 

292 

293 def get_module_message_count(self, modname: str, type_name: str) -> int: 

294 """Get a module message count.""" 

295 return getattr(self.by_module[modname], type_name, 0) 

296 

297 def increase_single_message_count(self, type_name: str, increase: int) -> None: 

298 """Increase the message type count of an individual message type.""" 

299 setattr(self, type_name, getattr(self, type_name) + increase) 

300 

301 def increase_single_module_message_count( 

302 self, modname: str, type_name: MessageTypesFullName, increase: int 

303 ) -> None: 

304 """Increase the message type count of an individual message type of a module.""" 

305 self.by_module[modname][type_name] += increase 

306 

307 def reset_message_count(self) -> None: 

308 """Resets the message type count of the stats object.""" 

309 self.convention = 0 

310 self.error = 0 

311 self.fatal = 0 

312 self.info = 0 

313 self.refactor = 0 

314 self.warning = 0 

315 

316 

317def merge_stats(stats: list[LinterStats]) -> LinterStats: 

318 """Used to merge multiple stats objects into a new one when pylint is run in parallel mode.""" 

319 merged = LinterStats() 

320 for stat in stats: 

321 merged.bad_names["argument"] += stat.bad_names["argument"] 

322 merged.bad_names["attr"] += stat.bad_names["attr"] 

323 merged.bad_names["klass"] += stat.bad_names["klass"] 

324 merged.bad_names["class_attribute"] += stat.bad_names["class_attribute"] 

325 merged.bad_names["class_const"] += stat.bad_names["class_const"] 

326 merged.bad_names["const"] += stat.bad_names["const"] 

327 merged.bad_names["inlinevar"] += stat.bad_names["inlinevar"] 

328 merged.bad_names["function"] += stat.bad_names["function"] 

329 merged.bad_names["method"] += stat.bad_names["method"] 

330 merged.bad_names["module"] += stat.bad_names["module"] 

331 merged.bad_names["variable"] += stat.bad_names["variable"] 

332 merged.bad_names["typevar"] += stat.bad_names["typevar"] 

333 

334 for mod_key, mod_value in stat.by_module.items(): 

335 merged.by_module[mod_key] = mod_value 

336 

337 for msg_key, msg_value in stat.by_msg.items(): 

338 try: 

339 merged.by_msg[msg_key] += msg_value 

340 except KeyError: 

341 merged.by_msg[msg_key] = msg_value 

342 

343 merged.code_type_count["code"] += stat.code_type_count["code"] 

344 merged.code_type_count["comment"] += stat.code_type_count["comment"] 

345 merged.code_type_count["docstring"] += stat.code_type_count["docstring"] 

346 merged.code_type_count["empty"] += stat.code_type_count["empty"] 

347 merged.code_type_count["total"] += stat.code_type_count["total"] 

348 

349 for dep_key, dep_value in stat.dependencies.items(): 

350 try: 

351 merged.dependencies[dep_key].update(dep_value) 

352 except KeyError: 

353 merged.dependencies[dep_key] = dep_value 

354 

355 merged.duplicated_lines["nb_duplicated_lines"] += stat.duplicated_lines[ 

356 "nb_duplicated_lines" 

357 ] 

358 merged.duplicated_lines["percent_duplicated_lines"] += stat.duplicated_lines[ 

359 "percent_duplicated_lines" 

360 ] 

361 

362 merged.node_count["function"] += stat.node_count["function"] 

363 merged.node_count["klass"] += stat.node_count["klass"] 

364 merged.node_count["method"] += stat.node_count["method"] 

365 merged.node_count["module"] += stat.node_count["module"] 

366 

367 merged.undocumented["function"] += stat.undocumented["function"] 

368 merged.undocumented["klass"] += stat.undocumented["klass"] 

369 merged.undocumented["method"] += stat.undocumented["method"] 

370 merged.undocumented["module"] += stat.undocumented["module"] 

371 

372 merged.convention += stat.convention 

373 merged.error += stat.error 

374 merged.fatal += stat.fatal 

375 merged.info += stat.info 

376 merged.refactor += stat.refactor 

377 merged.statement += stat.statement 

378 merged.warning += stat.warning 

379 

380 merged.global_note += stat.global_note 

381 return merged