Exemplo n.º 1
0
        // unnest any child blocks
        private static void UnnestBlocks(JsBlock node)
        {
            // walk the list of items backwards -- if we come
            // to any blocks, unnest the block recursively.
            // Remove any empty statements as well.
            // We walk backwards because we could be adding any number of statements
            // and we don't want to have to modify the counter.
            for (int ndx = node.Count - 1; ndx >= 0; --ndx)
            {
                var nestedBlock = node[ndx] as JsBlock;
                if (nestedBlock != null)
                {
                    // unnest recursively
                    UnnestBlocks(nestedBlock);

                    // if the block has a block scope, then we can't really unnest it
                    // without merging lexical scopes
                    if (nestedBlock.BlockScope == null)
                    {
                        // remove the nested block
                        node.RemoveAt(ndx);

                        // then start adding the statements in the nested block to our own.
                        // go backwards so we can just keep using the same index
                        node.InsertRange(ndx, nestedBlock.Children);
                    }
                }
                else if (node[ndx] is JsEmptyStatement)
                {
                    // remove empty statements (lone semicolons)
                    node.RemoveAt(ndx);
                }
                else if (ndx > 0)
                {
                    // see if the previous node is a conditional-compilation comment, because
                    // we will also combine adjacent those
                    var previousComment = node[ndx - 1] as JsConditionalCompilationComment;
                    if (previousComment != null)
                    {
                        JsConditionalCompilationComment thisComment = node[ndx] as JsConditionalCompilationComment;
                        if (thisComment != null)
                        {
                            // two adjacent conditional comments -- combine them into the first.
                            previousComment.Statements.Append(thisComment.Statements);

                            // and remove the second one (which is now a duplicate)
                            node.RemoveAt(ndx);
                        }
                    }
                }
            }
        }
Exemplo n.º 2
0
        public override void Visit(JsBlock node)
        {
            if (node != null)
            {
                // there really is no point in nesting blocks that don't have any special scopes
                // attached to them. Unnest any now, before we start combining var statements.
                UnnestBlocks(node);

                // if we get here, we are going to want to optimize the curly-braces to eliminate
                // unneeded ones in all blocks except try/catch/finally. So make sure we reset the
                // force-braces properties for all blocks whose parent isn't a try-statement.
                node.ForceBraces = node.Parent is JsTryNode;

                if (m_combineAdjacentVars)
                {
                    // look at the statements in the block.
                    // if there are multiple var statements adjacent to each other, combine them.
                    // walk BACKWARDS down the list because we'll be removing items when we encounter
                    // multiple vars, etc.
                    // we also don't need to check the first one, since there is nothing before it.
                    for (int ndx = node.Count - 1; ndx > 0; --ndx)
                    {
                        // if the previous node is not a Var, then we don't need to try and combine
                        // it with the current node
                        var previousVar = node[ndx - 1] as JsVar;
                        if (previousVar != null && node[ndx] is JsVar)
                        {
                            // add the items in this VAR to the end of the previous
                            previousVar.Append(node[ndx]);

                            // delete this item from the block
                            node.RemoveAt(ndx);
                        }
                        else
                        {
                            // do the same thing for lexical declarations
                            var previousLex = node[ndx - 1] as JsLexicalDeclaration;
                            var thisLex     = node[ndx] as JsLexicalDeclaration;
                            if (previousLex != null && thisLex != null)
                            {
                                // but we can only combine them if they are the same type (let or const)
                                if (previousLex.StatementToken == thisLex.StatementToken)
                                {
                                    previousLex.Append(node[ndx]);
                                    node.RemoveAt(ndx);
                                }
                            }
                            else
                            {
                                // try doing the same for const-statements: combine adjacent ones
                                var previousConst = node[ndx - 1] as JsConstStatement;
                                if (previousConst != null && node[ndx] is JsConstStatement)
                                {
                                    // they are both ConstStatements, so adding the current one to the
                                    // previous one will combine them, then delete the latter one.
                                    previousConst.Append(node[ndx]);
                                    node.RemoveAt(ndx);
                                }
                            }
                        }
                    }
                }

                // recurse down the tree after we've combined the adjacent var statements
                base.Visit(node);
            }
        }
