eric6/Plugins/CheckerPlugins/CodeStyleChecker/Simplify/SimplifyNodeVisitor.py

changeset 8195
db7f2badd374
parent 8194
b925628bf91f
child 8202
df194f43119c
equal deleted inserted replaced
8194:b925628bf91f 8195:db7f2badd374
7 Module implementing a node visitor checking for code that could be simplified. 7 Module implementing a node visitor checking for code that could be simplified.
8 """ 8 """
9 9
10 import ast 10 import ast
11 import collections 11 import collections
12 import copy
12 import itertools 13 import itertools
13 14
14 try: 15 try:
15 from ast import unparse 16 from ast import unparse
16 except ImportError: 17 except ImportError:
84 @param node reference to the BoolOp node 85 @param node reference to the BoolOp node
85 @type ast.BoolOp 86 @type ast.BoolOp
86 """ 87 """
87 self.__check101(node) 88 self.__check101(node)
88 self.__check109(node) 89 self.__check109(node)
90 self.__check221(node)
91 self.__check222(node)
92 self.__check223(node)
93 self.__check224(node)
89 94
90 self.generic_visit(node) 95 self.generic_visit(node)
91 96
92 def visit_If(self, node): 97 def visit_If(self, node):
93 """ 98 """
103 self.__check114(node) 108 self.__check114(node)
104 self.__check116(node) 109 self.__check116(node)
105 110
106 self.generic_visit(node) 111 self.generic_visit(node)
107 112
113 def visit_IfExp(self, node):
114 """
115 Public method to process an IfExp node.
116
117 @param node reference to the IfExp node
118 @type ast.IfExp
119 """
120 self.__check211(node)
121 self.__check212(node)
122 self.__check213(node)
123
124 self.generic_visit(node)
125
108 def visit_For(self, node): 126 def visit_For(self, node):
109 """ 127 """
110 Public method to process a For node. 128 Public method to process a For node.
111 129
112 @param node reference to the For node 130 @param node reference to the For node
159 177
160 @param node reference to the Compare node 178 @param node reference to the Compare node
161 @type ast.Compare 179 @type ast.Compare
162 """ 180 """
163 self.__check118(node) 181 self.__check118(node)
182 self.__check301(node)
164 183
165 self.generic_visit(node) 184 self.generic_visit(node)
166 185
167 def visit_ClassDef(self, node): 186 def visit_ClassDef(self, node):
168 """ 187 """
171 @param node reference to the ClassDef node 190 @param node reference to the ClassDef node
172 @type ast.ClassDef 191 @type ast.ClassDef
173 """ 192 """
174 self.__check119(node) 193 self.__check119(node)
175 self.__check120(node) 194 self.__check120(node)
195
196 self.generic_visit(node)
197
198 def visit_UnaryOp(self, node):
199 """
200 Public method to process a UnaryOp node.
201
202 @param node reference to the UnaryOp node
203 @type ast.UnaryOp
204 """
205 self.__check201(node)
206 self.__check202(node)
207 self.__check203(node)
208 self.__check204(node)
209 self.__check205(node)
210 self.__check206(node)
211 self.__check207(node)
212 self.__check208(node)
176 213
177 self.generic_visit(node) 214 self.generic_visit(node)
178 215
179 ############################################################# 216 #############################################################
180 ## Helper methods for the various checkers below 217 ## Helper methods for the various checkers below
259 statementEqual = self.__isStatementEqual(a, b) 296 statementEqual = self.__isStatementEqual(a, b)
260 except RecursionError: # maximum recursion depth 297 except RecursionError: # maximum recursion depth
261 statementEqual = False 298 statementEqual = False
262 if not statementEqual: 299 if not statementEqual:
263 return False 300 return False
301
264 return True 302 return True
265 303
266 def __isStatementEqual(self, a: ast.stmt, b: ast.stmt) -> bool: 304 def __isSameExpression(self, a, b):
305 """
306 Private method to check, if two expressions are equal.
307
308 @param a first expression to be checked
309 @type ast.expr
310 @param b second expression to be checked
311 @type ast.expr
312 @return flag indicating equal expressions
313 @rtype bool
314 """
315 if isinstance(a, ast.Name) and isinstance(b, ast.Name):
316 return a.id == b.id
317 else:
318 return False
319
320 def __isStatementEqual(self, a, b):
267 """ 321 """
268 Private method to check, if two statements are equal. 322 Private method to check, if two statements are equal.
269 323
270 @param a reference to the first statement 324 @param a reference to the first statement
271 @type ast.stmt 325 @type ast.stmt
287 return True 341 return True
288 elif isinstance(a, list): 342 elif isinstance(a, list):
289 return all(itertools.starmap(self.__isStatementEqual, zip(a, b))) 343 return all(itertools.starmap(self.__isStatementEqual, zip(a, b)))
290 else: 344 else:
291 return a == b 345 return a == b
346
347 def __isExceptionCheck(self, node):
348 """
349 Private method to check, if the node is checking an exception.
350
351 @param node reference to the node to be checked
352 @type ast.If
353 @return flag indicating an exception check
354 @rtype bool
355 """
356 return (
357 len(node.body) == 1 and isinstance(node.body[0], ast.Raise)
358 )
359
360 def __negateTest(self, node):
361 """
362 Private method negate the given Compare node.
363
364 @param node reference to the node to be negated
365 @type ast.Compare
366 @return node with negated logic
367 @rtype ast.Compare
368 """
369 newNode = copy.deepcopy(node)
370 op = newNode.ops[0]
371 if isinstance(op, ast.Eq):
372 op = ast.NotEq()
373 elif isinstance(op, ast.NotEq):
374 op = ast.Eq()
375 elif isinstance(op, ast.Lt):
376 op = ast.GtE()
377 elif isinstance(op, ast.LtE):
378 op = ast.Gt()
379 elif isinstance(op, ast.Gt):
380 op = ast.LtE()
381 elif isinstance(op, ast.GtE):
382 op = ast.Lt()
383 elif isinstance(op, ast.Is):
384 op = ast.IsNot()
385 elif isinstance(op, ast.IsNot):
386 op = ast.Is()
387 elif isinstance(op, ast.In):
388 op = ast.NotIn()
389 elif isinstance(op, ast.NotIn):
390 op = ast.In()
391 newNode.ops = [op]
392 return newNode
292 393
293 ############################################################# 394 #############################################################
294 ## Methods to check for possible code simplifications below 395 ## Methods to check for possible code simplifications below
295 ############################################################# 396 #############################################################
296 397
953 isinstance(node.bases[0], ast.Name) and 1054 isinstance(node.bases[0], ast.Name) and
954 node.bases[0].id == "object" 1055 node.bases[0].id == "object"
955 ): 1056 ):
956 self.__error(node.lineno - 1, node.col_offset, "Y120", 1057 self.__error(node.lineno - 1, node.col_offset, "Y120",
957 node.name) 1058 node.name)
1059
1060 def __check201(self, node):
1061 """
1062 Private method to check for calls where an unary 'not' is used for
1063 an unequality.
1064
1065 @param node reference to the UnaryOp node
1066 @type ast.UnaryOp
1067 """
1068 # not a == b
1069 if not (
1070 (
1071 not isinstance(node.op, ast.Not) or
1072 not isinstance(node.operand, ast.Compare) or
1073 len(node.operand.ops) != 1 or
1074 not isinstance(node.operand.ops[0], ast.Eq)
1075 ) or
1076 isinstance(node.parent, ast.If) and
1077 self.__isExceptionCheck(node.parent)
1078 ):
1079 comparison = node.operand
1080 left = unparse(comparison.left)
1081 right = unparse(comparison.comparators[0])
1082 self.__error(node.lineno - 1, node.col_offset, "Y201",
1083 left, right)
1084
1085 def __check202(self, node):
1086 """
1087 Private method to check for calls where an unary 'not' is used for
1088 an equality.
1089
1090 @param node reference to the UnaryOp node
1091 @type ast.UnaryOp
1092 """
1093 # not a != b
1094 if not (
1095 (
1096 not isinstance(node.op, ast.Not) or
1097 not isinstance(node.operand, ast.Compare) or
1098 len(node.operand.ops) != 1 or
1099 not isinstance(node.operand.ops[0], ast.NotEq)
1100 ) or
1101 isinstance(node.parent, ast.If) and
1102 self.__isExceptionCheck(node.parent)
1103 ):
1104 comparison = node.operand
1105 left = unparse(comparison.left)
1106 right = unparse(comparison.comparators[0])
1107 self.__error(node.lineno - 1, node.col_offset, "Y202",
1108 left, right)
1109
1110 def __check203(self, node):
1111 """
1112 Private method to check for calls where an unary 'not' is used for
1113 an in-check.
1114
1115 @param node reference to the UnaryOp node
1116 @type ast.UnaryOp
1117 """
1118 # not a in b
1119 if not (
1120 (
1121 not isinstance(node.op, ast.Not) or
1122 not isinstance(node.operand, ast.Compare) or
1123 len(node.operand.ops) != 1 or
1124 not isinstance(node.operand.ops[0], ast.In)
1125 ) or
1126 isinstance(node.parent, ast.If) and
1127 self.__isExceptionCheck(node.parent)
1128 ):
1129 comparison = node.operand
1130 left = unparse(comparison.left)
1131 right = unparse(comparison.comparators[0])
1132 self.__error(node.lineno - 1, node.col_offset, "Y203",
1133 left, right)
1134
1135 def __check204(self, node):
1136 """
1137 Private method to check for calls of the type "not (a < b)".
1138
1139 @param node reference to the UnaryOp node
1140 @type ast.UnaryOp
1141 """
1142 # not a < b
1143 if not (
1144 (
1145 not isinstance(node.op, ast.Not) or
1146 not isinstance(node.operand, ast.Compare) or
1147 len(node.operand.ops) != 1 or
1148 not isinstance(node.operand.ops[0], ast.Lt)
1149 ) or
1150 isinstance(node.parent, ast.If) and
1151 self.__isExceptionCheck(node.parent)
1152 ):
1153 comparison = node.operand
1154 left = unparse(comparison.left)
1155 right = unparse(comparison.comparators[0])
1156 self.__error(node.lineno - 1, node.col_offset, "Y204",
1157 left, right)
1158
1159 def __check205(self, node):
1160 """
1161 Private method to check for calls of the type "not (a <= b)".
1162
1163 @param node reference to the UnaryOp node
1164 @type ast.UnaryOp
1165 """
1166 # not a <= b
1167 if not (
1168 (
1169 not isinstance(node.op, ast.Not) or
1170 not isinstance(node.operand, ast.Compare) or
1171 len(node.operand.ops) != 1 or
1172 not isinstance(node.operand.ops[0], ast.LtE)
1173 ) or
1174 isinstance(node.parent, ast.If) and
1175 self.__isExceptionCheck(node.parent)
1176 ):
1177 comparison = node.operand
1178 left = unparse(comparison.left)
1179 right = unparse(comparison.comparators[0])
1180 self.__error(node.lineno - 1, node.col_offset, "Y205",
1181 left, right)
1182
1183 def __check206(self, node):
1184 """
1185 Private method to check for calls of the type "not (a > b)".
1186
1187 @param node reference to the UnaryOp node
1188 @type ast.UnaryOp
1189 """
1190 # not a > b
1191 if not (
1192 (
1193 not isinstance(node.op, ast.Not) or
1194 not isinstance(node.operand, ast.Compare) or
1195 len(node.operand.ops) != 1 or
1196 not isinstance(node.operand.ops[0], ast.Gt)
1197 ) or
1198 isinstance(node.parent, ast.If) and
1199 self.__isExceptionCheck(node.parent)
1200 ):
1201 comparison = node.operand
1202 left = unparse(comparison.left)
1203 right = unparse(comparison.comparators[0])
1204 self.__error(node.lineno - 1, node.col_offset, "Y206",
1205 left, right)
1206
1207 def __check207(self, node):
1208 """
1209 Private method to check for calls of the type "not (a >= b)".
1210
1211 @param node reference to the UnaryOp node
1212 @type ast.UnaryOp
1213 """
1214 # not a >= b
1215 if not (
1216 (
1217 not isinstance(node.op, ast.Not) or
1218 not isinstance(node.operand, ast.Compare) or
1219 len(node.operand.ops) != 1 or
1220 not isinstance(node.operand.ops[0], ast.GtE)
1221 ) or
1222 isinstance(node.parent, ast.If) and
1223 self.__isExceptionCheck(node.parent)
1224 ):
1225 comparison = node.operand
1226 left = unparse(comparison.left)
1227 right = unparse(comparison.comparators[0])
1228 self.__error(node.lineno - 1, node.col_offset, "Y207",
1229 left, right)
1230
1231 def __check208(self, node):
1232 """
1233 Private method to check for calls of the type "not (not a)".
1234
1235 @param node reference to the UnaryOp node
1236 @type ast.UnaryOp
1237 """
1238 # not (not a)
1239 if (
1240 isinstance(node.op, ast.Not) and
1241 isinstance(node.operand, ast.UnaryOp) and
1242 isinstance(node.operand.op, ast.Not)
1243 ):
1244 var = unparse(node.operand.operand)
1245 self.__error(node.lineno - 1, node.col_offset, "Y208", var)
1246
1247 def __check211(self, node):
1248 """
1249 Private method to check for calls of the type "True if a else False".
1250
1251 @param node reference to the AST node to be checked
1252 @type ast.IfExp
1253 """
1254 # True if a else False
1255 if (
1256 isinstance(node.body, BOOL_CONST_TYPES) and
1257 node.body.value is True and
1258 isinstance(node.orelse, BOOL_CONST_TYPES) and
1259 node.orelse.value is False
1260 ):
1261 cond = unparse(node.test)
1262 if isinstance(node.test, ast.Name):
1263 newCond = "bool({0})".format(cond)
1264 else:
1265 newCond = cond
1266 self.__error(node.lineno - 1, node.col_offset, "Y211",
1267 cond, newCond)
1268
1269 def __check212(self, node):
1270 """
1271 Private method to check for calls of the type "False if a else True".
1272
1273 @param node reference to the AST node to be checked
1274 @type ast.IfExp
1275 """
1276 # False if a else True
1277 if (
1278 isinstance(node.body, BOOL_CONST_TYPES) and
1279 node.body.value is False and
1280 isinstance(node.orelse, BOOL_CONST_TYPES) and
1281 node.orelse.value is True
1282 ):
1283 cond = unparse(node.test)
1284 if isinstance(node.test, ast.Name):
1285 newCond = "not {0}".format(cond)
1286 else:
1287 if len(node.test.ops) == 1:
1288 newCond = unparse(self.__negateTest(node.test))
1289 else:
1290 newCond = "not ({0})".format(cond)
1291 self.__error(node.lineno - 1, node.col_offset, "Y212",
1292 cond, newCond)
1293
1294 def __check213(self, node):
1295 """
1296 Private method to check for calls of the type "b if not a else a".
1297
1298 @param node reference to the AST node to be checked
1299 @type ast.IfExp
1300 """
1301 # b if not a else a
1302 if (
1303 isinstance(node.test, ast.UnaryOp) and
1304 isinstance(node.test.op, ast.Not) and
1305 self.__isSameExpression(node.test.operand, node.orelse)
1306 ):
1307 a = unparse(node.test.operand)
1308 b = unparse(node.body)
1309 self.__error(node.lineno - 1, node.col_offset, "Y213", a, b)
1310
1311 def __check221(self, node):
1312 """
1313 Private method to check for calls of the type "a and not a".
1314
1315 @param node reference to the AST node to be checked
1316 @type ast.BoolOp
1317 """
1318 # a and not a
1319 if (
1320 isinstance(node.op, ast.And) and
1321 len(node.values) >= 2
1322 ):
1323 # We have a boolean And. Let's make sure there is two times the
1324 # same expression, but once with a "not"
1325 negatedExpressions = []
1326 nonNegatedExpressions = []
1327 for exp in node.values:
1328 if (
1329 isinstance(exp, ast.UnaryOp) and
1330 isinstance(exp.op, ast.Not)
1331 ):
1332 negatedExpressions.append(exp.operand)
1333 else:
1334 nonNegatedExpressions.append(exp)
1335 for negatedExpression in negatedExpressions:
1336 for nonNegatedExpression in nonNegatedExpressions:
1337 if self.__isSameExpression(
1338 negatedExpression, nonNegatedExpression
1339 ):
1340 negExp = unparse(negatedExpression)
1341 self.__error(node.lineno - 1, node.col_offset, "Y221",
1342 negExp)
1343
1344 def __check222(self, node):
1345 """
1346 Private method to check for calls of the type "a or not a".
1347
1348 @param node reference to the AST node to be checked
1349 @type ast.BoolOp
1350 """
1351 # a or not a
1352 if (
1353 isinstance(node.op, ast.Or) and
1354 len(node.values) >= 2
1355 ):
1356 # We have a boolean And. Let's make sure there is two times the
1357 # same expression, but once with a "not"
1358 negatedExpressions = []
1359 nonNegatedExpressions = []
1360 for exp in node.values:
1361 if (
1362 isinstance(exp, ast.UnaryOp) and
1363 isinstance(exp.op, ast.Not)
1364 ):
1365 negatedExpressions.append(exp.operand)
1366 else:
1367 nonNegatedExpressions.append(exp)
1368 for negatedExpression in negatedExpressions:
1369 for nonNegatedExpression in nonNegatedExpressions:
1370 if self.__isSameExpression(
1371 negatedExpression, nonNegatedExpression
1372 ):
1373 negExp = unparse(negatedExpression)
1374 self.__error(node.lineno - 1, node.col_offset, "Y222",
1375 negExp)
1376
1377 def __check223(self, node):
1378 """
1379 Private method to check for calls of the type "... or True".
1380
1381 @param node reference to the AST node to be checked
1382 @type ast.BoolOp
1383 """
1384 # a or True
1385 if isinstance(node.op, ast.Or):
1386 for exp in node.values:
1387 if isinstance(exp, BOOL_CONST_TYPES) and exp.value is True:
1388 self.__error(node.lineno - 1, node.col_offset, "Y223")
1389
1390 def __check224(self, node):
1391 """
1392 Private method to check for calls of the type "... and False".
1393
1394 @param node reference to the AST node to be checked
1395 @type ast.BoolOp
1396 """
1397 # a and False
1398 if isinstance(node.op, ast.And):
1399 for exp in node.values:
1400 if isinstance(exp, BOOL_CONST_TYPES) and exp.value is False:
1401 self.__error(node.lineno - 1, node.col_offset, "Y224")
1402
1403 def __check301(self, node):
1404 """
1405 Private method to check for Yoda conditions.
1406
1407 @param node reference to the AST node to be checked
1408 @type ast.Compare
1409 """
1410 # 42 == age
1411 if (
1412 isinstance(node.left, AST_CONST_TYPES) and
1413 len(node.ops) == 1 and
1414 isinstance(node.ops[0], ast.Eq)
1415 ):
1416 left = unparse(node.left)
1417 isPy37Str = isinstance(node.left, ast.Str)
1418 isPy38Str = (
1419 isinstance(node.left, ast.Constant) and
1420 isinstance(node.left.value, str)
1421 )
1422 if isPy37Str or isPy38Str:
1423 left = f"'{left}'"
1424 right = unparse(node.comparators[0])
1425 self.__error(node.lineno - 1, node.col_offset, "Y301",
1426 left, right)
958 1427
959 # 1428 #
960 # eflag: noqa = M891 1429 # eflag: noqa = M891

eric ide

mercurial