--- a/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Miscellaneous/MiscellaneousChecker.py Wed Nov 29 14:49:44 2023 +0100 +++ b/src/eric7/Plugins/CheckerPlugins/CodeStyleChecker/Miscellaneous/MiscellaneousChecker.py Wed Nov 29 18:07:53 2023 +0100 @@ -57,6 +57,7 @@ "M131", "M132", ## Comprehensions + "M180", "M181", "M182", "M183", @@ -66,13 +67,12 @@ "M187", "M188", "M189", + "M190", "M191", "M192", "M193", + "M194", "M195", - "M196", - "M197", - "M198", ## Dictionaries with sorted keys "M201", ## Property @@ -248,6 +248,7 @@ ( self.__checkComprehensions, ( + "M180", "M181", "M182", "M183", @@ -257,13 +258,12 @@ "M187", "M188", "M189", + "M190", "M191", "M192", "M193", + "M194", "M195", - "M196", - "M197", - "M198", ), ), (self.__checkDictWithSortedKeys, ("M201",)), @@ -847,56 +847,113 @@ def __checkComprehensions(self): """ Private method to check some comprehension related things. - """ + + This method is adapted from: flake8-comprehensions v3.14.0 + Original: Copyright (c) 2017 Adam Johnson + """ + visitedMapCalls = set() + for node in ast.walk(self.__tree): if isinstance(node, ast.Call) and isinstance(node.func, ast.Name): - nArgs = len(node.args) - nKwArgs = len(node.keywords) + numPositionalArgs = len(node.args) + numKeywordArgs = len(node.keywords) if ( - nArgs == 1 + numPositionalArgs == 1 and isinstance(node.args[0], ast.GeneratorExp) and node.func.id in ("list", "set") ): errorCode = { - "list": "M181", - "set": "M182", + "list": "M180", + "set": "M181", }[node.func.id] self.__error(node.lineno - 1, node.col_offset, errorCode) elif ( - nArgs == 1 + numPositionalArgs == 1 + and node.func.id == "dict" + and len(node.keywords) == 0 and isinstance(node.args[0], (ast.GeneratorExp, ast.ListComp)) and isinstance(node.args[0].elt, ast.Tuple) and len(node.args[0].elt.elts) == 2 - and node.func.id == "dict" ): if isinstance(node.args[0], ast.GeneratorExp): - errorCode = "M183" + errorCode = "M182" else: - errorCode = "M185" + errorCode = "M184" self.__error(node.lineno - 1, node.col_offset, errorCode) elif ( - nArgs == 1 + numPositionalArgs == 1 and isinstance(node.args[0], ast.ListComp) - and node.func.id in ("list", "set") + and node.func.id in ("list", "set", "any", "all") ): errorCode = { - "list": "M195", - "set": "M184", + "list": "M191", + "set": "M183", + "any": "M199", + "all": "M199", }[node.func.id] - self.__error(node.lineno - 1, node.col_offset, errorCode) - - elif nArgs == 1 and ( + self.__error( + node.lineno - 1, node.col_offset, errorCode, node.func.id + ) + + elif numPositionalArgs == 1 and ( isinstance(node.args[0], ast.Tuple) and node.func.id == "tuple" or isinstance(node.args[0], ast.List) and node.func.id == "list" ): errorCode = { - "tuple": "M197", - "list": "M198", + "tuple": "M189a", + "list": "M190a", + }[node.func.id] + ##suffix = "remove the outer call to {func}()." + self.__error( + node.lineno - 1, + node.col_offset, + errorCode, + type(node.args[0]).__name__.lower(), + node.func.id, + ) + + elif ( + numPositionalArgs == 1 + and numKeywordArgs == 0 + and isinstance(node.args[0], (ast.Dict, ast.DictComp)) + and node.func.id == "dict" + ): + if isinstance(node.args[0], ast.Dict): + type_ = "dict" + else: + type_ = "dict comprehension" + self.__error( + node.lineno - 1, + node.col_offset, + "M198", + type_, + ) + + elif ( + numPositionalArgs == 1 + and isinstance(node.args[0], (ast.Tuple, ast.List)) + and ( + node.func.id in ("tuple", "list", "set") + or ( + node.func.id == "dict" + and all( + isinstance(elt, ast.Tuple) and len(elt.elts) == 2 + for elt in node.args[0].elts + ) + ) + ) + ): + ##suffix = "rewrite as a {func} literal." + errorCode = { + "tuple": "M189b", + "list": "M190b", + "set": "M185", + "dict": "M186", }[node.func.id] self.__error( node.lineno - 1, @@ -907,37 +964,20 @@ ) elif ( - nArgs == 1 - and isinstance(node.args[0], (ast.Tuple, ast.List)) - and node.func.id in ("tuple", "list", "set", "dict") - ): - errorCode = { - "tuple": "M192", - "list": "M193", - "set": "M191", - "dict": "M191", - }[node.func.id] - self.__error( - node.lineno - 1, - node.col_offset, - errorCode, - type(node.args[0]).__name__.lower(), - node.func.id, - ) - - elif ( - nArgs == 0 + numPositionalArgs == 0 and not any(isinstance(a, ast.Starred) for a in node.args) and not any(k.arg is None for k in node.keywords) and node.func.id == "dict" ) or ( - nArgs == 0 and nKwArgs == 0 and node.func.id in ("tuple", "list") + numKeywordArgs == 0 and numKeywordArgs == 0 and node.func.id in ( + "tuple", "list" + ) ): - self.__error(node.lineno - 1, node.col_offset, "M186", node.func.id) + self.__error(node.lineno - 1, node.col_offset, "M188", node.func.id) elif ( node.func.id in {"list", "reversed"} - and nArgs > 0 + and numPositionalArgs > 0 and isinstance(node.args[0], ast.Call) and isinstance(node.args[0].func, ast.Name) and node.args[0].func.id == "sorted" @@ -957,7 +997,7 @@ self.__error( node.lineno - 1, node.col_offset, - "M187a", + "M193a", node.func.id, node.args[0].func.id, ) @@ -965,22 +1005,29 @@ self.__error( node.lineno - 1, node.col_offset, - "M187b", + "M193b", node.func.id, node.args[0].func.id, not reverseFlagValue, ) + ##if reverse_flag_value is None: + ##remediation = " - toggle reverse argument to sorted()" + ##else: + ##remediation = " - use sorted(..., reverse={!r})".format( + ##not reverse_flag_value + ##) + else: self.__error( node.lineno - 1, node.col_offset, - "M187c", + "M193c", node.func.id, node.args[0].func.id, ) elif ( - nArgs > 0 + numPositionalArgs > 0 and isinstance(node.args[0], ast.Call) and isinstance(node.args[0].func, ast.Name) and ( @@ -999,14 +1046,14 @@ self.__error( node.lineno - 1, node.col_offset, - "M188", + "M194", node.args[0].func.id, node.func.id, ) elif ( node.func.id in {"reversed", "set", "sorted"} - and nArgs > 0 + and numPositionalArgs > 0 and isinstance(node.args[0], ast.Subscript) and isinstance(node.args[0].slice, ast.Slice) and node.args[0].slice.lower is None @@ -1016,16 +1063,66 @@ and isinstance(node.args[0].slice.step.operand, ast.Constant) and node.args[0].slice.step.operand.n == 1 ): - self.__error(node.lineno - 1, node.col_offset, "M189", node.func.id) - - elif isinstance(node, (ast.ListComp, ast.SetComp)) and ( + self.__error(node.lineno - 1, node.col_offset, "M195", node.func.id) + + elif ( + node.func.id == "map" + and node not in visitedMapCalls + and len(node.args) == 2 + and isinstance(node.args[0], ast.Lambda) + ): + self.__error(node.lineno - 1, node.col_offset, "M197", "generator expression") + + elif ( + node.func.id in ("list", "set", "dict") + and len(node.args) == 1 + and isinstance(node.args[0], ast.Call) + and isinstance(node.args[0].func, ast.Name) + and node.args[0].func.id == "map" + and len(node.args[0].args) == 2 + and isinstance(node.args[0].args[0], ast.Lambda) + ): + # To avoid raising M197 on the map() call inside the list/set/dict. + mapCall = node.args[0] + visitedMapCalls.add(mapCall) + + rewriteable = True + if node.func.id == "dict": + # For the generator expression to be rewriteable as a + # dict comprehension, its lambda must return a 2-tuple. + lambdaNode = node.args[0].args[0] + if ( + not isinstance(lambdaNode.body, (ast.List, ast.Tuple)) + or len(lambdaNode.body.elts) != 2 + ): + rewriteable = False + + if rewriteable: + comprehensionType = f"{node.func.id} comprehension" + self.__error(node.lineno - 1, node.col_offset, "M197", comprehensionType) + + elif isinstance(node, (ast.DictComp, ast.ListComp, ast.SetComp)) and ( len(node.generators) == 1 and not node.generators[0].ifs and not node.generators[0].is_async and ( - isinstance(node.elt, ast.Name) - and isinstance(node.generators[0].target, ast.Name) - and node.elt.id == node.generators[0].target.id + ( + isinstance(node, (ast.ListComp, ast.SetComp)) + and isinstance(node.elt, ast.Name) + and isinstance(node.generators[0].target, ast.Name) + and node.elt.id == node.generators[0].target.id + ) + or ( + isinstance(node, ast.DictComp) + and isinstance(node.key, ast.Name) + and isinstance(node.value, ast.Name) + and isinstance(node.generators[0].target, ast.Tuple) + and len(node.generators[0].target.elts) == 2 + and isinstance(node.generators[0].target.elts[0], ast.Name) + and node.generators[0].target.elts[0].id == node.key.id + and isinstance(node.generators[0].target.elts[1], ast.Name) + and node.generators[0].target.elts[1].id == node.value.id + ) ) ): compType = {