Plugins/CheckerPlugins/CodeStyleChecker/MiscellaneousChecker.py

changeset 6183
29384109306c
parent 6182
f293e95b914d
child 6184
789e88d94899
equal deleted inserted replaced
6182:f293e95b914d 6183:29384109306c
10 import sys 10 import sys
11 import ast 11 import ast
12 import re 12 import re
13 import itertools 13 import itertools
14 from string import Formatter 14 from string import Formatter
15
16
17 def composeCallPath(node):
18 """
19 Generator function to assemble the call path of a given node.
20
21 @param node node to assemble call path for
22 @type ast.Node
23 @return call path components
24 @rtype str
25 """
26 if isinstance(node, ast.Attribute):
27 for v in composeCallPath(node.value):
28 yield v
29 yield node.attr
30 elif isinstance(node, ast.Name):
31 yield node.id
15 32
16 33
17 class MiscellaneousChecker(object): 34 class MiscellaneousChecker(object):
18 """ 35 """
19 Class implementing a checker for miscellaneous checks. 36 Class implementing a checker for miscellaneous checks.
25 42
26 "M191", "M192", "M193", "M194", 43 "M191", "M192", "M193", "M194",
27 "M195", "M196", "M197", "M198", 44 "M195", "M196", "M197", "M198",
28 45
29 "M201", 46 "M201",
47
48 "M501", "M502", "M503", "M504", "M505",
49 "M511", "M512", "M513",
30 50
31 "M601", 51 "M601",
32 "M611", "M612", "M613", 52 "M611", "M612", "M613",
33 "M621", "M622", "M623", "M624", "M625", 53 "M621", "M622", "M623", "M624", "M625",
34 "M631", "M632", 54 "M631", "M632",
108 (self.__checkDictWithSortedKeys, ("M201",)), 128 (self.__checkDictWithSortedKeys, ("M201",)),
109 (self.__checkPep3101, ("M601",)), 129 (self.__checkPep3101, ("M601",)),
110 (self.__checkFormatString, ("M611", "M612", "M613", 130 (self.__checkFormatString, ("M611", "M612", "M613",
111 "M621", "M622", "M623", "M624", "M625", 131 "M621", "M622", "M623", "M624", "M625",
112 "M631", "M632")), 132 "M631", "M632")),
133 (self.__checkBugBear, ("M501", "M502", "M503", "M504", "M505", "M511", "M512", "M513",)),
113 (self.__checkLogging, ("M651", "M652", "M653", "M654", "M655")), 134 (self.__checkLogging, ("M651", "M652", "M653", "M654", "M655")),
114 (self.__checkFuture, ("M701", "M702")), 135 (self.__checkFuture, ("M701", "M702")),
115 (self.__checkGettext, ("M711",)), 136 (self.__checkGettext, ("M711",)),
116 (self.__checkPrintStatements, ("M801",)), 137 (self.__checkPrintStatements, ("M801",)),
117 (self.__checkTuple, ("M811", )), 138 (self.__checkTuple, ("M811", )),
598 619
599 def __checkMutableDefault(self): 620 def __checkMutableDefault(self):
600 """ 621 """
601 Private method to check for use of mutable types as default arguments. 622 Private method to check for use of mutable types as default arguments.
602 """ 623 """
603 mutableTypes = [ 624 mutableTypes = (
604 ast.Call, 625 ast.Call,
605 ast.Dict, 626 ast.Dict,
606 ast.List, 627 ast.List,
607 ast.Set, 628 ast.Set,
608 ] 629 )
630 mutableCalls = (
631 "Counter",
632 "OrderedDict",
633 "collections.Counter",
634 "collections.OrderedDict",
635 "collections.defaultdict",
636 "collections.deque",
637 "defaultdict",
638 "deque",
639 "dict",
640 "list",
641 "set",
642 )
609 643
610 for node in ast.walk(self.__tree): 644 for node in ast.walk(self.__tree):
611 if isinstance(node, ast.FunctionDef): 645 if isinstance(node, ast.FunctionDef):
612 for default in node.args.defaults: 646 for default in node.args.defaults:
613 if any(isinstance(default, mutableType) 647 if any(isinstance(default, mutableType)
614 for mutableType in mutableTypes): 648 for mutableType in mutableTypes):
615 typeName = type(default).__name__ 649 typeName = type(default).__name__
616 if isinstance(default, ast.Call): 650 if isinstance(default, ast.Call):
617 errorCode = "M822" 651 callPath = '.'.join(composeCallPath(default.func))
652 if callPath in mutableCalls:
653 self.__error(default.lineno - 1,
654 default.col_offset,
655 "M823", callPath + "()")
656 else:
657 self.__error(default.lineno - 1,
658 default.col_offset,
659 "M822", typeName)
618 else: 660 else:
619 errorCode = "M821" 661 self.__error(default.lineno - 1,
620 self.__error(default.lineno - 1, default.col_offset, 662 default.col_offset,
621 errorCode, typeName) 663 "M821", typeName)
622 664
623 def __dictShouldBeChecked(self, node): 665 def __dictShouldBeChecked(self, node):
624 """ 666 """
625 Private function to test, if the node should be checked. 667 Private function to test, if the node should be checked.
626 668
665 for node in ast.walk(self.__tree): 707 for node in ast.walk(self.__tree):
666 if isinstance(node, ast.ImportFrom) and \ 708 if isinstance(node, ast.ImportFrom) and \
667 any(name.asname == '_' for name in node.names): 709 any(name.asname == '_' for name in node.names):
668 self.__error(node.lineno - 1, node.col_offset, "M711", 710 self.__error(node.lineno - 1, node.col_offset, "M711",
669 node.names[0].name) 711 node.names[0].name)
712
713 def __checkBugBear(self):
714 """
715 Private method to bugbear checks.
716 """
717 visitor = BugBearVisitor()
718 visitor.visit(self.__tree)
719 for node, reason in visitor.violations:
720 self.__error(node.lineno - 1, node.col_offset, reason)
670 721
671 722
672 class TextVisitor(ast.NodeVisitor): 723 class TextVisitor(ast.NodeVisitor):
673 """ 724 """
674 Class implementing a node visitor for bytes and str instances. 725 Class implementing a node visitor for bytes and str instances.
977 if self.__withinLoggingArgument(): 1028 if self.__withinLoggingArgument():
978 self.violations.append((node, "M654")) 1029 self.violations.append((node, "M654"))
979 1030
980 super(LoggingVisitor, self).generic_visit(node) 1031 super(LoggingVisitor, self).generic_visit(node)
981 1032
1033
1034 class BugBearVisitor(ast.NodeVisitor):
1035 """
1036 Class implementing a node visitor to check for various topics.
1037 """
1038 #
1039 # This class was implemented along the BugBear flake8 extension (v 18.2.0).
1040 # Original: Copyright (c) 2016 Ɓukasz Langa
1041 #
1042
1043 NodeWindowSize = 4
1044
1045 def __init__(self):
1046 """
1047 Constructor
1048 """
1049 super(BugBearVisitor, self).__init__()
1050
1051 self.__nodeStack = []
1052 self.__nodeWindow = []
1053 self.violations = []
1054
1055 def visit(self, node):
1056 """
1057 Public method to traverse a given AST node.
1058
1059 @param node AST node to be traversed
1060 @type ast.Node
1061 """
1062 self.__nodeStack.append(node)
1063 self.__nodeWindow.append(node)
1064 self.__nodeWindow = \
1065 self.__nodeWindow[-BugBearVisitor.NodeWindowSize:]
1066
1067 super(BugBearVisitor, self).visit(node)
1068
1069 self.__nodeStack.pop()
1070
1071 def visit_UAdd(self, node):
1072 """
1073 Public method to handle unary additions.
1074
1075 @param node reference to the node to be processed
1076 @type ast.UAdd
1077 """
1078 trailingNodes = list(map(type, self.__nodeWindow[-4:]))
1079 if trailingNodes == [ast.UnaryOp, ast.UAdd, ast.UnaryOp, ast.UAdd]:
1080 originator = self.__nodeWindow[-4]
1081 self.violations.append((originator, "M501"))
1082
1083 self.generic_visit(node)
1084
1085 def visit_Call(self, node):
1086 """
1087 Public method to handle a function call.
1088
1089 @param node reference to the node to be processed
1090 @type ast.Call
1091 """
1092 if sys.version_info >= (3, 0):
1093 validPaths = ("six", "future.utils", "builtins")
1094 methodsDict = {
1095 "M511": ("iterkeys", "itervalues", "iteritems", "iterlists"),
1096 "M512": ("viewkeys", "viewvalues", "viewitems", "viewlists"),
1097 "M513": ("next",),
1098 }
1099 else:
1100 validPaths = ()
1101 methodsDict = {}
1102
1103 if isinstance(node.func, ast.Attribute):
1104 for code, methods in methodsDict.items():
1105 if node.func.attr in methods:
1106 callPath = ".".join(composeCallPath(node.func.value))
1107 if callPath not in validPaths:
1108 self.violations.append((node, code))
1109 break
1110 else:
1111 self.__checkForM502(node)
1112 else:
1113 try:
1114 if (
1115 node.func.id in ("getattr", "hasattr") and
1116 node.args[1].s == "__call__"
1117 ):
1118 self.violations.append((node, "M503"))
1119 except (AttributeError, IndexError):
1120 pass
1121
1122 self.generic_visit(node)
1123
1124 def visit_Attribute(self, node):
1125 """
1126 Public method to handle attributes.
1127
1128 @param node reference to the node to be processed
1129 @type ast.Attribute
1130 """
1131 callPath = list(composeCallPath(node))
1132
1133 if '.'.join(callPath) == 'sys.maxint' and sys.version_info >= (3, 0):
1134 self.violations.append((node, "M504"))
1135
1136 elif len(callPath) == 2 and callPath[1] == 'message' and \
1137 sys.version_info >= (2, 6):
1138 name = callPath[0]
1139 for elem in reversed(self.__nodeStack[:-1]):
1140 if isinstance(elem, ast.ExceptHandler) and elem.name == name:
1141 self.violations.append((node, "M505"))
1142 break
1143
1144 def __checkForM502(self, node):
1145 """
1146 Private method to check the use of *strip().
1147
1148 @param node reference to the node to be processed
1149 @type ast.Call
1150 """
1151 if node.func.attr not in ("lstrip", "rstrip", "strip"):
1152 return # method name doesn't match
1153
1154 if len(node.args) != 1 or not isinstance(node.args[0], ast.Str):
1155 return # used arguments don't match the builtin strip
1156
1157 s = node.args[0].s
1158 if len(s) == 1:
1159 return # stripping just one character
1160
1161 if len(s) == len(set(s)):
1162 return # no characters appear more than once
1163
1164 self.violations.append((node, "M502"))
1165
982 # 1166 #
983 # eflag: noqa = M702 1167 # eflag: noqa = M702

eric ide

mercurial