52 self.__filename = filename |
55 self.__filename = filename |
53 self.__source = source[:] |
56 self.__source = source[:] |
54 self.__tree = copy.deepcopy(tree) |
57 self.__tree = copy.deepcopy(tree) |
55 self.__args = args |
58 self.__args = args |
56 |
59 |
57 ### parameters for unused arguments checks |
|
58 ##self.__ignoreAbstract "IgnoreAbstract": False, |
|
59 ##self.__ignoreOverload "IgnoreOverload": False, |
|
60 ##self.__ignoreOverride "IgnoreOverride": False, |
|
61 ##self.__ignoreStubs "IgnoreStubs": False, |
|
62 ##self.__ignoreVariadicNames "IgnoreVariadicNames": False, |
|
63 ##self.__ignoreLambdas "IgnoreLambdas": False, |
|
64 ##self.__ignoreNestedFunctions "IgnoreNestedFunctions": False, |
|
65 ##self.__ignoreDunderMethods "IgnoreDunderMethods": False, |
|
66 |
|
67 # statistics counters |
60 # statistics counters |
68 self.counters = {} |
61 self.counters = {} |
69 |
62 |
70 # collection of detected errors |
63 # collection of detected errors |
71 self.errors = [] |
64 self.errors = [] |
72 |
65 |
73 checkersWithCodes = [ |
66 checkersWithCodes = [ |
74 (self.__checkUnusedArguments, ("U100", "U101")), |
67 (self.__checkUnusedArguments, ("U100", "U101")), |
|
68 (self.__checkUnusedGlobals, ("U200", )), |
75 ] |
69 ] |
76 |
70 |
77 self.__checkers = [] |
71 self.__checkers = [] |
78 for checker, codes in checkersWithCodes: |
72 for checker, codes in checkersWithCodes: |
79 if any(not (code and self.__ignoreCode(code)) for code in codes): |
73 if any(not (code and self.__ignoreCode(code)) for code in codes): |
362 if args.kwarg is not None: |
356 if args.kwarg is not None: |
363 orderedArguments.append(args.kwarg) |
357 orderedArguments.append(args.kwarg) |
364 |
358 |
365 return orderedArguments |
359 return orderedArguments |
366 |
360 |
|
361 ####################################################################### |
|
362 ## Unused Arguments |
|
363 ## |
|
364 ## adapted from: flake8-unused-arguments v0.0.13 |
|
365 ####################################################################### |
|
366 |
|
367 def __checkUnusedGlobals(self): |
|
368 """ |
|
369 Private method to check for unused global variables. |
|
370 """ |
|
371 errors = {} |
|
372 loadCounter = GlobalVariableLoadCounter() |
|
373 loadCounter.visit(self.__tree) |
|
374 |
|
375 globalVariables = self.__extractGlobalVariables() |
|
376 |
|
377 for varId, loads in loadCounter.getLoads(): |
|
378 if varId in globalVariables and loads == 0: |
|
379 storeInfo = loadCounter.getStoreInfo(varId) |
|
380 errorInfo = (storeInfo.lineno - 1, storeInfo.offset, "U200", varId) |
|
381 errors[varId] = errorInfo |
|
382 |
|
383 for node in self.__tree.body[::-1]: |
|
384 if isinstance(node, ast.Assign): |
|
385 for target in node.targets: |
|
386 if isinstance(target, ast.Name) and target.id in errors: |
|
387 errors.pop(target.id) |
|
388 elif ( |
|
389 isinstance(node, ast.AnnAssign) |
|
390 and isinstance(node.target, ast.Name) |
|
391 and node.target.id in errors |
|
392 ): |
|
393 errors.pop(node.target.id) |
|
394 else: |
|
395 break |
|
396 |
|
397 if self.__args["IgnoreDunderGlobals"]: |
|
398 # eliminate some special cases |
|
399 for name in list(errors.keys()): |
|
400 if name.startswith("__") and name.endswith("__"): |
|
401 errors.pop(name) |
|
402 |
|
403 for varId in errors: |
|
404 self.__error(*errors[varId]) |
|
405 |
|
406 def __extractGlobalVariables(self): |
|
407 """ |
|
408 Private method to get the names of all global variables. |
|
409 |
|
410 @return set containing the defined global variable names |
|
411 @rtype set of str |
|
412 """ |
|
413 variables = set() |
|
414 |
|
415 for assignment in self.__tree.body: |
|
416 if isinstance(assignment, ast.Assign): |
|
417 for target in assignment.targets: |
|
418 if isinstance(target, ast.Name): |
|
419 variables.add(target.id) |
|
420 elif ( |
|
421 isinstance(assignment, ast.AnnAssign) |
|
422 and isinstance(assignment.target, ast.Name) |
|
423 ): |
|
424 variables.add(assignment.target.id) |
|
425 |
|
426 return variables |
|
427 |
|
428 ####################################################################### |
|
429 ## Class used by 'Unused Arguments' |
|
430 ## |
|
431 ## adapted from: flake8-unused-arguments v0.0.13 |
|
432 ####################################################################### |
|
433 |
367 |
434 |
368 class FunctionFinder(ast.NodeVisitor): |
435 class FunctionFinder(ast.NodeVisitor): |
369 """ |
436 """ |
370 Class to find all defined functions and methods. |
437 Class to find all defined functions and methods. |
371 """ |
438 """ |
406 else: |
473 else: |
407 for obj in functionNode.body: |
474 for obj in functionNode.body: |
408 self.visit(obj) |
475 self.visit(obj) |
409 |
476 |
410 visit_AsyncFunctionDef = visit_FunctionDef = visit_Lambda = __visitFunctionTypes |
477 visit_AsyncFunctionDef = visit_FunctionDef = visit_Lambda = __visitFunctionTypes |
|
478 |
|
479 ####################################################################### |
|
480 ## Class used by 'Unused Globals' |
|
481 ## |
|
482 ## adapted from: flake8-unused-globals v0.1.9 |
|
483 ####################################################################### |
|
484 |
|
485 |
|
486 GlobalVariableStoreInfo = collections.namedtuple( |
|
487 "GlobalVariableStoreInfo", ["lineno", "offset"] |
|
488 ) |
|
489 |
|
490 |
|
491 class GlobalVariableLoadCounter(ast.NodeVisitor): |
|
492 """ |
|
493 Class to find all defined global variables and count their usages. |
|
494 """ |
|
495 |
|
496 def __init__(self): |
|
497 """ |
|
498 Constructor |
|
499 """ |
|
500 super().__init__() |
|
501 |
|
502 self.__loads = {} |
|
503 self.__storeInfo = {} |
|
504 |
|
505 def visit_Name(self, nameNode): |
|
506 """ |
|
507 Public method to record the definition and use of a global variable. |
|
508 |
|
509 @param nameNode reference to the name node to be processed |
|
510 @type ast.Name |
|
511 """ |
|
512 if isinstance(nameNode.ctx, ast.Load) and nameNode.id in self.__loads: |
|
513 self.__loads[nameNode.id] += 1 |
|
514 elif ( |
|
515 isinstance(nameNode.ctx, ast.Store) |
|
516 and nameNode.id not in self.__storeInfo |
|
517 ): |
|
518 self.__loads[nameNode.id] = 0 |
|
519 self.__storeInfo[nameNode.id] = GlobalVariableStoreInfo( |
|
520 lineno=nameNode.lineno, offset=nameNode.col_offset |
|
521 ) |
|
522 |
|
523 def getLoads(self): |
|
524 """ |
|
525 Public method to get an iterator of the detected variable loads. |
|
526 |
|
527 @return DESCRIPTION |
|
528 @rtype TYPE |
|
529 """ |
|
530 return self.__loads.items() |
|
531 |
|
532 def getStoreInfo(self, variableId): |
|
533 """ |
|
534 Public method to get the store info data of a given variable ID. |
|
535 |
|
536 @param variableId variable ID to retrieve the store info for |
|
537 @type str |
|
538 @return named tuple containing the line number and column offset |
|
539 @rtype GlobalVariableStoreInfo |
|
540 """ |
|
541 try: |
|
542 return self.__storeInfo[variableId] |
|
543 except KeyError: |
|
544 return None |