1615 |
1617 |
1616 |
1618 |
1617 ####################################################################### |
1619 ####################################################################### |
1618 ## BugBearVisitor |
1620 ## BugBearVisitor |
1619 ## |
1621 ## |
1620 ## adapted from: flake8-bugbear v23.7.10 |
1622 ## adapted from: flake8-bugbear v23.11.26 |
1621 ## |
1623 ## |
1622 ## Original: Copyright (c) 2016 Łukasz Langa |
1624 ## Original: Copyright (c) 2016 Łukasz Langa |
1623 ####################################################################### |
1625 ####################################################################### |
1624 |
1626 |
1625 BugBearContext = namedtuple("BugBearContext", ["node", "stack"]) |
1627 BugBearContext = namedtuple("BugBearContext", ["node", "stack"]) |
1894 @yield node references as determined by the ast.walk() function |
1896 @yield node references as determined by the ast.walk() function |
1895 @ytype ast.Node |
1897 @ytype ast.Node |
1896 """ |
1898 """ |
1897 for node in nodes: |
1899 for node in nodes: |
1898 yield from ast.walk(node) |
1900 yield from ast.walk(node) |
|
1901 |
|
1902 def __getNamesFromTuple(self, node): |
|
1903 """ |
|
1904 Private method to get the names from an ast.Tuple node. |
|
1905 |
|
1906 @param node ast node to be processed |
|
1907 @type ast.Tuple |
|
1908 @yield names |
|
1909 @ytype str |
|
1910 """ |
|
1911 for dim in node.elts: |
|
1912 if isinstance(dim, ast.Name): |
|
1913 yield dim.id |
|
1914 elif isinstance(dim, ast.Tuple): |
|
1915 yield from self.__getNamesFromTuple(dim) |
|
1916 |
|
1917 def __getDictCompLoopVarNames(self, node): |
|
1918 """ |
|
1919 Private method to get the names of comprehension loop variables. |
|
1920 |
|
1921 @param node ast node to be processed |
|
1922 @type ast.DictComp |
|
1923 @yield loop variable names |
|
1924 @ytype str |
|
1925 """ |
|
1926 for gen in node.generators: |
|
1927 if isinstance(gen.target, ast.Name): |
|
1928 yield gen.target.id |
|
1929 elif isinstance(gen.target, ast.Tuple): |
|
1930 yield from self.__getNamesFromTuple(gen.target) |
1899 |
1931 |
1900 def visit(self, node): |
1932 def visit(self, node): |
1901 """ |
1933 """ |
1902 Public method to traverse a given AST node. |
1934 Public method to traverse a given AST node. |
1903 |
1935 |
2022 and self.__isIdentifier(node.args[1]) |
2054 and self.__isIdentifier(node.args[1]) |
2023 and iskeyword(AstUtilities.getValue(node.args[1])) |
2055 and iskeyword(AstUtilities.getValue(node.args[1])) |
2024 ): |
2056 ): |
2025 self.violations.append((node, "M510")) |
2057 self.violations.append((node, "M510")) |
2026 |
2058 |
2027 self.__checkForM526(node) |
2059 self.__checkForM526(node) |
2028 |
2060 |
2029 self.__checkForM528(node) |
2061 self.__checkForM528(node) |
2030 self.__checkForM534(node) |
2062 self.__checkForM534(node) |
2031 |
2063 |
2032 self.generic_visit(node) |
2064 self.generic_visit(node) |
2263 """ |
2296 """ |
2264 self.__checkForM505(node) |
2297 self.__checkForM505(node) |
2265 |
2298 |
2266 self.generic_visit(node) |
2299 self.generic_visit(node) |
2267 |
2300 |
|
2301 def visit_ImportFrom(self, node): |
|
2302 """ |
|
2303 Public method to check from imports. |
|
2304 |
|
2305 @param node reference to the node to be processed |
|
2306 @type ast.Import |
|
2307 """ |
|
2308 self.visit_Import(node) |
|
2309 |
2268 def visit_Set(self, node): |
2310 def visit_Set(self, node): |
2269 """ |
2311 """ |
2270 Public method to check a set. |
2312 Public method to check a set. |
2271 |
2313 |
2272 @param node reference to the node to be processed |
2314 @param node reference to the node to be processed |
2284 @type ast.Call |
2326 @type ast.Call |
2285 """ |
2327 """ |
2286 if isinstance(node, ast.Import): |
2328 if isinstance(node, ast.Import): |
2287 for name in node.names: |
2329 for name in node.names: |
2288 self.__M505Imports.add(name.asname or name.name) |
2330 self.__M505Imports.add(name.asname or name.name) |
|
2331 elif isinstance(node, ast.ImportFrom): |
|
2332 for name in node.names: |
|
2333 self.__M505Imports.add(f"{node.module}.{name.name or name.asname}") |
2289 elif isinstance(node, ast.Call): |
2334 elif isinstance(node, ast.Call): |
2290 if node.func.attr not in ("lstrip", "rstrip", "strip"): |
2335 if node.func.attr not in ("lstrip", "rstrip", "strip"): |
2291 return # method name doesn't match |
2336 return # method name doesn't match |
2292 |
2337 |
2293 if ( |
2338 if ( |
2384 """ |
2429 """ |
2385 item = node.items[0] |
2430 item = node.items[0] |
2386 itemContext = item.context_expr |
2431 itemContext = item.context_expr |
2387 if ( |
2432 if ( |
2388 hasattr(itemContext, "func") |
2433 hasattr(itemContext, "func") |
2389 and isinstance(itemContext.func, ast.Attribute) |
|
2390 and ( |
2434 and ( |
2391 itemContext.func.attr == "assertRaises" |
2435 ( |
|
2436 isinstance(itemContext.func, ast.Attribute) |
|
2437 and ( |
|
2438 itemContext.func.attr == "assertRaises" |
|
2439 or ( |
|
2440 itemContext.func.attr == "raises" |
|
2441 and isinstance(itemContext.func.value, ast.Name) |
|
2442 and itemContext.func.value.id == "pytest" |
|
2443 and "match" |
|
2444 not in [kwd.arg for kwd in itemContext.keywords] |
|
2445 ) |
|
2446 ) |
|
2447 ) |
2392 or ( |
2448 or ( |
2393 itemContext.func.attr == "raises" |
2449 isinstance(itemContext.func, ast.Name) |
2394 and isinstance(itemContext.func.value, ast.Name) |
2450 and itemContext.func.id == "raises" |
2395 and itemContext.func.value.id == "pytest" |
2451 and isinstance(itemContext.func.ctx, ast.Load) |
|
2452 and "pytest.raises" in self.__M505Imports |
2396 and "match" not in [kwd.arg for kwd in itemContext.keywords] |
2453 and "match" not in [kwd.arg for kwd in itemContext.keywords] |
2397 ) |
2454 ) |
2398 ) |
2455 ) |
2399 and len(itemContext.args) == 1 |
2456 and len(itemContext.args) == 1 |
2400 and isinstance(itemContext.args[0], ast.Name) |
2457 and isinstance(itemContext.args[0], ast.Name) |
2843 |
2900 |
2844 if node.func.attr in ("sub", "subn"): |
2901 if node.func.attr in ("sub", "subn"): |
2845 check(3, "count") |
2902 check(3, "count") |
2846 elif node.func.attr == "split": |
2903 elif node.func.attr == "split": |
2847 check(2, "maxsplit") |
2904 check(2, "maxsplit") |
|
2905 |
|
2906 def __checkForM535(self, node: ast.DictComp): |
|
2907 """ |
|
2908 Private method to check that a static key isn't used in a dict comprehension. |
|
2909 |
|
2910 Record a warning if a likely unchanging key is used - either a constant, |
|
2911 or a variable that isn't coming from the generator expression. |
|
2912 |
|
2913 @param node reference to the node to be processed |
|
2914 @type ast.DictComp |
|
2915 """ |
|
2916 """Check that a static key isn't used in a dict comprehension. |
|
2917 |
|
2918 Emit a warning if a likely unchanging key is used - either a constant, |
|
2919 or a variable that isn't coming from the generator expression. |
|
2920 """ |
|
2921 if isinstance(node.key, ast.Constant): |
|
2922 self.violations.append((node, "M535", node.key.value)) |
|
2923 elif isinstance(node.key, ast.Name): |
|
2924 if node.key.id not in self.__getDictCompLoopVarNames(node): |
|
2925 self.violations.append((node, "M535", node.key.id)) |
2848 |
2926 |
2849 |
2927 |
2850 class NameFinder(ast.NodeVisitor): |
2928 class NameFinder(ast.NodeVisitor): |
2851 """ |
2929 """ |
2852 Class to extract a name out of a tree of nodes. |
2930 Class to extract a name out of a tree of nodes. |