1705 yield node |
1718 yield node |
1706 if not isinstance(node, BugBearVisitor.FUNCTION_NODES): |
1719 if not isinstance(node, BugBearVisitor.FUNCTION_NODES): |
1707 for child in ast.iter_child_nodes(node): |
1720 for child in ast.iter_child_nodes(node): |
1708 yield from self.__childrenInScope(child) |
1721 yield from self.__childrenInScope(child) |
1709 |
1722 |
1710 def visit(self, node): |
1723 def __flattenExcepthandler(self, node): |
1711 """ |
1724 """ |
1712 Public method to traverse a given AST node. |
1725 Private method to flatten the list of exceptions handled by an except handler. |
1713 |
1726 |
1714 @param node AST node to be traversed |
1727 @param node reference to the node to be processed |
1715 @type ast.Node |
1728 @type ast.Node |
1716 """ |
1729 @yield reference to the exception type node |
1717 isContextful = isinstance(node, BugBearVisitor.CONTEXTFUL_NODES) |
1730 @ytype ast.Node |
1718 |
1731 """ |
1719 if isContextful: |
1732 if not isinstance(node, ast.Tuple): |
1720 context = BugBearContext(node, []) |
1733 yield node |
1721 self.contexts.append(context) |
1734 return |
1722 |
1735 |
1723 self.nodeStack.append(node) |
1736 exprList = node.elts.copy() |
1724 self.nodeWindow.append(node) |
1737 while len(exprList): |
1725 self.nodeWindow = self.nodeWindow[-BugBearVisitor.NodeWindowSize :] |
1738 expr = exprList.pop(0) |
1726 |
1739 if isinstance(expr, ast.Starred) and isinstance( |
1727 super().visit(node) |
1740 expr.value, (ast.List, ast.Tuple) |
1728 |
1741 ): |
1729 self.nodeStack.pop() |
1742 exprList.extend(expr.value.elts) |
1730 |
1743 continue |
1731 if isContextful: |
1744 yield expr |
1732 self.contexts.pop() |
1745 |
1733 |
1746 def __checkRedundantExcepthandlers(self, names, node): |
1734 def visit_ExceptHandler(self, node): |
1747 """ |
1735 """ |
1748 Private method to check for redundant exception types in an exception handler. |
1736 Public method to handle exception handlers. |
1749 |
1737 |
1750 @param names list of exception types to be checked |
1738 @param node reference to the node to be processed |
1751 @type list of ast.Name |
1739 @type ast.ExceptHandler |
1752 @param node reference to the exception handler node |
|
1753 @type ast.ExceptionHandler |
|
1754 @return tuple containing the error data |
|
1755 @rtype tuple of (ast.Node, str, str, str, str) |
1740 """ |
1756 """ |
1741 redundantExceptions = { |
1757 redundantExceptions = { |
1742 "OSError": { |
1758 "OSError": { |
1743 # All of these are actually aliases of OSError since Python 3.3 |
1759 # All of these are actually aliases of OSError since Python 3.3 |
1744 "IOError", |
1760 "IOError", |
1751 "ValueError": { |
1767 "ValueError": { |
1752 "binascii.Error", |
1768 "binascii.Error", |
1753 }, |
1769 }, |
1754 } |
1770 } |
1755 |
1771 |
|
1772 # See if any of the given exception names could be removed, e.g. from: |
|
1773 # (MyError, MyError) # duplicate names |
|
1774 # (MyError, BaseException) # everything derives from the Base |
|
1775 # (Exception, TypeError) # builtins where one subclasses another |
|
1776 # (IOError, OSError) # IOError is an alias of OSError since Python3.3 |
|
1777 # but note that other cases are impractical to handle from the AST. |
|
1778 # We expect this is mostly useful for users who do not have the |
|
1779 # builtin exception hierarchy memorised, and include a 'shadowed' |
|
1780 # subtype without realising that it's redundant. |
|
1781 good = sorted(set(names), key=names.index) |
|
1782 if "BaseException" in good: |
|
1783 good = ["BaseException"] |
|
1784 # Remove redundant exceptions that the automatic system either handles |
|
1785 # poorly (usually aliases) or can't be checked (e.g. it's not an |
|
1786 # built-in exception). |
|
1787 for primary, equivalents in redundantExceptions.items(): |
|
1788 if primary in good: |
|
1789 good = [g for g in good if g not in equivalents] |
|
1790 |
|
1791 for name, other in itertools.permutations(tuple(good), 2): |
|
1792 if ( |
|
1793 self.__typesafeIssubclass( |
|
1794 getattr(builtins, name, type), getattr(builtins, other, ()) |
|
1795 ) |
|
1796 and name in good |
|
1797 ): |
|
1798 good.remove(name) |
|
1799 if good != names: |
|
1800 desc = good[0] if len(good) == 1 else "({0})".format(", ".join(good)) |
|
1801 as_ = " as " + node.name if node.name is not None else "" |
|
1802 return (node, "M514", ", ".join(names), as_, desc) |
|
1803 |
|
1804 return None |
|
1805 |
|
1806 def __walkList(self, nodes): |
|
1807 """ |
|
1808 Private method to walk a given list of nodes. |
|
1809 |
|
1810 @param nodes list of nodes to walk |
|
1811 @type list of ast.Node |
|
1812 @yield node references as determined by the ast.walk() function |
|
1813 @ytype ast.Node |
|
1814 """ |
|
1815 for node in nodes: |
|
1816 yield from ast.walk(node) |
|
1817 |
|
1818 def visit(self, node): |
|
1819 """ |
|
1820 Public method to traverse a given AST node. |
|
1821 |
|
1822 @param node AST node to be traversed |
|
1823 @type ast.Node |
|
1824 """ |
|
1825 isContextful = isinstance(node, BugBearVisitor.CONTEXTFUL_NODES) |
|
1826 |
|
1827 if isContextful: |
|
1828 context = BugBearContext(node, []) |
|
1829 self.contexts.append(context) |
|
1830 |
|
1831 self.nodeStack.append(node) |
|
1832 self.nodeWindow.append(node) |
|
1833 self.nodeWindow = self.nodeWindow[-BugBearVisitor.NodeWindowSize :] |
|
1834 |
|
1835 super().visit(node) |
|
1836 |
|
1837 self.nodeStack.pop() |
|
1838 |
|
1839 if isContextful: |
|
1840 self.contexts.pop() |
|
1841 |
|
1842 def visit_ExceptHandler(self, node): |
|
1843 """ |
|
1844 Public method to handle exception handlers. |
|
1845 |
|
1846 @param node reference to the node to be processed |
|
1847 @type ast.ExceptHandler |
|
1848 """ |
1756 if node.type is None: |
1849 if node.type is None: |
1757 # bare except is handled by pycodestyle already |
1850 # bare except is handled by pycodestyle already |
1758 pass |
1851 pass |
1759 |
1852 |
1760 elif isinstance(node.type, ast.Tuple): |
1853 else: |
1761 names = [self.__toNameStr(e) for e in node.type.elts] |
1854 handlers = self.__flattenExcepthandler(node.type) |
1762 as_ = " as " + node.name if node.name is not None else "" |
1855 names = [] |
1763 if len(names) == 0: |
1856 badHandlers = [] |
1764 self.violations.append((node, "M501", as_)) |
1857 ignoredHandlers = [] |
1765 elif len(names) == 1: |
1858 for handler in handlers: |
|
1859 if isinstance(handler, (ast.Name, ast.Attribute)): |
|
1860 name = self.__toNameStr(handler) |
|
1861 if name is None: |
|
1862 ignoredHandlers.append(handler) |
|
1863 else: |
|
1864 names.append(name) |
|
1865 elif isinstance(handler, (ast.Call, ast.Starred)): |
|
1866 ignoredHandlers.append(handler) |
|
1867 else: |
|
1868 badHandlers.append(handler) |
|
1869 if badHandlers: |
|
1870 self.violations.append((node, "M530")) |
|
1871 if len(names) == 0 and not badHandlers and not ignoredHandlers: |
|
1872 self.violations.append((node, "M529")) |
|
1873 elif ( |
|
1874 len(names) == 1 |
|
1875 and not badHandlers |
|
1876 and not ignoredHandlers |
|
1877 and isinstance(node.type, ast.Tuple) |
|
1878 ): |
1766 self.violations.append((node, "M513", *names)) |
1879 self.violations.append((node, "M513", *names)) |
1767 else: |
1880 else: |
1768 # See if any of the given exception names could be removed, e.g. from: |
1881 maybeError = self.__checkRedundantExcepthandlers(names, node) |
1769 # (MyError, MyError) # duplicate names |
1882 if maybeError is not None: |
1770 # (MyError, BaseException) # everything derives from the Base |
1883 self.violations.append(maybeError) |
1771 # (Exception, TypeError) # builtins where one subclasses another |
|
1772 # (IOError, OSError) # IOError is an alias of OSError since Python3.3 |
|
1773 # but note that other cases are impractical to handle from the AST. |
|
1774 # We expect this is mostly useful for users who do not have the |
|
1775 # builtin exception hierarchy memorised, and include a 'shadowed' |
|
1776 # subtype without realising that it's redundant. |
|
1777 good = sorted(set(names), key=names.index) |
|
1778 if "BaseException" in good: |
|
1779 good = ["BaseException"] |
|
1780 # Remove redundant exceptions that the automatic system either handles |
|
1781 # poorly (usually aliases) or can't be checked (e.g. it's not an |
|
1782 # built-in exception). |
|
1783 for primary, equivalents in redundantExceptions.items(): |
|
1784 if primary in good: |
|
1785 good = [g for g in good if g not in equivalents] |
|
1786 |
|
1787 for name, other in itertools.permutations(tuple(good), 2): |
|
1788 if ( |
|
1789 self.__typesafeIssubclass( |
|
1790 getattr(builtins, name, type), getattr(builtins, other, ()) |
|
1791 ) |
|
1792 and name in good |
|
1793 ): |
|
1794 good.remove(name) |
|
1795 if good != names: |
|
1796 desc = ( |
|
1797 good[0] if len(good) == 1 else "({0})".format(", ".join(good)) |
|
1798 ) |
|
1799 self.violations.append((node, "M514", ", ".join(names), as_, desc)) |
|
1800 |
1884 |
1801 self.generic_visit(node) |
1885 self.generic_visit(node) |
1802 |
1886 |
1803 def visit_UAdd(self, node): |
1887 def visit_UAdd(self, node): |
1804 """ |
1888 """ |
2059 if isinstance(value, ast.FormattedValue): |
2158 if isinstance(value, ast.FormattedValue): |
2060 return |
2159 return |
2061 |
2160 |
2062 self.violations.append((node, "M581")) |
2161 self.violations.append((node, "M581")) |
2063 |
2162 |
|
2163 def visit_AnnAssign(self, node): |
|
2164 """ |
|
2165 Public method to check annotated assign statements. |
|
2166 |
|
2167 @param node reference to the node to be processed |
|
2168 @type ast.AnnAssign |
|
2169 """ |
|
2170 self.__checkForM532(node) |
|
2171 |
|
2172 self.generic_visit(node) |
|
2173 |
|
2174 def visit_Import(self, node): |
|
2175 """ |
|
2176 Public method to check imports. |
|
2177 |
|
2178 @param node reference to the node to be processed |
|
2179 @type ast.Import |
|
2180 """ |
|
2181 self.__checkForM505(node) |
|
2182 |
|
2183 self.generic_visit(node) |
|
2184 |
|
2185 def visit_Set(self, node): |
|
2186 """ |
|
2187 Public method to check a set. |
|
2188 |
|
2189 @param node reference to the node to be processed |
|
2190 @type ast.Set |
|
2191 """ |
|
2192 self.__checkForM533(node) |
|
2193 |
|
2194 self.generic_visit(node) |
|
2195 |
2064 def __checkForM505(self, node): |
2196 def __checkForM505(self, node): |
2065 """ |
2197 """ |
2066 Private method to check the use of *strip(). |
2198 Private method to check the use of *strip(). |
2067 |
2199 |
2068 @param node reference to the node to be processed |
2200 @param node reference to the node to be processed |
2069 @type ast.Call |
2201 @type ast.Call |
2070 """ |
2202 """ |
2071 if node.func.attr not in ("lstrip", "rstrip", "strip"): |
2203 if isinstance(node, ast.Import): |
2072 return # method name doesn't match |
2204 for name in node.names: |
2073 |
2205 self.__M505Imports.add(name.asname or name.name) |
2074 if len(node.args) != 1 or not AstUtilities.isString(node.args[0]): |
2206 elif isinstance(node, ast.Call): |
2075 return # used arguments don't match the builtin strip |
2207 if node.func.attr not in ("lstrip", "rstrip", "strip"): |
2076 |
2208 return # method name doesn't match |
2077 s = AstUtilities.getValue(node.args[0]) |
2209 |
2078 if len(s) == 1: |
2210 if ( |
2079 return # stripping just one character |
2211 isinstance(node.func.value, ast.Name) |
2080 |
2212 and node.func.value.id in self.__M505Imports |
2081 if len(s) == len(set(s)): |
2213 ): |
2082 return # no characters appear more than once |
2214 return # method is being run on an imported module |
2083 |
2215 |
2084 self.violations.append((node, "M505")) |
2216 if len(node.args) != 1 or not AstUtilities.isString(node.args[0]): |
|
2217 return # used arguments don't match the builtin strip |
|
2218 |
|
2219 s = AstUtilities.getValue(node.args[0]) |
|
2220 if len(s) == 1: |
|
2221 return # stripping just one character |
|
2222 |
|
2223 if len(s) == len(set(s)): |
|
2224 return # no characters appear more than once |
|
2225 |
|
2226 self.violations.append((node, "M505")) |
2085 |
2227 |
2086 def __checkForM507(self, node): |
2228 def __checkForM507(self, node): |
2087 """ |
2229 """ |
2088 Private method to check for unused loop variables. |
2230 Private method to check for unused loop variables. |
2089 |
2231 |
2150 self.violations.append((node, "M516")) |
2292 self.violations.append((node, "M516")) |
2151 |
2293 |
2152 def __checkForM517(self, node): |
2294 def __checkForM517(self, node): |
2153 """ |
2295 """ |
2154 Private method to check for use of the evil syntax |
2296 Private method to check for use of the evil syntax |
2155 'with assertRaises(Exception):. |
2297 'with assertRaises(Exception): or 'with pytest.raises(Exception):'. |
2156 |
2298 |
2157 @param node reference to the node to be processed |
2299 @param node reference to the node to be processed |
2158 @type ast.With |
2300 @type ast.With |
2159 """ |
2301 """ |
2160 item = node.items[0] |
2302 item = node.items[0] |
2161 itemContext = item.context_expr |
2303 itemContext = item.context_expr |
2162 if ( |
2304 if ( |
2163 hasattr(itemContext, "func") |
2305 hasattr(itemContext, "func") |
2164 and hasattr(itemContext.func, "attr") |
2306 and isinstance(itemContext.func, ast.Attribute) |
2165 and itemContext.func.attr == "assertRaises" |
2307 and ( |
|
2308 itemContext.func.attr == "assertRaises" |
|
2309 or ( |
|
2310 itemContext.func.attr == "raises" |
|
2311 and isinstance(itemContext.func.value, ast.Name) |
|
2312 and itemContext.func.value.id == "pytest" |
|
2313 and "match" not in [kwd.arg for kwd in itemContext.keywords] |
|
2314 ) |
|
2315 ) |
2166 and len(itemContext.args) == 1 |
2316 and len(itemContext.args) == 1 |
2167 and isinstance(itemContext.args[0], ast.Name) |
2317 and isinstance(itemContext.args[0], ast.Name) |
2168 and itemContext.args[0].id == "Exception" |
2318 and itemContext.args[0].id == "Exception" |
2169 and not item.optional_vars |
2319 and not item.optional_vars |
2170 ): |
2320 ): |
2175 Private method to check for useless expressions. |
2325 Private method to check for useless expressions. |
2176 |
2326 |
2177 @param node reference to the node to be processed |
2327 @param node reference to the node to be processed |
2178 @type ast.FunctionDef |
2328 @type ast.FunctionDef |
2179 """ |
2329 """ |
2180 subnodeClasses = ( |
|
2181 ( |
|
2182 ast.Constant, |
|
2183 ast.List, |
|
2184 ast.Set, |
|
2185 ast.Dict, |
|
2186 ) |
|
2187 if sys.version_info >= (3, 8, 0) |
|
2188 else ( |
|
2189 ast.Num, |
|
2190 ast.Bytes, |
|
2191 ast.NameConstant, |
|
2192 ast.List, |
|
2193 ast.Set, |
|
2194 ast.Dict, |
|
2195 ) |
|
2196 ) |
|
2197 for subnode in node.body: |
2330 for subnode in node.body: |
2198 if not isinstance(subnode, ast.Expr): |
2331 if not isinstance(subnode, ast.Expr): |
2199 continue |
2332 continue |
2200 |
2333 |
2201 if isinstance(subnode.value, subnodeClasses) and not AstUtilities.isString( |
2334 if ( |
2202 subnode.value |
2335 sys.version_info < (3, 8, 0) |
|
2336 and isinstance( |
|
2337 subnode.value, |
|
2338 (ast.Num, ast.Bytes, ast.NameConstant, ast.List, ast.Set, ast.Dict), |
|
2339 ) |
|
2340 ) or ( |
|
2341 sys.version_info >= (3, 8, 0) |
|
2342 and ( |
|
2343 isinstance( |
|
2344 subnode.value, |
|
2345 (ast.List, ast.Set, ast.Dict), |
|
2346 ) |
|
2347 or ( |
|
2348 isinstance(subnode.value, ast.Constant) |
|
2349 and ( |
|
2350 isinstance( |
|
2351 subnode.value.value, |
|
2352 (int, float, complex, bytes, bool), |
|
2353 ) |
|
2354 or subnode.value.value is None |
|
2355 ) |
|
2356 ) |
|
2357 ) |
2203 ): |
2358 ): |
2204 self.violations.append((subnode, "M518")) |
2359 self.violations.append((subnode, "M518")) |
2205 |
2360 |
2206 def __checkForM519(self, node): |
2361 def __checkForM519(self, node): |
2207 """ |
2362 """ |
2495 if (starred.lineno, starred.col_offset) > ( |
2652 if (starred.lineno, starred.col_offset) > ( |
2496 firstKeyword.lineno, |
2653 firstKeyword.lineno, |
2497 firstKeyword.col_offset, |
2654 firstKeyword.col_offset, |
2498 ): |
2655 ): |
2499 self.violations.append((node, "M526")) |
2656 self.violations.append((node, "M526")) |
|
2657 |
|
2658 def __checkForM528(self, node): |
|
2659 """ |
|
2660 Private method to check for warn without stacklevel. |
|
2661 |
|
2662 @param node reference to the node to be processed |
|
2663 @type ast.Call |
|
2664 """ |
|
2665 if ( |
|
2666 isinstance(node.func, ast.Attribute) |
|
2667 and node.func.attr == "warn" |
|
2668 and isinstance(node.func.value, ast.Name) |
|
2669 and node.func.value.id == "warnings" |
|
2670 and not any(kw.arg == "stacklevel" for kw in node.keywords) |
|
2671 and len(node.args) < 3 |
|
2672 ): |
|
2673 self.violations.append((node, "M528")) |
|
2674 |
|
2675 def __checkForM531(self, loopNode): |
|
2676 """ |
|
2677 Private method to check that 'itertools.groupby' isn't iterated over more than |
|
2678 once. |
|
2679 |
|
2680 A warning is emitted when the generator returned by 'groupby()' is used |
|
2681 more than once inside a loop body or when it's used in a nested loop. |
|
2682 |
|
2683 @param loopNode reference to the node to be processed |
|
2684 @type ast.For or ast.AsyncFor |
|
2685 """ |
|
2686 # for <loop_node.target> in <loop_node.iter>: ... |
|
2687 if isinstance(loopNode.iter, ast.Call): |
|
2688 node = loopNode.iter |
|
2689 if (isinstance(node.func, ast.Name) and node.func.id in ("groupby",)) or ( |
|
2690 isinstance(node.func, ast.Attribute) |
|
2691 and node.func.attr == "groupby" |
|
2692 and isinstance(node.func.value, ast.Name) |
|
2693 and node.func.value.id == "itertools" |
|
2694 ): |
|
2695 # We have an invocation of groupby which is a simple unpacking |
|
2696 if isinstance(loopNode.target, ast.Tuple) and isinstance( |
|
2697 loopNode.target.elts[1], ast.Name |
|
2698 ): |
|
2699 groupName = loopNode.target.elts[1].id |
|
2700 else: |
|
2701 # Ignore any 'groupby()' invocation that isn't unpacked |
|
2702 return |
|
2703 |
|
2704 numUsages = 0 |
|
2705 for node in self.__walkList(loopNode.body): |
|
2706 # Handled nested loops |
|
2707 if isinstance(node, ast.For): |
|
2708 for nestedNode in self.__walkList(node.body): |
|
2709 if ( |
|
2710 isinstance(nestedNode, ast.Name) |
|
2711 and nestedNode.id == groupName |
|
2712 ): |
|
2713 self.violations.append((nestedNode, "M531")) |
|
2714 |
|
2715 # Handle multiple uses |
|
2716 if isinstance(node, ast.Name) and node.id == groupName: |
|
2717 numUsages += 1 |
|
2718 if numUsages > 1: |
|
2719 self.violations.append((nestedNode, "M531")) |
|
2720 |
|
2721 def __checkForM532(self, node): |
|
2722 """ |
|
2723 Private method to check for possible unintentional typing annotation. |
|
2724 |
|
2725 @param node reference to the node to be processed |
|
2726 @type ast.AnnAssign |
|
2727 """ |
|
2728 if ( |
|
2729 node.value is None |
|
2730 and hasattr(node.target, "value") |
|
2731 and isinstance(node.target.value, ast.Name) |
|
2732 and ( |
|
2733 isinstance(node.target, ast.Subscript) |
|
2734 or ( |
|
2735 isinstance(node.target, ast.Attribute) |
|
2736 and node.target.value.id != "self" |
|
2737 ) |
|
2738 ) |
|
2739 ): |
|
2740 self.violations.append((node, "M532")) |
|
2741 |
|
2742 def __checkForM533(self, node): |
|
2743 """ |
|
2744 Private method to check a set for duplicate items. |
|
2745 |
|
2746 @param node reference to the node to be processed |
|
2747 @type ast.Set |
|
2748 """ |
|
2749 constants = [ |
|
2750 item.value |
|
2751 for item in filter(lambda x: isinstance(x, ast.Constant), node.elts) |
|
2752 ] |
|
2753 if len(constants) != len(set(constants)): |
|
2754 self.violations.append((node, "M533")) |
2500 |
2755 |
2501 |
2756 |
2502 class NameFinder(ast.NodeVisitor): |
2757 class NameFinder(ast.NodeVisitor): |
2503 """ |
2758 """ |
2504 Class to extract a name out of a tree of nodes. |
2759 Class to extract a name out of a tree of nodes. |