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

branch
eric7
changeset 10999
c3cf24fe9113
parent 10997
d470b58626d2
child 11000
f8371a2dd08f
equal deleted inserted replaced
10998:6d7bddfde5fe 10999:c3cf24fe9113
10 import ast 10 import ast
11 import builtins 11 import builtins
12 import contextlib 12 import contextlib
13 import copy 13 import copy
14 import itertools 14 import itertools
15 import math
15 import re 16 import re
16 import sys 17 import sys
17 import tokenize 18 import tokenize
18 19
19 from collections import defaultdict, namedtuple 20 from collections import defaultdict, namedtuple
21 from dataclasses import dataclass
20 from keyword import iskeyword 22 from keyword import iskeyword
21 from string import Formatter 23 from string import Formatter
22 24
23 try: 25 try:
24 # Python 3.10+ 26 # Python 3.10+
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.
49 @ytype str 81 @ytype str
50 """ 82 """
51 if isinstance(node, ast.Attribute): 83 if isinstance(node, ast.Attribute):
52 yield from composeCallPath(node.value) 84 yield from composeCallPath(node.value)
53 yield node.attr 85 yield node.attr
86 elif isinstance(node, ast.Call):
87 yield from composeCallPath(node.func)
54 elif isinstance(node, ast.Name): 88 elif isinstance(node, ast.Name):
55 yield node.id 89 yield node.id
56 90
57 91
58 class MiscellaneousChecker: 92 class MiscellaneousChecker:
138 "M501", 172 "M501",
139 "M502", 173 "M502",
140 "M503", 174 "M503",
141 "M504", 175 "M504",
142 "M505", 176 "M505",
177 "M506",
143 "M507", 178 "M507",
179 "M508",
144 "M509", 180 "M509",
145 "M510", 181 "M510",
146 "M511", 182 "M511",
147 "M512", 183 "M512",
148 "M513", 184 "M513",
168 "M533", 204 "M533",
169 "M534", 205 "M534",
170 "M535", 206 "M535",
171 "M536", 207 "M536",
172 "M537", 208 "M537",
209 "M539",
210 "M540",
173 ## Bugbear, opininonated 211 ## Bugbear, opininonated
174 "M569", 212 "M569",
175 ## Bugbear++ 213 ## Bugbear++
176 "M581", 214 "M581",
177 "M582", 215 "M582",
194 "M711", 232 "M711",
195 ## print() statements 233 ## print() statements
196 "M801", 234 "M801",
197 ## one element tuple 235 ## one element tuple
198 "M811", 236 "M811",
199 ## Mutable Defaults
200 "M821",
201 "M822",
202 ## return statements 237 ## return statements
203 "M831", 238 "M831",
204 "M832", 239 "M832",
205 "M833", 240 "M833",
206 "M834", 241 "M834",
350 "M501", 385 "M501",
351 "M502", 386 "M502",
352 "M503", 387 "M503",
353 "M504", 388 "M504",
354 "M505", 389 "M505",
390 "M506",
355 "M507", 391 "M507",
392 "M508",
356 "M509", 393 "M509",
357 "M510", 394 "M510",
358 "M511", 395 "M511",
359 "M512", 396 "M512",
360 "M513", 397 "M513",
380 "M533", 417 "M533",
381 "M534", 418 "M534",
382 "M535", 419 "M535",
383 "M536", 420 "M536",
384 "M537", 421 "M537",
422 "M539",
423 "M540",
385 "M569", 424 "M569",
386 "M581", 425 "M581",
387 "M582", 426 "M582",
388 ), 427 ),
389 ), 428 ),
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.
1719 self.violations = [] 1691 self.violations = []
1720 self.contexts = [] 1692 self.contexts = []
1721 1693
1722 self.__M523Seen = set() 1694 self.__M523Seen = set()
1723 self.__M505Imports = set() 1695 self.__M505Imports = set()
1696 self.__M540CaughtException = None
1724 1697
1725 @property 1698 @property
1726 def nodeStack(self): 1699 def nodeStack(self):
1727 """ 1700 """
1728 Public method to get a reference to the most recent node stack. 1701 Public method to get a reference to the most recent node stack.
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
2197 Public method to handle assignments. 2153 Public method to handle assignments.
2198 2154
2199 @param node reference to the node to be processed 2155 @param node reference to the node to be processed
2200 @type ast.Assign 2156 @type ast.Assign
2201 """ 2157 """
2158 self.__checkForM540Usage(node.value)
2202 if len(node.targets) == 1: 2159 if len(node.targets) == 1:
2203 target = node.targets[0] 2160 target = node.targets[0]
2204 if ( 2161 if (
2205 isinstance(target, ast.Attribute) 2162 isinstance(target, ast.Attribute)
2206 and isinstance(target.value, ast.Name) 2163 and isinstance(target.value, ast.Name)
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
2328 2297
2329 @param node reference to the node to be processed 2298 @param node reference to the node to be processed
2330 @type ast.ClassDef 2299 @type ast.ClassDef
2331 """ 2300 """
2332 self.__checkForM521(node) 2301 self.__checkForM521(node)
2333 self.__checkForM524AndM527(node) 2302 self.__checkForM524_M527(node)
2334 2303
2335 self.generic_visit(node) 2304 self.generic_visit(node)
2336 2305
2337 def visit_Try(self, node): 2306 def visit_Try(self, node):
2338 """ 2307 """
2362 Public method to handle 'raise' statements. 2331 Public method to handle 'raise' statements.
2363 2332
2364 @param node reference to the node to be processed 2333 @param node reference to the node to be processed
2365 @type ast.Raise 2334 @type ast.Raise
2366 """ 2335 """
2336 if node.exc is None:
2337 self.__M540CaughtException = None
2338 else:
2339 self.__checkForM540Usage(node.exc)
2340 self.__checkForM540Usage(node.cause)
2367 self.__checkForM516(node) 2341 self.__checkForM516(node)
2368 2342
2369 self.generic_visit(node) 2343 self.generic_visit(node)
2370 2344
2371 def visit_With(self, node): 2345 def visit_With(self, node):
2399 2373
2400 @param node reference to the node to be processed 2374 @param node reference to the node to be processed
2401 @type ast.AnnAssign 2375 @type ast.AnnAssign
2402 """ 2376 """
2403 self.__checkForM532(node) 2377 self.__checkForM532(node)
2378 self.__checkForM540Usage(node.value)
2404 2379
2405 self.generic_visit(node) 2380 self.generic_visit(node)
2406 2381
2407 def visit_Import(self, node): 2382 def visit_Import(self, node):
2408 """ 2383 """
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

eric ide

mercurial