Exemplo n.º 3
0
        private static int RelocateVar(JsBlock block, int insertAt, JsVar varStatement)
        {
            // if the var statement is at the next position to insert, then we don't need
            // to do anything.
            if (block[insertAt] != varStatement)
            {
                // check to see if the current position is a var and we are the NEXT statement.
                // if that's the case, we don't need to break out the initializer, just append all the
                // vardecls as-is to the current position.
                var existingVar = block[insertAt] as JsVar;
                if (existingVar != null && block[insertAt + 1] == varStatement)
                {
                    // just append our vardecls to the insertion point, then delete our statement
                    existingVar.Append(varStatement);
                    block.RemoveAt(insertAt + 1);
                }
                else
                {
                    // iterate through the decls and count how many have initializers
                    var initializerCount = 0;
                    for (var ndx = 0; ndx < varStatement.Count; ++ndx)
                    {
                        if (varStatement[ndx].Initializer != null)
                        {
                            ++initializerCount;
                        }
                    }

                    // if there are more than two decls with initializers, then we won't actually
                    // be gaining anything by moving the var to the top. We'll get rid of the four
                    // bytes for the "var ", but we'll be adding two bytes for the name and comma
                    // because name=init will still need to remain behind.
                    if (initializerCount <= 2)
                    {
                        // first iterate through all the declarations in the var statement,
                        // constructing an expression statement that is made up of assignment
                        // operators for each of the declarations that have initializers (if any)
                        // and removing all the initializers
                        var assignments = new List <JsAstNode>();
                        for (var ndx = 0; ndx < varStatement.Count; ++ndx)
                        {
                            var varDecl = varStatement[ndx];
                            if (varDecl.Initializer != null)
                            {
                                if (varDecl.IsCCSpecialCase)
                                {
                                    // create a vardecl with the same name and no initializer
                                    var copyDecl = new JsVariableDeclaration(varDecl.Context, varDecl.Parser)
                                    {
                                        Identifier    = varDecl.Identifier,
                                        NameContext   = varDecl.VariableField.OriginalContext,
                                        VariableField = varDecl.VariableField
                                    };

                                    // replace the special vardecl with the copy
                                    varStatement[ndx] = copyDecl;

                                    // add the original vardecl to the list of "assignments"
                                    assignments.Add(varDecl);

                                    // add the new decl to the field's declaration list, and remove the old one
                                    // because we're going to change that to an assignment.
                                    varDecl.VariableField.Declarations.Add(copyDecl);
                                    varDecl.VariableField.Declarations.Remove(varDecl);
                                }
                                else
                                {
                                    // hold on to the object so we don't lose it to the GC
                                    var initializer = varDecl.Initializer;

                                    // remove it from the vardecl
                                    varDecl.Initializer = null;

                                    // create an assignment operator for a lookup to the name
                                    // as the left, and the initializer as the right, and add it to the list
                                    var lookup = new JsLookup(varDecl.VariableField.OriginalContext, varDecl.Parser)
                                    {
                                        Name          = varDecl.Identifier,
                                        VariableField = varDecl.VariableField,
                                    };
                                    assignments.Add(new JsBinaryOperator(varDecl.Context, varDecl.Parser)
                                    {
                                        Operand1        = lookup,
                                        Operand2        = initializer,
                                        OperatorToken   = JsToken.Assign,
                                        OperatorContext = varDecl.AssignContext
                                    });

                                    // add the new lookup to the field's references
                                    varDecl.VariableField.References.Add(lookup);
                                }
                            }
                        }

                        // now if there were any initializers...
                        if (assignments.Count > 0)
                        {
                            // we want to create one big expression from all the assignments and replace the
                            // var statement with the assignment(s) expression. Start at position n=1 and create
                            // a binary operator of n-1 as the left, n as the right, and using a comma operator.
                            var expression = assignments[0];
                            for (var ndx = 1; ndx < assignments.Count; ++ndx)
                            {
                                expression = JsCommaOperator.CombineWithComma(null, expression.Parser, expression, assignments[ndx]);
                            }

                            // replace the var with the expression.
                            // we still have a pointer to the var, so we can insert it back into the proper
                            // place next.
                            varStatement.Parent.ReplaceChild(varStatement, expression);
                        }
                        else
                        {
                            // no initializers.
                            // if the parent is a for-in statement...
                            var forInParent = varStatement.Parent as JsForIn;
                            if (forInParent != null)
                            {
                                // we want to replace the var statement with a lookup for the var
                                // there should be only one vardecl
                                var varDecl = varStatement[0];
                                var lookup  = new JsLookup(varDecl.VariableField.OriginalContext, varStatement.Parser)
                                {
                                    Name          = varDecl.Identifier,
                                    VariableField = varDecl.VariableField
                                };
                                varStatement.Parent.ReplaceChild(varStatement, lookup);
                                varDecl.VariableField.References.Add(lookup);
                            }
                            else
                            {
                                // just remove the var statement altogether
                                varStatement.Parent.ReplaceChild(varStatement, null);
                            }
                        }

                        // if the statement at the insertion point is a var-statement already,
                        // then we just need to append our vardecls to it. Otherwise we'll insert our
                        // var statement at the right point
                        if (existingVar != null)
                        {
                            // append the varstatement we want to move to the existing var, which will
                            // transfer all the vardecls to it.
                            existingVar.Append(varStatement);
                        }
                        else
                        {
                            // move the var to the insert point, incrementing the position or next time
                            block.Insert(insertAt, varStatement);
                        }
                    }
                }
            }

            return(insertAt);
        }
        private static int RelocateVar(JsBlock block, int insertAt, JsVar varStatement)
        {
            // if the var statement is at the next position to insert, then we don't need
            // to do anything.
            if (block[insertAt] != varStatement)
            {
                // check to see if the current position is a var and we are the NEXT statement.
                // if that's the case, we don't need to break out the initializer, just append all the
                // vardecls as-is to the current position.
                var existingVar = block[insertAt] as JsVar;
                if (existingVar != null && block[insertAt + 1] == varStatement)
                {
                    // just append our vardecls to the insertion point, then delete our statement
                    existingVar.Append(varStatement);
                    block.RemoveAt(insertAt + 1);
                }
                else
                {
                    // iterate through the decls and count how many have initializers
                    var initializerCount = 0;
                    for (var ndx = 0; ndx < varStatement.Count; ++ndx)
                    {
                        if (varStatement[ndx].Initializer != null)
                        {
                            ++initializerCount;
                        }
                    }

                    // if there are more than two decls with initializers, then we won't actually
                    // be gaining anything by moving the var to the top. We'll get rid of the four
                    // bytes for the "var ", but we'll be adding two bytes for the name and comma
                    // because name=init will still need to remain behind.
                    if (initializerCount <= 2)
                    {
                        // first iterate through all the declarations in the var statement,
                        // constructing an expression statement that is made up of assignment
                        // operators for each of the declarations that have initializers (if any)
                        // and removing all the initializers
                        var assignments = new List<JsAstNode>();
                        for (var ndx = 0; ndx < varStatement.Count; ++ndx)
                        {
                            var varDecl = varStatement[ndx];
                            if (varDecl.Initializer != null)
                            {
                                if (varDecl.IsCCSpecialCase)
                                {
                                    // create a vardecl with the same name and no initializer
                                    var copyDecl = new JsVariableDeclaration(varDecl.Context, varDecl.Parser)
                                        {
                                            Identifier = varDecl.Identifier,
                                            NameContext = varDecl.VariableField.OriginalContext,
                                            VariableField = varDecl.VariableField
                                        };

                                    // replace the special vardecl with the copy
                                    varStatement[ndx] = copyDecl;

                                    // add the original vardecl to the list of "assignments"
                                    assignments.Add(varDecl);

                                    // add the new decl to the field's declaration list, and remove the old one
                                    // because we're going to change that to an assignment.
                                    varDecl.VariableField.Declarations.Add(copyDecl);
                                    varDecl.VariableField.Declarations.Remove(varDecl);
                                }
                                else
                                {
                                    // hold on to the object so we don't lose it to the GC
                                    var initializer = varDecl.Initializer;

                                    // remove it from the vardecl
                                    varDecl.Initializer = null;

                                    // create an assignment operator for a lookup to the name
                                    // as the left, and the initializer as the right, and add it to the list
                                    var lookup = new JsLookup(varDecl.VariableField.OriginalContext, varDecl.Parser)
                                        {
                                            Name = varDecl.Identifier,
                                            VariableField = varDecl.VariableField,
                                        };
                                    assignments.Add(new JsBinaryOperator(varDecl.Context, varDecl.Parser)
                                        {
                                            Operand1 = lookup,
                                            Operand2 = initializer,
                                            OperatorToken = JsToken.Assign,
                                            OperatorContext = varDecl.AssignContext
                                        });

                                    // add the new lookup to the field's references
                                    varDecl.VariableField.References.Add(lookup);
                                }
                            }
                        }

                        // now if there were any initializers...
                        if (assignments.Count > 0)
                        {
                            // we want to create one big expression from all the assignments and replace the
                            // var statement with the assignment(s) expression. Start at position n=1 and create
                            // a binary operator of n-1 as the left, n as the right, and using a comma operator.
                            var expression = assignments[0];
                            for (var ndx = 1; ndx < assignments.Count; ++ndx)
                            {
                                expression = JsCommaOperator.CombineWithComma(null, expression.Parser, expression, assignments[ndx]);
                            }

                            // replace the var with the expression.
                            // we still have a pointer to the var, so we can insert it back into the proper
                            // place next.
                            varStatement.Parent.ReplaceChild(varStatement, expression);
                        }
                        else
                        {
                            // no initializers.
                            // if the parent is a for-in statement...
                            var forInParent = varStatement.Parent as JsForIn;
                            if (forInParent != null)
                            {
                                // we want to replace the var statement with a lookup for the var
                                // there should be only one vardecl
                                var varDecl = varStatement[0];
                                var lookup = new JsLookup(varDecl.VariableField.OriginalContext, varStatement.Parser)
                                    {
                                        Name = varDecl.Identifier,
                                        VariableField = varDecl.VariableField
                                    };
                                varStatement.Parent.ReplaceChild(varStatement, lookup);
                                varDecl.VariableField.References.Add(lookup);
                            }
                            else
                            {
                                // just remove the var statement altogether
                                varStatement.Parent.ReplaceChild(varStatement, null);
                            }
                        }

                        // if the statement at the insertion point is a var-statement already,
                        // then we just need to append our vardecls to it. Otherwise we'll insert our
                        // var statement at the right point
                        if (existingVar != null)
                        {
                            // append the varstatement we want to move to the existing var, which will
                            // transfer all the vardecls to it.
                            existingVar.Append(varStatement);
                        }
                        else
                        {
                            // move the var to the insert point, incrementing the position or next time
                            block.Insert(insertAt, varStatement);
                        }
                    }
                }
            }

            return insertAt;
        }
        // unnest any child blocks
        private static void UnnestBlocks(JsBlock node)
        {
            // walk the list of items backwards -- if we come
            // to any blocks, unnest the block recursively.
            // Remove any empty statements as well.
            // We walk backwards because we could be adding any number of statements
            // and we don't want to have to modify the counter.
            for (int ndx = node.Count - 1; ndx >= 0; --ndx)
            {
                var nestedBlock = node[ndx] as JsBlock;
                if (nestedBlock != null)
                {
                    // unnest recursively
                    UnnestBlocks(nestedBlock);

                    // if the block has a block scope, then we can't really unnest it
                    // without merging lexical scopes
                    if (nestedBlock.BlockScope == null)
                    {
                        // remove the nested block
                        node.RemoveAt(ndx);

                        // then start adding the statements in the nested block to our own.
                        // go backwards so we can just keep using the same index
                        node.InsertRange(ndx, nestedBlock.Children);
                    }
                }
                else if (node[ndx] is JsEmptyStatement)
                {
                    // remove empty statements (lone semicolons)
                    node.RemoveAt(ndx);
                }
                else if (ndx > 0)
                {
                    // see if the previous node is a conditional-compilation comment, because
                    // we will also combine adjacent those
                    var previousComment = node[ndx - 1] as JsConditionalCompilationComment;
                    if (previousComment != null)
                    {
                        JsConditionalCompilationComment thisComment = node[ndx] as JsConditionalCompilationComment;
                        if (thisComment != null)
                        {
                            // two adjacent conditional comments -- combine them into the first.
                            previousComment.Statements.Append(thisComment.Statements);

                            // and remove the second one (which is now a duplicate)
                            node.RemoveAt(ndx);
                        }
                    }
                }
            }
        }
        public override void Visit(JsBlock node)
        {
            if (node != null)
            {
                // there really is no point in nesting blocks that don't have any special scopes
                // attached to them. Unnest any now, before we start combining var statements.
                UnnestBlocks(node);

                // if we get here, we are going to want to optimize the curly-braces to eliminate
                // unneeded ones in all blocks except try/catch/finally. So make sure we reset the
                // force-braces properties for all blocks whose parent isn't a try-statement.
                node.ForceBraces = node.Parent is JsTryNode;

                if (m_combineAdjacentVars)
                {
                    // look at the statements in the block.
                    // if there are multiple var statements adjacent to each other, combine them.
                    // walk BACKWARDS down the list because we'll be removing items when we encounter
                    // multiple vars, etc.
                    // we also don't need to check the first one, since there is nothing before it.
                    for (int ndx = node.Count - 1; ndx > 0; --ndx)
                    {
                        // if the previous node is not a Var, then we don't need to try and combine
                        // it with the current node
                        var previousVar = node[ndx - 1] as JsVar;
                        if (previousVar != null && node[ndx] is JsVar)
                        {
                            // add the items in this VAR to the end of the previous
                            previousVar.Append(node[ndx]);

                            // delete this item from the block
                            node.RemoveAt(ndx);
                        }
                        else
                        {
                            // do the same thing for lexical declarations
                            var previousLex = node[ndx - 1] as JsLexicalDeclaration;
                            var thisLex = node[ndx] as JsLexicalDeclaration;
                            if (previousLex != null && thisLex != null)
                            {
                                // but we can only combine them if they are the same type (let or const)
                                if (previousLex.StatementToken == thisLex.StatementToken)
                                {
                                    previousLex.Append(node[ndx]);
                                    node.RemoveAt(ndx);
                                }
                            }
                            else
                            {
                                // try doing the same for const-statements: combine adjacent ones
                                var previousConst = node[ndx - 1] as JsConstStatement;
                                if (previousConst != null && node[ndx] is JsConstStatement)
                                {
                                    // they are both ConstStatements, so adding the current one to the
                                    // previous one will combine them, then delete the latter one.
                                    previousConst.Append(node[ndx]);
                                    node.RemoveAt(ndx);
                                }
                            }
                        }
                    }
                }

                // recurse down the tree after we've combined the adjacent var statements
                base.Visit(node);
            }
        }
 private void CombineWithPreviousExpression(JsBlock node, int ndx)
 {
     JsIfNode ifNode;
     JsForNode forNode;
     JsWhileNode whileNode;
     JsReturnNode returnNode;
     if (node[ndx].IsExpression)
     {
         CombineTwoExpressions(node, ndx);
     }
     else if ((returnNode = node[ndx] as JsReturnNode) != null)
     {
         CombineReturnWithExpression(node, ndx, returnNode);
     }
     else if ((forNode = node[ndx] as JsForNode) != null)
     {
         CombineForNodeWithExpression(node, ndx, forNode);
     }
     else if ((ifNode = node[ndx] as JsIfNode) != null)
     {
         // transform: expr;if(cond)... => if(expr,cond)...
         // combine the previous expression with the if-condition via comma, then delete
         // the previous statement.
         ifNode.Condition = JsCommaOperator.CombineWithComma(null, m_parser, node[ndx - 1], ifNode.Condition);
         node.RemoveAt(ndx - 1);
     }
     else if ((whileNode = node[ndx] as JsWhileNode) != null
         && m_parser.Settings.IsModificationAllowed(JsTreeModifications.ChangeWhileToFor))
     {
         // transform: expr;while(cond)... => for(expr;cond;)...
         // zero-sum, and maybe a little worse for performance because of the nop iterator,
         // but combines two statements into one, which may have savings later on.
         var initializer = node[ndx - 1];
         node[ndx] = new JsForNode(null, m_parser)
         {
             Initializer = initializer,
             Condition = whileNode.Condition,
             Body = whileNode.Body
         };
         node.RemoveAt(ndx - 1);
     }
 }
        private void CombineReturnWithExpression(JsBlock node, int ndx, JsReturnNode returnNode)
        {
            // see if the return node has an expression operand
            if (returnNode.Operand != null && returnNode.Operand.IsExpression)
            {
                // check for lookup[ASSIGN]expr2;return expr1.
                var beforeExpr = node[ndx - 1] as JsBinaryOperator;
                JsLookup lookup;
                if (beforeExpr != null
                    && beforeExpr.IsAssign
                    && (lookup = beforeExpr.Operand1 as JsLookup) != null)
                {
                    if (returnNode.Operand.IsEquivalentTo(lookup))
                    {
                        // we have lookup[ASSIGN]expr2;return lookup.
                        // if lookup is a local variable in the current scope, we can replace with return expr2;
                        // if lookup is an outer reference, we can replace with return lookup[ASSIGN]expr2
                        if (beforeExpr.OperatorToken == JsToken.Assign)
                        {
                            // check to see if lookup is in the current scope from which we are returning
                            if (lookup.VariableField == null
                                || lookup.VariableField.OuterField != null
                                || lookup.VariableField.IsReferencedInnerScope)
                            {
                                // transform: lookup[ASSIGN]expr2;return lookup => return lookup[ASSIGN]expr2
                                // lookup points to outer field (or we don't know)
                                // replace the operand on the return node with the previous expression and
                                // delete the previous node.
                                // first be sure to remove the lookup in the return operand from the references
                                // to field.
                                JsDetachReferences.Apply(returnNode.Operand);
                                returnNode.Operand = beforeExpr;
                                node[ndx - 1] = null;
                            }
                            else
                            {
                                // transform: lookup[ASSIGN]expr2;return lookup => return expr2
                                // lookup is a variable local to the current scope, so when we return, the
                                // variable won't exists anymore anyway.
                                // replace the operand on the return node oprand with the right-hand operand of the
                                // previous expression and delete the previous node.
                                // we're eliminating the two lookups altogether, so remove them both from the
                                // field's reference table.
                                var varField = lookup.VariableField;
                                JsDetachReferences.Apply(lookup, returnNode.Operand);

                                returnNode.Operand = beforeExpr.Operand2;
                                node[ndx - 1] = null;

                                // now that we've eliminated the two lookups, see if the local variable isn't
                                // referenced anymore. If it isn't, we might be able to remove the variable, too.
                                // (need to pick up those changes to keep track of a field's declarations, though)
                                if (varField.RefCount == 0)
                                {
                                    // it's not. if there's only one declaration and it either has no initializer or
                                    // is initialized to a constant, get rid of it.
                                    var nameDecl = varField.OnlyDeclaration;
                                    if (nameDecl != null)
                                    {
                                        // we only had one declaration.
                                        if (nameDecl.Initializer == null || nameDecl.Initializer.IsConstant)
                                        {
                                            // and it either had no initializer or it was initialized to a constant.
                                            // but it has no references, so let's whack it. Actually, only if it was
                                            // a var-decl (leave parameter and function decls alone).
                                            var varDecl = nameDecl as JsVariableDeclaration;
                                            if (varDecl != null)
                                            {
                                                // save the declaration parent (var, const, or let) and remove the
                                                // child vardecl from its list
                                                var declStatement = varDecl.Parent as JsDeclaration;
                                                declStatement.Remove(varDecl);
                                                varField.WasRemoved = true;

                                                // if the parent statement is now empty, remove it, too. this will
                                                // move everything up one index, but that'll just mean an extra loop.
                                                if (declStatement.Count == 0)
                                                {
                                                    declStatement.Parent.ReplaceChild(declStatement, null);
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        else
                        {
                            // it's an assignment, but it's not =. That means it's one of the OP= operators.
                            // we can't remove the field altogether. But we can move the assignment into the
                            // return statement and get rid of the lone lookup.
                            // transform: lookup OP= expr;return lookup   =>   return lookup OP= expr;
                            if (lookup.VariableField != null)
                            {
                                // we're getting rid of the lookup, so remove it from the field's list of references
                                JsDetachReferences.Apply(returnNode.Operand);
                            }

                            // remove the expression from the block and put it in the operand of
                            // the return statement.
                            node.RemoveAt(ndx - 1);
                            returnNode.Operand = beforeExpr;

                            // is this field scoped only to this function?
                            if (lookup.VariableField != null
                                && lookup.VariableField.OuterField == null
                                && !lookup.VariableField.IsReferencedInnerScope)
                            {
                                // in fact, the lookup is in the current scope, so assigning to it is a waste
                                // because we're going to return (this is a return statement, after all).
                                // we can get rid of the assignment part and just keep the operator:
                                // transform: lookup OP= expr;return lookup   =>   return lookup OP expr;
                                beforeExpr.OperatorToken = JsScanner.StripAssignment(beforeExpr.OperatorToken);
                            }
                        }
                    }
                    else
                    {
                        // transform: expr1;return expr2 to return expr1,expr2
                        var binOp = JsCommaOperator.CombineWithComma(null, m_parser, node[ndx - 1], returnNode.Operand);

                        // replace the operand on the return node with the new expression and
                        // delete the previous node
                        returnNode.Operand = binOp;
                        node[ndx - 1] = null;
                    }
                }
                else
                {
                    // transform: expr1;return expr2 to return expr1,expr2
                    var binOp = JsCommaOperator.CombineWithComma(null, m_parser, node[ndx - 1], returnNode.Operand);

                    // replace the operand on the return node with the new expression and
                    // delete the previous node
                    returnNode.Operand = binOp;
                    node[ndx - 1] = null;
                }
            }
        }
 private static void StripDebugStatements(JsBlock node)
 {
     // walk the list backwards
     for (int ndx = node.Count - 1; ndx >= 0; --ndx)
     {
         // if this item pops positive...
         if (node[ndx].IsDebuggerStatement)
         {
             // just remove it
             JsDetachReferences.Apply(node[ndx]);
             node.RemoveAt(ndx);
         }
     }
 }
        public override void Visit(JsBlock node)
        {
            if (node != null)
            {
                // if this block has a block scope, then look at the lexically-declared names (if any)
                // and throw an error if any are defined as var's within this scope (ES6 rules).
                // if this is the body of a function object, use the function scope.
                JsActivationObject lexicalScope = node.BlockScope;
                if (lexicalScope == null)
                {
                    var functionObject = node.Parent as JsFunctionObject;
                    if (functionObject != null)
                    {
                        lexicalScope = functionObject.FunctionScope;
                    }
                }

                if (lexicalScope != null)
                {
                    foreach (var lexDecl in lexicalScope.LexicallyDeclaredNames)
                    {
                        var varDecl = lexicalScope.VarDeclaredName(lexDecl.Name);
                        if (varDecl != null)
                        {
                            // collision.
                            // if the lexical declaration is a let or const declaration (as opposed to a function declaration),
                            // then force the warning to an error. This is so the function declaration will remain a warning if
                            // it collides with a var.
                            varDecl.NameContext.HandleError(JsError.DuplicateLexicalDeclaration, lexDecl is JsLexicalDeclaration);

                            // mark them both a no-rename to preserve the collision in the output
                            lexDecl.VariableField.IfNotNull(v => v.CanCrunch = false);
                            varDecl.VariableField.IfNotNull(v => v.CanCrunch = false);
                        }
                    }
                }

                // we might things differently if these statements are the body collection for a function
                // because we can assume the implicit return statement at the end of it
                bool isFunctionLevel = (node.Parent is JsFunctionObject);

                // if we want to remove debug statements...
                if (m_parser.Settings.StripDebugStatements && m_parser.Settings.IsModificationAllowed(JsTreeModifications.StripDebugStatements))
                {
                    // do it now before we try doing other things
                    StripDebugStatements(node);
                }

                // analyze all the statements in our block and recurse them
                if (node.BlockScope != null)
                {
                    m_scopeStack.Push(node.BlockScope);
                }
                try
                {
                    // don't call the base class to recurse -- let's walk the block
                    // backwards in case any of the children opt to delete themselves.
                    for (var ndx = node.Count - 1; ndx >= 0; --ndx)
                    {
                        node[ndx].Accept(this);
                    }
                }
                finally
                {
                    if (node.BlockScope != null)
                    {
                        m_scopeStack.Pop();
                    }
                }

                if (m_parser.Settings.RemoveUnneededCode)
                {
                    // go forward, and check the count each iteration because we might be ADDING statements to the block.
                    // let's look at all our if-statements. If a true-clause ends in a return, then we don't
                    // need the else-clause; we can pull its statements out and stick them after the if-statement.
                    // also, if we encounter a return-, break- or continue-statement, we can axe everything after it
                    for (var ndx = 0; ndx < node.Count; ++ndx)
                    {
                        // see if it's an if-statement with both a true and a false block
                        var ifNode = node[ndx] as JsIfNode;
                        if (ifNode != null
                            && ifNode.TrueBlock != null
                            && ifNode.TrueBlock.Count > 0
                            && ifNode.FalseBlock != null)
                        {
                            // now check to see if the true block ends in a return statement
                            if (ifNode.TrueBlock[ifNode.TrueBlock.Count - 1] is JsReturnNode)
                            {
                                // transform: if(cond){statements1;return}else{statements2} to if(cond){statements1;return}statements2
                                // it does. insert all the false-block statements after the if-statement
                                node.InsertRange(ndx + 1, ifNode.FalseBlock.Children);

                                // and then remove the false block altogether
                                ifNode.FalseBlock = null;
                            }
                        }
                        else if (node[ndx] is JsReturnNode
                            || node[ndx] is JsBreak
                            || node[ndx] is JsContinueNode
                            || node[ndx] is JsThrowNode)
                        {
                            // we have an exit node -- no statments afterwards will be executed, so clear them out.
                            // transform: {...;return;...} to {...;return}
                            // transform: {...;break;...} to {...;break}
                            // transform: {...;continue;...} to {...;continue}
                            // transform: {...;throw;...} to {...;throw}
                            // we've found an exit statement, and it's not the last statement in the function.
                            // walk the rest of the statements and delete anything that isn't a function declaration
                            // or a var- or const-statement.
                            for (var ndxRemove = node.Count - 1; ndxRemove > ndx; --ndxRemove)
                            {
                                var funcObject = node[ndxRemove] as JsFunctionObject;
                                if (funcObject == null || funcObject.FunctionType != JsFunctionType.Declaration)
                                {
                                    // if it's a const-statement, leave it.
                                    // we COULD check to see if the constant is referenced anywhere and delete
                                    // any that aren't. Maybe later.
                                    // we also don't want to do like the var-statements and remove the initializers.
                                    // Not sure if any browsers would fail a const WITHOUT an initializer.
                                    if (!(node[ndxRemove] is JsConstStatement))
                                    {
                                        var varStatement = node[ndxRemove] as JsVar;
                                        if (varStatement != null)
                                        {
                                            // var statements can't be removed, but any initializers should
                                            // be deleted since they won't get executed.
                                            for (var ndxDecl = 0; ndxDecl < varStatement.Count; ++ndxDecl)
                                            {
                                                if (varStatement[ndxDecl].Initializer != null)
                                                {
                                                    varStatement[ndxDecl].Initializer = null;
                                                }
                                            }
                                        }
                                        else
                                        {
                                            // not a function declaration, and not a var statement -- get rid of it
                                            JsDetachReferences.Apply(node[ndxRemove]);
                                            node.RemoveAt(ndxRemove);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }

                // now check the last statement -- if it's an if-statement where the true-block is a single return
                // and there is no false block, convert this one statement to a conditional. We might back it out later
                // if we don't combine the conditional with other stuff.
                // but we can only do this if we're at the functional level because of the implied return at the end
                // of that block.
                if (isFunctionLevel && node.Count > 0
                    && m_parser.Settings.IsModificationAllowed(JsTreeModifications.IfConditionReturnToCondition))
                {
                    JsReturnNode returnNode;
                    var ifNode = FindLastStatement(node) as JsIfNode;
                    if (ifNode != null && ifNode.FalseBlock == null
                        && ifNode.TrueBlock.Count == 1
                        && (returnNode = ifNode.TrueBlock[0] as JsReturnNode) != null)
                    {
                        // if the return node doesn't have an operand, then we can just replace the if-statement with its conditional
                        if (returnNode.Operand == null)
                        {
                            // if the condition is a constant, then eliminate it altogether
                            if (ifNode.Condition.IsConstant)
                            {
                                // delete the node altogether. Because the condition is a constant,
                                // there is no else-block, and the if-block only contains a return
                                // with no expression, we don't have anything to detach.
                                node.ReplaceChild(ifNode, null);
                            }
                            else
                            {
                                // transform: {...;if(cond)return;} to {...;cond;}
                                node.ReplaceChild(ifNode, ifNode.Condition);
                            }
                        }
                        else if (returnNode.Operand.IsExpression)
                        {
                            // this is a strategic replacement that might pay off later. And if
                            // it doesn't, we'll eventually back it out after all the other stuff
                            // if applied on top of it.
                            // transform: if(cond)return expr;} to return cond?expr:void 0}
                            var conditional = new JsConditional(null, m_parser)
                                {
                                    Condition = ifNode.Condition,
                                    TrueExpression = returnNode.Operand,
                                    FalseExpression = CreateVoidNode()
                                };

                            // replace the if-statement with the new return node
                            node.ReplaceChild(ifNode, new JsReturnNode(ifNode.Context, m_parser)
                                {
                                    Operand = conditional
                                });
                            Optimize(conditional);
                        }
                    }
                }

                // now walk through and combine adjacent expression statements, and adjacent var-for statements
                // and adjecent expression-return statements
                if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.CombineAdjacentExpressionStatements))
                {
                    CombineExpressions(node);
                }

                // check to see if we want to combine a preceding var with a for-statement
                if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.MoveVarIntoFor))
                {
                    // look at the statements in the block.
                    // walk BACKWARDS down the list because we'll be removing items when we encounter
                    // var statements that can be moved inside a for statement's initializer
                    // we also don't need to check the first one, since there is nothing before it.
                    for (int ndx = node.Count - 1; ndx > 0; --ndx)
                    {
                        // see if the previous statement is a var statement
                        // (we've already combined adjacent var-statements)
                        JsForNode forNode;
                        JsWhileNode whileNode;
                        var previousVar = node[ndx - 1] as JsVar;
                        if (previousVar != null && (forNode = node[ndx] as JsForNode) != null)
                        {
                            // BUT if the var statement has any initializers containing an in-operator, first check
                            // to see if we haven't killed that move before we try moving it. Opera 11 seems to have
                            // an issue with that syntax, even if properly parenthesized.
                            if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.MoveInExpressionsIntoForStatement)
                                || !previousVar.ContainsInOperator)
                            {
                                // and see if the forNode's initializer is empty
                                if (forNode.Initializer != null)
                                {
                                    // not empty -- see if it is a Var node
                                    JsVar varInitializer = forNode.Initializer as JsVar;
                                    if (varInitializer != null)
                                    {
                                        // transform: var decls1;for(var decls2;...) to for(var decls1,decls2;...)
                                        // we want to PREPEND the initializers in the previous var-statement
                                        // to our for-statement's initializer var-statement list
                                        varInitializer.InsertAt(0, previousVar);

                                        // then remove the previous var statement
                                        node.RemoveAt(ndx - 1);
                                        // this will bump the for node up one position in the list, so the next iteration
                                        // will be right back on this node in case there are other var statements we need
                                        // to combine
                                    }
                                    else
                                    {
                                        // we want to see if the initializer expression is a series of one or more
                                        // simple assignments to variables that are in the previous var statement.
                                        // if all the expressions are assignments to variables that are defined in the
                                        // previous var statement, then we can just move the var statement into the
                                        // for statement.
                                        var binaryOp = forNode.Initializer as JsBinaryOperator;
                                        if (binaryOp != null && AreAssignmentsInVar(binaryOp, previousVar))
                                        {
                                            // transform: var decls;for(expr1;...) to for(var decls,expr1;...)
                                            // WHERE expr1 only consists of assignments to variables that are declared
                                            // in that previous var-statement.
                                            // TODO: we *could* also do it is the expr1 assignments are to lookups that are
                                            // defined in THIS scope (not any outer scopes), because it wouldn't hurt to have
                                            // then in a var statement again.
                                            // create a list and fill it with all the var-decls created from the assignment
                                            // operators in the expression
                                            var varDecls = new List<JsVariableDeclaration>();
                                            ConvertAssignmentsToVarDecls(binaryOp, varDecls, m_parser);

                                            // then go through and append each one to the var statement before us
                                            foreach (var varDecl in varDecls)
                                            {
                                                previousVar.Append(varDecl);
                                            }

                                            // move the previous var-statement into our initializer
                                            forNode.Initializer = previousVar;

                                            // and remove the previous var-statement from the list.
                                            node.RemoveAt(ndx - 1);

                                            // this will bump the for node up one position in the list, so the next iteration
                                            // will be right back on this node, but the initializer will not be null
                                        }
                                    }
                                }
                                else
                                {
                                    // transform: var decls;for(;...) to for(var decls;...)
                                    // if it's empty, then we're free to add the previous var statement
                                    // to this for statement's initializer. remove it from it's current
                                    // position and add it as the initializer
                                    node.RemoveAt(ndx - 1);
                                    forNode.Initializer = previousVar;
                                    // this will bump the for node up one position in the list, so the next iteration
                                    // will be right back on this node, but the initializer will not be null
                                }
                            }
                        }
                        else if (previousVar != null
                            && (whileNode = node[ndx] as JsWhileNode) != null
                            && m_parser.Settings.IsModificationAllowed(JsTreeModifications.ChangeWhileToFor))
                        {
                            // transform: var ...;while(cond)... => for(var ...;cond;)...
                            node[ndx] = new JsForNode(null, m_parser)
                                {
                                    Initializer = previousVar,
                                    Condition = whileNode.Condition,
                                    Body = whileNode.Body
                                };
                            node.RemoveAt(ndx - 1);
                        }
                    }
                }

                // see if the last statement is a return statement
                JsReturnNode lastReturn;
                if ((lastReturn = FindLastStatement(node) as JsReturnNode) != null)
                {
                    // set this flag to true if we end up adding an expression to the block.
                    // before exiting, we'll go through and combine adjacent expressions again if this
                    // flag has been set to true.
                    bool changedStatementToExpression = false;

                    // get the index of the statement before the last return
                    // (skip over function decls and importand comments)
                    var indexPrevious = PreviousStatementIndex(node, lastReturn);

                    // just out of curiosity, let's see if we fit a common pattern:
                    //      var name=expr;return name;
                    // or
                    //      const name=expr;return name;
                    // if so, we can cut out the var and simply return the expression
                    JsLookup lookup;
                    if ((lookup = lastReturn.Operand as JsLookup) != null && indexPrevious >= 0)
                    {
                        // use the base class for both the var- and const-statements so we will
                        // pick them both up at the same time
                        var varStatement = node[indexPrevious] as JsDeclaration;
                        if (varStatement != null)
                        {
                            // if the last vardecl in the var statement matches the return lookup, and no
                            // other references exist for this field (refcount == 1)...
                            JsVariableDeclaration varDecl;
                            if ((varDecl = varStatement[varStatement.Count - 1]).Initializer != null
                                && varDecl.IsEquivalentTo(lookup)
                                && varDecl.VariableField.RefCount == 1)
                            {
                                // clean up the field's references because we're removing both the lookup reference
                                // in the return statement and the vardecl.
                                varDecl.VariableField.References.Remove(lookup);
                                varDecl.VariableField.Declarations.Remove(varDecl);

                                if (varStatement.Count == 1)
                                {
                                    // transform: ...;var name=expr;return name} to ...;return expr}
                                    // there's only one vardecl in the var, so get rid of the entire statement
                                    lastReturn.Operand = varDecl.Initializer;
                                    node.RemoveAt(indexPrevious);
                                }
                                else
                                {
                                    // multiple vardecls are in the statement; we only need to get rid of the last one
                                    lastReturn.Operand = varDecl.Initializer;
                                    varStatement[varStatement.Count - 1] = null;
                                }
                            }
                        }
                    }

                    // check to see if we can combine the return statement with a previous if-statement
                    // into a simple return-conditional. The true statement needs to have no false block,
                    // and only one statement in the true block.
                    JsConditional conditional;
                    JsIfNode previousIf;
                    while (indexPrevious >= 0
                        && lastReturn != null
                        && (previousIf = node[indexPrevious] as JsIfNode) != null
                        && previousIf.TrueBlock != null && previousIf.TrueBlock.Count == 1
                        && previousIf.FalseBlock == null)
                    {
                        // assume no change is made for this loop
                        bool somethingChanged = false;

                        // and that one true-block statement needs to be a return statement
                        var previousReturn = previousIf.TrueBlock[0] as JsReturnNode;
                        if (previousReturn != null)
                        {
                            if (lastReturn.Operand == null)
                            {
                                if (previousReturn.Operand == null)
                                {
                                    // IF we are at the function level, then the block ends in an implicit return (undefined)
                                    // and we can change this if to just the condition. If we aren't at the function level,
                                    // then we have to leave the return, but we can replace the if with just the condition.
                                    if (!isFunctionLevel)
                                    {
                                        // not at the function level, so the return must stay.
                                        if (previousIf.Condition.IsConstant)
                                        {
                                            // transform: if(cond)return;return} to return}
                                            node.RemoveAt(indexPrevious);
                                            somethingChanged = true;
                                        }
                                        else
                                        {
                                            // transform: if(cond)return;return} to cond;return}
                                            node[indexPrevious] = previousIf.Condition;
                                        }
                                    }
                                    else if (previousIf.Condition.IsConstant)
                                    {
                                        // transform: remove if(cond)return;return} because cond is a constant
                                        node.ReplaceChild(lastReturn, null);
                                        node.RemoveAt(indexPrevious);
                                        somethingChanged = true;
                                    }
                                    else
                                    {
                                        // transform: if(cond)return;return} to cond}
                                        // replace the final return with just the condition, then remove the previous if
                                        if (node.ReplaceChild(lastReturn, previousIf.Condition))
                                        {
                                            node.RemoveAt(indexPrevious);
                                            somethingChanged = true;
                                        }
                                    }
                                }
                                else
                                {
                                    // transform: if(cond)return expr;return} to return cond?expr:void 0
                                    conditional = new JsConditional(null, m_parser)
                                        {
                                            Condition = previousIf.Condition,
                                            TrueExpression = previousReturn.Operand,
                                            FalseExpression = CreateVoidNode()
                                        };

                                    // replace the final return with the new return, then delete the previous if-statement
                                    if (node.ReplaceChild(lastReturn, new JsReturnNode(null, m_parser)
                                        {
                                            Operand = conditional
                                        }))
                                    {
                                        node.RemoveAt(indexPrevious);
                                        Optimize(conditional);
                                        somethingChanged = true;
                                    }
                                }
                            }
                            else
                            {
                                if (previousReturn.Operand == null)
                                {
                                    // transform: if(cond)return;return expr} to return cond?void 0:expr
                                    conditional = new JsConditional(null, m_parser)
                                        {
                                            Condition = previousIf.Condition,
                                            TrueExpression = CreateVoidNode(),
                                            FalseExpression = lastReturn.Operand
                                        };

                                    // replace the final return with the new return, then delete the previous if-statement
                                    if (node.ReplaceChild(lastReturn, new JsReturnNode(null, m_parser)
                                        {
                                            Operand = conditional
                                        }))
                                    {
                                        node.RemoveAt(indexPrevious);
                                        Optimize(conditional);
                                        somethingChanged = true;
                                    }
                                }
                                else if (previousReturn.Operand.IsEquivalentTo(lastReturn.Operand))
                                {
                                    if (previousIf.Condition.IsConstant)
                                    {
                                        // the condition is constant, and the returns return the same thing.
                                        // get rid of the if statement altogether.
                                        // transform: if(cond)return expr;return expr} to return expr}
                                        JsDetachReferences.Apply(previousReturn.Operand);
                                        node.RemoveAt(indexPrevious);
                                        somethingChanged = true;
                                    }
                                    else
                                    {
                                        // transform: if(cond)return expr;return expr} to return cond,expr}
                                        // create a new binary op with the condition and the final-return operand,
                                        // replace the operand on the final-return with the new binary operator,
                                        // and then delete the previous if-statement
                                        JsDetachReferences.Apply(previousReturn.Operand);
                                        lastReturn.Operand = JsCommaOperator.CombineWithComma(null, m_parser, previousIf.Condition, lastReturn.Operand);
                                        node.RemoveAt(indexPrevious);
                                        somethingChanged = true;
                                    }
                                }
                                else
                                {
                                    // transform: if(cond)return expr1;return expr2} to return cond?expr1:expr2}
                                    // create a new conditional with the condition and the return operands,
                                    // replace the operand on the final-return with the new conditional operator,
                                    // and then delete the previous if-statement
                                    // transform: if(cond)return expr1;return expr2} to return cond?expr1:expr2}
                                    conditional = new JsConditional(null, m_parser)
                                        {
                                            Condition = previousIf.Condition,
                                            TrueExpression = previousReturn.Operand,
                                            FalseExpression = lastReturn.Operand
                                        };

                                    // replace the operand on the final-return with the new conditional operator,
                                    // and then delete the previous if-statement
                                    lastReturn.Operand = conditional;
                                    node.RemoveAt(indexPrevious);
                                    Optimize(conditional);
                                    somethingChanged = true;
                                }
                            }
                        }

                        if (!somethingChanged)
                        {
                            // nothing changed -- break out of the loop
                            break;
                        }
                        else
                        {
                            // set the flag that indicates something changed in at least one of these loops
                            changedStatementToExpression = true;

                            // and since we changed something, we need to bump the index down one
                            // AFTER we grab the last return node (which has slipped into the same position
                            // as the previous node)
                            lastReturn = node[indexPrevious--] as JsReturnNode;
                        }
                    }

                    // if we added any more expressions since we ran our expression-combination logic,
                    // run it again.
                    if (changedStatementToExpression
                        && m_parser.Settings.IsModificationAllowed(JsTreeModifications.CombineAdjacentExpressionStatements))
                    {
                        CombineExpressions(node);
                    }

                    // and FINALLY, we want to see if what we did previously didn't pan out and we end
                    // in something like return cond?expr:void 0, in which case we want to change it
                    // back to a simple if(condition)return expr; (saves four bytes).
                    // see if the last statement is a return statement that returns a conditional
                    if (lastReturn != null
                        && (conditional = lastReturn.Operand as JsConditional) != null)
                    {
                        var unaryOperator = conditional.FalseExpression as JsUnaryOperator;
                        if (unaryOperator != null
                            && unaryOperator.OperatorToken == JsToken.Void
                            && unaryOperator.Operand is JsConstantWrapper)
                        {
                            unaryOperator = conditional.TrueExpression as JsUnaryOperator;
                            if (unaryOperator != null && unaryOperator.OperatorToken == JsToken.Void)
                            {
                                if (isFunctionLevel)
                                {
                                    // transform: ...;return cond?void 0:void 0} to ...;cond}
                                    // function level ends in an implicit "return void 0"
                                    node.ReplaceChild(lastReturn, conditional.Condition);
                                }
                                else
                                {
                                    // transform: ...;return cond?void 0:void 0} to ...;cond;return}
                                    // non-function level doesn't end in an implicit return,
                                    // so we need to break them out into two statements
                                    node.ReplaceChild(lastReturn, conditional.Condition);
                                    node.Append(new JsReturnNode(null, m_parser));
                                }
                            }
                            else if (isFunctionLevel)
                            {
                                // transform: ...;return cond?expr:void 0} to ...;if(cond)return expr}
                                // (only works at the function-level because of the implicit return statement)
                                var ifNode = new JsIfNode(lastReturn.Context, m_parser)
                                    {
                                        Condition = conditional.Condition,
                                        TrueBlock = JsAstNode.ForceToBlock(new JsReturnNode(null, m_parser)
                                            {
                                                Operand = conditional.TrueExpression
                                            })
                                    };
                                node.ReplaceChild(lastReturn, ifNode);
                            }
                        }
                        else if (isFunctionLevel)
                        {
                            unaryOperator = conditional.TrueExpression as JsUnaryOperator;
                            if (unaryOperator != null
                                && unaryOperator.OperatorToken == JsToken.Void
                                && unaryOperator.Operand is JsConstantWrapper)
                            {
                                // transform: ...;return cond?void 0;expr} to ...;if(!cond)return expr}
                                // (only works at the function level because of the implicit return)
                                // get the logical-not of the conditional
                                var logicalNot = new JsLogicalNot(conditional.Condition, m_parser);
                                logicalNot.Apply();

                                // create a new if-node based on the condition, with the branches swapped
                                // (true-expression goes to false-branch, false-expression goes to true-branch
                                var ifNode = new JsIfNode(lastReturn.Context, m_parser)
                                    {
                                        Condition = conditional.Condition,
                                        TrueBlock = JsAstNode.ForceToBlock(new JsReturnNode(null, m_parser)
                                            {
                                                Operand = conditional.FalseExpression
                                            })
                                    };
                                node.ReplaceChild(lastReturn, ifNode);
                            }
                        }
                    }
                }

                if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.CombineEquivalentIfReturns))
                {
                    // walk backwards looking for if(cond1)return expr1;if(cond2)return expr2;
                    // (backwards, because we'll be combining those into one statement, reducing the number of statements.
                    // don't go all the way to zero, because each loop will compare the statement to the PREVIOUS
                    // statement, and the first statement (index==0) has no previous statement.
                    for (var ndx = node.Count - 1; ndx > 0; --ndx)
                    {
                        // see if the current statement is an if-statement with no else block, and a true
                        // block that contains a single return-statement WITH an expression.
                        JsAstNode currentExpr = null;
                        JsAstNode condition2;
                        if (IsIfReturnExpr(node[ndx], out condition2, ref currentExpr) != null)
                        {
                            // see if the previous statement is also the same pattern, but with
                            // the equivalent expression as its return operand
                            JsAstNode condition1;
                            var matchedExpression = currentExpr;
                            var ifNode = IsIfReturnExpr(node[ndx - 1], out condition1, ref matchedExpression);
                            if (ifNode != null)
                            {
                                // it is a match!
                                // let's combine them -- we'll add the current condition to the
                                // previous condition with a logical-or and delete the current statement.
                                // transform: if(cond1)return expr;if(cond2)return expr; to if(cond1||cond2)return expr;
                                ifNode.Condition = new JsBinaryOperator(null, m_parser)
                                    {
                                        Operand1 = condition1,
                                        Operand2 = condition2,
                                        OperatorToken = JsToken.LogicalOr,
                                        TerminatingContext = ifNode.TerminatingContext ?? node.TerminatingContext
                                    };
                                JsDetachReferences.Apply(currentExpr);
                                node.RemoveAt(ndx);
                            }
                        }
                    }
                }

                if (isFunctionLevel
                    && m_parser.Settings.IsModificationAllowed(JsTreeModifications.InvertIfReturn))
                {
                    // walk backwards looking for if (cond) return; whenever we encounter that statement,
                    // we can change it to if (!cond) and put all subsequent statements in the block inside the
                    // if's true-block.
                    for (var ndx = node.Count - 1; ndx >= 0; --ndx)
                    {
                        var ifNode = node[ndx] as JsIfNode;
                        if (ifNode != null
                            && ifNode.FalseBlock == null
                            && ifNode.TrueBlock != null
                            && ifNode.TrueBlock.Count == 1)
                        {
                            var returnNode = ifNode.TrueBlock[0] as JsReturnNode;
                            if (returnNode != null && returnNode.Operand == null)
                            {
                                // we have if(cond)return;
                                // logical-not the condition, remove the return statement,
                                // and move all subsequent sibling statements inside the if-statement.
                                JsLogicalNot.Apply(ifNode.Condition, m_parser);
                                ifNode.TrueBlock.Clear();

                                var ndxMove = ndx + 1;
                                if (node.Count == ndxMove + 1)
                                {
                                    // there's only one statement after our if-node.
                                    // see if it's ALSO an if-node with no else block.
                                    var secondIfNode = node[ndxMove] as JsIfNode;
                                    if (secondIfNode != null && (secondIfNode.FalseBlock == null || secondIfNode.FalseBlock.Count == 0))
                                    {
                                        // it is!
                                        // transform: if(cond1)return;if(cond2){...} => if(!cond1&&cond2){...}
                                        // (the cond1 is already inverted at this point)
                                        // combine cond2 with cond1 via a logical-and,
                                        // move all secondIf statements inside the if-node,
                                        // remove the secondIf node.
                                        node.RemoveAt(ndxMove);
                                        ifNode.Condition = new JsBinaryOperator(null, m_parser)
                                            {
                                                Operand1 = ifNode.Condition,
                                                Operand2 = secondIfNode.Condition,
                                                OperatorToken = JsToken.LogicalAnd
                                            };

                                        ifNode.TrueBlock = secondIfNode.TrueBlock;
                                    }
                                    else if (node[ndxMove].IsExpression
                                        && m_parser.Settings.IsModificationAllowed(JsTreeModifications.IfConditionCallToConditionAndCall))
                                    {
                                        // now we have if(cond)expr; optimize that!
                                        var expression = node[ndxMove];
                                        node.RemoveAt(ndxMove);
                                        IfConditionExpressionToExpression(ifNode, expression);
                                    }
                                }

                                // just move all the following statements inside the if-statement
                                while (node.Count > ndxMove)
                                {
                                    var movedNode = node[ndxMove];
                                    node.RemoveAt(ndxMove);
                                    ifNode.TrueBlock.Append(movedNode);
                                }
                            }
                        }
                    }
                }
                else
                {
                    var isIteratorBlock = node.Parent is JsForNode
                        || node.Parent is JsForIn
                        || node.Parent is JsWhileNode
                        || node.Parent is JsDoWhile;

                    if (isIteratorBlock
                        && m_parser.Settings.IsModificationAllowed(JsTreeModifications.InvertIfContinue))
                    {
                        // walk backwards looking for if (cond) continue; whenever we encounter that statement,
                        // we can change it to if (!cond) and put all subsequent statements in the block inside the
                        // if's true-block.
                        for (var ndx = node.Count - 1; ndx >= 0; --ndx)
                        {
                            var ifNode = node[ndx] as JsIfNode;
                            if (ifNode != null
                                && ifNode.FalseBlock == null
                                && ifNode.TrueBlock != null
                                && ifNode.TrueBlock.Count == 1)
                            {
                                var continueNode = ifNode.TrueBlock[0] as JsContinueNode;

                                // if there's no label, then we're good. Otherwise we can only make this optimization
                                // if the label refers to the parent iterator node.
                                if (continueNode != null
                                    && (string.IsNullOrEmpty(continueNode.Label) || (LabelMatchesParent(continueNode.Label, node.Parent))))
                                {
                                    // if this is the last statement, then we don't really need the if at all
                                    // and can just replace it with its condition
                                    if (ndx < node.Count - 1)
                                    {
                                        // we have if(cond)continue;st1;...stn;
                                        // logical-not the condition, remove the continue statement,
                                        // and move all subsequent sibling statements inside the if-statement.
                                        JsLogicalNot.Apply(ifNode.Condition, m_parser);
                                        ifNode.TrueBlock.Clear();

                                        // TODO: if we removed a labeled continue, do we need to fix up some label references?

                                        var ndxMove = ndx + 1;
                                        if (node.Count == ndxMove + 1)
                                        {
                                            // there's only one statement after our if-node.
                                            // see if it's ALSO an if-node with no else block.
                                            var secondIfNode = node[ndxMove] as JsIfNode;
                                            if (secondIfNode != null && (secondIfNode.FalseBlock == null || secondIfNode.FalseBlock.Count == 0))
                                            {
                                                // it is!
                                                // transform: if(cond1)continue;if(cond2){...} => if(!cond1&&cond2){...}
                                                // (the cond1 is already inverted at this point)
                                                // combine cond2 with cond1 via a logical-and,
                                                // move all secondIf statements inside the if-node,
                                                // remove the secondIf node.
                                                ifNode.Condition = new JsBinaryOperator(null, m_parser)
                                                    {
                                                        Operand1 = ifNode.Condition,
                                                        Operand2 = secondIfNode.Condition,
                                                        OperatorToken = JsToken.LogicalAnd
                                                    };

                                                ifNode.TrueBlock = secondIfNode.TrueBlock;
                                                node.RemoveAt(ndxMove);
                                            }
                                            else if (node[ndxMove].IsExpression
                                                && m_parser.Settings.IsModificationAllowed(JsTreeModifications.IfConditionCallToConditionAndCall))
                                            {
                                                // now we have if(cond)expr; optimize that!
                                                var expression = node[ndxMove];
                                                node.RemoveAt(ndxMove);
                                                IfConditionExpressionToExpression(ifNode, expression);
                                            }
                                        }

                                        // just move all the following statements inside the if-statement
                                        while (node.Count > ndxMove)
                                        {
                                            var movedNode = node[ndxMove];
                                            node.RemoveAt(ndxMove);
                                            ifNode.TrueBlock.Append(movedNode);
                                        }
                                    }
                                    else
                                    {
                                        // we have if(cond)continue} -- nothing after the if.
                                        // the loop is going to continue anyway, so replace the if-statement
                                        // with the condition and be done
                                        if (ifNode.Condition.IsConstant)
                                        {
                                            // consition is constant -- get rid of the if-statement altogether
                                            node.RemoveAt(ndx);
                                        }
                                        else
                                        {
                                            // condition isn't constant
                                            node[ndx] = ifNode.Condition;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }