1486 self.violations.append((node, "M654")) |
1519 self.violations.append((node, "M654")) |
1487 |
1520 |
1488 super().generic_visit(node) |
1521 super().generic_visit(node) |
1489 |
1522 |
1490 |
1523 |
|
1524 BugBearContext = namedtuple("BugBearContext", ["node", "stack"]) |
|
1525 |
|
1526 |
1491 class BugBearVisitor(ast.NodeVisitor): |
1527 class BugBearVisitor(ast.NodeVisitor): |
1492 """ |
1528 """ |
1493 Class implementing a node visitor to check for various topics. |
1529 Class implementing a node visitor to check for various topics. |
1494 """ |
1530 """ |
1495 |
1531 |
1496 # |
1532 # |
1497 # This class was implemented along the BugBear flake8 extension (v 19.3.0). |
1533 # This class was implemented along the BugBear flake8 extension (v 22.9.11). |
1498 # Original: Copyright (c) 2016 Łukasz Langa |
1534 # Original: Copyright (c) 2016 Łukasz Langa |
1499 # |
1535 # |
1500 # TODO: update to v22.7.1 |
1536 |
|
1537 CONTEXTFUL_NODES = ( |
|
1538 ast.Module, |
|
1539 ast.ClassDef, |
|
1540 ast.AsyncFunctionDef, |
|
1541 ast.FunctionDef, |
|
1542 ast.Lambda, |
|
1543 ast.ListComp, |
|
1544 ast.SetComp, |
|
1545 ast.DictComp, |
|
1546 ast.GeneratorExp, |
|
1547 ) |
|
1548 |
|
1549 FUNCTION_NODES = ( |
|
1550 ast.AsyncFunctionDef, |
|
1551 ast.FunctionDef, |
|
1552 ast.Lambda, |
|
1553 ) |
1501 |
1554 |
1502 NodeWindowSize = 4 |
1555 NodeWindowSize = 4 |
1503 |
1556 |
1504 def __init__(self): |
1557 def __init__(self): |
1505 """ |
1558 """ |
1506 Constructor |
1559 Constructor |
1507 """ |
1560 """ |
1508 super().__init__() |
1561 super().__init__() |
1509 |
1562 |
1510 self.__nodeStack = [] |
1563 self.nodeWindow = [] |
1511 self.__nodeWindow = [] |
|
1512 self.violations = [] |
1564 self.violations = [] |
|
1565 self.contexts = [] |
|
1566 |
|
1567 self.__M523Seen = set() |
|
1568 |
|
1569 @property |
|
1570 def nodeStack(self): |
|
1571 """ |
|
1572 Public method to get a reference to the most recent node stack. |
|
1573 |
|
1574 @return reference to the most recent node stack |
|
1575 @rtype list |
|
1576 """ |
|
1577 if len(self.contexts) == 0: |
|
1578 return [] |
|
1579 |
|
1580 context, stack = self.contexts[-1] |
|
1581 return stack |
|
1582 |
|
1583 def __isIdentifier(self, arg): |
|
1584 """ |
|
1585 Private method to check if arg is a valid identifier. |
|
1586 |
|
1587 See https://docs.python.org/2/reference/lexical_analysis.html#identifiers |
|
1588 |
|
1589 @param arg reference to an argument node |
|
1590 @type ast.Node |
|
1591 @return flag indicating a valid identifier |
|
1592 @rtype TYPE |
|
1593 """ |
|
1594 if not AstUtilities.isString(arg): |
|
1595 return False |
|
1596 |
|
1597 return ( |
|
1598 re.match(r"^[A-Za-z_][A-Za-z0-9_]*$", AstUtilities.getValue(arg)) |
|
1599 is not None |
|
1600 ) |
|
1601 |
|
1602 def __composeCallPath(self, node): |
|
1603 """ |
|
1604 Private method get the individual elements of the call path of a node. |
|
1605 |
|
1606 @param node reference to the node |
|
1607 @type ast.Node |
|
1608 @yield one element of the call path |
|
1609 @ytype ast.Node |
|
1610 """ |
|
1611 if isinstance(node, ast.Attribute): |
|
1612 yield from self.__composeCallPath(node.value) |
|
1613 yield node.attr |
|
1614 elif isinstance(node, ast.Call): |
|
1615 yield from self.__composeCallPath(node.func) |
|
1616 elif isinstance(node, ast.Name): |
|
1617 yield node.id |
|
1618 |
|
1619 def __toNameStr(self, node): |
|
1620 """ |
|
1621 Private method to turn Name and Attribute nodes to strings, handling any |
|
1622 depth of attribute accesses. |
|
1623 |
|
1624 |
|
1625 @param node reference to the node |
|
1626 @type ast.Name or ast.Attribute |
|
1627 @return string representation |
|
1628 @rtype str |
|
1629 """ |
|
1630 if isinstance(node, ast.Name): |
|
1631 return node.id |
|
1632 |
|
1633 if isinstance(node, ast.Call): |
|
1634 return self.__toNameStr(node.func) |
|
1635 |
|
1636 try: |
|
1637 return self.__toNameStr(node.value) + "." + node.attr |
|
1638 except AttributeError: |
|
1639 return self.__toNameStr(node.value) |
|
1640 |
|
1641 def __typesafeIssubclass(self, obj, classOrTuple): |
|
1642 """ |
|
1643 Private method implementing a type safe issubclass() function. |
|
1644 |
|
1645 @param obj reference to the object to be tested |
|
1646 @type any |
|
1647 @param classOrTuple type to check against |
|
1648 @type type |
|
1649 @return flag indicating a subclass |
|
1650 @rtype bool |
|
1651 """ |
|
1652 try: |
|
1653 return issubclass(obj, classOrTuple) |
|
1654 except TypeError: |
|
1655 # User code specifies a type that is not a type in our current run. |
|
1656 # Might be their error, might be a difference in our environments. |
|
1657 # We don't know so we ignore this. |
|
1658 return False |
|
1659 |
|
1660 def __getAssignedNames(self, loopNode): |
|
1661 """ |
|
1662 Private method to get the names of a for loop. |
|
1663 |
|
1664 @param loopNode reference to the node to be processed |
|
1665 @type ast.For |
|
1666 @yield DESCRIPTION |
|
1667 @ytype TYPE |
|
1668 """ |
|
1669 loopTargets = (ast.For, ast.AsyncFor, ast.comprehension) |
|
1670 for node in self.__childrenInScope(loopNode): |
|
1671 if isinstance(node, (ast.Assign)): |
|
1672 for child in node.targets: |
|
1673 yield from self.__namesFromAssignments(child) |
|
1674 if isinstance(node, loopTargets + (ast.AnnAssign, ast.AugAssign)): |
|
1675 yield from self.__namesFromAssignments(node.target) |
|
1676 |
|
1677 def __namesFromAssignments(self, assignTarget): |
|
1678 """ |
|
1679 Private method to get names of an assignment. |
|
1680 |
|
1681 @param assignTarget reference to the node to be processed |
|
1682 @type ast.Node |
|
1683 @yield name of the assignment |
|
1684 @ytype str |
|
1685 """ |
|
1686 if isinstance(assignTarget, ast.Name): |
|
1687 yield assignTarget.id |
|
1688 elif isinstance(assignTarget, ast.Starred): |
|
1689 yield from self.__namesFromAssignments(assignTarget.value) |
|
1690 elif isinstance(assignTarget, (ast.List, ast.Tuple)): |
|
1691 for child in assignTarget.elts: |
|
1692 yield from self.__namesFromAssignments(child) |
|
1693 |
|
1694 def __childrenInScope(self, node): |
|
1695 """ |
|
1696 Private method to get all child nodes in the given scope. |
|
1697 |
|
1698 @param node reference to the node to be processed |
|
1699 @type ast.Node |
|
1700 @yield reference to a child node |
|
1701 @ytype ast.Node |
|
1702 """ |
|
1703 yield node |
|
1704 if not isinstance(node, BugBearVisitor.FUNCTION_NODES): |
|
1705 for child in ast.iter_child_nodes(node): |
|
1706 yield from self.__childrenInScope(child) |
1513 |
1707 |
1514 def visit(self, node): |
1708 def visit(self, node): |
1515 """ |
1709 """ |
1516 Public method to traverse a given AST node. |
1710 Public method to traverse a given AST node. |
1517 |
1711 |
1518 @param node AST node to be traversed |
1712 @param node AST node to be traversed |
1519 @type ast.Node |
1713 @type ast.Node |
1520 """ |
1714 """ |
1521 self.__nodeStack.append(node) |
1715 isContextful = isinstance(node, BugBearVisitor.CONTEXTFUL_NODES) |
1522 self.__nodeWindow.append(node) |
1716 |
1523 self.__nodeWindow = self.__nodeWindow[-BugBearVisitor.NodeWindowSize :] |
1717 if isContextful: |
|
1718 context = BugBearContext(node, []) |
|
1719 self.contexts.append(context) |
|
1720 |
|
1721 self.nodeStack.append(node) |
|
1722 self.nodeWindow.append(node) |
|
1723 self.nodeWindow = self.nodeWindow[-BugBearVisitor.NodeWindowSize :] |
1524 |
1724 |
1525 super().visit(node) |
1725 super().visit(node) |
1526 |
1726 |
1527 self.__nodeStack.pop() |
1727 self.nodeStack.pop() |
|
1728 |
|
1729 if isContextful: |
|
1730 self.contexts.pop() |
|
1731 |
|
1732 def visit_ExceptHandler(self, node): |
|
1733 """ |
|
1734 Public method to handle exception handlers. |
|
1735 |
|
1736 @param node reference to the node to be processed |
|
1737 @type ast.ExceptHandler |
|
1738 """ |
|
1739 redundantExceptions = { |
|
1740 "OSError": { |
|
1741 # All of these are actually aliases of OSError since Python 3.3 |
|
1742 "IOError", |
|
1743 "EnvironmentError", |
|
1744 "WindowsError", |
|
1745 "mmap.error", |
|
1746 "socket.error", |
|
1747 "select.error", |
|
1748 }, |
|
1749 "ValueError": { |
|
1750 "binascii.Error", |
|
1751 }, |
|
1752 } |
|
1753 |
|
1754 if node.type is None: |
|
1755 # bare except is handled by pycodestyle already |
|
1756 pass |
|
1757 |
|
1758 elif isinstance(node.type, ast.Tuple): |
|
1759 names = [self.__toNameStr(e) for e in node.type.elts] |
|
1760 as_ = " as " + node.name if node.name is not None else "" |
|
1761 if len(names) == 0: |
|
1762 self.violations.append((node, "M501", as_)) |
|
1763 elif len(names) == 1: |
|
1764 self.violations.append((node, "M513", *names)) |
|
1765 else: |
|
1766 # See if any of the given exception names could be removed, e.g. from: |
|
1767 # (MyError, MyError) # duplicate names |
|
1768 # (MyError, BaseException) # everything derives from the Base |
|
1769 # (Exception, TypeError) # builtins where one subclasses another |
|
1770 # (IOError, OSError) # IOError is an alias of OSError since Python3.3 |
|
1771 # but note that other cases are impractical to handle from the AST. |
|
1772 # We expect this is mostly useful for users who do not have the |
|
1773 # builtin exception hierarchy memorised, and include a 'shadowed' |
|
1774 # subtype without realising that it's redundant. |
|
1775 good = sorted(set(names), key=names.index) |
|
1776 if "BaseException" in good: |
|
1777 good = ["BaseException"] |
|
1778 # Remove redundant exceptions that the automatic system either handles |
|
1779 # poorly (usually aliases) or can't be checked (e.g. it's not an |
|
1780 # built-in exception). |
|
1781 for primary, equivalents in redundantExceptions.items(): |
|
1782 if primary in good: |
|
1783 good = [g for g in good if g not in equivalents] |
|
1784 |
|
1785 for name, other in itertools.permutations(tuple(good), 2): |
|
1786 if ( |
|
1787 self.__typesafeIssubclass( |
|
1788 getattr(builtins, name, type), getattr(builtins, other, ()) |
|
1789 ) |
|
1790 and name in good |
|
1791 ): |
|
1792 good.remove(name) |
|
1793 if good != names: |
|
1794 desc = ( |
|
1795 good[0] if len(good) == 1 else "({0})".format(", ".join(good)) |
|
1796 ) |
|
1797 self.violations.append((node, "M514", ", ".join(names), as_, desc)) |
|
1798 |
|
1799 self.generic_visit(node) |
1528 |
1800 |
1529 def visit_UAdd(self, node): |
1801 def visit_UAdd(self, node): |
1530 """ |
1802 """ |
1531 Public method to handle unary additions. |
1803 Public method to handle unary additions. |
1532 |
1804 |
1533 @param node reference to the node to be processed |
1805 @param node reference to the node to be processed |
1534 @type ast.UAdd |
1806 @type ast.UAdd |
1535 """ |
1807 """ |
1536 trailingNodes = list(map(type, self.__nodeWindow[-4:])) |
1808 trailingNodes = list(map(type, self.nodeWindow[-4:])) |
1537 if trailingNodes == [ast.UnaryOp, ast.UAdd, ast.UnaryOp, ast.UAdd]: |
1809 if trailingNodes == [ast.UnaryOp, ast.UAdd, ast.UnaryOp, ast.UAdd]: |
1538 originator = self.__nodeWindow[-4] |
1810 originator = self.nodeWindow[-4] |
1539 self.violations.append((originator, "M501")) |
1811 self.violations.append((originator, "M502")) |
1540 |
1812 |
1541 self.generic_visit(node) |
1813 self.generic_visit(node) |
1542 |
1814 |
1543 def visit_Call(self, node): |
1815 def visit_Call(self, node): |
1544 """ |
1816 """ |
1545 Public method to handle a function call. |
1817 Public method to handle a function call. |
1546 |
1818 |
1547 @param node reference to the node to be processed |
1819 @param node reference to the node to be processed |
1548 @type ast.Call |
1820 @type ast.Call |
1549 """ |
1821 """ |
1550 validPaths = ("six", "future.utils", "builtins") |
|
1551 methodsDict = { |
|
1552 "M521": ("iterkeys", "itervalues", "iteritems", "iterlists"), |
|
1553 "M522": ("viewkeys", "viewvalues", "viewitems", "viewlists"), |
|
1554 "M523": ("next",), |
|
1555 } |
|
1556 |
|
1557 if isinstance(node.func, ast.Attribute): |
1822 if isinstance(node.func, ast.Attribute): |
1558 for code, methods in methodsDict.items(): |
1823 self.__checkForM505(node) |
1559 if node.func.attr in methods: |
|
1560 callPath = ".".join(composeCallPath(node.func.value)) |
|
1561 if callPath not in validPaths: |
|
1562 self.violations.append((node, code)) |
|
1563 break |
|
1564 else: |
|
1565 self.__checkForM502(node) |
|
1566 else: |
1824 else: |
1567 with contextlib.suppress(AttributeError, IndexError): |
1825 with contextlib.suppress(AttributeError, IndexError): |
1568 # bad super() call |
1826 # bad super() call |
1569 if isinstance(node.func, ast.Name) and node.func.id == "super": |
1827 if isinstance(node.func, ast.Name) and node.func.id == "super": |
1570 args = node.args |
1828 args = node.args |
1573 and isinstance(args[0], ast.Attribute) |
1831 and isinstance(args[0], ast.Attribute) |
1574 and isinstance(args[0].value, ast.Name) |
1832 and isinstance(args[0].value, ast.Name) |
1575 and args[0].value.id == "self" |
1833 and args[0].value.id == "self" |
1576 and args[0].attr == "__class__" |
1834 and args[0].attr == "__class__" |
1577 ): |
1835 ): |
1578 self.violations.append((node, "M509")) |
1836 self.violations.append((node, "M582")) |
1579 |
1837 |
1580 # bad getattr and setattr |
1838 # bad getattr and setattr |
1581 if ( |
1839 if ( |
1582 node.func.id in ("getattr", "hasattr") |
1840 node.func.id in ("getattr", "hasattr") |
1583 and node.args[1].s == "__call__" |
1841 and node.args[1].s == "__call__" |
1584 ): |
1842 ): |
1585 self.violations.append((node, "M511")) |
1843 self.violations.append((node, "M504")) |
1586 if ( |
1844 if ( |
1587 node.func.id == "getattr" |
1845 node.func.id == "getattr" |
1588 and len(node.args) == 2 |
1846 and len(node.args) == 2 |
1589 and AstUtilities.isString(node.args[1]) |
1847 and self.__isIdentifier(node.args[1]) |
|
1848 and iskeyword(AstUtilities.getValue(node.args[1])) |
1590 ): |
1849 ): |
1591 self.violations.append((node, "M512")) |
1850 self.violations.append((node, "M509")) |
1592 elif ( |
1851 elif ( |
1593 node.func.id == "setattr" |
1852 node.func.id == "setattr" |
1594 and len(node.args) == 3 |
1853 and len(node.args) == 3 |
1595 and AstUtilities.isString(node.args[1]) |
1854 and self.__isIdentifier(node.args[1]) |
|
1855 and iskeyword(AstUtilities.getValue(node.args[1])) |
1596 ): |
1856 ): |
1597 self.violations.append((node, "M513")) |
1857 self.violations.append((node, "M510")) |
1598 |
1858 |
1599 self.generic_visit(node) |
1859 self.generic_visit(node) |
1600 |
|
1601 def visit_Attribute(self, node): |
|
1602 """ |
|
1603 Public method to handle attributes. |
|
1604 |
|
1605 @param node reference to the node to be processed |
|
1606 @type ast.Attribute |
|
1607 """ |
|
1608 callPath = list(composeCallPath(node)) |
|
1609 |
|
1610 if ".".join(callPath) == "sys.maxint": |
|
1611 self.violations.append((node, "M504")) |
|
1612 |
|
1613 elif len(callPath) == 2 and callPath[1] == "message": |
|
1614 name = callPath[0] |
|
1615 for elem in reversed(self.__nodeStack[:-1]): |
|
1616 if isinstance(elem, ast.ExceptHandler) and elem.name == name: |
|
1617 self.violations.append((node, "M505")) |
|
1618 break |
|
1619 |
1860 |
1620 def visit_Assign(self, node): |
1861 def visit_Assign(self, node): |
1621 """ |
1862 """ |
1622 Public method to handle assignments. |
1863 Public method to handle assignments. |
1623 |
1864 |
1624 @param node reference to the node to be processed |
1865 @param node reference to the node to be processed |
1625 @type ast.Assign |
1866 @type ast.Assign |
1626 """ |
1867 """ |
1627 if isinstance(self.__nodeStack[-2], ast.ClassDef): |
1868 if len(node.targets) == 1: |
1628 # By using 'hasattr' below we're ignoring starred arguments, slices |
|
1629 # and tuples for simplicity. |
|
1630 assignTargets = {t.id for t in node.targets if hasattr(t, "id")} |
|
1631 if "__metaclass__" in assignTargets: |
|
1632 self.violations.append((node, "M524")) |
|
1633 |
|
1634 elif len(node.targets) == 1: |
|
1635 target = node.targets[0] |
1869 target = node.targets[0] |
1636 if ( |
1870 if ( |
1637 isinstance(target, ast.Attribute) |
1871 isinstance(target, ast.Attribute) |
1638 and isinstance(target.value, ast.Name) |
1872 and isinstance(target.value, ast.Name) |
1639 and (target.value.id, target.attr) == ("os", "environ") |
1873 and (target.value.id, target.attr) == ("os", "environ") |
1640 ): |
1874 ): |
1641 self.violations.append((node, "M506")) |
1875 self.violations.append((node, "M503")) |
1642 |
1876 |
1643 self.generic_visit(node) |
1877 self.generic_visit(node) |
1644 |
1878 |
1645 def visit_For(self, node): |
1879 def visit_For(self, node): |
1646 """ |
1880 """ |
1673 """ |
1966 """ |
1674 if ( |
1967 if ( |
1675 AstUtilities.isNameConstant(node.test) |
1968 AstUtilities.isNameConstant(node.test) |
1676 and AstUtilities.getValue(node.test) is False |
1969 and AstUtilities.getValue(node.test) is False |
1677 ): |
1970 ): |
1678 self.violations.append((node, "M503")) |
1971 self.violations.append((node, "M511")) |
|
1972 |
|
1973 self.generic_visit(node) |
|
1974 |
|
1975 def visit_FunctionDef(self, node): |
|
1976 """ |
|
1977 Public method to handle function definitions. |
|
1978 |
|
1979 @param node reference to the node to be processed |
|
1980 @type ast.FunctionDef |
|
1981 """ |
|
1982 self.__checkForM518(node) |
|
1983 self.__checkForM519(node) |
|
1984 self.__checkForM521(node) |
|
1985 |
|
1986 self.generic_visit(node) |
|
1987 |
|
1988 def visit_ClassDef(self, node): |
|
1989 """ |
|
1990 Public method to handle class definitions. |
|
1991 |
|
1992 @param node reference to the node to be processed |
|
1993 @type ast.ClassDef |
|
1994 """ |
|
1995 self.__checkForM518(node) |
|
1996 self.__checkForM521(node) |
|
1997 self.__checkForM524(node) |
|
1998 |
|
1999 self.generic_visit(node) |
|
2000 |
|
2001 def visit_Try(self, node): |
|
2002 """ |
|
2003 Public method to handle 'try' statements'. |
|
2004 |
|
2005 @param node reference to the node to be processed |
|
2006 @type ast.Try |
|
2007 """ |
|
2008 self.__checkForM512(node) |
|
2009 self.__checkForM525(node) |
|
2010 |
|
2011 self.generic_visit(node) |
|
2012 |
|
2013 def visit_Compare(self, node): |
|
2014 """ |
|
2015 Public method to handle comparison statements. |
|
2016 |
|
2017 @param node reference to the node to be processed |
|
2018 @type ast.Compare |
|
2019 """ |
|
2020 self.__checkForM515(node) |
|
2021 |
|
2022 self.generic_visit(node) |
|
2023 |
|
2024 def visit_Raise(self, node): |
|
2025 """ |
|
2026 Public method to handle 'raise' statements. |
|
2027 |
|
2028 @param node reference to the node to be processed |
|
2029 @type ast.Raise |
|
2030 """ |
|
2031 self.__checkForM516(node) |
|
2032 |
|
2033 self.generic_visit(node) |
|
2034 |
|
2035 def visit_With(self, node): |
|
2036 """ |
|
2037 Public method to handle 'with' statements. |
|
2038 |
|
2039 @param node reference to the node to be processed |
|
2040 @type ast.With |
|
2041 """ |
|
2042 self.__checkForM517(node) |
|
2043 self.__checkForM522(node) |
1679 |
2044 |
1680 self.generic_visit(node) |
2045 self.generic_visit(node) |
1681 |
2046 |
1682 def visit_JoinedStr(self, node): |
2047 def visit_JoinedStr(self, node): |
1683 """ |
2048 """ |
1730 usedNames = set(body.getNames()) |
2095 usedNames = set(body.getNames()) |
1731 for name in sorted(ctrlNames - usedNames): |
2096 for name in sorted(ctrlNames - usedNames): |
1732 n = targets.getNames()[name][0] |
2097 n = targets.getNames()[name][0] |
1733 self.violations.append((n, "M507", name)) |
2098 self.violations.append((n, "M507", name)) |
1734 |
2099 |
|
2100 def __checkForM512(self, node): |
|
2101 """ |
|
2102 Private method to check for return/continue/break inside finally blocks. |
|
2103 |
|
2104 @param node reference to the node to be processed |
|
2105 @type ast.Try |
|
2106 """ |
|
2107 |
|
2108 def _loop(node, badNodeTypes): |
|
2109 if isinstance(node, (ast.AsyncFunctionDef, ast.FunctionDef)): |
|
2110 return |
|
2111 |
|
2112 if isinstance(node, (ast.While, ast.For)): |
|
2113 badNodeTypes = (ast.Return,) |
|
2114 |
|
2115 elif isinstance(node, badNodeTypes): |
|
2116 self.violations.append((node, "M512")) |
|
2117 |
|
2118 for child in ast.iter_child_nodes(node): |
|
2119 _loop(child, badNodeTypes) |
|
2120 |
|
2121 for child in node.finalbody: |
|
2122 _loop(child, (ast.Return, ast.Continue, ast.Break)) |
|
2123 |
|
2124 def __checkForM515(self, node): |
|
2125 """ |
|
2126 Private method to check for pointless comparisons. |
|
2127 |
|
2128 @param node reference to the node to be processed |
|
2129 @type ast.Compare |
|
2130 """ |
|
2131 if isinstance(self.nodeStack[-2], ast.Expr): |
|
2132 self.violations.append((node, "M515")) |
|
2133 |
|
2134 def __checkForM516(self, node): |
|
2135 """ |
|
2136 Private method to check for raising a literal instead of an exception. |
|
2137 |
|
2138 @param node reference to the node to be processed |
|
2139 @type ast.Raise |
|
2140 """ |
|
2141 if ( |
|
2142 AstUtilities.isNameConstant(node.exc) |
|
2143 or AstUtilities.isNumber(node.exc) |
|
2144 or AstUtilities.isString(node.exc) |
|
2145 ): |
|
2146 self.violations.append((node, "M516")) |
|
2147 |
|
2148 def __checkForM517(self, node): |
|
2149 """ |
|
2150 Private method to check for use of the evil syntax |
|
2151 'with assertRaises(Exception):. |
|
2152 |
|
2153 @param node reference to the node to be processed |
|
2154 @type ast.With |
|
2155 """ |
|
2156 item = node.items[0] |
|
2157 itemContext = item.context_expr |
|
2158 if ( |
|
2159 hasattr(itemContext, "func") |
|
2160 and hasattr(itemContext.func, "attr") |
|
2161 and itemContext.func.attr == "assertRaises" |
|
2162 and len(itemContext.args) == 1 |
|
2163 and isinstance(itemContext.args[0], ast.Name) |
|
2164 and itemContext.args[0].id == "Exception" |
|
2165 and not item.optional_vars |
|
2166 ): |
|
2167 self.violations.append((node, "M517")) |
|
2168 |
|
2169 def __checkForM518(self, node): |
|
2170 """ |
|
2171 Private method to check for useless expressions. |
|
2172 |
|
2173 @param node reference to the node to be processed |
|
2174 @type ast.FunctionDef |
|
2175 """ |
|
2176 subnodeClasses = ( |
|
2177 ( |
|
2178 ast.Constant, |
|
2179 ast.List, |
|
2180 ast.Set, |
|
2181 ast.Dict, |
|
2182 ) |
|
2183 if sys.version_info >= (3, 8, 0) |
|
2184 else ( |
|
2185 ast.Num, |
|
2186 ast.Bytes, |
|
2187 ast.NameConstant, |
|
2188 ast.List, |
|
2189 ast.Set, |
|
2190 ast.Dict, |
|
2191 ) |
|
2192 ) |
|
2193 for subnode in node.body: |
|
2194 if not isinstance(subnode, ast.Expr): |
|
2195 continue |
|
2196 |
|
2197 if isinstance(subnode.value, subnodeClasses) and not AstUtilities.isString( |
|
2198 subnode.value |
|
2199 ): |
|
2200 self.violations.append((subnode, "M518")) |
|
2201 |
|
2202 def __checkForM519(self, node): |
|
2203 """ |
|
2204 Private method to check for use of 'functools.lru_cache' or 'functools.cache'. |
|
2205 |
|
2206 @param node reference to the node to be processed |
|
2207 @type ast.FunctionDef |
|
2208 """ |
|
2209 caches = { |
|
2210 "functools.cache", |
|
2211 "functools.lru_cache", |
|
2212 "cache", |
|
2213 "lru_cache", |
|
2214 } |
|
2215 |
|
2216 if ( |
|
2217 len(node.decorator_list) == 0 |
|
2218 or len(self.contexts) < 2 |
|
2219 or not isinstance(self.contexts[-2].node, ast.ClassDef) |
|
2220 ): |
|
2221 return |
|
2222 |
|
2223 # Preserve decorator order so we can get the lineno from the decorator node |
|
2224 # rather than the function node (this location definition changes in Python 3.8) |
|
2225 resolvedDecorators = ( |
|
2226 ".".join(self.__composeCallPath(decorator)) |
|
2227 for decorator in node.decorator_list |
|
2228 ) |
|
2229 for idx, decorator in enumerate(resolvedDecorators): |
|
2230 if decorator in {"classmethod", "staticmethod"}: |
|
2231 return |
|
2232 |
|
2233 if decorator in caches: |
|
2234 self.violations.append((node.decorator_list[idx], "M519")) |
|
2235 return |
|
2236 |
|
2237 def __checkForM520(self, node): |
|
2238 """ |
|
2239 Private method to check for a loop that modifies its iterable. |
|
2240 |
|
2241 @param node reference to the node to be processed |
|
2242 @type ast.For or ast.AsyncFor |
|
2243 """ |
|
2244 targets = NameFinder() |
|
2245 targets.visit(node.target) |
|
2246 ctrlNames = set(targets.getNames()) |
|
2247 |
|
2248 iterset = M520NameFinder() |
|
2249 iterset.visit(node.iter) |
|
2250 itersetNames = set(iterset.getNames()) |
|
2251 |
|
2252 for name in sorted(ctrlNames): |
|
2253 if name in itersetNames: |
|
2254 n = targets.getNames()[name][0] |
|
2255 self.violations.append((n, "M520")) |
|
2256 |
|
2257 def __checkForM521(self, node): |
|
2258 """ |
|
2259 Private method to check for use of an f-string as docstring. |
|
2260 |
|
2261 @param node reference to the node to be processed |
|
2262 @type ast.FunctionDef or ast.ClassDef |
|
2263 """ |
|
2264 if ( |
|
2265 node.body |
|
2266 and isinstance(node.body[0], ast.Expr) |
|
2267 and isinstance(node.body[0].value, ast.JoinedStr) |
|
2268 ): |
|
2269 self.violations.append((node.body[0].value, "M521")) |
|
2270 |
|
2271 def __checkForM522(self, node): |
|
2272 """ |
|
2273 Private method to check for use of an f-string as docstring. |
|
2274 |
|
2275 @param node reference to the node to be processed |
|
2276 @type ast.With |
|
2277 """ |
|
2278 item = node.items[0] |
|
2279 itemContext = item.context_expr |
|
2280 if ( |
|
2281 hasattr(itemContext, "func") |
|
2282 and hasattr(itemContext.func, "value") |
|
2283 and hasattr(itemContext.func.value, "id") |
|
2284 and itemContext.func.value.id == "contextlib" |
|
2285 and hasattr(itemContext.func, "attr") |
|
2286 and itemContext.func.attr == "suppress" |
|
2287 and len(itemContext.args) == 0 |
|
2288 ): |
|
2289 self.violations.append((node, "M522")) |
|
2290 |
|
2291 def __checkForM523(self, loopNode): |
|
2292 """ |
|
2293 Private method to check that functions (including lambdas) do not use loop |
|
2294 variables. |
|
2295 |
|
2296 @param loopNode reference to the node to be processed |
|
2297 @type ast.For, ast.AsyncFor, ast.While, ast.ListComp, ast.SetComp,ast.DictComp, |
|
2298 or ast.GeneratorExp |
|
2299 """ |
|
2300 suspiciousVariables = [] |
|
2301 for node in ast.walk(loopNode): |
|
2302 if isinstance(node, BugBearVisitor.FUNCTION_NODES): |
|
2303 argnames = { |
|
2304 arg.arg for arg in ast.walk(node.args) if isinstance(arg, ast.arg) |
|
2305 } |
|
2306 if isinstance(node, ast.Lambda): |
|
2307 bodyNodes = ast.walk(node.body) |
|
2308 else: |
|
2309 bodyNodes = itertools.chain.from_iterable(map(ast.walk, node.body)) |
|
2310 for name in bodyNodes: |
|
2311 if ( |
|
2312 isinstance(name, ast.Name) |
|
2313 and name.id not in argnames |
|
2314 and isinstance(name.ctx, ast.Load) |
|
2315 ): |
|
2316 err = (name.lineno, name.col_offset, name.id, name) |
|
2317 if err not in self.__M523Seen: |
|
2318 self.__M523Seen.add(err) # dedupe across nested loops |
|
2319 suspiciousVariables.append(err) |
|
2320 |
|
2321 if suspiciousVariables: |
|
2322 reassignedInLoop = set(self.__getAssignedNames(loopNode)) |
|
2323 |
|
2324 for err in sorted(suspiciousVariables): |
|
2325 if reassignedInLoop.issuperset(err[2]): |
|
2326 self.violations.append((err[3], "M523", err[2])) |
|
2327 |
|
2328 def __checkForM524(self, node): |
|
2329 """ |
|
2330 Private method to check for inheritance from abstract classes in abc and lack of |
|
2331 any methods decorated with abstract*. |
|
2332 |
|
2333 @param node reference to the node to be processed |
|
2334 @type ast.ClassDef |
|
2335 """ # __IGNORE_WARNING_D234r__ |
|
2336 |
|
2337 def isAbcClass(value): |
|
2338 if isinstance(value, ast.keyword): |
|
2339 return value.arg == "metaclass" and isAbcClass(value.value) |
|
2340 |
|
2341 abcNames = ("ABC", "ABCMeta") |
|
2342 return (isinstance(value, ast.Name) and value.id in abcNames) or ( |
|
2343 isinstance(value, ast.Attribute) |
|
2344 and value.attr in abcNames |
|
2345 and isinstance(value.value, ast.Name) |
|
2346 and value.value.id == "abc" |
|
2347 ) |
|
2348 |
|
2349 def isAbstractDecorator(expr): |
|
2350 return (isinstance(expr, ast.Name) and expr.id[:8] == "abstract") or ( |
|
2351 isinstance(expr, ast.Attribute) and expr.attr[:8] == "abstract" |
|
2352 ) |
|
2353 |
|
2354 if not any(map(isAbcClass, (*node.bases, *node.keywords))): |
|
2355 return |
|
2356 |
|
2357 for stmt in node.body: |
|
2358 if isinstance(stmt, (ast.FunctionDef, ast.AsyncFunctionDef)) and any( |
|
2359 map(isAbstractDecorator, stmt.decorator_list) |
|
2360 ): |
|
2361 return |
|
2362 |
|
2363 self.violations.append((node, "M524", node.name)) |
|
2364 |
|
2365 def __checkForM525(self, node): |
|
2366 """ |
|
2367 Private method to check for exceptions being handled multiple times. |
|
2368 |
|
2369 @param node reference to the node to be processed |
|
2370 @type ast.Try |
|
2371 """ |
|
2372 seen = [] |
|
2373 |
|
2374 for handler in node.handlers: |
|
2375 if isinstance(handler.type, (ast.Name, ast.Attribute)): |
|
2376 name = ".".join(self.__composeCallPath(handler.type)) |
|
2377 seen.append(name) |
|
2378 elif isinstance(handler.type, ast.Tuple): |
|
2379 # to avoid checking the same as M514, remove duplicates per except |
|
2380 uniques = set() |
|
2381 for entry in handler.type.elts: |
|
2382 name = ".".join(self.__composeCallPath(entry)) |
|
2383 uniques.add(name) |
|
2384 seen.extend(uniques) |
|
2385 |
|
2386 # sort to have a deterministic output |
|
2387 duplicates = sorted({x for x in seen if seen.count(x) > 1}) |
|
2388 for duplicate in duplicates: |
|
2389 self.violations.append((node, "M525", duplicate)) |
|
2390 |
1735 |
2391 |
1736 class NameFinder(ast.NodeVisitor): |
2392 class NameFinder(ast.NodeVisitor): |
1737 """ |
2393 """ |
1738 Class to extract a name out of a tree of nodes. |
2394 Class to extract a name out of a tree of nodes. |
1739 """ |
2395 """ |
1759 """ |
2415 """ |
1760 Public method to traverse a given AST node. |
2416 Public method to traverse a given AST node. |
1761 |
2417 |
1762 @param node AST node to be traversed |
2418 @param node AST node to be traversed |
1763 @type ast.Node |
2419 @type ast.Node |
|
2420 @return reference to the last processed node |
|
2421 @rtype ast.Node |
1764 """ |
2422 """ |
1765 if isinstance(node, list): |
2423 if isinstance(node, list): |
1766 for elem in node: |
2424 for elem in node: |
1767 super().visit(elem) |
2425 super().visit(elem) |
|
2426 return node |
1768 else: |
2427 else: |
1769 super().visit(node) |
2428 return super().visit(node) |
1770 |
2429 |
1771 def getNames(self): |
2430 def getNames(self): |
1772 """ |
2431 """ |
1773 Public method to return the extracted names and Name nodes. |
2432 Public method to return the extracted names and Name nodes. |
1774 |
2433 |
1775 @return dictionary containing the names as keys and the list of nodes |
2434 @return dictionary containing the names as keys and the list of nodes |
1776 @rtype dict |
2435 @rtype dict |
1777 """ |
2436 """ |
1778 return self.__names |
2437 return self.__names |
|
2438 |
|
2439 |
|
2440 class M520NameFinder(NameFinder): |
|
2441 """ |
|
2442 Class to extract a name out of a tree of nodes ignoring names defined within the |
|
2443 local scope of a comprehension. |
|
2444 """ |
|
2445 |
|
2446 def visit_GeneratorExp(self, node): |
|
2447 """ |
|
2448 Public method to handle a generator expressions. |
|
2449 |
|
2450 @param node reference to the node to be processed |
|
2451 @type ast.GeneratorExp |
|
2452 """ |
|
2453 self.visit(node.generators) |
|
2454 |
|
2455 def visit_ListComp(self, node): |
|
2456 """ |
|
2457 Public method to handle a list comprehension. |
|
2458 |
|
2459 @param node reference to the node to be processed |
|
2460 @type TYPE |
|
2461 """ |
|
2462 self.visit(node.generators) |
|
2463 |
|
2464 def visit_DictComp(self, node): |
|
2465 """ |
|
2466 Public method to handle a dictionary comprehension. |
|
2467 |
|
2468 @param node reference to the node to be processed |
|
2469 @type TYPE |
|
2470 """ |
|
2471 self.visit(node.generators) |
|
2472 |
|
2473 def visit_comprehension(self, node): |
|
2474 """ |
|
2475 Public method to handle the 'for' of a comprehension. |
|
2476 |
|
2477 @param node reference to the node to be processed |
|
2478 @type ast.comprehension |
|
2479 """ |
|
2480 self.visit(node.iter) |
|
2481 |
|
2482 def visit_Lambda(self, node): |
|
2483 """ |
|
2484 Public method to handle a Lambda function. |
|
2485 |
|
2486 @param node reference to the node to be processed |
|
2487 @type ast.Lambda |
|
2488 """ |
|
2489 self.visit(node.body) |
|
2490 for lambdaArg in node.args.args: |
|
2491 self.getNames().pop(lambdaArg.arg, None) |
1779 |
2492 |
1780 |
2493 |
1781 class ReturnVisitor(ast.NodeVisitor): |
2494 class ReturnVisitor(ast.NodeVisitor): |
1782 """ |
2495 """ |
1783 Class implementing a node visitor to check return statements. |
2496 Class implementing a node visitor to check return statements. |