35 |
37 |
36 import AstUtilities |
38 import AstUtilities |
37 |
39 |
38 from .eradicate import Eradicator |
40 from .eradicate import Eradicator |
39 from .MiscellaneousDefaults import MiscellaneousCheckerDefaultArgs |
41 from .MiscellaneousDefaults import MiscellaneousCheckerDefaultArgs |
|
42 |
|
43 |
|
44 BugbearMutableLiterals = ("Dict", "List", "Set") |
|
45 BugbearMutableComprehensions = ("ListComp", "DictComp", "SetComp") |
|
46 BugbearMutableCalls = ( |
|
47 "Counter", |
|
48 "OrderedDict", |
|
49 "collections.Counter", |
|
50 "collections.OrderedDict", |
|
51 "collections.defaultdict", |
|
52 "collections.deque", |
|
53 "defaultdict", |
|
54 "deque", |
|
55 "dict", |
|
56 "list", |
|
57 "set", |
|
58 ) |
|
59 BugbearImmutableCalls = ( |
|
60 "tuple", |
|
61 "frozenset", |
|
62 "types.MappingProxyType", |
|
63 "MappingProxyType", |
|
64 "re.compile", |
|
65 "operator.attrgetter", |
|
66 "operator.itemgetter", |
|
67 "operator.methodcaller", |
|
68 "attrgetter", |
|
69 "itemgetter", |
|
70 "methodcaller", |
|
71 ) |
40 |
72 |
41 |
73 |
42 def composeCallPath(node): |
74 def composeCallPath(node): |
43 """ |
75 """ |
44 Generator function to assemble the call path of a given node. |
76 Generator function to assemble the call path of a given node. |
405 ), |
444 ), |
406 (self.__checkFuture, ("M701", "M702")), |
445 (self.__checkFuture, ("M701", "M702")), |
407 (self.__checkGettext, ("M711",)), |
446 (self.__checkGettext, ("M711",)), |
408 (self.__checkPrintStatements, ("M801",)), |
447 (self.__checkPrintStatements, ("M801",)), |
409 (self.__checkTuple, ("M811",)), |
448 (self.__checkTuple, ("M811",)), |
410 (self.__checkMutableDefault, ("M821", "M822")), |
|
411 (self.__checkReturn, ("M831", "M832", "M833", "M834")), |
449 (self.__checkReturn, ("M831", "M832", "M833", "M834")), |
412 (self.__checkLineContinuation, ("M841",)), |
450 (self.__checkLineContinuation, ("M841",)), |
413 (self.__checkImplicitStringConcat, ("M851", "M852")), |
451 (self.__checkImplicitStringConcat, ("M851", "M852")), |
414 (self.__checkExplicitStringConcat, ("M853",)), |
452 (self.__checkExplicitStringConcat, ("M853",)), |
415 (self.__checkCommentedCode, ("M891",)), |
453 (self.__checkCommentedCode, ("M891",)), |
1184 node.col_offset, |
1222 node.col_offset, |
1185 "M200", |
1223 "M200", |
1186 compType[node.__class__], |
1224 compType[node.__class__], |
1187 ) |
1225 ) |
1188 |
1226 |
1189 def __checkMutableDefault(self): |
|
1190 """ |
|
1191 Private method to check for use of mutable types as default arguments. |
|
1192 """ |
|
1193 mutableTypes = ( |
|
1194 ast.Call, |
|
1195 ast.Dict, |
|
1196 ast.List, |
|
1197 ast.Set, |
|
1198 ast.DictComp, |
|
1199 ast.ListComp, |
|
1200 ast.SetComp, |
|
1201 ) |
|
1202 mutableCalls = ( |
|
1203 "Counter", |
|
1204 "OrderedDict", |
|
1205 "collections.Counter", |
|
1206 "collections.OrderedDict", |
|
1207 "collections.defaultdict", |
|
1208 "collections.deque", |
|
1209 "defaultdict", |
|
1210 "deque", |
|
1211 "dict", |
|
1212 "list", |
|
1213 "set", |
|
1214 ) |
|
1215 immutableCalls = ( |
|
1216 "tuple", |
|
1217 "frozenset", |
|
1218 "types.MappingProxyType", |
|
1219 "MappingProxyType", |
|
1220 "re.compile", |
|
1221 "operator.attrgetter", |
|
1222 "operator.itemgetter", |
|
1223 "operator.methodcaller", |
|
1224 "attrgetter", |
|
1225 "itemgetter", |
|
1226 "methodcaller", |
|
1227 ) |
|
1228 functionDefs = [ast.FunctionDef] |
|
1229 with contextlib.suppress(AttributeError): |
|
1230 functionDefs.append(ast.AsyncFunctionDef) |
|
1231 |
|
1232 for node in ast.walk(self.__tree): |
|
1233 if any(isinstance(node, functionDef) for functionDef in functionDefs): |
|
1234 defaults = node.args.defaults[:] |
|
1235 with contextlib.suppress(AttributeError): |
|
1236 defaults += node.args.kw_defaults[:] |
|
1237 for default in defaults: |
|
1238 if any( |
|
1239 isinstance(default, mutableType) for mutableType in mutableTypes |
|
1240 ): |
|
1241 typeName = type(default).__name__ |
|
1242 if isinstance(default, ast.Call): |
|
1243 callPath = ".".join(composeCallPath(default.func)) |
|
1244 if callPath in mutableCalls: |
|
1245 self.__error( |
|
1246 default.lineno - 1, |
|
1247 default.col_offset, |
|
1248 "M823", |
|
1249 callPath + "()", |
|
1250 ) |
|
1251 elif callPath not in immutableCalls: |
|
1252 self.__error( |
|
1253 default.lineno - 1, |
|
1254 default.col_offset, |
|
1255 "M822", |
|
1256 typeName, |
|
1257 ) |
|
1258 else: |
|
1259 self.__error( |
|
1260 default.lineno - 1, default.col_offset, "M821", typeName |
|
1261 ) |
|
1262 |
|
1263 def __dictShouldBeChecked(self, node): |
1227 def __dictShouldBeChecked(self, node): |
1264 """ |
1228 """ |
1265 Private function to test, if the node should be checked. |
1229 Private function to test, if the node should be checked. |
1266 |
1230 |
1267 @param node reference to the AST node |
1231 @param node reference to the AST node |
1674 |
1638 |
1675 |
1639 |
1676 ####################################################################### |
1640 ####################################################################### |
1677 ## BugBearVisitor |
1641 ## BugBearVisitor |
1678 ## |
1642 ## |
1679 ## adapted from: flake8-bugbear v24.4.26 |
1643 ## adapted from: flake8-bugbear v24.8.19 |
1680 ## |
1644 ## |
1681 ## Original: Copyright (c) 2016 Łukasz Langa |
1645 ## Original: Copyright (c) 2016 Łukasz Langa |
1682 ####################################################################### |
1646 ####################################################################### |
1683 |
1647 |
1684 BugBearContext = namedtuple("BugBearContext", ["node", "stack"]) |
1648 BugBearContext = namedtuple("BugBearContext", ["node", "stack"]) |
|
1649 |
|
1650 @dataclass |
|
1651 class M540CaughtException: |
|
1652 """ |
|
1653 Class to hold the data for a caught exception. |
|
1654 """ |
|
1655 name : str |
|
1656 hasNote: bool |
1685 |
1657 |
1686 |
1658 |
1687 class BugBearVisitor(ast.NodeVisitor): |
1659 class BugBearVisitor(ast.NodeVisitor): |
1688 """ |
1660 """ |
1689 Class implementing a node visitor to check for various topics. |
1661 Class implementing a node visitor to check for various topics. |
1752 |
1725 |
1753 return ( |
1726 return ( |
1754 re.match(r"^[A-Za-z_][A-Za-z0-9_]*$", AstUtilities.getValue(arg)) |
1727 re.match(r"^[A-Za-z_][A-Za-z0-9_]*$", AstUtilities.getValue(arg)) |
1755 is not None |
1728 is not None |
1756 ) |
1729 ) |
1757 |
|
1758 def __composeCallPath(self, node): |
|
1759 """ |
|
1760 Private method get the individual elements of the call path of a node. |
|
1761 |
|
1762 @param node reference to the node |
|
1763 @type ast.Node |
|
1764 @yield one element of the call path |
|
1765 @ytype ast.Node |
|
1766 """ |
|
1767 if isinstance(node, ast.Attribute): |
|
1768 yield from self.__composeCallPath(node.value) |
|
1769 yield node.attr |
|
1770 elif isinstance(node, ast.Call): |
|
1771 yield from self.__composeCallPath(node.func) |
|
1772 elif isinstance(node, ast.Name): |
|
1773 yield node.id |
|
1774 |
1730 |
1775 def toNameStr(self, node): |
1731 def toNameStr(self, node): |
1776 """ |
1732 """ |
1777 Public method to turn Name and Attribute nodes to strings, handling any |
1733 Public method to turn Name and Attribute nodes to strings, handling any |
1778 depth of attribute accesses. |
1734 depth of attribute accesses. |
2075 @param node reference to the node to be processed |
2031 @param node reference to the node to be processed |
2076 @type ast.ExceptHandler |
2032 @type ast.ExceptHandler |
2077 """ |
2033 """ |
2078 if node.type is None: |
2034 if node.type is None: |
2079 # bare except is handled by pycodestyle already |
2035 # bare except is handled by pycodestyle already |
2080 pass |
2036 self.generic_visit(node) |
2081 |
2037 return |
|
2038 |
|
2039 oldM540CaughtException = self.__M540CaughtException |
|
2040 if node.name is None: |
|
2041 self.__M540CaughtException = None |
2082 else: |
2042 else: |
2083 handlers = self.__flattenExcepthandler(node.type) |
2043 self.__M540CaughtException = M540CaughtException(node.name, False) |
2084 names = [] |
2044 |
2085 badHandlers = [] |
2045 names = self.__checkForM513_M529_M530(node) |
2086 ignoredHandlers = [] |
2046 |
2087 for handler in handlers: |
2047 if ( |
2088 if isinstance(handler, (ast.Name, ast.Attribute)): |
2048 "BaseException" in names |
2089 name = self.toNameStr(handler) |
2049 and not ExceptBaseExceptionVisitor(node).reRaised() |
2090 if name is None: |
2050 ): |
2091 ignoredHandlers.append(handler) |
2051 self.violations.append((node, "M536")) |
2092 else: |
|
2093 names.append(name) |
|
2094 elif isinstance(handler, (ast.Call, ast.Starred)): |
|
2095 ignoredHandlers.append(handler) |
|
2096 else: |
|
2097 badHandlers.append(handler) |
|
2098 if badHandlers: |
|
2099 self.violations.append((node, "M530")) |
|
2100 if len(names) == 0 and not badHandlers and not ignoredHandlers: |
|
2101 self.violations.append((node, "M529")) |
|
2102 elif ( |
|
2103 len(names) == 1 |
|
2104 and not badHandlers |
|
2105 and not ignoredHandlers |
|
2106 and isinstance(node.type, ast.Tuple) |
|
2107 ): |
|
2108 self.violations.append((node, "M513", *names)) |
|
2109 else: |
|
2110 maybeError = self.__checkRedundantExcepthandlers(names, node) |
|
2111 if maybeError is not None: |
|
2112 self.violations.append(maybeError) |
|
2113 if ( |
|
2114 "BaseException" in names |
|
2115 and not ExceptBaseExceptionVisitor(node).reRaised() |
|
2116 ): |
|
2117 self.violations.append((node, "M536")) |
|
2118 |
2052 |
2119 self.generic_visit(node) |
2053 self.generic_visit(node) |
|
2054 |
|
2055 if ( |
|
2056 self.__M540CaughtException is not None |
|
2057 and self.__M540CaughtException.hasNote |
|
2058 ): |
|
2059 self.violations.append((node, "M540")) |
|
2060 self.__M540CaughtException = oldM540CaughtException |
2120 |
2061 |
2121 def visit_UAdd(self, node): |
2062 def visit_UAdd(self, node): |
2122 """ |
2063 """ |
2123 Public method to handle unary additions. |
2064 Public method to handle unary additions. |
2124 |
2065 |
2137 Public method to handle a function call. |
2078 Public method to handle a function call. |
2138 |
2079 |
2139 @param node reference to the node to be processed |
2080 @param node reference to the node to be processed |
2140 @type ast.Call |
2081 @type ast.Call |
2141 """ |
2082 """ |
|
2083 isM540AddNote = False |
|
2084 |
2142 if isinstance(node.func, ast.Attribute): |
2085 if isinstance(node.func, ast.Attribute): |
2143 self.__checkForM505(node) |
2086 self.__checkForM505(node) |
|
2087 isM540AddNote = self.__checkForM540AddNote(node.func) |
2144 else: |
2088 else: |
2145 with contextlib.suppress(AttributeError, IndexError): |
2089 with contextlib.suppress(AttributeError, IndexError): |
2146 # bad super() call |
2090 # bad super() call |
2147 if isinstance(node.func, ast.Name) and node.func.id == "super": |
2091 if isinstance(node.func, ast.Name) and node.func.id == "super": |
2148 args = node.args |
2092 args = node.args |
2178 |
2122 |
2179 self.__checkForM526(node) |
2123 self.__checkForM526(node) |
2180 |
2124 |
2181 self.__checkForM528(node) |
2125 self.__checkForM528(node) |
2182 self.__checkForM534(node) |
2126 self.__checkForM534(node) |
|
2127 self.__checkForM539(node) |
|
2128 |
|
2129 # no need for copying, if used in nested calls it will be set to None |
|
2130 currentM540CaughtException = self.__M540CaughtException |
|
2131 if not isM540AddNote: |
|
2132 self.__checkForM540Usage(node.args) |
|
2133 self.__checkForM540Usage(node.keywords) |
2183 |
2134 |
2184 self.generic_visit(node) |
2135 self.generic_visit(node) |
2185 |
2136 |
|
2137 if isM540AddNote: |
|
2138 # Avoid nested calls within the parameter list using the variable itself. |
|
2139 # e.g. `e.add_note(str(e))` |
|
2140 self.__M540CaughtException = currentM540CaughtException |
|
2141 |
2186 def visit_Module(self, node): |
2142 def visit_Module(self, node): |
2187 """ |
2143 """ |
2188 Public method to handle a module node. |
2144 Public method to handle a module node. |
2189 |
2145 |
2190 @param node reference to the node to be processed |
2146 @param node reference to the node to be processed |
2308 ): |
2265 ): |
2309 self.violations.append((node, "M511")) |
2266 self.violations.append((node, "M511")) |
2310 |
2267 |
2311 self.generic_visit(node) |
2268 self.generic_visit(node) |
2312 |
2269 |
|
2270 def visit_AsyncFunctionDef(self, node): |
|
2271 """ |
|
2272 Public method to handle async function definitions. |
|
2273 |
|
2274 @param node reference to the node to be processed |
|
2275 @type ast.AsyncFunctionDef |
|
2276 """ |
|
2277 self.__checkForM506_M508(node) |
|
2278 |
|
2279 self.generic_visit(node) |
|
2280 |
2313 def visit_FunctionDef(self, node): |
2281 def visit_FunctionDef(self, node): |
2314 """ |
2282 """ |
2315 Public method to handle function definitions. |
2283 Public method to handle function definitions. |
2316 |
2284 |
2317 @param node reference to the node to be processed |
2285 @param node reference to the node to be processed |
2318 @type ast.FunctionDef |
2286 @type ast.FunctionDef |
2319 """ |
2287 """ |
|
2288 self.__checkForM506_M508(node) |
2320 self.__checkForM519(node) |
2289 self.__checkForM519(node) |
2321 self.__checkForM521(node) |
2290 self.__checkForM521(node) |
2322 |
2291 |
2323 self.generic_visit(node) |
2292 self.generic_visit(node) |
2324 |
2293 |
2446 for name in node.names: |
2421 for name in node.names: |
2447 self.__M505Imports.add(name.asname or name.name) |
2422 self.__M505Imports.add(name.asname or name.name) |
2448 elif isinstance(node, ast.ImportFrom): |
2423 elif isinstance(node, ast.ImportFrom): |
2449 for name in node.names: |
2424 for name in node.names: |
2450 self.__M505Imports.add(f"{node.module}.{name.name or name.asname}") |
2425 self.__M505Imports.add(f"{node.module}.{name.name or name.asname}") |
2451 elif isinstance(node, ast.Call): |
2426 elif isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute): |
2452 if node.func.attr not in ("lstrip", "rstrip", "strip"): |
2427 if node.func.attr not in ("lstrip", "rstrip", "strip"): |
2453 return # method name doesn't match |
2428 return # method name doesn't match |
2454 |
2429 |
2455 if ( |
2430 if ( |
2456 isinstance(node.func.value, ast.Name) |
2431 isinstance(node.func.value, ast.Name) |
2467 |
2442 |
2468 if len(value) == len(set(value)): |
2443 if len(value) == len(set(value)): |
2469 return # no characters appear more than once |
2444 return # no characters appear more than once |
2470 |
2445 |
2471 self.violations.append((node, "M505")) |
2446 self.violations.append((node, "M505")) |
|
2447 |
|
2448 def __checkForM506_M508(self, node): |
|
2449 """ |
|
2450 Private method to check the use of mutable literals, comprehensions and calls. |
|
2451 |
|
2452 @param node reference to the node to be processed |
|
2453 @type ast.AsyncFunctionDef or ast.FunctionDef |
|
2454 """ |
|
2455 visitor = FunctionDefDefaultsVisitor("M506", "M508") |
|
2456 visitor.visit(node.args.defaults + node.args.kw_defaults) |
|
2457 self.violations.extend(visitor.errors) |
2472 |
2458 |
2473 def __checkForM507(self, node): |
2459 def __checkForM507(self, node): |
2474 """ |
2460 """ |
2475 Private method to check for unused loop variables. |
2461 Private method to check for unused loop variables. |
2476 |
2462 |
2509 for child in ast.iter_child_nodes(node): |
2495 for child in ast.iter_child_nodes(node): |
2510 _loop(child, badNodeTypes) |
2496 _loop(child, badNodeTypes) |
2511 |
2497 |
2512 for child in node.finalbody: |
2498 for child in node.finalbody: |
2513 _loop(child, (ast.Return, ast.Continue, ast.Break)) |
2499 _loop(child, (ast.Return, ast.Continue, ast.Break)) |
|
2500 |
|
2501 def __checkForM513_M529_M530(self, node): |
|
2502 """ |
|
2503 Private method to check various exception handler situations. |
|
2504 |
|
2505 @param node reference to the node to be processed |
|
2506 @type ast.ExceptHandler |
|
2507 """ |
|
2508 handlers = self.__flattenExcepthandler(node.type) |
|
2509 names = [] |
|
2510 badHandlers = [] |
|
2511 ignoredHandlers = [] |
|
2512 |
|
2513 for handler in handlers: |
|
2514 if isinstance(handler, (ast.Name, ast.Attribute)): |
|
2515 name = self.toNameStr(handler) |
|
2516 if name is None: |
|
2517 ignoredHandlers.append(handler) |
|
2518 else: |
|
2519 names.append(name) |
|
2520 elif isinstance(handler, (ast.Call, ast.Starred)): |
|
2521 ignoredHandlers.append(handler) |
|
2522 else: |
|
2523 badHandlers.append(handler) |
|
2524 if badHandlers: |
|
2525 self.violations.append((node, "M530")) |
|
2526 if len(names) == 0 and not badHandlers and not ignoredHandlers: |
|
2527 self.violations.append((node, "M529")) |
|
2528 elif ( |
|
2529 len(names) == 1 |
|
2530 and not badHandlers |
|
2531 and not ignoredHandlers |
|
2532 and isinstance(node.type, ast.Tuple) |
|
2533 ): |
|
2534 self.violations.append((node, "M513", *names)) |
|
2535 else: |
|
2536 maybeError = self.__checkRedundantExcepthandlers(names, node) |
|
2537 if maybeError is not None: |
|
2538 self.violations.append(maybeError) |
|
2539 return names |
2514 |
2540 |
2515 def __checkForM515(self, node): |
2541 def __checkForM515(self, node): |
2516 """ |
2542 """ |
2517 Private method to check for pointless comparisons. |
2543 Private method to check for pointless comparisons. |
2518 |
2544 |
2623 return |
2649 return |
2624 |
2650 |
2625 # Preserve decorator order so we can get the lineno from the decorator node |
2651 # Preserve decorator order so we can get the lineno from the decorator node |
2626 # rather than the function node (this location definition changes in Python 3.8) |
2652 # rather than the function node (this location definition changes in Python 3.8) |
2627 resolvedDecorators = ( |
2653 resolvedDecorators = ( |
2628 ".".join(self.__composeCallPath(decorator)) |
2654 ".".join(composeCallPath(decorator)) |
2629 for decorator in node.decorator_list |
2655 for decorator in node.decorator_list |
2630 ) |
2656 ) |
2631 for idx, decorator in enumerate(resolvedDecorators): |
2657 for idx, decorator in enumerate(resolvedDecorators): |
2632 if decorator in {"classmethod", "staticmethod"}: |
2658 if decorator in {"classmethod", "staticmethod"}: |
2633 return |
2659 return |
2762 |
2788 |
2763 for err in sorted(suspiciousVariables): |
2789 for err in sorted(suspiciousVariables): |
2764 if reassignedInLoop.issuperset(err[2]): |
2790 if reassignedInLoop.issuperset(err[2]): |
2765 self.violations.append((err[3], "M523", err[2])) |
2791 self.violations.append((err[3], "M523", err[2])) |
2766 |
2792 |
2767 def __checkForM524AndM527(self, node): |
2793 def __checkForM524_M527(self, node): |
2768 """ |
2794 """ |
2769 Private method to check for inheritance from abstract classes in abc and lack of |
2795 Private method to check for inheritance from abstract classes in abc and lack of |
2770 any methods decorated with abstract*. |
2796 any methods decorated with abstract*. |
2771 |
2797 |
2772 @param node reference to the node to be processed |
2798 @param node reference to the node to be processed |
2857 """ |
2883 """ |
2858 seen = [] |
2884 seen = [] |
2859 |
2885 |
2860 for handler in node.handlers: |
2886 for handler in node.handlers: |
2861 if isinstance(handler.type, (ast.Name, ast.Attribute)): |
2887 if isinstance(handler.type, (ast.Name, ast.Attribute)): |
2862 name = ".".join(self.__composeCallPath(handler.type)) |
2888 name = ".".join(composeCallPath(handler.type)) |
2863 seen.append(name) |
2889 seen.append(name) |
2864 elif isinstance(handler.type, ast.Tuple): |
2890 elif isinstance(handler.type, ast.Tuple): |
2865 # to avoid checking the same as M514, remove duplicates per except |
2891 # to avoid checking the same as M514, remove duplicates per except |
2866 uniques = set() |
2892 uniques = set() |
2867 for entry in handler.type.elts: |
2893 for entry in handler.type.elts: |
2868 name = ".".join(self.__composeCallPath(entry)) |
2894 name = ".".join(composeCallPath(entry)) |
2869 uniques.add(name) |
2895 uniques.add(name) |
2870 seen.extend(uniques) |
2896 seen.extend(uniques) |
2871 |
2897 |
2872 # sort to have a deterministic output |
2898 # sort to have a deterministic output |
2873 duplicates = sorted({x for x in seen if seen.count(x) > 1}) |
2899 duplicates = sorted({x for x in seen if seen.count(x) > 1}) |
3004 @param node reference to the node to be processed |
3030 @param node reference to the node to be processed |
3005 @type ast.Call |
3031 @type ast.Call |
3006 """ |
3032 """ |
3007 if not isinstance(node.func, ast.Attribute): |
3033 if not isinstance(node.func, ast.Attribute): |
3008 return |
3034 return |
3009 if not isinstance(node.func.value, ast.Name) or node.func.value.id != "re": |
3035 func = node.func |
|
3036 if not isinstance(func.value, ast.Name) or func.value.id != "re": |
3010 return |
3037 return |
3011 |
3038 |
3012 def check(numArgs, paramName): |
3039 def check(numArgs, paramName): |
3013 if len(node.args) > numArgs: |
3040 if len(node.args) > numArgs: |
3014 self.violations.append((node, "M534", node.func.attr, paramName)) |
3041 arg = node.args[numArgs] |
3015 |
3042 self.violations.append((arg, "M534", func.attr, paramName)) |
3016 if node.func.attr in ("sub", "subn"): |
3043 |
|
3044 if func.attr in ("sub", "subn"): |
3017 check(3, "count") |
3045 check(3, "count") |
3018 elif node.func.attr == "split": |
3046 elif func.attr == "split": |
3019 check(2, "maxsplit") |
3047 check(2, "maxsplit") |
3020 |
3048 |
3021 def __checkForM535(self, node): |
3049 def __checkForM535(self, node): |
3022 """ |
3050 """ |
3023 Private method to check that a static key isn't used in a dict comprehension. |
3051 Private method to check that a static key isn't used in a dict comprehension. |
3032 self.violations.append((node, "M535", node.key.value)) |
3060 self.violations.append((node, "M535", node.key.value)) |
3033 elif isinstance( |
3061 elif isinstance( |
3034 node.key, ast.Name |
3062 node.key, ast.Name |
3035 ) and node.key.id not in self.__getDictCompLoopAndNamedExprVarNames(node): |
3063 ) and node.key.id not in self.__getDictCompLoopAndNamedExprVarNames(node): |
3036 self.violations.append((node, "M535", node.key.id)) |
3064 self.violations.append((node, "M535", node.key.id)) |
|
3065 |
|
3066 def __checkForM539(self, node): |
|
3067 """ |
|
3068 Private method to check for correct ContextVar usage. |
|
3069 |
|
3070 @param node reference to the node to be processed |
|
3071 @type ast.Call |
|
3072 """ |
|
3073 if not ( |
|
3074 (isinstance(node.func, ast.Name) and node.func.id == "ContextVar") |
|
3075 or ( |
|
3076 isinstance(node.func, ast.Attribute) |
|
3077 and node.func.attr == "ContextVar" |
|
3078 and isinstance(node.func.value, ast.Name) |
|
3079 and node.func.value.id == "contextvars" |
|
3080 ) |
|
3081 ): |
|
3082 return |
|
3083 |
|
3084 # ContextVar only takes one kw currently, but better safe than sorry |
|
3085 for kw in node.keywords: |
|
3086 if kw.arg == "default": |
|
3087 break |
|
3088 else: |
|
3089 return |
|
3090 |
|
3091 visitor = FunctionDefDefaultsVisitor("M539", "M539") |
|
3092 visitor.visit(kw.value) |
|
3093 self.violations.extend(visitor.errors) |
|
3094 |
|
3095 def __checkForM540AddNote(self, node): |
|
3096 """ |
|
3097 Private method to check add_note usage. |
|
3098 |
|
3099 @param node reference to the node to be processed |
|
3100 @type ast.Attribute |
|
3101 @return flag |
|
3102 @rtype bool |
|
3103 """ |
|
3104 if ( |
|
3105 node.attr == "add_note" |
|
3106 and isinstance(node.value, ast.Name) |
|
3107 and self.__M540CaughtException |
|
3108 and node.value.id == self.__M540CaughtException.name |
|
3109 ): |
|
3110 self.__M540CaughtException.hasNote = True |
|
3111 return True |
|
3112 |
|
3113 return False |
|
3114 |
|
3115 def __checkForM540Usage(self, node): |
|
3116 """ |
|
3117 Private method to check the usage of exceptions with added note. |
|
3118 |
|
3119 @param node reference to the node to be processed |
|
3120 @type ast.expr or None |
|
3121 """ |
|
3122 def superwalk(node: ast.AST | list[ast.AST]): |
|
3123 """ |
|
3124 Function to walk an AST node or a list of AST nodes. |
|
3125 |
|
3126 @param node reference to the node or a list of nodes to be processed |
|
3127 @type ast.AST or list[ast.AST] |
|
3128 @yield next node to be processed |
|
3129 @ytype ast.AST |
|
3130 """ |
|
3131 if isinstance(node, list): |
|
3132 for n in node: |
|
3133 yield from ast.walk(n) |
|
3134 else: |
|
3135 yield from ast.walk(node) |
|
3136 |
|
3137 if not self.__M540CaughtException or node is None: |
|
3138 return |
|
3139 |
|
3140 for n in superwalk(node): |
|
3141 if isinstance(n, ast.Name) and n.id == self.__M540CaughtException.name: |
|
3142 self.__M540CaughtException = None |
|
3143 break |
3037 |
3144 |
3038 def __checkForM569(self, node): |
3145 def __checkForM569(self, node): |
3039 """ |
3146 """ |
3040 Private method to check for changes to a loop's mutable iterable. |
3147 Private method to check for changes to a loop's mutable iterable. |
3041 |
3148 |
3299 |
3406 |
3300 @return dictionary containing the names as keys and the list of nodes |
3407 @return dictionary containing the names as keys and the list of nodes |
3301 @rtype dict |
3408 @rtype dict |
3302 """ |
3409 """ |
3303 return self.__names |
3410 return self.__names |
|
3411 |
|
3412 |
|
3413 class FunctionDefDefaultsVisitor(ast.NodeVisitor): |
|
3414 """ |
|
3415 Class used by M506, M508 and M539. |
|
3416 """ |
|
3417 |
|
3418 def __init__( |
|
3419 self, |
|
3420 errorCodeCalls, # M506 or M539 |
|
3421 errorCodeLiterals, # M508 or M539 |
|
3422 ): |
|
3423 """ |
|
3424 Constructor |
|
3425 |
|
3426 @param errorCodeCalls error code for ast.Call nodes |
|
3427 @type str |
|
3428 @param errorCodeLiterals error code for literal nodes |
|
3429 @type str |
|
3430 """ |
|
3431 self.__errorCodeCalls = errorCodeCalls |
|
3432 self.__errorCodeLiterals = errorCodeLiterals |
|
3433 for nodeType in BugbearMutableLiterals + BugbearMutableComprehensions: |
|
3434 setattr( |
|
3435 self, f"visit_{nodeType}", self.__visitMutableLiteralOrComprehension |
|
3436 ) |
|
3437 self.errors = [] |
|
3438 self.__argDepth = 0 |
|
3439 |
|
3440 super().__init__() |
|
3441 |
|
3442 def __visitMutableLiteralOrComprehension(self, node): |
|
3443 """ |
|
3444 Private method to flag mutable literals and comprehensions. |
|
3445 |
|
3446 @param node AST node to be processed |
|
3447 @type ast.Dict, ast.List, ast.Set, ast.ListComp, ast.DictComp or ast.SetComp |
|
3448 """ |
|
3449 # Flag M506 if mutable literal/comprehension is not nested. |
|
3450 # We only flag these at the top level of the expression as we |
|
3451 # cannot easily guarantee that nested mutable structures are not |
|
3452 # made immutable by outer operations, so we prefer no false positives. |
|
3453 # e.g. |
|
3454 # >>> def this_is_fine(a=frozenset({"a", "b", "c"})): ... |
|
3455 # |
|
3456 # >>> def this_is_not_fine_but_hard_to_detect(a=(lambda x: x)([1, 2, 3])) |
|
3457 # |
|
3458 # We do still search for cases of B008 within mutable structures though. |
|
3459 if self.__argDepth == 1: |
|
3460 self.errors.append((node, self.__errorCodeCalls)) |
|
3461 |
|
3462 # Check for nested functions. |
|
3463 self.generic_visit(node) |
|
3464 |
|
3465 def visit_Call(self, node): |
|
3466 """ |
|
3467 Public method to process Call nodes. |
|
3468 |
|
3469 @param node AST node to be processed |
|
3470 @type ast.Call |
|
3471 """ |
|
3472 callPath = ".".join(composeCallPath(node.func)) |
|
3473 if callPath in BugbearMutableCalls: |
|
3474 self.errors.append((node, self.__errorCodeCalls)) |
|
3475 self.generic_visit(node) |
|
3476 return |
|
3477 |
|
3478 if callPath in BugbearImmutableCalls: |
|
3479 self.generic_visit(node) |
|
3480 return |
|
3481 |
|
3482 # Check if function call is actually a float infinity/NaN literal |
|
3483 if callPath == "float" and len(node.args) == 1: |
|
3484 try: |
|
3485 value = float(ast.literal_eval(node.args[0])) |
|
3486 except Exception: |
|
3487 pass |
|
3488 else: |
|
3489 if math.isfinite(value): |
|
3490 self.errors.append((node, self.__errorCodeLiterals)) |
|
3491 else: |
|
3492 self.errors.append((node, self.__errorCodeLiterals)) |
|
3493 |
|
3494 # Check for nested functions. |
|
3495 self.generic_visit(node) |
|
3496 |
|
3497 def visit_Lambda(self, node): |
|
3498 """ |
|
3499 Public method to process Lambda nodes. |
|
3500 |
|
3501 @param node AST node to be processed |
|
3502 @type ast.Lambda |
|
3503 """ |
|
3504 # Don't recurse into lambda expressions |
|
3505 # as they are evaluated at call time. |
|
3506 pass |
|
3507 |
|
3508 def visit(self, node): |
|
3509 """ |
|
3510 Public method to traverse an AST node or a list of AST nodes. |
|
3511 |
|
3512 This is an extended method that can also handle a list of AST nodes. |
|
3513 |
|
3514 @param node AST node or list of AST nodes to be processed |
|
3515 @type ast.AST or list of ast.AST |
|
3516 """ |
|
3517 self.__argDepth += 1 |
|
3518 if isinstance(node, list): |
|
3519 for elem in node: |
|
3520 if elem is not None: |
|
3521 super().visit(elem) |
|
3522 else: |
|
3523 super().visit(node) |
|
3524 self.__argDepth -= 1 |
3304 |
3525 |
3305 |
3526 |
3306 class M520NameFinder(NameFinder): |
3527 class M520NameFinder(NameFinder): |
3307 """ |
3528 """ |
3308 Class to extract a name out of a tree of nodes ignoring names defined within the |
3529 Class to extract a name out of a tree of nodes ignoring names defined within the |