Search in sources :

Example 6 with Tri

use of com.google.javascript.jscomp.base.Tri in project closure-compiler by google.

the class PeepholeMinimizeConditions method performConditionSubstitutions.

/**
 * Try to minimize the given condition by applying local substitutions.
 *
 * The following types of transformations are performed:
 *   x || true        --> true
 *   x && true        --> x
 *   x ? false : true --> !x
 *   x ? true : y     --> x || y
 *   x ? x : y        --> x || y
 *
 *   Returns the replacement for n, or the original if no change was made
 */
private Node performConditionSubstitutions(Node n) {
    Node parent = n.getParent();
    switch(n.getToken()) {
        case OR:
        case AND:
            {
                Node left = n.getFirstChild();
                Node right = n.getLastChild();
                // Because the expression is in a boolean context minimize
                // the children, this can't be done in the general case.
                left = performConditionSubstitutions(left);
                right = performConditionSubstitutions(right);
                // Remove useless conditionals
                // Handle the following cases:
                // x || false --> x
                // x && true --> x
                // This works regardless of whether x has side effects.
                // 
                // If x does not have side effects:
                // x || true  --> true
                // x && false  --> false
                // 
                // If x may have side effects:
                // x || true  --> x,true
                // x && false  --> x,false
                // 
                // In the last two cases, code size may increase slightly (adding
                // some parens because the comma operator has a low precedence) but
                // the new AST is easier for other passes to handle.
                Tri rightVal = getSideEffectFreeBooleanValue(right);
                if (getSideEffectFreeBooleanValue(right) != Tri.UNKNOWN) {
                    Token type = n.getToken();
                    Node replacement = null;
                    boolean rval = rightVal.toBoolean(true);
                    // (x && TRUE) => x
                    if ((type == Token.OR && !rval) || (type == Token.AND && rval)) {
                        replacement = left;
                    } else if (!mayHaveSideEffects(left)) {
                        replacement = right;
                    } else {
                        // expr_with_sideeffects || true  =>  expr_with_sideeffects, true
                        // expr_with_sideeffects && false  =>  expr_with_sideeffects, false
                        n.detachChildren();
                        replacement = IR.comma(left, right);
                    }
                    if (replacement != null) {
                        n.detachChildren();
                        n.replaceWith(replacement);
                        reportChangeToEnclosingScope(parent);
                        return replacement;
                    }
                }
                return n;
            }
        case HOOK:
            {
                Node condition = n.getFirstChild();
                Node trueNode = n.getSecondChild();
                Node falseNode = n.getLastChild();
                // Because the expression is in a boolean context minimize
                // the result children, this can't be done in the general case.
                // The condition is handled in the general case in #optimizeSubtree
                trueNode = performConditionSubstitutions(trueNode);
                falseNode = performConditionSubstitutions(falseNode);
                // Handle five cases:
                // x ? true : false --> x
                // x ? false : true --> !x
                // x ? true : y     --> x || y
                // x ? y : false    --> x && y
                // Only when x is NAME, hence x does not have side effects
                // x ? x : y        --> x || y
                Node replacement = null;
                Tri trueNodeVal = getSideEffectFreeBooleanValue(trueNode);
                Tri falseNodeVal = getSideEffectFreeBooleanValue(falseNode);
                if (trueNodeVal == Tri.TRUE && falseNodeVal == Tri.FALSE) {
                    // Remove useless conditionals, keep the condition
                    condition.detach();
                    replacement = condition;
                } else if (trueNodeVal == Tri.FALSE && falseNodeVal == Tri.TRUE) {
                    // Remove useless conditionals, keep the condition
                    condition.detach();
                    replacement = IR.not(condition);
                } else if (trueNodeVal == Tri.TRUE) {
                    // Remove useless true case.
                    n.detachChildren();
                    replacement = IR.or(condition, falseNode);
                } else if (falseNodeVal == Tri.FALSE) {
                    // Remove useless false case
                    n.detachChildren();
                    replacement = IR.and(condition, trueNode);
                } else if (!mayHaveSideEffects(condition) && !mayHaveSideEffects(trueNode) && condition.isEquivalentTo(trueNode)) {
                    // Remove redundant condition
                    n.detachChildren();
                    replacement = IR.or(trueNode, falseNode);
                }
                if (replacement != null) {
                    n.replaceWith(replacement);
                    reportChangeToEnclosingScope(replacement);
                    n = replacement;
                }
                return n;
            }
        default:
            // while(true) --> while(1)
            Tri nVal = getSideEffectFreeBooleanValue(n);
            if (nVal != Tri.UNKNOWN) {
                boolean result = nVal.toBoolean(true);
                int equivalentResult = result ? 1 : 0;
                return maybeReplaceChildWithNumber(n, equivalentResult);
            }
            // We can't do anything else currently.
            return n;
    }
}
Also used : MeasuredNode(com.google.javascript.jscomp.MinimizedCondition.MeasuredNode) Node(com.google.javascript.rhino.Node) Tri(com.google.javascript.jscomp.base.Tri) Token(com.google.javascript.rhino.Token)

