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

branch
eric7
changeset 10510
fa7b8ebfbe13
parent 10439
21c28b0f9e41
child 10516
72baef0baa76
equal deleted inserted replaced
10509:094ab8028423 10510:fa7b8ebfbe13
165 "M531", 165 "M531",
166 "M532", 166 "M532",
167 "M533", 167 "M533",
168 "M534", 168 "M534",
169 "M535", 169 "M535",
170 "M536",
171 "M537",
172 "M538",
170 ## Bugbear++ 173 ## Bugbear++
171 "M581", 174 "M581",
172 "M582", 175 "M582",
173 ## Format Strings 176 ## Format Strings
174 "M601", 177 "M601",
372 "M531", 375 "M531",
373 "M532", 376 "M532",
374 "M533", 377 "M533",
375 "M534", 378 "M534",
376 "M535", 379 "M535",
380 "M536",
381 "M537",
382 "M538",
377 "M581", 383 "M581",
378 "M582", 384 "M582",
379 ), 385 ),
380 ), 386 ),
381 (self.__checkPep3101, ("M601",)), 387 (self.__checkPep3101, ("M601",)),
1649 1655
1650 1656
1651 ####################################################################### 1657 #######################################################################
1652 ## BugBearVisitor 1658 ## BugBearVisitor
1653 ## 1659 ##
1654 ## adapted from: flake8-bugbear v23.11.26 1660 ## adapted from: flake8-bugbear v24.1.17
1655 ## 1661 ##
1656 ## Original: Copyright (c) 2016 Łukasz Langa 1662 ## Original: Copyright (c) 2016 Łukasz Langa
1657 ####################################################################### 1663 #######################################################################
1658 1664
1659 BugBearContext = namedtuple("BugBearContext", ["node", "stack"]) 1665 BugBearContext = namedtuple("BugBearContext", ["node", "stack"])
1745 elif isinstance(node, ast.Call): 1751 elif isinstance(node, ast.Call):
1746 yield from self.__composeCallPath(node.func) 1752 yield from self.__composeCallPath(node.func)
1747 elif isinstance(node, ast.Name): 1753 elif isinstance(node, ast.Name):
1748 yield node.id 1754 yield node.id
1749 1755
1750 def __toNameStr(self, node): 1756 def toNameStr(self, node):
1751 """ 1757 """
1752 Private method to turn Name and Attribute nodes to strings, handling any 1758 Public method to turn Name and Attribute nodes to strings, handling any
1753 depth of attribute accesses. 1759 depth of attribute accesses.
1754 1760
1755 1761
1756 @param node reference to the node 1762 @param node reference to the node
1757 @type ast.Name or ast.Attribute 1763 @type ast.Name or ast.Attribute
1760 """ 1766 """
1761 if isinstance(node, ast.Name): 1767 if isinstance(node, ast.Name):
1762 return node.id 1768 return node.id
1763 1769
1764 if isinstance(node, ast.Call): 1770 if isinstance(node, ast.Call):
1765 return self.__toNameStr(node.func) 1771 return self.toNameStr(node.func)
1766 1772
1767 try: 1773 try:
1768 return self.__toNameStr(node.value) + "." + node.attr 1774 return self.toNameStr(node.value) + "." + node.attr
1769 except AttributeError: 1775 except AttributeError:
1770 return self.__toNameStr(node.value) 1776 return self.toNameStr(node.value)
1771 1777
1772 def __typesafeIssubclass(self, obj, classOrTuple): 1778 def __typesafeIssubclass(self, obj, classOrTuple):
1773 """ 1779 """
1774 Private method implementing a type safe issubclass() function. 1780 Private method implementing a type safe issubclass() function.
1775 1781
1944 if isinstance(dim, ast.Name): 1950 if isinstance(dim, ast.Name):
1945 yield dim.id 1951 yield dim.id
1946 elif isinstance(dim, ast.Tuple): 1952 elif isinstance(dim, ast.Tuple):
1947 yield from self.__getNamesFromTuple(dim) 1953 yield from self.__getNamesFromTuple(dim)
1948 1954
1949 def __getDictCompLoopVarNames(self, node): 1955 def __getDictCompLoopAndNamedExprVarNames(self, node):
1950 """ 1956 """
1951 Private method to get the names of comprehension loop variables. 1957 Private method to get the names of comprehension loop variables.
1952 1958
1953 @param node ast node to be processed 1959 @param node ast node to be processed
1954 @type ast.DictComp 1960 @type ast.DictComp
1955 @yield loop variable names 1961 @yield loop variable names
1956 @ytype str 1962 @ytype str
1957 """ 1963 """
1964 finder = NamedExprFinder()
1958 for gen in node.generators: 1965 for gen in node.generators:
1959 if isinstance(gen.target, ast.Name): 1966 if isinstance(gen.target, ast.Name):
1960 yield gen.target.id 1967 yield gen.target.id
1961 elif isinstance(gen.target, ast.Tuple): 1968 elif isinstance(gen.target, ast.Tuple):
1962 yield from self.__getNamesFromTuple(gen.target) 1969 yield from self.__getNamesFromTuple(gen.target)
1963 1970
1971 finder.visit(gen.ifs)
1972
1973 yield from finder.getNames().keys()
1974
1975 def __inClassInit(self):
1976 """
1977 Private method to check, if we are inside an '__init__' method.
1978
1979 @return flag indicating being within the '__init__' method
1980 @rtype bool
1981 """
1982 return (
1983 len(self.contexts) >= 2
1984 and isinstance(self.contexts[-2].node, ast.ClassDef)
1985 and isinstance(self.contexts[-1].node, ast.FunctionDef)
1986 and self.contexts[-1].node.name == "__init__"
1987 )
1988
1989 def visit_Return(self, node):
1990 """
1991 Public method to handle 'Return' nodes.
1992
1993 @param node reference to the node to be processed
1994 @type ast.Return
1995 """
1996 if self.__inClassInit() and node.value is not None:
1997 self.violations.append((node, "M537"))
1998
1999 self.generic_visit(node)
2000
2001 def visit_Yield(self, node):
2002 """
2003 Public method to handle 'Yield' nodes.
2004
2005 @param node reference to the node to be processed
2006 @type ast.Yield
2007 """
2008 if self.__inClassInit():
2009 self.violations.append((node, "M537"))
2010
2011 self.generic_visit(node)
2012
2013 def visit_YieldFrom(self, node) -> None:
2014 """
2015 Public method to handle 'YieldFrom' nodes.
2016
2017 @param node reference to the node to be processed
2018 @type ast.YieldFrom
2019 """
2020 if self.__inClassInit():
2021 self.violations.append((node, "M537"))
2022
2023 self.generic_visit(node)
2024
1964 def visit(self, node): 2025 def visit(self, node):
1965 """ 2026 """
1966 Public method to traverse a given AST node. 2027 Public method to traverse a given AST node.
1967 2028
1968 @param node AST node to be traversed 2029 @param node AST node to be traversed
1982 2043
1983 self.nodeStack.pop() 2044 self.nodeStack.pop()
1984 2045
1985 if isContextful: 2046 if isContextful:
1986 self.contexts.pop() 2047 self.contexts.pop()
2048
2049 self.__checkForM518(node)
1987 2050
1988 def visit_ExceptHandler(self, node): 2051 def visit_ExceptHandler(self, node):
1989 """ 2052 """
1990 Public method to handle exception handlers. 2053 Public method to handle exception handlers.
1991 2054
2001 names = [] 2064 names = []
2002 badHandlers = [] 2065 badHandlers = []
2003 ignoredHandlers = [] 2066 ignoredHandlers = []
2004 for handler in handlers: 2067 for handler in handlers:
2005 if isinstance(handler, (ast.Name, ast.Attribute)): 2068 if isinstance(handler, (ast.Name, ast.Attribute)):
2006 name = self.__toNameStr(handler) 2069 name = self.toNameStr(handler)
2007 if name is None: 2070 if name is None:
2008 ignoredHandlers.append(handler) 2071 ignoredHandlers.append(handler)
2009 else: 2072 else:
2010 names.append(name) 2073 names.append(name)
2011 elif isinstance(handler, (ast.Call, ast.Starred)): 2074 elif isinstance(handler, (ast.Call, ast.Starred)):
2025 self.violations.append((node, "M513", *names)) 2088 self.violations.append((node, "M513", *names))
2026 else: 2089 else:
2027 maybeError = self.__checkRedundantExcepthandlers(names, node) 2090 maybeError = self.__checkRedundantExcepthandlers(names, node)
2028 if maybeError is not None: 2091 if maybeError is not None:
2029 self.violations.append(maybeError) 2092 self.violations.append(maybeError)
2093 if (
2094 "BaseException" in names
2095 and not ExceptBaseExceptionVisitor(node).reRaised()
2096 ):
2097 self.violations.append((node, "M536"))
2030 2098
2031 self.generic_visit(node) 2099 self.generic_visit(node)
2032 2100
2033 def visit_UAdd(self, node): 2101 def visit_UAdd(self, node):
2034 """ 2102 """
2100 Public method to handle a module node. 2168 Public method to handle a module node.
2101 2169
2102 @param node reference to the node to be processed 2170 @param node reference to the node to be processed
2103 @type ast.Module 2171 @type ast.Module
2104 """ 2172 """
2105 self.__checkForM518(node)
2106
2107 self.generic_visit(node) 2173 self.generic_visit(node)
2108 2174
2109 def visit_Assign(self, node): 2175 def visit_Assign(self, node):
2110 """ 2176 """
2111 Public method to handle assignments. 2177 Public method to handle assignments.
2133 """ 2199 """
2134 self.__checkForM507(node) 2200 self.__checkForM507(node)
2135 self.__checkForM520(node) 2201 self.__checkForM520(node)
2136 self.__checkForM523(node) 2202 self.__checkForM523(node)
2137 self.__checkForM531(node) 2203 self.__checkForM531(node)
2204 self.__checkForM538(node)
2138 2205
2139 self.generic_visit(node) 2206 self.generic_visit(node)
2140 2207
2141 def visit_AsyncFor(self, node): 2208 def visit_AsyncFor(self, node):
2142 """ 2209 """
2228 Public method to handle function definitions. 2295 Public method to handle function definitions.
2229 2296
2230 @param node reference to the node to be processed 2297 @param node reference to the node to be processed
2231 @type ast.FunctionDef 2298 @type ast.FunctionDef
2232 """ 2299 """
2233 self.__checkForM518(node)
2234 self.__checkForM519(node) 2300 self.__checkForM519(node)
2235 self.__checkForM521(node) 2301 self.__checkForM521(node)
2236 2302
2237 self.generic_visit(node) 2303 self.generic_visit(node)
2238 2304
2241 Public method to handle class definitions. 2307 Public method to handle class definitions.
2242 2308
2243 @param node reference to the node to be processed 2309 @param node reference to the node to be processed
2244 @type ast.ClassDef 2310 @type ast.ClassDef
2245 """ 2311 """
2246 self.__checkForM518(node)
2247 self.__checkForM521(node) 2312 self.__checkForM521(node)
2248 self.__checkForM524AndM527(node) 2313 self.__checkForM524AndM527(node)
2249 2314
2250 self.generic_visit(node) 2315 self.generic_visit(node)
2251 2316
2470 itemContext.func.attr == "assertRaises" 2535 itemContext.func.attr == "assertRaises"
2471 or ( 2536 or (
2472 itemContext.func.attr == "raises" 2537 itemContext.func.attr == "raises"
2473 and isinstance(itemContext.func.value, ast.Name) 2538 and isinstance(itemContext.func.value, ast.Name)
2474 and itemContext.func.value.id == "pytest" 2539 and itemContext.func.value.id == "pytest"
2475 and "match" not in [kwd.arg for kwd in itemContext.keywords] 2540 and "match" not in (kwd.arg for kwd in itemContext.keywords)
2476 ) 2541 )
2477 ) 2542 )
2478 ) 2543 )
2479 or ( 2544 or (
2480 isinstance(itemContext.func, ast.Name) 2545 isinstance(itemContext.func, ast.Name)
2481 and itemContext.func.id == "raises" 2546 and itemContext.func.id == "raises"
2482 and isinstance(itemContext.func.ctx, ast.Load) 2547 and isinstance(itemContext.func.ctx, ast.Load)
2483 and "pytest.raises" in self.__M505Imports 2548 and "pytest.raises" in self.__M505Imports
2484 and "match" not in [kwd.arg for kwd in itemContext.keywords] 2549 and "match" not in (kwd.arg for kwd in itemContext.keywords)
2485 ) 2550 )
2486 ) 2551 )
2487 and len(itemContext.args) == 1 2552 and len(itemContext.args) == 1
2488 and isinstance(itemContext.args[0], ast.Name) 2553 and isinstance(itemContext.args[0], ast.Name)
2489 and itemContext.args[0].id == "Exception" 2554 and itemContext.args[0].id in ("Exception", "BaseException")
2490 and not item.optional_vars 2555 and not item.optional_vars
2491 ): 2556 ):
2492 self.violations.append((node, "M517")) 2557 self.violations.append((node, "M517"))
2493 2558
2494 def __checkForM518(self, node): 2559 def __checkForM518(self, node):
2496 Private method to check for useless expressions. 2561 Private method to check for useless expressions.
2497 2562
2498 @param node reference to the node to be processed 2563 @param node reference to the node to be processed
2499 @type ast.FunctionDef 2564 @type ast.FunctionDef
2500 """ 2565 """
2501 for subnode in node.body: 2566 if not isinstance(node, ast.Expr):
2502 if not isinstance(subnode, ast.Expr): 2567 return
2503 continue 2568
2504 2569 if isinstance(
2505 if isinstance( 2570 node.value,
2506 subnode.value, 2571 (ast.List, ast.Set, ast.Dict, ast.Tuple),
2507 (ast.List, ast.Set, ast.Dict), 2572 ) or (
2508 ) or ( 2573 isinstance(node.value, ast.Constant)
2509 isinstance(subnode.value, ast.Constant) 2574 and (
2510 and ( 2575 isinstance(
2511 isinstance( 2576 node.value.value,
2512 subnode.value.value, 2577 (int, float, complex, bytes, bool),
2513 (int, float, complex, bytes, bool),
2514 )
2515 or subnode.value.value is None
2516 ) 2578 )
2517 ): 2579 or node.value.value is None
2518 self.violations.append((subnode, "M518")) 2580 )
2581 ):
2582 self.violations.append((node, "M518", node.value.__class__.__name__))
2519 2583
2520 def __checkForM519(self, node): 2584 def __checkForM519(self, node):
2521 """ 2585 """
2522 Private method to check for use of 'functools.lru_cache' or 'functools.cache'. 2586 Private method to check for use of 'functools.lru_cache' or 'functools.cache'.
2523 2587
2932 if node.func.attr in ("sub", "subn"): 2996 if node.func.attr in ("sub", "subn"):
2933 check(3, "count") 2997 check(3, "count")
2934 elif node.func.attr == "split": 2998 elif node.func.attr == "split":
2935 check(2, "maxsplit") 2999 check(2, "maxsplit")
2936 3000
2937 def __checkForM535(self, node: ast.DictComp): 3001 def __checkForM535(self, node):
2938 """ 3002 """
2939 Private method to check that a static key isn't used in a dict comprehension. 3003 Private method to check that a static key isn't used in a dict comprehension.
2940 3004
2941 Record a warning if a likely unchanging key is used - either a constant, 3005 Record a warning if a likely unchanging key is used - either a constant,
2942 or a variable that isn't coming from the generator expression. 3006 or a variable that isn't coming from the generator expression.
2943 3007
2944 @param node reference to the node to be processed 3008 @param node reference to the node to be processed
2945 @type ast.DictComp 3009 @type ast.DictComp
2946 """
2947 """Check that a static key isn't used in a dict comprehension.
2948
2949 Emit a warning if a likely unchanging key is used - either a constant,
2950 or a variable that isn't coming from the generator expression.
2951 """ 3010 """
2952 if isinstance(node.key, ast.Constant): 3011 if isinstance(node.key, ast.Constant):
2953 self.violations.append((node, "M535", node.key.value)) 3012 self.violations.append((node, "M535", node.key.value))
2954 elif isinstance( 3013 elif isinstance(
2955 node.key, ast.Name 3014 node.key, ast.Name
2956 ) and node.key.id not in self.__getDictCompLoopVarNames(node): 3015 ) and node.key.id not in self.__getDictCompLoopAndNamedExprVarNames(node):
2957 self.violations.append((node, "M535", node.key.id)) 3016 self.violations.append((node, "M535", node.key.id))
3017
3018 def __checkForM538(self, node):
3019 """
3020 Private method to check for changes to a loop's mutable iterable.
3021
3022 @param node loop node to be checked
3023 @type ast.For
3024 """
3025 if isinstance(node.iter, ast.Name):
3026 name = self.toNameStr(node.iter)
3027 elif isinstance(node.iter, ast.Attribute):
3028 name = self.toNameStr(node.iter)
3029 else:
3030 return
3031 checker = M538Checker(name, self)
3032 checker.visit(node.body)
3033 for mutation in checker.mutations:
3034 self.violations.append((mutation, "M538"))
3035
3036
3037 class M538Checker(ast.NodeVisitor):
3038 """
3039 Class traversing a 'for' loop body to check for modifications to a loop's
3040 mutable iterable.
3041 """
3042
3043 # https://docs.python.org/3/library/stdtypes.html#mutable-sequence-types
3044 MUTATING_FUNCTIONS = (
3045 "append",
3046 "sort",
3047 "reverse",
3048 "remove",
3049 "clear",
3050 "extend",
3051 "insert",
3052 "pop",
3053 "popitem",
3054 )
3055
3056 def __init__(self, name, bugbear):
3057 """
3058 Constructor
3059
3060 @param name name of the iterator
3061 @type str
3062 @param bugbear reference to the bugbear visitor
3063 @type BugBearVisitor
3064 """
3065 self.__name = name
3066 self.__bb = bugbear
3067 self.mutations = []
3068
3069 def visit_Delete(self, node):
3070 """
3071 Public method handling 'Delete' nodes.
3072
3073 @param node reference to the node to be processed
3074 @type ast.Delete
3075 """
3076 for target in node.targets:
3077 if isinstance(target, ast.Subscript):
3078 name = self.__bb.toNameStr(target.value)
3079 elif isinstance(target, (ast.Attribute, ast.Name)):
3080 name = self.__bb.toNameStr(target)
3081 else:
3082 name = "" # fallback
3083 self.generic_visit(target)
3084
3085 if name == self.__name:
3086 self.mutations.append(node)
3087
3088 def visit_Call(self, node):
3089 """
3090 Public method handling 'Call' nodes.
3091
3092 @param node reference to the node to be processed
3093 @type ast.Call
3094 """
3095 if isinstance(node.func, ast.Attribute):
3096 name = self.__bb.toNameStr(node.func.value)
3097 functionObject = name
3098 functionName = node.func.attr
3099
3100 if (
3101 functionObject == self.__name
3102 and functionName in self.MUTATING_FUNCTIONS
3103 ):
3104 self.mutations.append(node)
3105
3106 self.generic_visit(node)
3107
3108 def visit(self, node):
3109 """
3110 Public method to inspect an ast node.
3111
3112 Like super-visit but supports iteration over lists.
3113
3114 @param node AST node to be traversed
3115 @type TYPE
3116 @return reference to the last processed node
3117 @rtype ast.Node
3118 """
3119 if not isinstance(node, list):
3120 return super().visit(node)
3121
3122 for elem in node:
3123 super().visit(elem)
3124 return node
3125
3126
3127 class ExceptBaseExceptionVisitor(ast.NodeVisitor):
3128 """
3129 Class to determine, if a 'BaseException' is re-raised.
3130 """
3131
3132 def __init__(self, exceptNode):
3133 """
3134 Constructor
3135
3136 @param exceptNode exception node to be inspected
3137 @type ast.ExceptHandler
3138 """
3139 super().__init__()
3140 self.__root = exceptNode
3141 self.__reRaised = False
3142
3143 def reRaised(self) -> bool:
3144 """
3145 Public method to check, if the exception is re-raised.
3146
3147 @return flag indicating a re-raised exception
3148 @rtype bool
3149 """
3150 self.visit(self.__root)
3151 return self.__reRaised
3152
3153 def visit_Raise(self, node):
3154 """
3155 Public method to handle 'Raise' nodes.
3156
3157 If we find a corresponding `raise` or `raise e` where e was from
3158 `except BaseException as e:` then we mark re_raised as True and can
3159 stop scanning.
3160
3161 @param node reference to the node to be processed
3162 @type ast.Raise
3163 """
3164 if node.exc is None or (
3165 isinstance(node.exc, ast.Name) and node.exc.id == self.__root.name
3166 ):
3167 self.__reRaised = True
3168 return
3169
3170 super().generic_visit(node)
3171
3172 def visit_ExceptHandler(self, node: ast.ExceptHandler):
3173 """
3174 Public method to handle 'ExceptHandler' nodes.
3175
3176 @param node reference to the node to be processed
3177 @type ast.ExceptHandler
3178 """
3179 if node is not self.__root:
3180 return # entered a nested except - stop searching
3181
3182 super().generic_visit(node)
2958 3183
2959 3184
2960 class NameFinder(ast.NodeVisitor): 3185 class NameFinder(ast.NodeVisitor):
2961 """ 3186 """
2962 Class to extract a name out of a tree of nodes. 3187 Class to extract a name out of a tree of nodes.
2992 for elem in node: 3217 for elem in node:
2993 super().visit(elem) 3218 super().visit(elem)
2994 return node 3219 return node
2995 else: 3220 else:
2996 return super().visit(node) 3221 return super().visit(node)
3222
3223 def getNames(self):
3224 """
3225 Public method to return the extracted names and Name nodes.
3226
3227 @return dictionary containing the names as keys and the list of nodes
3228 @rtype dict
3229 """
3230 return self.__names
3231
3232
3233 class NamedExprFinder(ast.NodeVisitor):
3234 """
3235 Class to extract names defined through an ast.NamedExpr.
3236 """
3237
3238 def __init__(self):
3239 """
3240 Constructor
3241 """
3242 super().__init__()
3243
3244 self.__names = {}
3245
3246 def visit_NamedExpr(self, node: ast.NamedExpr):
3247 """
3248 Public method handling 'NamedExpr' nodes.
3249
3250 @param node reference to the node to be processed
3251 @type ast.NamedExpr
3252 """
3253 self.__names.setdefault(node.target.id, []).append(node.target)
3254
3255 self.generic_visit(node)
3256
3257 def visit(self, node):
3258 """
3259 Public method to traverse a given AST node.
3260
3261 Like super-visit but supports iteration over lists.
3262
3263 @param node AST node to be traversed
3264 @type TYPE
3265 @return reference to the last processed node
3266 @rtype ast.Node
3267 """
3268 if not isinstance(node, list):
3269 super().visit(node)
3270
3271 for elem in node:
3272 super().visit(elem)
3273
3274 return node
2997 3275
2998 def getNames(self): 3276 def getNames(self):
2999 """ 3277 """
3000 Public method to return the extracted names and Name nodes. 3278 Public method to return the extracted names and Name nodes.
3001 3279

eric ide

mercurial