1637 |
1639 |
1638 |
1640 |
1639 ####################################################################### |
1641 ####################################################################### |
1640 ## BugBearVisitor |
1642 ## BugBearVisitor |
1641 ## |
1643 ## |
1642 ## adapted from: flake8-bugbear v24.8.19 |
1644 ## adapted from: flake8-bugbear v24.12.12 |
1643 ## |
1645 ## |
1644 ## Original: Copyright (c) 2016 Łukasz Langa |
1646 ## Original: Copyright (c) 2016 Łukasz Langa |
1645 ####################################################################### |
1647 ####################################################################### |
1646 |
1648 |
1647 BugBearContext = namedtuple("BugBearContext", ["node", "stack"]) |
1649 BugBearContext = namedtuple("BugBearContext", ["node", "stack"]) |
1653 Class to hold the data for a caught exception. |
1655 Class to hold the data for a caught exception. |
1654 """ |
1656 """ |
1655 |
1657 |
1656 name: str |
1658 name: str |
1657 hasNote: bool |
1659 hasNote: bool |
|
1660 |
|
1661 |
|
1662 class M541UnhandledKeyType: |
|
1663 """ |
|
1664 Class to hold a dictionary key of a type that we do not check for duplicates. |
|
1665 """ |
|
1666 |
|
1667 |
|
1668 class M541VariableKeyType: |
|
1669 """ |
|
1670 Class to hold the name of a variable key type. |
|
1671 """ |
|
1672 |
|
1673 def __init__(self, name): |
|
1674 """ |
|
1675 Constructor |
|
1676 |
|
1677 @param name name of the variable key type |
|
1678 @type str |
|
1679 """ |
|
1680 self.name = name |
1658 |
1681 |
1659 |
1682 |
1660 class BugBearVisitor(ast.NodeVisitor): |
1683 class BugBearVisitor(ast.NodeVisitor): |
1661 """ |
1684 """ |
1662 Class implementing a node visitor to check for various topics. |
1685 Class implementing a node visitor to check for various topics. |
1694 |
1717 |
1695 self.__M523Seen = set() |
1718 self.__M523Seen = set() |
1696 self.__M505Imports = set() |
1719 self.__M505Imports = set() |
1697 self.__M540CaughtException = None |
1720 self.__M540CaughtException = None |
1698 |
1721 |
|
1722 self.__inTryStar = "" |
|
1723 |
1699 @property |
1724 @property |
1700 def nodeStack(self): |
1725 def nodeStack(self): |
1701 """ |
1726 """ |
1702 Public method to get a reference to the most recent node stack. |
1727 Public method to get a reference to the most recent node stack. |
1703 |
1728 |
1840 ): |
1865 ): |
1841 exprList.extend(expr.value.elts) |
1866 exprList.extend(expr.value.elts) |
1842 continue |
1867 continue |
1843 yield expr |
1868 yield expr |
1844 |
1869 |
1845 def __checkRedundantExcepthandlers(self, names, node): |
1870 def __checkRedundantExcepthandlers(self, names, node, inTryStar): |
1846 """ |
1871 """ |
1847 Private method to check for redundant exception types in an exception handler. |
1872 Private method to check for redundant exception types in an exception handler. |
1848 |
1873 |
1849 @param names list of exception types to be checked |
1874 @param names list of exception types to be checked |
1850 @type list of ast.Name |
1875 @type list of ast.Name |
1851 @param node reference to the exception handler node |
1876 @param node reference to the exception handler node |
1852 @type ast.ExceptionHandler |
1877 @type ast.ExceptionHandler |
|
1878 @param inTryStar character indicating an 'except*' handler |
|
1879 @type str |
1853 @return tuple containing the error data |
1880 @return tuple containing the error data |
1854 @rtype tuple of (ast.Node, str, str, str, str) |
1881 @rtype tuple of (ast.Node, str, str, str, str) |
1855 """ |
1882 """ |
1856 redundantExceptions = { |
1883 redundantExceptions = { |
1857 "OSError": { |
1884 "OSError": { |
1896 ): |
1923 ): |
1897 good.remove(name) |
1924 good.remove(name) |
1898 if good != names: |
1925 if good != names: |
1899 desc = good[0] if len(good) == 1 else "({0})".format(", ".join(good)) |
1926 desc = good[0] if len(good) == 1 else "({0})".format(", ".join(good)) |
1900 as_ = " as " + node.name if node.name is not None else "" |
1927 as_ = " as " + node.name if node.name is not None else "" |
1901 return (node, "M514", ", ".join(names), as_, desc) |
1928 return (node, "M514", ", ".join(names), as_, desc, inTryStar) |
1902 |
1929 |
1903 return None |
1930 return None |
1904 |
1931 |
1905 def __walkList(self, nodes): |
1932 def __walkList(self, nodes): |
1906 """ |
1933 """ |
2041 if node.name is None: |
2068 if node.name is None: |
2042 self.__M540CaughtException = None |
2069 self.__M540CaughtException = None |
2043 else: |
2070 else: |
2044 self.__M540CaughtException = M540CaughtException(node.name, False) |
2071 self.__M540CaughtException = M540CaughtException(node.name, False) |
2045 |
2072 |
2046 names = self.__checkForM513_M529_M530(node) |
2073 names = self.__checkForM513_M514_M529_M530(node) |
2047 |
2074 |
2048 if "BaseException" in names and not ExceptBaseExceptionVisitor(node).reRaised(): |
2075 if "BaseException" in names and not ExceptBaseExceptionVisitor(node).reRaised(): |
2049 self.violations.append((node, "M536")) |
2076 self.violations.append((node, "M536")) |
2050 |
2077 |
2051 self.generic_visit(node) |
2078 self.generic_visit(node) |
2301 |
2328 |
2302 self.generic_visit(node) |
2329 self.generic_visit(node) |
2303 |
2330 |
2304 def visit_Try(self, node): |
2331 def visit_Try(self, node): |
2305 """ |
2332 """ |
2306 Public method to handle 'try' statements'. |
2333 Public method to handle 'try' statements. |
2307 |
2334 |
2308 @param node reference to the node to be processed |
2335 @param node reference to the node to be processed |
2309 @type ast.Try |
2336 @type ast.Try |
2310 """ |
2337 """ |
2311 self.__checkForM512(node) |
2338 self.__checkForM512(node) |
2312 self.__checkForM525(node) |
2339 self.__checkForM525(node) |
2313 |
2340 |
2314 self.generic_visit(node) |
2341 self.generic_visit(node) |
|
2342 |
|
2343 def visit_TryStar(self, node): |
|
2344 """ |
|
2345 Public method to handle 'except*' statements. |
|
2346 |
|
2347 @param node reference to the node to be processed |
|
2348 @type ast.TryStar |
|
2349 """ |
|
2350 outerTryStar = self.__inTryStar |
|
2351 self.__inTryStar = "*" |
|
2352 self.visit_Try(node) |
|
2353 self.__inTryStar = outerTryStar |
2315 |
2354 |
2316 def visit_Compare(self, node): |
2355 def visit_Compare(self, node): |
2317 """ |
2356 """ |
2318 Public method to handle comparison statements. |
2357 Public method to handle comparison statements. |
2319 |
2358 |
2403 |
2442 |
2404 @param node reference to the node to be processed |
2443 @param node reference to the node to be processed |
2405 @type ast.Set |
2444 @type ast.Set |
2406 """ |
2445 """ |
2407 self.__checkForM533(node) |
2446 self.__checkForM533(node) |
|
2447 |
|
2448 self.generic_visit(node) |
|
2449 |
|
2450 def visit_Dict(self, node): |
|
2451 """ |
|
2452 Public method to check a dictionary. |
|
2453 |
|
2454 @param node reference to the node to be processed |
|
2455 @type ast.Dict |
|
2456 """ |
|
2457 self.__checkForM541(node) |
2408 |
2458 |
2409 self.generic_visit(node) |
2459 self.generic_visit(node) |
2410 |
2460 |
2411 def __checkForM505(self, node): |
2461 def __checkForM505(self, node): |
2412 """ |
2462 """ |
2486 |
2536 |
2487 if isinstance(node, (ast.While, ast.For)): |
2537 if isinstance(node, (ast.While, ast.For)): |
2488 badNodeTypes = (ast.Return,) |
2538 badNodeTypes = (ast.Return,) |
2489 |
2539 |
2490 elif isinstance(node, badNodeTypes): |
2540 elif isinstance(node, badNodeTypes): |
2491 self.violations.append((node, "M512")) |
2541 self.violations.append((node, "M512", self.__inTryStar)) |
2492 |
2542 |
2493 for child in ast.iter_child_nodes(node): |
2543 for child in ast.iter_child_nodes(node): |
2494 _loop(child, badNodeTypes) |
2544 _loop(child, badNodeTypes) |
2495 |
2545 |
2496 for child in node.finalbody: |
2546 for child in node.finalbody: |
2497 _loop(child, (ast.Return, ast.Continue, ast.Break)) |
2547 _loop(child, (ast.Return, ast.Continue, ast.Break)) |
2498 |
2548 |
2499 def __checkForM513_M529_M530(self, node): |
2549 def __checkForM513_M514_M529_M530(self, node): |
2500 """ |
2550 """ |
2501 Private method to check various exception handler situations. |
2551 Private method to check various exception handler situations. |
2502 |
2552 |
2503 @param node reference to the node to be processed |
2553 @param node reference to the node to be processed |
2504 @type ast.ExceptHandler |
2554 @type ast.ExceptHandler |
2522 else: |
2572 else: |
2523 badHandlers.append(handler) |
2573 badHandlers.append(handler) |
2524 if badHandlers: |
2574 if badHandlers: |
2525 self.violations.append((node, "M530")) |
2575 self.violations.append((node, "M530")) |
2526 if len(names) == 0 and not badHandlers and not ignoredHandlers: |
2576 if len(names) == 0 and not badHandlers and not ignoredHandlers: |
2527 self.violations.append((node, "M529")) |
2577 self.violations.append((node, "M529", self.__inTryStar)) |
2528 elif ( |
2578 elif ( |
2529 len(names) == 1 |
2579 len(names) == 1 |
2530 and not badHandlers |
2580 and not badHandlers |
2531 and not ignoredHandlers |
2581 and not ignoredHandlers |
2532 and isinstance(node.type, ast.Tuple) |
2582 and isinstance(node.type, ast.Tuple) |
2533 ): |
2583 ): |
2534 self.violations.append((node, "M513", *names)) |
2584 self.violations.append((node, "M513", *names, self.__inTryStar)) |
2535 else: |
2585 else: |
2536 maybeError = self.__checkRedundantExcepthandlers(names, node) |
2586 maybeError = self.__checkRedundantExcepthandlers( |
|
2587 names, node, self.__inTryStar |
|
2588 ) |
2537 if maybeError is not None: |
2589 if maybeError is not None: |
2538 self.violations.append(maybeError) |
2590 self.violations.append(maybeError) |
2539 return names |
2591 return names |
2540 |
2592 |
2541 def __checkForM515(self, node): |
2593 def __checkForM515(self, node): |
2848 if not any(map(isAbcClass, (*node.bases, *node.keywords))): |
2900 if not any(map(isAbcClass, (*node.bases, *node.keywords))): |
2849 return |
2901 return |
2850 |
2902 |
2851 for stmt in node.body: |
2903 for stmt in node.body: |
2852 # Ignore abc's that declares a class attribute that must be set |
2904 # Ignore abc's that declares a class attribute that must be set |
2853 if isinstance(stmt, (ast.AnnAssign, ast.Assign)): |
2905 if isinstance(stmt, ast.AnnAssign) and stmt.value is None: |
2854 hasAbstractMethod = True |
2906 hasAbstractMethod = True |
2855 continue |
2907 continue |
2856 |
2908 |
2857 # only check function defs |
2909 # only check function defs |
2858 if not isinstance(stmt, (ast.FunctionDef, ast.AsyncFunctionDef)): |
2910 if not isinstance(stmt, (ast.FunctionDef, ast.AsyncFunctionDef)): |
2895 seen.extend(uniques) |
2947 seen.extend(uniques) |
2896 |
2948 |
2897 # sort to have a deterministic output |
2949 # sort to have a deterministic output |
2898 duplicates = sorted({x for x in seen if seen.count(x) > 1}) |
2950 duplicates = sorted({x for x in seen if seen.count(x) > 1}) |
2899 for duplicate in duplicates: |
2951 for duplicate in duplicates: |
2900 self.violations.append((node, "M525", duplicate)) |
2952 self.violations.append((node, "M525", duplicate, self.__inTryStar)) |
2901 |
2953 |
2902 def __checkForM526(self, node): |
2954 def __checkForM526(self, node): |
2903 """ |
2955 """ |
2904 Private method to check for Star-arg unpacking after keyword argument. |
2956 Private method to check for Star-arg unpacking after keyword argument. |
2905 |
2957 |
2933 and node.func.attr == "warn" |
2985 and node.func.attr == "warn" |
2934 and isinstance(node.func.value, ast.Name) |
2986 and isinstance(node.func.value, ast.Name) |
2935 and node.func.value.id == "warnings" |
2987 and node.func.value.id == "warnings" |
2936 and not any(kw.arg == "stacklevel" for kw in node.keywords) |
2988 and not any(kw.arg == "stacklevel" for kw in node.keywords) |
2937 and len(node.args) < 3 |
2989 and len(node.args) < 3 |
|
2990 and not any(isinstance(a, ast.Starred) for a in node.args) |
|
2991 and not any(kw.arg is None for kw in node.keywords) |
2938 ): |
2992 ): |
2939 self.violations.append((node, "M528")) |
2993 self.violations.append((node, "M528")) |
2940 |
2994 |
2941 def __checkForM531(self, loopNode): |
2995 def __checkForM531(self, loopNode): |
2942 """ |
2996 """ |
3139 |
3193 |
3140 for n in superwalk(node): |
3194 for n in superwalk(node): |
3141 if isinstance(n, ast.Name) and n.id == self.__M540CaughtException.name: |
3195 if isinstance(n, ast.Name) and n.id == self.__M540CaughtException.name: |
3142 self.__M540CaughtException = None |
3196 self.__M540CaughtException = None |
3143 break |
3197 break |
|
3198 |
|
3199 def __checkForM541(self, node): |
|
3200 """ |
|
3201 Private method to check for duplicate key value pairs in a dictionary literal. |
|
3202 |
|
3203 @param node reference to the node to be processed |
|
3204 @type ast.Dict |
|
3205 """ # noqa: D234r |
|
3206 |
|
3207 def convertToValue(item): |
|
3208 """ |
|
3209 Function to extract the value of a given item. |
|
3210 |
|
3211 @param item node to extract value from |
|
3212 @type ast.Ast |
|
3213 @return value of the node |
|
3214 @rtype Any |
|
3215 """ |
|
3216 if isinstance(item, ast.Constant): |
|
3217 return item.value |
|
3218 elif isinstance(item, ast.Tuple): |
|
3219 return tuple(convertToValue(i) for i in item.elts) |
|
3220 elif isinstance(item, ast.Name): |
|
3221 return M541VariableKeyType(item.id) |
|
3222 else: |
|
3223 return M541UnhandledKeyType() |
|
3224 |
|
3225 keys = [convertToValue(key) for key in node.keys] |
|
3226 keyCounts = Counter(keys) |
|
3227 duplicateKeys = [key for key, count in keyCounts.items() if count > 1] |
|
3228 for key in duplicateKeys: |
|
3229 keyIndices = [i for i, iKey in enumerate(keys) if iKey == key] |
|
3230 seen = set() |
|
3231 for index in keyIndices: |
|
3232 value = convertToValue(node.values[index]) |
|
3233 if value in seen: |
|
3234 keyNode = node.keys[index] |
|
3235 self.violations.append((keyNode, "M541")) |
|
3236 seen.add(value) |
3144 |
3237 |
3145 def __checkForM569(self, node): |
3238 def __checkForM569(self, node): |
3146 """ |
3239 """ |
3147 Private method to check for changes to a loop's mutable iterable. |
3240 Private method to check for changes to a loop's mutable iterable. |
3148 |
3241 |