1287 @return dictionary containing the names as keys and the list of nodes |
1301 @return dictionary containing the names as keys and the list of nodes |
1288 @rtype dict |
1302 @rtype dict |
1289 """ |
1303 """ |
1290 return self.__names |
1304 return self.__names |
1291 |
1305 |
|
1306 |
|
1307 class ReturnVisitor(ast.NodeVisitor): |
|
1308 """ |
|
1309 Class implementing a node visitor to check return statements. |
|
1310 """ |
|
1311 Assigns = 'assigns' |
|
1312 Refs = 'refs' |
|
1313 Returns = 'returns' |
|
1314 |
|
1315 def __init__(self): |
|
1316 """ |
|
1317 Constructor |
|
1318 """ |
|
1319 super(ReturnVisitor, self).__init__() |
|
1320 |
|
1321 self.__stack = [] |
|
1322 self.violations = [] |
|
1323 |
|
1324 @property |
|
1325 def assigns(self): |
|
1326 """ |
|
1327 Public method to get the Assign nodes. |
|
1328 |
|
1329 @return dictionary containing the node name as key and line number |
|
1330 as value |
|
1331 @rtype dict |
|
1332 """ |
|
1333 return self.__stack[-1][ReturnVisitor.Assigns] |
|
1334 |
|
1335 @property |
|
1336 def refs(self): |
|
1337 """ |
|
1338 Public method to get the References nodes. |
|
1339 |
|
1340 @return dictionary containing the node name as key and line number |
|
1341 as value |
|
1342 @rtype dict |
|
1343 """ |
|
1344 return self.__stack[-1][ReturnVisitor.Refs] |
|
1345 |
|
1346 @property |
|
1347 def returns(self): |
|
1348 """ |
|
1349 Public method to get the Return nodes. |
|
1350 |
|
1351 @return dictionary containing the node name as key and line number |
|
1352 as value |
|
1353 @rtype dict |
|
1354 """ |
|
1355 return self.__stack[-1][ReturnVisitor.Returns] |
|
1356 |
|
1357 def __visitWithStack(self, node): |
|
1358 """ |
|
1359 Private method to traverse a given function node using a stack. |
|
1360 |
|
1361 @param node AST node to be traversed |
|
1362 @type ast.FunctionDef or ast.AsyncFunctionDef |
|
1363 """ |
|
1364 self.__stack.append({ |
|
1365 ReturnVisitor.Assigns: defaultdict(list), |
|
1366 ReturnVisitor.Refs: defaultdict(list), |
|
1367 ReturnVisitor.Returns: [] |
|
1368 }) |
|
1369 |
|
1370 self.generic_visit(node) |
|
1371 self.__checkFunction(node) |
|
1372 self.__stack.pop() |
|
1373 |
|
1374 def visit_FunctionDef(self, node): |
|
1375 """ |
|
1376 Public method to handle a function definition. |
|
1377 |
|
1378 @param node reference to the node to handle |
|
1379 @type ast.FunctionDef |
|
1380 """ |
|
1381 self.__visitWithStack(node) |
|
1382 |
|
1383 def visit_AsyncFunctionDef(self, node): |
|
1384 """ |
|
1385 Public method to handle a function definition. |
|
1386 |
|
1387 @param node reference to the node to handle |
|
1388 @type ast.AsyncFunctionDef |
|
1389 """ |
|
1390 self.__visitWithStack(node) |
|
1391 |
|
1392 def visit_Return(self, node): |
|
1393 """ |
|
1394 Public method to handle a return node. |
|
1395 |
|
1396 @param node reference to the node to handle |
|
1397 @type ast.Return |
|
1398 """ |
|
1399 self.returns.append(node) |
|
1400 self.generic_visit(node) |
|
1401 |
|
1402 def visit_Assign(self, node): |
|
1403 """ |
|
1404 Public method to handle an assign node. |
|
1405 |
|
1406 @param node reference to the node to handle |
|
1407 @type ast.Assign |
|
1408 """ |
|
1409 if not self.__stack: |
|
1410 return |
|
1411 |
|
1412 for target in node.targets: |
|
1413 self.__visitAssignTarget(target) |
|
1414 self.generic_visit(node.value) |
|
1415 |
|
1416 def visit_Name(self, node): |
|
1417 """ |
|
1418 Public method to handle a name node. |
|
1419 |
|
1420 @param node reference to the node to handle |
|
1421 @type ast.Name |
|
1422 """ |
|
1423 if self.__stack: |
|
1424 self.refs[node.id].append(node.lineno) |
|
1425 |
|
1426 def __visitAssignTarget(self, node): |
|
1427 """ |
|
1428 Private method to handle an assign target node. |
|
1429 |
|
1430 @param node reference to the node to handle |
|
1431 @type ast.AST |
|
1432 """ |
|
1433 if isinstance(node, ast.Tuple): |
|
1434 for elt in node.elts: |
|
1435 self.__visitAssignTarget(elt) |
|
1436 return |
|
1437 |
|
1438 if isinstance(node, ast.Name): |
|
1439 self.assigns[node.id].append(node.lineno) |
|
1440 return |
|
1441 |
|
1442 self.generic_visit(node) |
|
1443 |
|
1444 def __checkFunction(self, node): |
|
1445 """ |
|
1446 Private method to check a function definition node. |
|
1447 |
|
1448 @param node reference to the node to check |
|
1449 @type ast.AsyncFunctionDef or ast.FunctionDef |
|
1450 """ |
|
1451 if not self.returns or not node.body: |
|
1452 return |
|
1453 |
|
1454 if len(node.body) == 1 and isinstance(node.body[-1], ast.Return): |
|
1455 # skip functions that consist of `return None` only |
|
1456 return |
|
1457 |
|
1458 if not self.__resultExists(): |
|
1459 self.__checkUnnecessaryReturnNone() |
|
1460 return |
|
1461 |
|
1462 self.__checkImplicitReturnValue() |
|
1463 self.__checkImplicitReturn(node.body[-1]) |
|
1464 |
|
1465 for n in self.returns: |
|
1466 if n.value: |
|
1467 self.__checkUnnecessaryAssign(n.value) |
|
1468 |
|
1469 def __isNone(self, node): |
|
1470 """ |
|
1471 Private method to check, if a node value is None. |
|
1472 |
|
1473 @param node reference to the node to check |
|
1474 @type ast.AST |
|
1475 @return flag indicating the node contains a None value |
|
1476 """ |
|
1477 return isinstance(node, ast.NameConstant) and node.value is None |
|
1478 |
|
1479 def __resultExists(self): |
|
1480 """ |
|
1481 Private method to check the existance of a return result. |
|
1482 |
|
1483 @return flag indicating the existence of a return result |
|
1484 @rtype bool |
|
1485 """ |
|
1486 for node in self.returns: |
|
1487 value = node.value |
|
1488 if value and not self.__isNone(value): |
|
1489 return True |
|
1490 |
|
1491 return False |
|
1492 |
|
1493 def __checkImplicitReturnValue(self): |
|
1494 """ |
|
1495 Private method to check for implicit return values. |
|
1496 """ |
|
1497 for node in self.returns: |
|
1498 if not node.value: |
|
1499 self.violations.append((node, "M832")) |
|
1500 |
|
1501 def __checkUnnecessaryReturnNone(self): |
|
1502 """ |
|
1503 Private method to check for an unnecessary 'return None' statement. |
|
1504 """ |
|
1505 for node in self.returns: |
|
1506 if self.__isNone(node.value): |
|
1507 self.violations.append((node, "M831")) |
|
1508 |
|
1509 def __checkImplicitReturn(self, node): |
|
1510 """ |
|
1511 Private method to check for an implicit return statement. |
|
1512 |
|
1513 @param node reference to the node to check |
|
1514 @type ast.AST |
|
1515 """ |
|
1516 if isinstance(node, ast.If): |
|
1517 if not node.body or not node.orelse: |
|
1518 self.violations.append((node, "M833")) |
|
1519 return |
|
1520 |
|
1521 self.__checkImplicitReturn(node.body[-1]) |
|
1522 self.__checkImplicitReturn(node.orelse[-1]) |
|
1523 return |
|
1524 |
|
1525 if isinstance(node, ast.For) and node.orelse: |
|
1526 self.__checkImplicitReturn(node.orelse[-1]) |
|
1527 return |
|
1528 |
|
1529 if isinstance(node, ast.With): |
|
1530 self.__checkImplicitReturn(node.body[-1]) |
|
1531 return |
|
1532 |
|
1533 if not isinstance(node, |
|
1534 (ast.Return, ast.Raise, ast.While, ast.Try)): |
|
1535 self.violations.append((node, "M833")) |
|
1536 |
|
1537 def __checkUnnecessaryAssign(self, node): |
|
1538 """ |
|
1539 Private method to check for an unnecessary assign statement. |
|
1540 |
|
1541 @param node reference to the node to check |
|
1542 @type ast.AST |
|
1543 """ |
|
1544 if not isinstance(node, ast.Name): |
|
1545 return |
|
1546 |
|
1547 varname = node.id |
|
1548 returnLineno = node.lineno |
|
1549 |
|
1550 if varname not in self.assigns: |
|
1551 return |
|
1552 |
|
1553 if varname not in self.refs: |
|
1554 self.violations.append((node, "M834")) |
|
1555 return |
|
1556 |
|
1557 if self.__hasRefsBeforeNextAssign(varname, returnLineno): |
|
1558 return |
|
1559 |
|
1560 self.violations.append((node, "M834")) |
|
1561 |
|
1562 def __hasRefsBeforeNextAssign(self, varname, returnLineno): |
|
1563 """ |
|
1564 Private method to check for references before a following assign |
|
1565 statement. |
|
1566 |
|
1567 @param varname variable name to check for |
|
1568 @type str |
|
1569 @param returnLineno line number of the return statement |
|
1570 @type int |
|
1571 @return flag indicating the existence of references |
|
1572 @rtype bool |
|
1573 """ |
|
1574 beforeAssign = 0 |
|
1575 afterAssign = None |
|
1576 |
|
1577 for lineno in sorted(self.assigns[varname]): |
|
1578 if lineno > returnLineno: |
|
1579 afterAssign = lineno |
|
1580 break |
|
1581 |
|
1582 if lineno <= returnLineno: |
|
1583 beforeAssign = lineno |
|
1584 |
|
1585 for lineno in self.refs[varname]: |
|
1586 if lineno == returnLineno: |
|
1587 continue |
|
1588 |
|
1589 if afterAssign: |
|
1590 if beforeAssign < lineno <= afterAssign: |
|
1591 return True |
|
1592 |
|
1593 elif beforeAssign < lineno: |
|
1594 return True |
|
1595 |
|
1596 return False |
1292 # |
1597 # |
1293 # eflag: noqa = M702 |
1598 # eflag: noqa = M702 |