src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Miscellaneous/MiscellaneousChecker.py

branch
eric7
changeset 9327
2b768afcaee1
parent 9274
86fab0c74430
child 9473
3f23dbf37dbe
equal deleted inserted replaced
9326:1d8eadd8873e 9327:2b768afcaee1
5 5
6 """ 6 """
7 Module implementing a checker for miscellaneous checks. 7 Module implementing a checker for miscellaneous checks.
8 """ 8 """
9 9
10 import ast
11 import builtins
12 import contextlib
13 import copy
14 import itertools
15 import re
10 import sys 16 import sys
11 import ast 17 import tokenize
12 import re 18 from collections import defaultdict, namedtuple
13 import itertools 19 from keyword import iskeyword
14 from string import Formatter 20 from string import Formatter
15 from collections import defaultdict
16 import tokenize
17 import copy
18 import contextlib
19 21
20 import AstUtilities 22 import AstUtilities
21 23
22 from .eradicate import Eradicator 24 from .eradicate import Eradicator
23 25
104 "M501", 106 "M501",
105 "M502", 107 "M502",
106 "M503", 108 "M503",
107 "M504", 109 "M504",
108 "M505", 110 "M505",
109 "M506",
110 "M507", 111 "M507",
111 "M508",
112 "M509", 112 "M509",
113 "M510",
113 "M511", 114 "M511",
114 "M512", 115 "M512",
115 "M513", 116 "M513",
117 "M514",
118 "M515",
119 "M516",
120 "M517",
121 "M518",
122 "M519",
123 "M520",
116 "M521", 124 "M521",
117 "M522", 125 "M522",
118 "M523", 126 "M523",
119 "M524", 127 "M524",
128 "M525",
129 ## Bugbear++
130 "M581",
131 "M582",
120 ## Format Strings 132 ## Format Strings
121 "M601", 133 "M601",
122 "M611", 134 "M611",
123 "M612", 135 "M612",
124 "M613", 136 "M613",
279 "M501", 291 "M501",
280 "M502", 292 "M502",
281 "M503", 293 "M503",
282 "M504", 294 "M504",
283 "M505", 295 "M505",
284 "M506",
285 "M507", 296 "M507",
286 "M508",
287 "M509", 297 "M509",
298 "M510",
288 "M511", 299 "M511",
289 "M512", 300 "M512",
290 "M513", 301 "M513",
302 "M514",
303 "M515",
304 "M516",
305 "M517",
306 "M518",
307 "M519",
308 "M520",
291 "M521", 309 "M521",
292 "M522", 310 "M522",
293 "M523", 311 "M523",
294 "M524", 312 "M524",
313 "M525",
314 "M581",
315 "M582",
295 ), 316 ),
296 ), 317 ),
297 (self.__checkPep3101, ("M601",)), 318 (self.__checkPep3101, ("M601",)),
298 ( 319 (
299 self.__checkFormatString, 320 self.__checkFormatString,
994 mutableTypes = ( 1015 mutableTypes = (
995 ast.Call, 1016 ast.Call,
996 ast.Dict, 1017 ast.Dict,
997 ast.List, 1018 ast.List,
998 ast.Set, 1019 ast.Set,
1020 ast.DictComp,
1021 ast.ListComp,
1022 ast.SetComp,
999 ) 1023 )
1000 mutableCalls = ( 1024 mutableCalls = (
1001 "Counter", 1025 "Counter",
1002 "OrderedDict", 1026 "OrderedDict",
1003 "collections.Counter", 1027 "collections.Counter",
1011 "set", 1035 "set",
1012 ) 1036 )
1013 immutableCalls = ( 1037 immutableCalls = (
1014 "tuple", 1038 "tuple",
1015 "frozenset", 1039 "frozenset",
1040 "types.MappingProxyType",
1041 "MappingProxyType",
1042 "re.compile",
1043 "operator.attrgetter",
1044 "operator.itemgetter",
1045 "operator.methodcaller",
1046 "attrgetter",
1047 "itemgetter",
1048 "methodcaller",
1016 ) 1049 )
1017 functionDefs = [ast.FunctionDef] 1050 functionDefs = [ast.FunctionDef]
1018 with contextlib.suppress(AttributeError): 1051 with contextlib.suppress(AttributeError):
1019 functionDefs.append(ast.AsyncFunctionDef) 1052 functionDefs.append(ast.AsyncFunctionDef)
1020 1053
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 """
1648 1882
1649 @param node reference to the node to be processed 1883 @param node reference to the node to be processed
1650 @type ast.For 1884 @type ast.For
1651 """ 1885 """
1652 self.__checkForM507(node) 1886 self.__checkForM507(node)
1887 self.__checkForM520(node)
1888 self.__checkForM523(node)
1653 1889
1654 self.generic_visit(node) 1890 self.generic_visit(node)
1655 1891
1656 def visit_AsyncFor(self, node): 1892 def visit_AsyncFor(self, node):
1657 """ 1893 """
1659 1895
1660 @param node reference to the node to be processed 1896 @param node reference to the node to be processed
1661 @type ast.AsyncFor 1897 @type ast.AsyncFor
1662 """ 1898 """
1663 self.__checkForM507(node) 1899 self.__checkForM507(node)
1900 self.__checkForM520(node)
1901 self.__checkForM523(node)
1902
1903 self.generic_visit(node)
1904
1905 def visit_While(self, node):
1906 """
1907 Public method to handle 'while' statements.
1908
1909 @param node reference to the node to be processed
1910 @type ast.While
1911 """
1912 self.__checkForM523(node)
1913
1914 self.generic_visit(node)
1915
1916 def visit_ListComp(self, node):
1917 """
1918 Public method to handle list comprehensions.
1919
1920 @param node reference to the node to be processed
1921 @type ast.ListComp
1922 """
1923 self.__checkForM523(node)
1924
1925 self.generic_visit(node)
1926
1927 def visit_SetComp(self, node):
1928 """
1929 Public method to handle set comprehensions.
1930
1931 @param node reference to the node to be processed
1932 @type ast.SetComp
1933 """
1934 self.__checkForM523(node)
1935
1936 self.generic_visit(node)
1937
1938 def visit_DictComp(self, node):
1939 """
1940 Public method to handle dictionary comprehensions.
1941
1942 @param node reference to the node to be processed
1943 @type ast.DictComp
1944 """
1945 self.__checkForM523(node)
1946
1947 self.generic_visit(node)
1948
1949 def visit_GeneratorExp(self, node):
1950 """
1951 Public method to handle generator expressions.
1952
1953 @param node reference to the node to be processed
1954 @type ast.GeneratorExp
1955 """
1956 self.__checkForM523(node)
1664 1957
1665 self.generic_visit(node) 1958 self.generic_visit(node)
1666 1959
1667 def visit_Assert(self, node): 1960 def visit_Assert(self, node):
1668 """ 1961 """
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 """
1688 """ 2053 """
1689 for value in node.values: 2054 for value in node.values:
1690 if isinstance(value, ast.FormattedValue): 2055 if isinstance(value, ast.FormattedValue):
1691 return 2056 return
1692 2057
1693 self.violations.append((node, "M508")) 2058 self.violations.append((node, "M581"))
1694 2059
1695 def __checkForM502(self, node): 2060 def __checkForM505(self, node):
1696 """ 2061 """
1697 Private method to check the use of *strip(). 2062 Private method to check the use of *strip().
1698 2063
1699 @param node reference to the node to be processed 2064 @param node reference to the node to be processed
1700 @type ast.Call 2065 @type ast.Call
1710 return # stripping just one character 2075 return # stripping just one character
1711 2076
1712 if len(s) == len(set(s)): 2077 if len(s) == len(set(s)):
1713 return # no characters appear more than once 2078 return # no characters appear more than once
1714 2079
1715 self.violations.append((node, "M502")) 2080 self.violations.append((node, "M505"))
1716 2081
1717 def __checkForM507(self, node): 2082 def __checkForM507(self, node):
1718 """ 2083 """
1719 Private method to check for unused loop variables. 2084 Private method to check for unused loop variables.
1720 2085
1721 @param node reference to the node to be processed 2086 @param node reference to the node to be processed
1722 @type ast.For 2087 @type ast.For or ast.AsyncFor
1723 """ 2088 """
1724 targets = NameFinder() 2089 targets = NameFinder()
1725 targets.visit(node.target) 2090 targets.visit(node.target)
1726 ctrlNames = set(filter(lambda s: not s.startswith("_"), targets.getNames())) 2091 ctrlNames = set(filter(lambda s: not s.startswith("_"), targets.getNames()))
1727 body = NameFinder() 2092 body = NameFinder()
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.

eric ide

mercurial