Example 7 with Tri

use of com.google.javascript.jscomp.base.Tri in project closure-compiler by google.

the class NodeUtil method getBooleanValue.

/**
 * Gets the boolean value of a node that represents an expression, or {@code Tri.UNKNOWN} if no
 * such value can be determined by static analysis.
 *
 * <p>This method does not consider whether the node may have side-effects.
 */
static Tri getBooleanValue(Node n) {
    // which we will call if none of these match.
    switch(n.getToken()) {
        case NULL:
        case FALSE:
        case VOID:
            return Tri.FALSE;
        case TRUE:
        case REGEXP:
        case FUNCTION:
        case CLASS:
        case NEW:
        case ARRAYLIT:
        case OBJECTLIT:
            return Tri.TRUE;
        case TEMPLATELIT:
            if (n.hasOneChild()) {
                Node templateLitString = n.getOnlyChild();
                checkState(templateLitString.isTemplateLitString(), templateLitString);
                String cookedString = templateLitString.getCookedString();
                return Tri.forBoolean(cookedString != null && !cookedString.isEmpty());
            } else {
                return Tri.UNKNOWN;
            }
        case STRINGLIT:
            return Tri.forBoolean(n.getString().length() > 0);
        case NUMBER:
            return Tri.forBoolean(n.getDouble() != 0);
        case BIGINT:
            return Tri.forBoolean(!n.getBigInt().equals(BigInteger.ZERO));
        case NOT:
            return getBooleanValue(n.getLastChild()).not();
        case NAME:
            // We assume here that programs don't change the value of these global variables.
            switch(n.getString()) {
                case "undefined":
                case "NaN":
                    return Tri.FALSE;
                case "Infinity":
                    return Tri.TRUE;
                default:
                    return Tri.UNKNOWN;
            }
        case BITNOT:
        case POS:
        case NEG:
            {
                Double doubleVal = getNumberValue(n);
                if (doubleVal != null) {
                    boolean isFalsey = doubleVal.isNaN() || isEitherZero(doubleVal);
                    return Tri.forBoolean(!isFalsey);
                }
                BigInteger bigintVal = getBigIntValue(n);
                if (bigintVal != null) {
                    boolean isFalsey = bigintVal.equals(BigInteger.ZERO);
                    return Tri.forBoolean(!isFalsey);
                }
                return Tri.UNKNOWN;
            }
        case ASSIGN:
        case COMMA:
            // For ASSIGN and COMMA the value is the value of the RHS.
            return getBooleanValue(n.getLastChild());
        case AND:
        case ASSIGN_AND:
            {
                Tri lhs = getBooleanValue(n.getFirstChild());
                Tri rhs = getBooleanValue(n.getLastChild());
                return lhs.and(rhs);
            }
        case OR:
        case ASSIGN_OR:
            {
                Tri lhs = getBooleanValue(n.getFirstChild());
                Tri rhs = getBooleanValue(n.getLastChild());
                return lhs.or(rhs);
            }
        case HOOK:
            {
                Tri trueValue = getBooleanValue(n.getSecondChild());
                Tri falseValue = getBooleanValue(n.getLastChild());
                if (trueValue.equals(falseValue)) {
                    return trueValue;
                } else {
                    return Tri.UNKNOWN;
                }
            }
        case COALESCE:
        case ASSIGN_COALESCE:
            {
                Tri lhs = getBooleanValue(n.getFirstChild());
                Tri rhs = getBooleanValue(n.getLastChild());
                if (lhs.equals(Tri.TRUE) || lhs.equals(rhs)) {
                    return lhs;
                } else {
                    return Tri.UNKNOWN;
                }
            }
        default:
            return Tri.UNKNOWN;
    }
}
Also used : Node(com.google.javascript.rhino.Node) BigInteger(java.math.BigInteger) Tri(com.google.javascript.jscomp.base.Tri)

