eric6/Plugins/CheckerPlugins/CodeStyleChecker/AnnotationsChecker.py

changeset 7247
bf9379f964f3
parent 7246
c32a350d2414
child 7360
9190402e4505
equal deleted inserted replaced
7246:c32a350d2414 7247:bf9379f964f3
23 "A101", "A102", 23 "A101", "A102",
24 24
25 ## Return Annotations 25 ## Return Annotations
26 "A201", "A202", "A203", "A204", "A205", "A206", 26 "A201", "A202", "A203", "A204", "A205", "A206",
27 27
28 ## Annotation Coverage
29 "A881",
30
31 ## Annotation Complexity
32 "A891",
33
28 ## Syntax Error 34 ## Syntax Error
29 "A999", 35 "A999",
30 ] 36 ]
31 37
32 def __init__(self, source, filename, select, ignore, expected, repeat): 38 def __init__(self, source, filename, select, ignore, expected, repeat,
39 args):
33 """ 40 """
34 Constructor 41 Constructor
35 42
36 @param source source code to be checked 43 @param source source code to be checked
37 @type list of str 44 @type list of str
43 @type list of str 50 @type list of str
44 @param expected list of expected codes 51 @param expected list of expected codes
45 @type list of str 52 @type list of str
46 @param repeat flag indicating to report each occurrence of a code 53 @param repeat flag indicating to report each occurrence of a code
47 @type bool 54 @type bool
55 @param args dictionary of arguments for the annotation checks
56 @type dict
48 """ 57 """
49 self.__select = tuple(select) 58 self.__select = tuple(select)
50 self.__ignore = ('',) if select else tuple(ignore) 59 self.__ignore = ('',) if select else tuple(ignore)
51 self.__expected = expected[:] 60 self.__expected = expected[:]
52 self.__repeat = repeat 61 self.__repeat = repeat
53 self.__filename = filename 62 self.__filename = filename
54 self.__source = source[:] 63 self.__source = source[:]
64 self.__args = args
55 65
56 # statistics counters 66 # statistics counters
57 self.counters = {} 67 self.counters = {}
58 68
59 # collection of detected errors 69 # collection of detected errors
63 ( 73 (
64 self.__checkFunctionAnnotations, 74 self.__checkFunctionAnnotations,
65 ("A001", "A002", "A003", "A101", "A102", 75 ("A001", "A002", "A003", "A101", "A102",
66 "A201", "A202", "A203", "A204", "A205", "A206",) 76 "A201", "A202", "A203", "A204", "A205", "A206",)
67 ), 77 ),
78 (self.__checkAnnotationsCoverage, ("A881",)),
79 (self.__checkAnnotationComplexity, ("A891",)),
68 ] 80 ]
81
82 self.__defaultArgs = {
83 "MinimumCoverage": 75, # % of type annotation coverage
84 "MaximumComplexity": 3,
85 }
69 86
70 self.__checkers = [] 87 self.__checkers = []
71 for checker, codes in checkersWithCodes: 88 for checker, codes in checkersWithCodes:
72 if any(not (code and self.__ignoreCode(code)) 89 if any(not (code and self.__ignoreCode(code))
73 for code in codes): 90 for code in codes):
176 for issue in visitor.issues: 193 for issue in visitor.issues:
177 node = issue[0] 194 node = issue[0]
178 reason = issue[1] 195 reason = issue[1]
179 params = issue[2:] 196 params = issue[2:]
180 self.__error(node.lineno - 1, node.col_offset, reason, *params) 197 self.__error(node.lineno - 1, node.col_offset, reason, *params)
198
199 def __checkAnnotationsCoverage(self):
200 """
201 Private method to check for function annotation coverage.
202 """
203 minAnnotationsCoverage = self.__args.get(
204 "MinimumCoverage", self.__defaultArgs["MinimumCoverage"])
205 if minAnnotationsCoverage == 0:
206 # 0 means it is switched off
207 return
208
209 functionDefs = [
210 f for f in ast.walk(self.__tree)
211 if isinstance(f, (ast.AsyncFunctionDef, ast.FunctionDef))
212 ]
213 if not functionDefs:
214 # no functions/methods at all
215 return
216
217 functionDefAnnotationsInfo = [
218 hasTypeAnnotations(f) for f in functionDefs
219 ]
220 annotationsCoverage = int(
221 len(list(filter(None, functionDefAnnotationsInfo))) /
222 len(functionDefAnnotationsInfo) * 100
223 )
224 if annotationsCoverage < minAnnotationsCoverage:
225 self.__error(0, 0, "A881", annotationsCoverage)
226
227 def __checkAnnotationComplexity(self):
228 """
229 Private method to check the type annotation complexity.
230 """
231 maxAnnotationComplexity = self.__args.get(
232 "MaximumComplexity", self.__defaultArgs["MaximumComplexity"])
233 typeAnnotations = []
234
235 functionDefs = [
236 f for f in ast.walk(self.__tree)
237 if isinstance(f, (ast.AsyncFunctionDef, ast.FunctionDef))
238 ]
239 for functionDef in functionDefs:
240 typeAnnotations += list(filter(
241 None, [a.annotation for a in functionDef.args.args]))
242 if functionDef.returns:
243 typeAnnotations.append(functionDef.returns)
244 typeAnnotations += [a.annotation for a in ast.walk(self.__tree)
245 if isinstance(a, ast.AnnAssign) and a.annotation]
246 for annotation in typeAnnotations:
247 complexity = getAnnotationComplexity(annotation)
248 if complexity > maxAnnotationComplexity:
249 self.__error(annotation.lineno - 1, annotation.col_offset,
250 "A891", complexity, maxAnnotationComplexity)
181 251
182 252
183 class FunctionVisitor(ast.NodeVisitor): 253 class FunctionVisitor(ast.NodeVisitor):
184 """ 254 """
185 Class implementing a node visitor to check function annotations. 255 Class implementing a node visitor to check function annotations.
277 self.__classifyArgumentError( 347 self.__classifyArgumentError(
278 arg, argType, classMethodType) 348 arg, argType, classMethodType)
279 349
280 # check function return annotation 350 # check function return annotation
281 if not node.returns: 351 if not node.returns:
282 lineno = node.body[0].lineno 352 lineno = node.lineno
283 colOffset = self.__sourceLines[lineno - 1].find(":") + 1 353 colOffset = self.__sourceLines[lineno - 1].rfind(":") + 1
284 self.__classifyReturnError(classMethodType, visibilityType, 354 self.__classifyReturnError(classMethodType, visibilityType,
285 lineno, colOffset) 355 lineno, colOffset)
286 356
287 def __classifyReturnError(self, methodType, visibilityType, lineno, 357 def __classifyReturnError(self, methodType, visibilityType, lineno,
288 colOffset): 358 colOffset):
344 elif argType == "vararg": 414 elif argType == "vararg":
345 self.issues.append((argNode, "A002", argNode.arg)) 415 self.issues.append((argNode, "A002", argNode.arg))
346 else: 416 else:
347 # args and kwonlyargs 417 # args and kwonlyargs
348 self.issues.append((argNode, "A001", argNode.arg)) 418 self.issues.append((argNode, "A001", argNode.arg))
419
420 ######################################################################
421 ## some utility functions below
422 ######################################################################
423
424
425 def hasTypeAnnotations(funcNode):
426 """
427 Function to check for type annotations.
428
429 @param funcNode reference to the function definition node to be checked
430 @type ast.AsyncFunctionDef or ast.FunctionDef
431 @return flag indicating the presence of type annotations
432 @rtype bool
433 """
434 hasReturnAnnotation = funcNode.returns is not None
435 hasArgsAnnotations = any(a for a in funcNode.args.args
436 if a.annotation is not None)
437 hasKwargsAnnotations = (funcNode.args and
438 funcNode.args.kwarg and
439 funcNode.args.kwarg.annotation is not None)
440 hasKwonlyargsAnnotations = any(a for a in funcNode.args.kwonlyargs
441 if a.annotation is not None)
442
443 return any((hasReturnAnnotation, hasArgsAnnotations, hasKwargsAnnotations,
444 hasKwonlyargsAnnotations))
445
446
447 def getAnnotationComplexity(annotationNode):
448 """
449 Function to determine the annotation complexity.
450
451 @param annotationNode reference to the node to determine the annotation
452 complexity for
453 @type ast.AST
454 @return annotation complexity
455 @rtype = int
456 """
457 if isinstance(annotationNode, ast.Str):
458 annotationNode = ast.parse(annotationNode.s).body[0].value
459 if isinstance(annotationNode, ast.Subscript):
460 return 1 + getAnnotationComplexity(annotationNode.slice.value)
461 if isinstance(annotationNode, ast.Tuple):
462 return max(getAnnotationComplexity(n) for n in annotationNode.elts)
463 return 1

eric ide

mercurial