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

branch
eric7
changeset 9595
2bd590c40309
parent 9482
a2bc06a54d9d
child 9653
e67609152c5e
equal deleted inserted replaced
9594:bd9550caf22f 9595:2bd590c40309
124 "M521", 124 "M521",
125 "M522", 125 "M522",
126 "M523", 126 "M523",
127 "M524", 127 "M524",
128 "M525", 128 "M525",
129 "M526",
130 "M527",
129 ## Bugbear++ 131 ## Bugbear++
130 "M581", 132 "M581",
131 "M582", 133 "M582",
132 ## Format Strings 134 ## Format Strings
133 "M601", 135 "M601",
307 "M521", 309 "M521",
308 "M522", 310 "M522",
309 "M523", 311 "M523",
310 "M524", 312 "M524",
311 "M525", 313 "M525",
314 "M526",
315 "M527",
312 "M581", 316 "M581",
313 "M582", 317 "M582",
314 ), 318 ),
315 ), 319 ),
316 (self.__checkPep3101, ("M601",)), 320 (self.__checkPep3101, ("M601",)),
1526 """ 1530 """
1527 Class implementing a node visitor to check for various topics. 1531 Class implementing a node visitor to check for various topics.
1528 """ 1532 """
1529 1533
1530 # 1534 #
1531 # This class was implemented along the BugBear flake8 extension (v 22.9.11). 1535 # This class was implemented along the BugBear flake8 extension (v 22.12.6).
1532 # Original: Copyright (c) 2016 Łukasz Langa 1536 # Original: Copyright (c) 2016 Łukasz Langa
1533 # 1537 #
1534 1538
1535 CONTEXTFUL_NODES = ( 1539 CONTEXTFUL_NODES = (
1536 ast.Module, 1540 ast.Module,
1852 and self.__isIdentifier(node.args[1]) 1856 and self.__isIdentifier(node.args[1])
1853 and iskeyword(AstUtilities.getValue(node.args[1])) 1857 and iskeyword(AstUtilities.getValue(node.args[1]))
1854 ): 1858 ):
1855 self.violations.append((node, "M510")) 1859 self.violations.append((node, "M510"))
1856 1860
1861 self.__checkForM526(node)
1862
1857 self.generic_visit(node) 1863 self.generic_visit(node)
1858 1864
1859 def visit_Assign(self, node): 1865 def visit_Assign(self, node):
1860 """ 1866 """
1861 Public method to handle assignments. 1867 Public method to handle assignments.
1990 @param node reference to the node to be processed 1996 @param node reference to the node to be processed
1991 @type ast.ClassDef 1997 @type ast.ClassDef
1992 """ 1998 """
1993 self.__checkForM518(node) 1999 self.__checkForM518(node)
1994 self.__checkForM521(node) 2000 self.__checkForM521(node)
1995 self.__checkForM524(node) 2001 self.__checkForM524AndM527(node)
1996 2002
1997 self.generic_visit(node) 2003 self.generic_visit(node)
1998 2004
1999 def visit_Try(self, node): 2005 def visit_Try(self, node):
2000 """ 2006 """
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
2383 2473
2384 # sort to have a deterministic output 2474 # sort to have a deterministic output
2385 duplicates = sorted({x for x in seen if seen.count(x) > 1}) 2475 duplicates = sorted({x for x in seen if seen.count(x) > 1})
2386 for duplicate in duplicates: 2476 for duplicate in duplicates:
2387 self.violations.append((node, "M525", duplicate)) 2477 self.violations.append((node, "M525", duplicate))
2478
2479 def __checkForM526(self, node):
2480 """
2481 Private method to check for Star-arg unpacking after keyword argument.
2482
2483 @param node reference to the node to be processed
2484 @type ast.Call
2485 """
2486 if not node.keywords:
2487 return
2488
2489 starreds = [arg for arg in node.args if isinstance(arg, ast.Starred)]
2490 if not starreds:
2491 return
2492
2493 firstKeyword = node.keywords[0].value
2494 for starred in starreds:
2495 if (starred.lineno, starred.col_offset) > (
2496 firstKeyword.lineno,
2497 firstKeyword.col_offset,
2498 ):
2499 self.violations.append((node, "M526"))
2388 2500
2389 2501
2390 class NameFinder(ast.NodeVisitor): 2502 class NameFinder(ast.NodeVisitor):
2391 """ 2503 """
2392 Class to extract a name out of a tree of nodes. 2504 Class to extract a name out of a tree of nodes.

eric ide

mercurial