Example 8 with Tri

use of com.google.javascript.jscomp.base.Tri in project closure-compiler by google.

the class TypeCheck method visit.

/**
 * This is the meat of the type checking. It is basically one big switch, with each case
 * representing one type of parse tree node. The individual cases are usually pretty
 * straightforward.
 *
 * @param t The node traversal object that supplies context, such as the scope chain to use in
 *     name lookups as well as error reporting.
 * @param n The node being visited.
 * @param parent The parent of the node n.
 */
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
    JSType childType;
    JSType leftType;
    JSType rightType;
    Node left;
    Node right;
    // To be explicitly set to false if the node is not typeable.
    boolean typeable = true;
    validator.expectWellFormedTemplatizedType(n);
    switch(n.getToken()) {
        case CAST:
            Node expr = n.getFirstChild();
            JSType exprType = getJSType(expr);
            JSType castType = getJSType(n);
            // way.
            if (!expr.isObjectLit()) {
                validator.expectCanCast(n, castType, exprType);
            }
            ensureTyped(n, castType);
            expr.setJSTypeBeforeCast(exprType);
            if (castType.restrictByNotNullOrUndefined().isSubtypeOf(exprType) || expr.isObjectLit()) {
                expr.setJSType(castType);
            }
            break;
        case NAME:
            typeable = visitName(t, n, parent);
            break;
        case COMMA:
            ensureTyped(n, getJSType(n.getLastChild()));
            break;
        case THIS:
            ensureTyped(n, t.getTypedScope().getTypeOfThis());
            break;
        case NULL:
            ensureTyped(n, NULL_TYPE);
            break;
        case NUMBER:
            ensureTyped(n, NUMBER_TYPE);
            break;
        case BIGINT:
            ensureTyped(n, BIGINT_TYPE);
            break;
        case GETTER_DEF:
        case SETTER_DEF:
            // Object literal keys are handled with OBJECTLIT
            break;
        case ARRAYLIT:
            ensureTyped(n, ARRAY_TYPE);
            break;
        case REGEXP:
            ensureTyped(n, REGEXP_TYPE);
            break;
        case GETPROP:
            visitGetProp(t, n);
            typeable = !(parent.isAssign() && parent.getFirstChild() == n);
            break;
        case OPTCHAIN_GETPROP:
            visitOptChainGetProp(n);
            break;
        case OPTCHAIN_GETELEM:
            visitOptChainGetElem(n);
            break;
        case GETELEM:
            visitGetElem(n);
            // The type of GETELEM is always unknown, so no point counting that.
            // If that unknown leaks elsewhere (say by an assignment to another
            // variable), then it will be counted.
            typeable = false;
            break;
        case VAR:
        case LET:
        case CONST:
            visitVar(t, n);
            typeable = false;
            break;
        case NEW:
            visitNew(n);
            break;
        case OPTCHAIN_CALL:
            // We reuse the `visitCall` functionality for OptChain call nodes because we don't report
            // an error for regular calls when the callee is null or undefined. However, we make sure
            // `typeable` isn't explicitly unset as OptChain nodes are always typed during inference.
            visitCall(t, n);
            break;
        case CALL:
            visitCall(t, n);
            typeable = !parent.isExprResult();
            break;
        case RETURN:
            visitReturn(t, n);
            typeable = false;
            break;
        case YIELD:
            visitYield(t, n);
            break;
        case DEC:
        case INC:
            left = n.getFirstChild();
            checkPropCreation(left);
            if (getJSType(n).isNumber()) {
                validator.expectNumber(left, getJSType(left), "increment/decrement");
                ensureTyped(n, NUMBER_TYPE);
            } else {
                validator.expectBigIntOrNumber(left, getJSType(left), "increment/decrement");
            }
            break;
        case VOID:
            ensureTyped(n, VOID_TYPE);
            break;
        case STRINGLIT:
        case TYPEOF:
        case TEMPLATELIT:
        case TEMPLATELIT_STRING:
            ensureTyped(n, STRING_TYPE);
            break;
        case TAGGED_TEMPLATELIT:
            visitTaggedTemplateLit(n);
            ensureTyped(n);
            break;
        case BITNOT:
            visitBitwiseNOT(n);
            break;
        case POS:
            visitUnaryPlus(n);
            break;
        case NEG:
            visitUnaryMinus(n);
            break;
        case EQ:
        case NE:
        case SHEQ:
        case SHNE:
            {
                left = n.getFirstChild();
                right = n.getLastChild();
                if (left.isTypeOf()) {
                    if (right.isStringLit()) {
                        checkTypeofString(right, right.getString());
                    }
                } else if (right.isTypeOf() && left.isStringLit()) {
                    checkTypeofString(left, left.getString());
                }
                leftType = getJSType(left);
                rightType = getJSType(right);
                // We do not want to warn about explicit comparisons to VOID. People
                // often do this if they think their type annotations screwed up.
                // 
                // We do want to warn about cases where people compare things like
                // (Array|null) == (Function|null)
                // because it probably means they screwed up.
                // 
                // This heuristic here is not perfect, but should catch cases we
                // care about without too many false negatives.
                JSType leftTypeRestricted = leftType.restrictByNotNullOrUndefined();
                JSType rightTypeRestricted = rightType.restrictByNotNullOrUndefined();
                Tri result = Tri.UNKNOWN;
                if (n.getToken() == Token.EQ || n.isNE()) {
                    result = leftTypeRestricted.testForEquality(rightTypeRestricted);
                    if (n.isNE()) {
                        result = result.not();
                    }
                } else {
                    // SHEQ or SHNE
                    if (!leftTypeRestricted.canTestForShallowEqualityWith(rightTypeRestricted)) {
                        result = n.getToken() == Token.SHEQ ? Tri.FALSE : Tri.TRUE;
                    }
                }
                if (result != Tri.UNKNOWN) {
                    report(n, DETERMINISTIC_TEST, leftType.toString(), rightType.toString(), result.toString());
                }
                ensureTyped(n, BOOLEAN_TYPE);
                break;
            }
        case LT:
        case LE:
        case GT:
        case GE:
            Node leftSide = n.getFirstChild();
            Node rightSide = n.getLastChild();
            leftType = getJSType(leftSide);
            rightType = getJSType(rightSide);
            if (rightType.isUnknownType()) {
                // validate comparable left
                validator.expectUnknownOrComparable(leftSide, leftType, "left side of comparison");
            } else if (leftType.isUnknownType()) {
                // validate comparable right
                validator.expectUnknownOrComparable(rightSide, rightType, "right side of comparison");
            } else if (rightType.isBigIntOrNumber()) {
                // validate left operand for numeric comparison
                validator.expectBigIntOrNumber(leftSide, leftType, "left side of numeric comparison");
            } else if (leftType.isBigIntOrNumber()) {
                // validate right operand for numeric comparison
                validator.expectBigIntOrNumber(rightSide, rightType, "right side of numeric comparison");
            } else {
                String errorMsg = "expected matching types in comparison";
                this.validator.expectMatchingTypesStrict(n, leftType, rightType, errorMsg);
                if (!leftType.matchesNumberContext() || !rightType.matchesNumberContext()) {
                    // Whether the comparison is numeric will be determined at runtime
                    // each time the expression is evaluated. Regardless, both operands
                    // should match a string context.
                    String message = "left side of comparison";
                    validator.expectString(leftSide, leftType, message);
                    validator.expectNotNullOrUndefined(t, leftSide, leftType, message, getNativeType(STRING_TYPE));
                    message = "right side of comparison";
                    validator.expectString(rightSide, rightType, message);
                    validator.expectNotNullOrUndefined(t, rightSide, rightType, message, getNativeType(STRING_TYPE));
                }
            }
            ensureTyped(n, BOOLEAN_TYPE);
            break;
        case IN:
            left = n.getFirstChild();
            right = n.getLastChild();
            rightType = getJSType(right);
            validator.expectStringOrSymbol(left, getJSType(left), "left side of 'in'");
            validator.expectObject(n, rightType, "'in' requires an object");
            if (rightType.isStruct()) {
                report(right, IN_USED_WITH_STRUCT);
            }
            ensureTyped(n, BOOLEAN_TYPE);
            break;
        case INSTANCEOF:
            left = n.getFirstChild();
            right = n.getLastChild();
            rightType = getJSType(right).restrictByNotNullOrUndefined();
            validator.expectAnyObject(left, getJSType(left), "deterministic instanceof yields false");
            validator.expectActualObject(right, rightType, "instanceof requires an object");
            ensureTyped(n, BOOLEAN_TYPE);
            break;
        case ASSIGN:
        case ASSIGN_OR:
        case ASSIGN_AND:
        case ASSIGN_COALESCE:
            visitAssign(t, n);
            typeable = false;
            break;
        case ASSIGN_LSH:
        case ASSIGN_RSH:
        case ASSIGN_URSH:
        case ASSIGN_DIV:
        case ASSIGN_MOD:
        case ASSIGN_BITOR:
        case ASSIGN_BITXOR:
        case ASSIGN_BITAND:
        case ASSIGN_SUB:
        case ASSIGN_ADD:
        case ASSIGN_MUL:
        case ASSIGN_EXPONENT:
            checkPropCreation(n.getFirstChild());
        case LSH:
        case RSH:
        case URSH:
        case DIV:
        case MOD:
        case BITOR:
        case BITXOR:
        case BITAND:
        case SUB:
        case ADD:
        case MUL:
        case EXPONENT:
            visitBinaryOperator(n.getToken(), n);
            break;
        case TRUE:
        case FALSE:
        case NOT:
        case DELPROP:
            ensureTyped(n, BOOLEAN_TYPE);
            break;
        case CASE:
            JSType switchType = getJSType(parent.getFirstChild());
            JSType caseType = getJSType(n.getFirstChild());
            validator.expectSwitchMatchesCase(n, switchType, caseType);
            typeable = false;
            break;
        case WITH:
            {
                Node child = n.getFirstChild();
                childType = getJSType(child);
                validator.expectObject(child, childType, "with requires an object");
                typeable = false;
                break;
            }
        case FUNCTION:
            visitFunction(n);
            break;
        case CLASS:
            visitClass(n);
            break;
        case MODULE_BODY:
            visitModuleBody(t, n);
            break;
        // These nodes require data flow analysis.
        case PARAM_LIST:
        case STRING_KEY:
        case MEMBER_FUNCTION_DEF:
        case COMPUTED_PROP:
        case MEMBER_FIELD_DEF:
        case COMPUTED_FIELD_DEF:
        case LABEL:
        case LABEL_NAME:
        case SWITCH:
        case BREAK:
        case CATCH:
        case TRY:
        case SCRIPT:
        case EXPORT:
        case EXPORT_SPEC:
        case EXPORT_SPECS:
        case IMPORT:
        case IMPORT_SPEC:
        case IMPORT_SPECS:
        case IMPORT_STAR:
        case EXPR_RESULT:
        case BLOCK:
        case ROOT:
        case EMPTY:
        case DEFAULT_CASE:
        case CONTINUE:
        case DEBUGGER:
        case THROW:
        case DO:
        case IF:
        case WHILE:
        case FOR:
        case TEMPLATELIT_SUB:
        case ITER_REST:
        case OBJECT_REST:
        case DESTRUCTURING_LHS:
            typeable = false;
            break;
        case DYNAMIC_IMPORT:
            visitDynamicImport(t, n);
            break;
        case ARRAY_PATTERN:
            ensureTyped(n);
            validator.expectAutoboxesToIterable(n, getJSType(n), "array pattern destructuring requires an Iterable");
            break;
        case OBJECT_PATTERN:
            visitObjectPattern(n);
            break;
        case DEFAULT_VALUE:
            checkCanAssignToWithScope(t, n, n.getFirstChild(), getJSType(n.getSecondChild()), /* info= */
            null, "default value has wrong type");
            // Every other usage of a destructuring pattern is checked while visiting the pattern,
            // but default values are different because they are a conditional assignment and the
            // pattern is not given the default value's type
            Node lhs = n.getFirstChild();
            Node rhs = n.getSecondChild();
            if (lhs.isArrayPattern()) {
                validator.expectAutoboxesToIterable(rhs, getJSType(rhs), "array pattern destructuring requires an Iterable");
            } else if (lhs.isObjectPattern()) {
                // Verify that the value is not null/undefined, since those can't be destructured.
                validator.expectObject(rhs, getJSType(rhs), "cannot destructure a 'null' or 'undefined' default value");
            }
            typeable = false;
            break;
        case CLASS_MEMBERS:
            {
                JSType typ = parent.getJSType().toMaybeFunctionType().getInstanceType();
                for (Node child = n.getFirstChild(); child != null; child = child.getNext()) {
                    visitObjectOrClassLiteralKey(child, n.getParent(), typ);
                    if (child.isSetterDef() || child.isGetterDef()) {
                        checkGetterOrSetterType(child, parent.getJSType().toMaybeFunctionType());
                    }
                }
                typeable = false;
                break;
            }
        case FOR_IN:
            Node obj = n.getSecondChild();
            if (getJSType(obj).isStruct()) {
                report(obj, IN_USED_WITH_STRUCT);
            }
            typeable = false;
            break;
        case FOR_OF:
        case FOR_AWAIT_OF:
            ensureTyped(n.getSecondChild());
            typeable = false;
            break;
        // These nodes are typed during the type inference.
        case SUPER:
        case NEW_TARGET:
        case IMPORT_META:
        case AWAIT:
        case AND:
        case HOOK:
        case OR:
        case COALESCE:
            ensureTyped(n);
            break;
        case OBJECTLIT:
            // If this is an enum, then give that type to the objectlit as well.
            if (parent.getJSType() instanceof EnumType) {
                ensureTyped(n, parent.getJSType());
            } else {
                ensureTyped(n);
            }
            JSType typ = getJSType(n);
            for (Node key = n.getFirstChild(); key != null; key = key.getNext()) {
                visitObjectOrClassLiteralKey(key, n, typ);
            }
            break;
        case ITER_SPREAD:
        case OBJECT_SPREAD:
            checkSpread(n);
            typeable = false;
            break;
        default:
            report(n, UNEXPECTED_TOKEN, n.getToken().toString());
            ensureTyped(n);
            break;
    }
    // Visit the body of blockless arrow functions
    if (NodeUtil.isBlocklessArrowFunctionResult(n)) {
        visitImplicitReturnExpression(t, n);
    }
    // TypedScope.
    if ((n.getParent().isForOf() || n.getParent().isForAwaitOf()) && n.getParent().getFirstChild() == n) {
        checkForOfTypes(t, n.getParent());
    }
    // Don't count externs since the user's code may not even use that part.
    typeable = typeable && !inExterns;
    if (typeable) {
        doPercentTypedAccounting(n);
    }
    checkJsdocInfoContainsObjectWithBadKey(n);
}
Also used : JSType(com.google.javascript.rhino.jstype.JSType) EnumType(com.google.javascript.rhino.jstype.EnumType) Node(com.google.javascript.rhino.Node) Tri(com.google.javascript.jscomp.base.Tri)

