2293 |
2299 |
2294 @param loopNode reference to the node to be processed |
2300 @param loopNode reference to the node to be processed |
2295 @type ast.For, ast.AsyncFor, ast.While, ast.ListComp, ast.SetComp,ast.DictComp, |
2301 @type ast.For, ast.AsyncFor, ast.While, ast.ListComp, ast.SetComp,ast.DictComp, |
2296 or ast.GeneratorExp |
2302 or ast.GeneratorExp |
2297 """ |
2303 """ |
|
2304 safe_functions = [] |
2298 suspiciousVariables = [] |
2305 suspiciousVariables = [] |
2299 for node in ast.walk(loopNode): |
2306 for node in ast.walk(loopNode): |
2300 if isinstance(node, BugBearVisitor.FUNCTION_NODES): |
2307 # check if function is immediately consumed to avoid false alarm |
|
2308 if isinstance(node, ast.Call): |
|
2309 # check for filter&reduce |
|
2310 if ( |
|
2311 isinstance(node.func, ast.Name) |
|
2312 and node.func.id in ("filter", "reduce", "map") |
|
2313 ) or ( |
|
2314 isinstance(node.func, ast.Attribute) |
|
2315 and node.func.attr == "reduce" |
|
2316 and isinstance(node.func.value, ast.Name) |
|
2317 and node.func.value.id == "functools" |
|
2318 ): |
|
2319 for arg in node.args: |
|
2320 if isinstance(arg, BugBearVisitor.FUNCTION_NODES): |
|
2321 safe_functions.append(arg) |
|
2322 |
|
2323 # check for key= |
|
2324 for keyword in node.keywords: |
|
2325 if keyword.arg == "key" and isinstance( |
|
2326 keyword.value, BugBearVisitor.FUNCTION_NODES |
|
2327 ): |
|
2328 safe_functions.append(keyword.value) |
|
2329 |
|
2330 # mark `return lambda: x` as safe |
|
2331 # does not (currently) check inner lambdas in a returned expression |
|
2332 # e.g. `return (lambda: x, ) |
|
2333 if isinstance(node, ast.Return) and isinstance( |
|
2334 node.value, BugBearVisitor.FUNCTION_NODES |
|
2335 ): |
|
2336 safe_functions.append(node.value) |
|
2337 |
|
2338 # find unsafe functions |
|
2339 if ( |
|
2340 isinstance(node, BugBearVisitor.FUNCTION_NODES) |
|
2341 and node not in safe_functions |
|
2342 ): |
2301 argnames = { |
2343 argnames = { |
2302 arg.arg for arg in ast.walk(node.args) if isinstance(arg, ast.arg) |
2344 arg.arg for arg in ast.walk(node.args) if isinstance(arg, ast.arg) |
2303 } |
2345 } |
2304 if isinstance(node, ast.Lambda): |
2346 if isinstance(node, ast.Lambda): |
2305 bodyNodes = ast.walk(node.body) |
2347 bodyNodes = ast.walk(node.body) |
2306 else: |
2348 else: |
2307 bodyNodes = itertools.chain.from_iterable(map(ast.walk, node.body)) |
2349 bodyNodes = itertools.chain.from_iterable(map(ast.walk, node.body)) |
|
2350 errors = [] |
2308 for name in bodyNodes: |
2351 for name in bodyNodes: |
2309 if ( |
2352 if isinstance(name, ast.Name) and name.id not in argnames: |
2310 isinstance(name, ast.Name) |
2353 if isinstance(name.ctx, ast.Load): |
2311 and name.id not in argnames |
2354 errors.append((name.lineno, name.col_offset, name.id, name)) |
2312 and isinstance(name.ctx, ast.Load) |
2355 elif isinstance(name.ctx, ast.Store): |
2313 ): |
2356 argnames.add(name.id) |
2314 err = (name.lineno, name.col_offset, name.id, name) |
2357 for err in errors: |
2315 if err not in self.__M523Seen: |
2358 if err[2] not in argnames and err not in self.__M523Seen: |
2316 self.__M523Seen.add(err) # dedupe across nested loops |
2359 self.__M523Seen.add(err) # dedupe across nested loops |
2317 suspiciousVariables.append(err) |
2360 suspiciousVariables.append(err) |
2318 |
2361 |
2319 if suspiciousVariables: |
2362 if suspiciousVariables: |
2320 reassignedInLoop = set(self.__getAssignedNames(loopNode)) |
2363 reassignedInLoop = set(self.__getAssignedNames(loopNode)) |
2321 |
2364 |
2322 for err in sorted(suspiciousVariables): |
2365 for err in sorted(suspiciousVariables): |
2323 if reassignedInLoop.issuperset(err[2]): |
2366 if reassignedInLoop.issuperset(err[2]): |
2324 self.violations.append((err[3], "M523", err[2])) |
2367 self.violations.append((err[3], "M523", err[2])) |
2325 |
2368 |
2326 def __checkForM524(self, node): |
2369 def __checkForM524AndM527(self, node): |
2327 """ |
2370 """ |
2328 Private method to check for inheritance from abstract classes in abc and lack of |
2371 Private method to check for inheritance from abstract classes in abc and lack of |
2329 any methods decorated with abstract*. |
2372 any methods decorated with abstract*. |
2330 |
2373 |
2331 @param node reference to the node to be processed |
2374 @param node reference to the node to be processed |
2332 @type ast.ClassDef |
2375 @type ast.ClassDef |
2333 """ # __IGNORE_WARNING_D234r__ |
2376 """ # __IGNORE_WARNING_D234r__ |
2334 |
2377 |
2335 def isAbcClass(value): |
2378 def isAbcClass(value, name="ABC"): |
2336 if isinstance(value, ast.keyword): |
2379 if isinstance(value, ast.keyword): |
2337 return value.arg == "metaclass" and isAbcClass(value.value) |
2380 return value.arg == "metaclass" and isAbcClass(value.value, "ABCMeta") |
2338 |
2381 |
2339 abcNames = ("ABC", "ABCMeta") |
2382 # class foo(ABC) |
2340 return (isinstance(value, ast.Name) and value.id in abcNames) or ( |
2383 # class foo(abc.ABC) |
|
2384 return (isinstance(value, ast.Name) and value.id == name) or ( |
2341 isinstance(value, ast.Attribute) |
2385 isinstance(value, ast.Attribute) |
2342 and value.attr in abcNames |
2386 and value.attr == name |
2343 and isinstance(value.value, ast.Name) |
2387 and isinstance(value.value, ast.Name) |
2344 and value.value.id == "abc" |
2388 and value.value.id == "abc" |
2345 ) |
2389 ) |
2346 |
2390 |
2347 def isAbstractDecorator(expr): |
2391 def isAbstractDecorator(expr): |
2348 return (isinstance(expr, ast.Name) and expr.id[:8] == "abstract") or ( |
2392 return (isinstance(expr, ast.Name) and expr.id[:8] == "abstract") or ( |
2349 isinstance(expr, ast.Attribute) and expr.attr[:8] == "abstract" |
2393 isinstance(expr, ast.Attribute) and expr.attr[:8] == "abstract" |
2350 ) |
2394 ) |
2351 |
2395 |
|
2396 def isOverload(expr): |
|
2397 return (isinstance(expr, ast.Name) and expr.id == "overload") or ( |
|
2398 isinstance(expr, ast.Attribute) and expr.attr == "overload" |
|
2399 ) |
|
2400 |
|
2401 def emptyBody(body): |
|
2402 def isStrOrEllipsis(node): |
|
2403 # ast.Ellipsis and ast.Str used in python<3.8 |
|
2404 return isinstance(node, (ast.Ellipsis, ast.Str)) or ( |
|
2405 isinstance(node, ast.Constant) |
|
2406 and (node.value is Ellipsis or isinstance(node.value, str)) |
|
2407 ) |
|
2408 |
|
2409 # Function body consist solely of `pass`, `...`, and/or (doc)string literals |
|
2410 return all( |
|
2411 isinstance(stmt, ast.Pass) |
|
2412 or (isinstance(stmt, ast.Expr) and isStrOrEllipsis(stmt.value)) |
|
2413 for stmt in body |
|
2414 ) |
|
2415 |
|
2416 # don't check multiple inheritance |
|
2417 if len(node.bases) + len(node.keywords) > 1: |
|
2418 return |
|
2419 |
|
2420 # only check abstract classes |
2352 if not any(map(isAbcClass, (*node.bases, *node.keywords))): |
2421 if not any(map(isAbcClass, (*node.bases, *node.keywords))): |
2353 return |
2422 return |
2354 |
2423 |
|
2424 hasAbstractMethod = False |
|
2425 |
|
2426 if not any(map(isAbcClass, (*node.bases, *node.keywords))): |
|
2427 return |
|
2428 |
2355 for stmt in node.body: |
2429 for stmt in node.body: |
2356 if isinstance(stmt, (ast.FunctionDef, ast.AsyncFunctionDef)) and any( |
2430 # Ignore abc's that declares a class attribute that must be set |
2357 map(isAbstractDecorator, stmt.decorator_list) |
2431 if isinstance(stmt, (ast.AnnAssign, ast.Assign)): |
|
2432 hasAbstractMethod = True |
|
2433 continue |
|
2434 |
|
2435 # only check function defs |
|
2436 if not isinstance(stmt, (ast.FunctionDef, ast.AsyncFunctionDef)): |
|
2437 continue |
|
2438 |
|
2439 hasAbstractDecorator = any(map(isAbstractDecorator, stmt.decorator_list)) |
|
2440 |
|
2441 hasAbstractMethod |= hasAbstractDecorator |
|
2442 |
|
2443 if ( |
|
2444 not hasAbstractDecorator |
|
2445 and emptyBody(stmt.body) |
|
2446 and not any(map(isOverload, stmt.decorator_list)) |
2358 ): |
2447 ): |
2359 return |
2448 self.violations.append((stmt, "M527", stmt.name)) |
2360 |
2449 |
2361 self.violations.append((node, "M524", node.name)) |
2450 if not hasAbstractMethod: |
|
2451 self.violations.append((node, "M524", node.name)) |
2362 |
2452 |
2363 def __checkForM525(self, node): |
2453 def __checkForM525(self, node): |
2364 """ |
2454 """ |
2365 Private method to check for exceptions being handled multiple times. |
2455 Private method to check for exceptions being handled multiple times. |
2366 |
2456 |