--- a/eric6/Plugins/CheckerPlugins/CodeStyleChecker/Simplify/SimplifyNodeVisitor.py Sat Apr 03 10:44:07 2021 +0200 +++ b/eric6/Plugins/CheckerPlugins/CodeStyleChecker/Simplify/SimplifyNodeVisitor.py Sat Apr 03 14:51:08 2021 +0200 @@ -9,6 +9,7 @@ import ast import collections +import copy import itertools try: @@ -86,6 +87,10 @@ """ self.__check101(node) self.__check109(node) + self.__check221(node) + self.__check222(node) + self.__check223(node) + self.__check224(node) self.generic_visit(node) @@ -105,6 +110,19 @@ self.generic_visit(node) + def visit_IfExp(self, node): + """ + Public method to process an IfExp node. + + @param node reference to the IfExp node + @type ast.IfExp + """ + self.__check211(node) + self.__check212(node) + self.__check213(node) + + self.generic_visit(node) + def visit_For(self, node): """ Public method to process a For node. @@ -161,6 +179,7 @@ @type ast.Compare """ self.__check118(node) + self.__check301(node) self.generic_visit(node) @@ -176,6 +195,24 @@ self.generic_visit(node) + def visit_UnaryOp(self, node): + """ + Public method to process a UnaryOp node. + + @param node reference to the UnaryOp node + @type ast.UnaryOp + """ + self.__check201(node) + self.__check202(node) + self.__check203(node) + self.__check204(node) + self.__check205(node) + self.__check206(node) + self.__check207(node) + self.__check208(node) + + self.generic_visit(node) + ############################################################# ## Helper methods for the various checkers below ############################################################# @@ -261,9 +298,26 @@ statementEqual = False if not statementEqual: return False + return True - def __isStatementEqual(self, a: ast.stmt, b: ast.stmt) -> bool: + def __isSameExpression(self, a, b): + """ + Private method to check, if two expressions are equal. + + @param a first expression to be checked + @type ast.expr + @param b second expression to be checked + @type ast.expr + @return flag indicating equal expressions + @rtype bool + """ + if isinstance(a, ast.Name) and isinstance(b, ast.Name): + return a.id == b.id + else: + return False + + def __isStatementEqual(self, a, b): """ Private method to check, if two statements are equal. @@ -290,6 +344,53 @@ else: return a == b + def __isExceptionCheck(self, node): + """ + Private method to check, if the node is checking an exception. + + @param node reference to the node to be checked + @type ast.If + @return flag indicating an exception check + @rtype bool + """ + return ( + len(node.body) == 1 and isinstance(node.body[0], ast.Raise) + ) + + def __negateTest(self, node): + """ + Private method negate the given Compare node. + + @param node reference to the node to be negated + @type ast.Compare + @return node with negated logic + @rtype ast.Compare + """ + newNode = copy.deepcopy(node) + op = newNode.ops[0] + if isinstance(op, ast.Eq): + op = ast.NotEq() + elif isinstance(op, ast.NotEq): + op = ast.Eq() + elif isinstance(op, ast.Lt): + op = ast.GtE() + elif isinstance(op, ast.LtE): + op = ast.Gt() + elif isinstance(op, ast.Gt): + op = ast.LtE() + elif isinstance(op, ast.GtE): + op = ast.Lt() + elif isinstance(op, ast.Is): + op = ast.IsNot() + elif isinstance(op, ast.IsNot): + op = ast.Is() + elif isinstance(op, ast.In): + op = ast.NotIn() + elif isinstance(op, ast.NotIn): + op = ast.In() + newNode.ops = [op] + return newNode + ############################################################# ## Methods to check for possible code simplifications below ############################################################# @@ -955,6 +1056,374 @@ ): self.__error(node.lineno - 1, node.col_offset, "Y120", node.name) + + def __check201(self, node): + """ + Private method to check for calls where an unary 'not' is used for + an unequality. + + @param node reference to the UnaryOp node + @type ast.UnaryOp + """ + # not a == b + if not ( + ( + not isinstance(node.op, ast.Not) or + not isinstance(node.operand, ast.Compare) or + len(node.operand.ops) != 1 or + not isinstance(node.operand.ops[0], ast.Eq) + ) or + isinstance(node.parent, ast.If) and + self.__isExceptionCheck(node.parent) + ): + comparison = node.operand + left = unparse(comparison.left) + right = unparse(comparison.comparators[0]) + self.__error(node.lineno - 1, node.col_offset, "Y201", + left, right) + + def __check202(self, node): + """ + Private method to check for calls where an unary 'not' is used for + an equality. + + @param node reference to the UnaryOp node + @type ast.UnaryOp + """ + # not a != b + if not ( + ( + not isinstance(node.op, ast.Not) or + not isinstance(node.operand, ast.Compare) or + len(node.operand.ops) != 1 or + not isinstance(node.operand.ops[0], ast.NotEq) + ) or + isinstance(node.parent, ast.If) and + self.__isExceptionCheck(node.parent) + ): + comparison = node.operand + left = unparse(comparison.left) + right = unparse(comparison.comparators[0]) + self.__error(node.lineno - 1, node.col_offset, "Y202", + left, right) + + def __check203(self, node): + """ + Private method to check for calls where an unary 'not' is used for + an in-check. + + @param node reference to the UnaryOp node + @type ast.UnaryOp + """ + # not a in b + if not ( + ( + not isinstance(node.op, ast.Not) or + not isinstance(node.operand, ast.Compare) or + len(node.operand.ops) != 1 or + not isinstance(node.operand.ops[0], ast.In) + ) or + isinstance(node.parent, ast.If) and + self.__isExceptionCheck(node.parent) + ): + comparison = node.operand + left = unparse(comparison.left) + right = unparse(comparison.comparators[0]) + self.__error(node.lineno - 1, node.col_offset, "Y203", + left, right) + + def __check204(self, node): + """ + Private method to check for calls of the type "not (a < b)". + + @param node reference to the UnaryOp node + @type ast.UnaryOp + """ + # not a < b + if not ( + ( + not isinstance(node.op, ast.Not) or + not isinstance(node.operand, ast.Compare) or + len(node.operand.ops) != 1 or + not isinstance(node.operand.ops[0], ast.Lt) + ) or + isinstance(node.parent, ast.If) and + self.__isExceptionCheck(node.parent) + ): + comparison = node.operand + left = unparse(comparison.left) + right = unparse(comparison.comparators[0]) + self.__error(node.lineno - 1, node.col_offset, "Y204", + left, right) + + def __check205(self, node): + """ + Private method to check for calls of the type "not (a <= b)". + + @param node reference to the UnaryOp node + @type ast.UnaryOp + """ + # not a <= b + if not ( + ( + not isinstance(node.op, ast.Not) or + not isinstance(node.operand, ast.Compare) or + len(node.operand.ops) != 1 or + not isinstance(node.operand.ops[0], ast.LtE) + ) or + isinstance(node.parent, ast.If) and + self.__isExceptionCheck(node.parent) + ): + comparison = node.operand + left = unparse(comparison.left) + right = unparse(comparison.comparators[0]) + self.__error(node.lineno - 1, node.col_offset, "Y205", + left, right) + + def __check206(self, node): + """ + Private method to check for calls of the type "not (a > b)". + + @param node reference to the UnaryOp node + @type ast.UnaryOp + """ + # not a > b + if not ( + ( + not isinstance(node.op, ast.Not) or + not isinstance(node.operand, ast.Compare) or + len(node.operand.ops) != 1 or + not isinstance(node.operand.ops[0], ast.Gt) + ) or + isinstance(node.parent, ast.If) and + self.__isExceptionCheck(node.parent) + ): + comparison = node.operand + left = unparse(comparison.left) + right = unparse(comparison.comparators[0]) + self.__error(node.lineno - 1, node.col_offset, "Y206", + left, right) + + def __check207(self, node): + """ + Private method to check for calls of the type "not (a >= b)". + + @param node reference to the UnaryOp node + @type ast.UnaryOp + """ + # not a >= b + if not ( + ( + not isinstance(node.op, ast.Not) or + not isinstance(node.operand, ast.Compare) or + len(node.operand.ops) != 1 or + not isinstance(node.operand.ops[0], ast.GtE) + ) or + isinstance(node.parent, ast.If) and + self.__isExceptionCheck(node.parent) + ): + comparison = node.operand + left = unparse(comparison.left) + right = unparse(comparison.comparators[0]) + self.__error(node.lineno - 1, node.col_offset, "Y207", + left, right) + + def __check208(self, node): + """ + Private method to check for calls of the type "not (not a)". + + @param node reference to the UnaryOp node + @type ast.UnaryOp + """ + # not (not a) + if ( + isinstance(node.op, ast.Not) and + isinstance(node.operand, ast.UnaryOp) and + isinstance(node.operand.op, ast.Not) + ): + var = unparse(node.operand.operand) + self.__error(node.lineno - 1, node.col_offset, "Y208", var) + + def __check211(self, node): + """ + Private method to check for calls of the type "True if a else False". + + @param node reference to the AST node to be checked + @type ast.IfExp + """ + # True if a else False + if ( + isinstance(node.body, BOOL_CONST_TYPES) and + node.body.value is True and + isinstance(node.orelse, BOOL_CONST_TYPES) and + node.orelse.value is False + ): + cond = unparse(node.test) + if isinstance(node.test, ast.Name): + newCond = "bool({0})".format(cond) + else: + newCond = cond + self.__error(node.lineno - 1, node.col_offset, "Y211", + cond, newCond) + + def __check212(self, node): + """ + Private method to check for calls of the type "False if a else True". + + @param node reference to the AST node to be checked + @type ast.IfExp + """ + # False if a else True + if ( + isinstance(node.body, BOOL_CONST_TYPES) and + node.body.value is False and + isinstance(node.orelse, BOOL_CONST_TYPES) and + node.orelse.value is True + ): + cond = unparse(node.test) + if isinstance(node.test, ast.Name): + newCond = "not {0}".format(cond) + else: + if len(node.test.ops) == 1: + newCond = unparse(self.__negateTest(node.test)) + else: + newCond = "not ({0})".format(cond) + self.__error(node.lineno - 1, node.col_offset, "Y212", + cond, newCond) + + def __check213(self, node): + """ + Private method to check for calls of the type "b if not a else a". + + @param node reference to the AST node to be checked + @type ast.IfExp + """ + # b if not a else a + if ( + isinstance(node.test, ast.UnaryOp) and + isinstance(node.test.op, ast.Not) and + self.__isSameExpression(node.test.operand, node.orelse) + ): + a = unparse(node.test.operand) + b = unparse(node.body) + self.__error(node.lineno - 1, node.col_offset, "Y213", a, b) + + def __check221(self, node): + """ + Private method to check for calls of the type "a and not a". + + @param node reference to the AST node to be checked + @type ast.BoolOp + """ + # a and not a + if ( + isinstance(node.op, ast.And) and + len(node.values) >= 2 + ): + # We have a boolean And. Let's make sure there is two times the + # same expression, but once with a "not" + negatedExpressions = [] + nonNegatedExpressions = [] + for exp in node.values: + if ( + isinstance(exp, ast.UnaryOp) and + isinstance(exp.op, ast.Not) + ): + negatedExpressions.append(exp.operand) + else: + nonNegatedExpressions.append(exp) + for negatedExpression in negatedExpressions: + for nonNegatedExpression in nonNegatedExpressions: + if self.__isSameExpression( + negatedExpression, nonNegatedExpression + ): + negExp = unparse(negatedExpression) + self.__error(node.lineno - 1, node.col_offset, "Y221", + negExp) + + def __check222(self, node): + """ + Private method to check for calls of the type "a or not a". + + @param node reference to the AST node to be checked + @type ast.BoolOp + """ + # a or not a + if ( + isinstance(node.op, ast.Or) and + len(node.values) >= 2 + ): + # We have a boolean And. Let's make sure there is two times the + # same expression, but once with a "not" + negatedExpressions = [] + nonNegatedExpressions = [] + for exp in node.values: + if ( + isinstance(exp, ast.UnaryOp) and + isinstance(exp.op, ast.Not) + ): + negatedExpressions.append(exp.operand) + else: + nonNegatedExpressions.append(exp) + for negatedExpression in negatedExpressions: + for nonNegatedExpression in nonNegatedExpressions: + if self.__isSameExpression( + negatedExpression, nonNegatedExpression + ): + negExp = unparse(negatedExpression) + self.__error(node.lineno - 1, node.col_offset, "Y222", + negExp) + + def __check223(self, node): + """ + Private method to check for calls of the type "... or True". + + @param node reference to the AST node to be checked + @type ast.BoolOp + """ + # a or True + if isinstance(node.op, ast.Or): + for exp in node.values: + if isinstance(exp, BOOL_CONST_TYPES) and exp.value is True: + self.__error(node.lineno - 1, node.col_offset, "Y223") + + def __check224(self, node): + """ + Private method to check for calls of the type "... and False". + + @param node reference to the AST node to be checked + @type ast.BoolOp + """ + # a and False + if isinstance(node.op, ast.And): + for exp in node.values: + if isinstance(exp, BOOL_CONST_TYPES) and exp.value is False: + self.__error(node.lineno - 1, node.col_offset, "Y224") + + def __check301(self, node): + """ + Private method to check for Yoda conditions. + + @param node reference to the AST node to be checked + @type ast.Compare + """ + # 42 == age + if ( + isinstance(node.left, AST_CONST_TYPES) and + len(node.ops) == 1 and + isinstance(node.ops[0], ast.Eq) + ): + left = unparse(node.left) + isPy37Str = isinstance(node.left, ast.Str) + isPy38Str = ( + isinstance(node.left, ast.Constant) and + isinstance(node.left.value, str) + ) + if isPy37Str or isPy38Str: + left = f"'{left}'" + right = unparse(node.comparators[0]) + self.__error(node.lineno - 1, node.col_offset, "Y301", + left, right) # # eflag: noqa = M891