Example 9 with Tri

use of com.google.javascript.jscomp.base.Tri in project closure-compiler by google.

the class JSTypeTest method testSymmetryOfTestForEquality.

@Test
public void testSymmetryOfTestForEquality() {
    List<JSType> listA = getTypesToTestForSymmetry();
    List<JSType> listB = getTypesToTestForSymmetry();
    for (JSType typeA : listA) {
        for (JSType typeB : listB) {
            Tri aOnB = typeA.testForEquality(typeB);
            Tri bOnA = typeB.testForEquality(typeA);
            assertWithMessage(lines("testForEquality not symmetrical:", "typeA: %s\ntypeB: %s", "a.testForEquality(b): %s", "b.testForEquality(a): %s"), typeA, typeB, aOnB, bOnA).that(aOnB == bOnA).isTrue();
        }
    }
}
Also used : Tri(com.google.javascript.jscomp.base.Tri) Test(org.junit.Test)

Example 10 with Tri

use of com.google.javascript.jscomp.base.Tri in project closure-compiler by google.

the class PeepholeRemoveDeadCode method tryFoldIf.

/**
 * Try folding IF nodes by removing dead branches.
 * @return the replacement node, if changed, or the original if not
 */
private Node tryFoldIf(Node n) {
    checkState(n.isIf(), n);
    Node parent = n.getParent();
    checkNotNull(parent);
    Token type = n.getToken();
    Node cond = n.getFirstChild();
    Node thenBody = cond.getNext();
    Node elseBody = thenBody.getNext();
    // if (x) { .. } else { } --> if (x) { ... }
    if (elseBody != null && !mayHaveSideEffects(elseBody)) {
        elseBody.detach();
        reportChangeToEnclosingScope(n);
        elseBody = null;
    }
    // if (x) { } else { ... } --> if (!x) { ... }
    if (!mayHaveSideEffects(thenBody) && elseBody != null) {
        elseBody.detach();
        thenBody.replaceWith(elseBody);
        Node notCond = new Node(Token.NOT);
        cond.replaceWith(notCond);
        reportChangeToEnclosingScope(n);
        notCond.addChildToFront(cond);
        cond = notCond;
        thenBody = cond.getNext();
        elseBody = null;
    }
    // `if (x()) { }` or `if (x?.()) { }`
    if (!mayHaveSideEffects(thenBody) && elseBody == null) {
        if (mayHaveSideEffects(cond)) {
            // `x()` or `x?.()` has side effects, just leave the condition on its own.
            cond.detach();
            Node replacement = NodeUtil.newExpr(cond);
            n.replaceWith(replacement);
            reportChangeToEnclosingScope(parent);
            return replacement;
        } else {
            // `x()` or `x?.()` has no side effects, the whole tree is useless now.
            NodeUtil.removeChild(parent, n);
            reportChangeToEnclosingScope(parent);
            return null;
        }
    }
    // Try transforms that apply to both IF and HOOK.
    Tri condValue = NodeUtil.getBooleanValue(cond);
    if (condValue == Tri.UNKNOWN) {
        // We can't remove branches otherwise!
        return n;
    }
    if (mayHaveSideEffects(cond)) {
        // Transform "if (a = 2) {x =2}" into "if (true) {a=2;x=2}"
        boolean newConditionValue = condValue == Tri.TRUE;
        // Add an elseBody if it is needed.
        if (!newConditionValue && elseBody == null) {
            elseBody = IR.block().srcref(n);
            n.addChildToBack(elseBody);
        }
        Node newCond = NodeUtil.booleanNode(newConditionValue);
        cond.replaceWith(newCond);
        Node branchToKeep = newConditionValue ? thenBody : elseBody;
        branchToKeep.addChildToFront(IR.exprResult(cond).srcref(cond));
        reportChangeToEnclosingScope(branchToKeep);
        cond = newCond;
    }
    boolean condTrue = condValue.toBoolean(true);
    if (n.hasTwoChildren()) {
        checkState(type == Token.IF);
        if (condTrue) {
            // Replace "if (true) { X }" with "X".
            Node thenStmt = n.getSecondChild();
            thenStmt.detach();
            n.replaceWith(thenStmt);
            reportChangeToEnclosingScope(thenStmt);
            return thenStmt;
        } else {
            // Remove "if (false) { X }" completely.
            NodeUtil.redeclareVarsInsideBranch(n);
            NodeUtil.removeChild(parent, n);
            reportChangeToEnclosingScope(parent);
            markFunctionsDeleted(n);
            return null;
        }
    } else {
        // Replace "if (true) { X } else { Y }" with X, or
        // replace "if (false) { X } else { Y }" with Y.
        Node trueBranch = n.getSecondChild();
        Node falseBranch = trueBranch.getNext();
        Node branchToKeep = condTrue ? trueBranch : falseBranch;
        Node branchToRemove = condTrue ? falseBranch : trueBranch;
        NodeUtil.redeclareVarsInsideBranch(branchToRemove);
        branchToKeep.detach();
        n.replaceWith(branchToKeep);
        reportChangeToEnclosingScope(branchToKeep);
        markFunctionsDeleted(n);
        return branchToKeep;
    }
}
Also used : Node(com.google.javascript.rhino.Node) Token(com.google.javascript.rhino.Token) Tri(com.google.javascript.jscomp.base.Tri)

Aggregations

Tri (com.google.javascript.jscomp.base.Tri)17 Node (com.google.javascript.rhino.Node)12 Token (com.google.javascript.rhino.Token)3 BigInteger (java.math.BigInteger)3 JSType (com.google.javascript.rhino.jstype.JSType)2 MeasuredNode (com.google.javascript.jscomp.MinimizedCondition.MeasuredNode)1 ValueType (com.google.javascript.jscomp.NodeUtil.ValueType)1 EnumType (com.google.javascript.rhino.jstype.EnumType)1 Test (org.junit.Test)1