示例#1
0
        public static JsBlock ForceToBlock(JsAstNode node)
        {
            // if the node is null or already a block, then we're
            // good to go -- just return it.
            var block = node as JsBlock;

            if (block == null && node != null)
            {
                // it's not a block, so create a new block, append the astnode
                // and return the block
                block = new JsBlock(node.Context.Clone(), node.Parser);
                block.Append(node);
            }

            return(block);
        }
示例#2
0
        private void AppendImportantComments(JsBlock block)
        {
            if (block != null)
            {
                // make sure any important comments before the closing brace are kept
                if (m_importantComments.Count > 0
                    && m_settings.PreserveImportantComments
                    && m_settings.IsModificationAllowed(JsTreeModifications.PreserveImportantComments))
                {
                    // we have important comments before the EOF. Add the comment(s) to the program.
                    foreach (var importantComment in m_importantComments)
                    {
                        block.Append(new JsImportantComment(importantComment, this));
                    }

                    m_importantComments.Clear();
                }
            }
        }
示例#3
0
        //---------------------------------------------------------------------------------------
        // ParseWithStatement
        //
        //  WithStatement :
        //    'with' '(' Expression ')' Statement
        //---------------------------------------------------------------------------------------
        private JsWithNode ParseWithStatement()
        {
            JsContext withCtx = m_currentToken.Clone();
            JsAstNode obj = null;
            JsBlock block = null;
            m_blockType.Add(BlockType.Block);
            try
            {
                GetNextToken();
                if (JsToken.LeftParenthesis != m_currentToken.Token)
                    ReportError(JsError.NoLeftParenthesis);
                GetNextToken();
                m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
                try
                {
                    obj = ParseExpression();
                    if (JsToken.RightParenthesis != m_currentToken.Token)
                    {
                        withCtx.UpdateWith(obj.Context);
                        ReportError(JsError.NoRightParenthesis);
                    }
                    else
                        withCtx.UpdateWith(m_currentToken);
                    GetNextToken();
                }
                catch (RecoveryTokenException exc)
                {
                    if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) == -1)
                    {
                        // give up
                        exc._partiallyComputedNode = null;
                        throw;
                    }
                    else
                    {
                        if (exc._partiallyComputedNode == null)
                            obj = new JsConstantWrapper(true, JsPrimitiveType.Boolean, CurrentPositionContext(), this);
                        else
                            obj = exc._partiallyComputedNode;
                        withCtx.UpdateWith(obj.Context);

                        if (exc._token == JsToken.RightParenthesis)
                            GetNextToken();
                    }
                }
                finally
                {
                    m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
                }

                // if the statements aren't withing curly-braces, throw a possible error
                if (JsToken.LeftCurly != m_currentToken.Token)
                {
                    ReportError(JsError.StatementBlockExpected, CurrentPositionContext(), true);
                }

                try
                {
                    // parse a Statement, not a SourceElement
                    // and ignore any important comments that spring up right here.
                    JsAstNode statement = ParseStatement(false, true);

                    // but make sure we save it as a block
                    block = statement as JsBlock;
                    if (block == null)
                    {
                        block = new JsBlock(statement.Context, this);
                        block.Append(statement);
                    }
                }
                catch (RecoveryTokenException exc)
                {
                    if (exc._partiallyComputedNode == null)
                    {
                        block = new JsBlock(CurrentPositionContext(), this);
                    }
                    else
                    {
                        block = exc._partiallyComputedNode as JsBlock;
                        if (block == null)
                        {
                            block = new JsBlock(exc._partiallyComputedNode.Context, this);
                            block.Append(exc._partiallyComputedNode);
                        }
                    }
                    exc._partiallyComputedNode = new JsWithNode(withCtx, this)
                        {
                            WithObject = obj,
                            Body = block
                        };
                    throw;
                }
            }
            finally
            {
                m_blockType.RemoveAt(m_blockType.Count - 1);
            }

            return new JsWithNode(withCtx, this)
                {
                    WithObject = obj,
                    Body = block
                };
        }
示例#4
0
        private JsAstNode ParseSwitchStatement()
        {
            JsContext switchCtx = m_currentToken.Clone();
            JsAstNode expr = null;
            JsAstNodeList cases = null;
            var braceOnNewLine = false;
            JsContext braceContext = null;
            m_blockType.Add(BlockType.Switch);
            try
            {
                // read switch(expr)
                GetNextToken();
                if (JsToken.LeftParenthesis != m_currentToken.Token)
                    ReportError(JsError.NoLeftParenthesis);
                GetNextToken();
                m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
                m_noSkipTokenSet.Add(NoSkipTokenSet.s_SwitchNoSkipTokenSet);
                try
                {
                    expr = ParseExpression();

                    if (JsToken.RightParenthesis != m_currentToken.Token)
                    {
                        ReportError(JsError.NoRightParenthesis);
                    }

                    GetNextToken();
                    if (JsToken.LeftCurly != m_currentToken.Token)
                    {
                        ReportError(JsError.NoLeftCurly);
                    }

                    braceOnNewLine = m_foundEndOfLine;
                    braceContext = m_currentToken.Clone();
                    GetNextToken();

                }
                catch (RecoveryTokenException exc)
                {
                    if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) == -1
                          && IndexOfToken(NoSkipTokenSet.s_SwitchNoSkipTokenSet, exc) == -1)
                    {
                        // give up
                        exc._partiallyComputedNode = null;
                        throw;
                    }
                    else
                    {
                        if (exc._partiallyComputedNode == null)
                            expr = new JsConstantWrapper(true, JsPrimitiveType.Boolean, CurrentPositionContext(), this);
                        else
                            expr = exc._partiallyComputedNode;

                        if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) != -1)
                        {
                            if (exc._token == JsToken.RightParenthesis)
                                GetNextToken();

                            if (JsToken.LeftCurly != m_currentToken.Token)
                            {
                                ReportError(JsError.NoLeftCurly);
                            }
                            braceOnNewLine = m_foundEndOfLine;
                            braceContext = m_currentToken.Clone();
                            GetNextToken();
                        }

                    }
                }
                finally
                {
                    m_noSkipTokenSet.Remove(NoSkipTokenSet.s_SwitchNoSkipTokenSet);
                    m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
                }

                // parse the switch body
                cases = new JsAstNodeList(CurrentPositionContext(), this);
                bool defaultStatement = false;
                m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockNoSkipTokenSet);
                try
                {
                    while (JsToken.RightCurly != m_currentToken.Token)
                    {
                        JsSwitchCase caseClause = null;
                        JsAstNode caseValue = null;
                        var caseCtx = m_currentToken.Clone();
                        JsContext colonContext = null;
                        m_noSkipTokenSet.Add(NoSkipTokenSet.s_CaseNoSkipTokenSet);
                        try
                        {
                            if (JsToken.Case == m_currentToken.Token)
                            {
                                // get the case
                                GetNextToken();
                                caseValue = ParseExpression();
                            }
                            else if (JsToken.Default == m_currentToken.Token)
                            {
                                // get the default
                                if (defaultStatement)
                                {
                                    // we report an error but we still accept the default
                                    ReportError(JsError.DupDefault, true);
                                }
                                else
                                {
                                    defaultStatement = true;
                                }
                                GetNextToken();
                            }
                            else
                            {
                                // This is an error, there is no case or default. Assume a default was missing and keep going
                                defaultStatement = true;
                                ReportError(JsError.BadSwitch);
                            }

                            if (JsToken.Colon != m_currentToken.Token)
                            {
                                ReportError(JsError.NoColon);
                            }
                            else
                            {
                                colonContext = m_currentToken.Clone();
                            }

                            // read the statements inside the case or default
                            GetNextToken();
                        }
                        catch (RecoveryTokenException exc)
                        {
                            // right now we can only get here for the 'case' statement
                            if (IndexOfToken(NoSkipTokenSet.s_CaseNoSkipTokenSet, exc) == -1)
                            {
                                // ignore the current case or default
                                exc._partiallyComputedNode = null;
                                throw;
                            }
                            else
                            {
                                caseValue = exc._partiallyComputedNode;

                                if (exc._token == JsToken.Colon)
                                {
                                    GetNextToken();
                                }
                            }
                        }
                        finally
                        {
                            m_noSkipTokenSet.Remove(NoSkipTokenSet.s_CaseNoSkipTokenSet);
                        }

                        m_blockType.Add(BlockType.Block);
                        try
                        {
                            var statements = new JsBlock(m_currentToken.Clone(), this);
                            m_noSkipTokenSet.Add(NoSkipTokenSet.s_SwitchNoSkipTokenSet);
                            m_noSkipTokenSet.Add(NoSkipTokenSet.s_StartStatementNoSkipTokenSet);
                            try
                            {
                                while (JsToken.RightCurly != m_currentToken.Token && JsToken.Case != m_currentToken.Token && JsToken.Default != m_currentToken.Token)
                                {
                                    try
                                    {
                                        // parse a Statement, not a SourceElement
                                        statements.Append(ParseStatement(false));
                                    }
                                    catch (RecoveryTokenException exc)
                                    {
                                        if (exc._partiallyComputedNode != null)
                                        {
                                            statements.Append(exc._partiallyComputedNode);
                                            exc._partiallyComputedNode = null;
                                        }

                                        if (IndexOfToken(NoSkipTokenSet.s_StartStatementNoSkipTokenSet, exc) == -1)
                                        {
                                            throw;
                                        }
                                    }
                                }
                            }
                            catch (RecoveryTokenException exc)
                            {
                                if (IndexOfToken(NoSkipTokenSet.s_SwitchNoSkipTokenSet, exc) == -1)
                                {
                                    caseClause = new JsSwitchCase(caseCtx, this)
                                        {
                                            CaseValue = caseValue,
                                            ColonContext = colonContext,
                                            Statements = statements
                                        };
                                    cases.Append(caseClause);
                                    throw;
                                }
                            }
                            finally
                            {
                                m_noSkipTokenSet.Remove(NoSkipTokenSet.s_StartStatementNoSkipTokenSet);
                                m_noSkipTokenSet.Remove(NoSkipTokenSet.s_SwitchNoSkipTokenSet);
                            }

                            caseCtx.UpdateWith(statements.Context);
                            caseClause = new JsSwitchCase(caseCtx, this)
                                {
                                    CaseValue = caseValue,
                                    ColonContext = colonContext,
                                    Statements = statements
                                };
                            cases.Append(caseClause);
                        }
                        finally
                        {
                            m_blockType.RemoveAt(m_blockType.Count - 1);
                        }
                    }
                }
                catch (RecoveryTokenException exc)
                {
                    if (IndexOfToken(NoSkipTokenSet.s_BlockNoSkipTokenSet, exc) == -1)
                    {
                        //save what you can a rethrow
                        switchCtx.UpdateWith(CurrentPositionContext());
                        exc._partiallyComputedNode = new JsSwitch(switchCtx, this)
                            {
                                Expression = expr,
                                BraceContext = braceContext,
                                Cases = cases,
                                BraceOnNewLine = braceOnNewLine
                            };
                        throw;
                    }
                }
                finally
                {
                    m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockNoSkipTokenSet);
                }
                switchCtx.UpdateWith(m_currentToken);
                GetNextToken();
            }
            finally
            {
                m_blockType.RemoveAt(m_blockType.Count - 1);
            }

            return new JsSwitch(switchCtx, this)
                {
                    Expression = expr,
                    BraceContext = braceContext,
                    Cases = cases,
                    BraceOnNewLine = braceOnNewLine
                };
        }
示例#5
0
        //---------------------------------------------------------------------------------------
        // ParseTryStatement
        //
        //  TryStatement :
        //    'try' Block Catch Finally
        //
        //  Catch :
        //    <empty> | 'catch' '(' Identifier ')' Block
        //
        //  Finally :
        //    <empty> |
        //    'finally' Block
        //---------------------------------------------------------------------------------------
        private JsAstNode ParseTryStatement()
        {
            JsContext tryCtx = m_currentToken.Clone();
            JsContext catchContext = null;
            JsContext finallyContext = null;
            JsBlock body = null;
            JsContext idContext = null;
            JsBlock handler = null;
            JsBlock finally_block = null;
            RecoveryTokenException excInFinally = null;
            m_blockType.Add(BlockType.Block);
            try
            {
                bool catchOrFinally = false;
                GetNextToken();
                if (JsToken.LeftCurly != m_currentToken.Token)
                {
                    ReportError(JsError.NoLeftCurly);
                }
                m_noSkipTokenSet.Add(NoSkipTokenSet.s_NoTrySkipTokenSet);
                try
                {
                    body = ParseBlock();
                }
                catch (RecoveryTokenException exc)
                {
                    if (IndexOfToken(NoSkipTokenSet.s_NoTrySkipTokenSet, exc) == -1)
                    {
                        // do nothing and just return the containing block, if any
                        throw;
                    }
                    else
                    {
                        body = exc._partiallyComputedNode as JsBlock;
                        if (body == null)
                        {
                            body = new JsBlock(exc._partiallyComputedNode.Context, this);
                            body.Append(exc._partiallyComputedNode);
                        }
                    }
                }
                finally
                {
                    m_noSkipTokenSet.Remove(NoSkipTokenSet.s_NoTrySkipTokenSet);
                }
                if (JsToken.Catch == m_currentToken.Token)
                {
                    m_noSkipTokenSet.Add(NoSkipTokenSet.s_NoTrySkipTokenSet);
                    try
                    {
                        catchOrFinally = true;
                        catchContext = m_currentToken.Clone();
                        GetNextToken();
                        if (JsToken.LeftParenthesis != m_currentToken.Token)
                        {
                            ReportError(JsError.NoLeftParenthesis);
                        }

                        GetNextToken();
                        if (JsToken.Identifier != m_currentToken.Token)
                        {
                            string identifier = JsKeyword.CanBeIdentifier(m_currentToken.Token);
                            if (null != identifier)
                            {
                                idContext = m_currentToken.Clone();
                            }
                            else
                            {
                                ReportError(JsError.NoIdentifier);
                            }
                        }
                        else
                        {
                            idContext = m_currentToken.Clone();
                        }

                        GetNextToken();
                        m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
                        try
                        {
                            if (JsToken.RightParenthesis != m_currentToken.Token)
                            {
                                ReportError(JsError.NoRightParenthesis);
                            }
                            GetNextToken();
                        }
                        catch (RecoveryTokenException exc)
                        {
                            if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) == -1)
                            {
                                exc._partiallyComputedNode = null;
                                // rethrow
                                throw;
                            }
                            else
                            {
                                if (m_currentToken.Token == JsToken.RightParenthesis)
                                {
                                    GetNextToken();
                                }
                            }
                        }
                        finally
                        {
                            m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
                        }

                        if (JsToken.LeftCurly != m_currentToken.Token)
                        {
                            ReportError(JsError.NoLeftCurly);
                        }

                        // parse the block
                        handler = ParseBlock();

                        tryCtx.UpdateWith(handler.Context);
                    }
                    catch (RecoveryTokenException exc)
                    {
                        if (exc._partiallyComputedNode == null)
                        {
                            handler = new JsBlock(CurrentPositionContext(), this);
                        }
                        else
                        {
                            handler = exc._partiallyComputedNode as JsBlock;
                            if (handler == null)
                            {
                                handler = new JsBlock(exc._partiallyComputedNode.Context, this);
                                handler.Append(exc._partiallyComputedNode);
                            }
                        }
                        if (IndexOfToken(NoSkipTokenSet.s_NoTrySkipTokenSet, exc) == -1)
                        {
                            throw;
                        }
                    }
                    finally
                    {
                        m_noSkipTokenSet.Remove(NoSkipTokenSet.s_NoTrySkipTokenSet);
                    }
                }

                try
                {
                    if (JsToken.Finally == m_currentToken.Token)
                    {
                        finallyContext = m_currentToken.Clone();
                        GetNextToken();
                        m_blockType.Add(BlockType.Finally);
                        try
                        {
                            finally_block = ParseBlock();
                            catchOrFinally = true;
                        }
                        finally
                        {
                            m_blockType.RemoveAt(m_blockType.Count - 1);
                        }
                        tryCtx.UpdateWith(finally_block.Context);
                    }
                }
                catch (RecoveryTokenException exc)
                {
                    excInFinally = exc; // thrown later so we can execute code below
                }

                if (!catchOrFinally)
                {
                    ReportError(JsError.NoCatch, true);
                    finally_block = new JsBlock(CurrentPositionContext(), this); // make a dummy empty block
                }
            }
            finally
            {
                m_blockType.RemoveAt(m_blockType.Count - 1);
            }

            JsParameterDeclaration catchParameter = null;
            if (idContext != null)
            {
                catchParameter = new JsParameterDeclaration(idContext, this)
                    {
                        Name = idContext.Code
                    };
            }

            if (excInFinally != null)
            {
                excInFinally._partiallyComputedNode = new JsTryNode(tryCtx, this)
                    {
                        TryBlock = body,
                        CatchContext = catchContext,
                        CatchParameter = catchParameter,
                        CatchBlock = handler,
                        FinallyContext = finallyContext,
                        FinallyBlock = finally_block
                    };
                throw excInFinally;
            }
            return new JsTryNode(tryCtx, this)
                {
                    TryBlock = body,
                    CatchContext = catchContext,
                    CatchParameter = catchParameter,
                    CatchBlock = handler,
                    FinallyContext = finallyContext,
                    FinallyBlock = finally_block
                };
        }
示例#6
0
        //---------------------------------------------------------------------------------------
        // ParseStatements
        //
        // statements :
        //   <empty> |
        //   statement statements
        //
        //---------------------------------------------------------------------------------------
        private JsBlock ParseStatements()
        {
            var program = new JsBlock(CurrentPositionContext(), this);
            m_blockType.Add(BlockType.Block);
            m_useCurrentForNext = false;
            try
            {
                // get the first token
                GetNextToken();

                // if the block doesn't have a proper file context, then let's set it from the
                // first token -- that token might have had a ///#source directive!
                if (string.IsNullOrEmpty(program.Context.Document.FileContext))
                {
                    program.Context.Document.FileContext = m_currentToken.Document.FileContext;
                }

                m_noSkipTokenSet.Add(NoSkipTokenSet.s_StartStatementNoSkipTokenSet);
                m_noSkipTokenSet.Add(NoSkipTokenSet.s_TopLevelNoSkipTokenSet);

                try
                {
                    var possibleDirectivePrologue = true;
                    int lastEndPosition = m_currentToken.EndPosition;
                    while (m_currentToken.Token != JsToken.EndOfFile)
                    {
                        JsAstNode ast = null;
                        try
                        {
                            // parse a statement -- pass true because we really want a SourceElement,
                            // which is a Statement OR a FunctionDeclaration. Technically, FunctionDeclarations
                            // are not statements!
                            ast = ParseStatement(true);

                            // if we are still possibly looking for directive prologues
                            if (possibleDirectivePrologue)
                            {
                                var constantWrapper = ast as JsConstantWrapper;
                                if (constantWrapper != null && constantWrapper.PrimitiveType == JsPrimitiveType.String)
                                {
                                    if (!(constantWrapper is JsDirectivePrologue))
                                    {
                                        // use a directive prologue node instead
                                        ast = new JsDirectivePrologue(constantWrapper.Value.ToString(), ast.Context, ast.Parser)
                                            {
                                                MayHaveIssues = constantWrapper.MayHaveIssues
                                            };
                                    }
                                }
                                else if (!m_newModule)
                                {
                                    // nope -- no longer finding directive prologues
                                    possibleDirectivePrologue = false;
                                }
                            }
                            else if (m_newModule)
                            {
                                // we aren't looking for directive prologues anymore, but we did scan
                                // into a new module after that last AST, so reset the flag because that
                                // new module might have directive prologues for next time
                                possibleDirectivePrologue = true;
                            }
                        }
                        catch (RecoveryTokenException exc)
                        {
                            if (TokenInList(NoSkipTokenSet.s_TopLevelNoSkipTokenSet, exc)
                                || TokenInList(NoSkipTokenSet.s_StartStatementNoSkipTokenSet, exc))
                            {
                                ast = exc._partiallyComputedNode;
                                GetNextToken();
                            }
                            else
                            {
                                m_useCurrentForNext = false;
                                do
                                {
                                    GetNextToken();
                                } while (m_currentToken.Token != JsToken.EndOfFile && !TokenInList(NoSkipTokenSet.s_TopLevelNoSkipTokenSet, m_currentToken.Token)
                                  && !TokenInList(NoSkipTokenSet.s_StartStatementNoSkipTokenSet, m_currentToken.Token));
                            }
                        }

                        if (null != ast)
                        {
                            // append the token to the program
                            program.Append(ast);

                            // set the last end position to be the start of the current token.
                            // if we parse the next statement and the end is still the start, we know
                            // something is up and might get into an infinite loop.
                            lastEndPosition = m_currentToken.EndPosition;
                        }
                        else if (!m_scanner.IsEndOfFile && m_currentToken.StartLinePosition == lastEndPosition)
                        {
                            // didn't parse a statement, we're not at the EOF, and we didn't move
                            // anywhere in the input stream. If we just keep looping around, we're going
                            // to get into an infinite loop. Break it.
                            m_currentToken.HandleError(JsError.ApplicationError, true);
                            break;
                        }
                    }

                    AppendImportantComments(program);
                }
                finally
                {
                    m_noSkipTokenSet.Remove(NoSkipTokenSet.s_TopLevelNoSkipTokenSet);
                    m_noSkipTokenSet.Remove(NoSkipTokenSet.s_StartStatementNoSkipTokenSet);
                }

            }
            catch (EndOfStreamException)
            {
            }

            program.UpdateWith(CurrentPositionContext());
            return program;
        }
示例#7
0
        private JsFunctionObject ParseFunction(JsFunctionType functionType, JsContext fncCtx)
        {
            JsLookup name = null;
            JsAstNodeList formalParameters = null;
            JsBlock body = null;
            bool inExpression = (functionType == JsFunctionType.Expression);
            JsContext paramsContext = null;

            GetNextToken();

            // get the function name or make an anonymous function if in expression "position"
            if (JsToken.Identifier == m_currentToken.Token)
            {
                name = new JsLookup(m_currentToken.Clone(), this)
                    {
                        Name = m_scanner.Identifier
                    };
                GetNextToken();
            }
            else
            {
                string identifier = JsKeyword.CanBeIdentifier(m_currentToken.Token);
                if (null != identifier)
                {
                    name = new JsLookup(m_currentToken.Clone(), this)
                        {
                            Name = identifier
                        };
                    GetNextToken();
                }
                else
                {
                    if (!inExpression)
                    {
                        // if this isn't a function expression, then we need to throw an error because
                        // function DECLARATIONS always need a valid identifier name
                        ReportError(JsError.NoIdentifier, m_currentToken.Clone(), true);

                        // BUT if the current token is a left paren, we don't want to use it as the name.
                        // (fix for issue #14152)
                        if (m_currentToken.Token != JsToken.LeftParenthesis
                            && m_currentToken.Token != JsToken.LeftCurly)
                        {
                            identifier = m_currentToken.Code;
                            name = new JsLookup(CurrentPositionContext(), this)
                                {
                                    Name = identifier
                                };
                            GetNextToken();
                        }
                    }
                }
            }

            // make a new state and save the old one
            List<BlockType> blockType = m_blockType;
            m_blockType = new List<BlockType>(16);
            Dictionary<string, LabelInfo> labelTable = m_labelTable;
            m_labelTable = new Dictionary<string, LabelInfo>();

            try
            {
                // get the formal parameters
                if (JsToken.LeftParenthesis != m_currentToken.Token)
                {
                    // we expect a left paren at this point for standard cross-browser support.
                    // BUT -- some versions of IE allow an object property expression to be a function name, like window.onclick.
                    // we still want to throw the error, because it syntax errors on most browsers, but we still want to
                    // be able to parse it and return the intended results.
                    // Skip to the open paren and use whatever is in-between as the function name. Doesn't matter that it's
                    // an invalid identifier; it won't be accessible as a valid field anyway.
                    bool expandedIndentifier = false;
                    while (m_currentToken.Token != JsToken.LeftParenthesis
                        && m_currentToken.Token != JsToken.LeftCurly
                        && m_currentToken.Token != JsToken.Semicolon
                        && m_currentToken.Token != JsToken.EndOfFile)
                    {
                        name.Context.UpdateWith(m_currentToken);
                        GetNextToken();
                        expandedIndentifier = true;
                    }

                    // if we actually expanded the identifier context, then we want to report that
                    // the function name needs to be an identifier. Otherwise we didn't expand the
                    // name, so just report that we expected an open paren at this point.
                    if (expandedIndentifier)
                    {
                        name.Name = name.Context.Code;
                        name.Context.HandleError(JsError.FunctionNameMustBeIdentifier, false);
                    }
                    else
                    {
                        ReportError(JsError.NoLeftParenthesis, true);
                    }
                }

                if (m_currentToken.Token == JsToken.LeftParenthesis)
                {
                    // create the parameter list
                    formalParameters = new JsAstNodeList(m_currentToken.Clone(), this);
                    paramsContext = m_currentToken.Clone();

                    // skip the open paren
                    GetNextToken();

                    // create the list of arguments and update the context
                    while (JsToken.RightParenthesis != m_currentToken.Token)
                    {
                        String id = null;
                        m_noSkipTokenSet.Add(NoSkipTokenSet.s_FunctionDeclNoSkipTokenSet);
                        try
                        {
                            JsParameterDeclaration paramDecl = null;
                            if (JsToken.Identifier != m_currentToken.Token && (id = JsKeyword.CanBeIdentifier(m_currentToken.Token)) == null)
                            {
                                if (JsToken.LeftCurly == m_currentToken.Token)
                                {
                                    ReportError(JsError.NoRightParenthesis);
                                    break;
                                }
                                else if (JsToken.Comma == m_currentToken.Token)
                                {
                                    // We're missing an argument (or previous argument was malformed and
                                    // we skipped to the comma.)  Keep trying to parse the argument list --
                                    // we will skip the comma below.
                                    ReportError(JsError.SyntaxError, true);
                                }
                                else
                                {
                                    ReportError(JsError.SyntaxError, true);
                                    SkipTokensAndThrow();
                                }
                            }
                            else
                            {
                                if (null == id)
                                {
                                    id = m_scanner.Identifier;
                                }

                                paramDecl = new JsParameterDeclaration(m_currentToken.Clone(), this)
                                    {
                                        Name = id,
                                        Position = formalParameters.Count
                                    };
                                formalParameters.Append(paramDecl);
                                GetNextToken();
                            }

                            // got an arg, it should be either a ',' or ')'
                            if (JsToken.RightParenthesis == m_currentToken.Token)
                            {
                                break;
                            }
                            else if (JsToken.Comma == m_currentToken.Token)
                            {
                                // append the comma context as the terminator for the parameter
                                paramDecl.IfNotNull(p => p.TerminatingContext = m_currentToken.Clone());
                            }
                            else
                            {
                                // deal with error in some "intelligent" way
                                if (JsToken.LeftCurly == m_currentToken.Token)
                                {
                                    ReportError(JsError.NoRightParenthesis);
                                    break;
                                }
                                else
                                {
                                    if (JsToken.Identifier == m_currentToken.Token)
                                    {
                                        // it's possible that the guy was writing the type in C/C++ style (i.e. int x)
                                        ReportError(JsError.NoCommaOrTypeDefinitionError);
                                    }
                                    else
                                        ReportError(JsError.NoComma);
                                }
                            }

                            GetNextToken();
                        }
                        catch (RecoveryTokenException exc)
                        {
                            if (IndexOfToken(NoSkipTokenSet.s_FunctionDeclNoSkipTokenSet, exc) == -1)
                                throw;
                        }
                        finally
                        {
                            m_noSkipTokenSet.Remove(NoSkipTokenSet.s_FunctionDeclNoSkipTokenSet);
                        }
                    }

                    fncCtx.UpdateWith(m_currentToken);
                    GetNextToken();
                }

                // read the function body of non-abstract functions.
                if (JsToken.LeftCurly != m_currentToken.Token)
                    ReportError(JsError.NoLeftCurly, true);

                m_blockType.Add(BlockType.Block);
                m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockNoSkipTokenSet);
                m_noSkipTokenSet.Add(NoSkipTokenSet.s_StartStatementNoSkipTokenSet);
                try
                {
                    // parse the block locally to get the exact end of function
                    body = new JsBlock(m_currentToken.Clone(), this);
                    body.BraceOnNewLine = m_foundEndOfLine;
                    GetNextToken();

                    var possibleDirectivePrologue = true;
                    while (JsToken.RightCurly != m_currentToken.Token)
                    {
                        try
                        {
                            // function body's are SourceElements (Statements + FunctionDeclarations)
                            var statement = ParseStatement(true);
                            if (possibleDirectivePrologue)
                            {
                                var constantWrapper = statement as JsConstantWrapper;
                                if (constantWrapper != null && constantWrapper.PrimitiveType == JsPrimitiveType.String)
                                {
                                    // if it's already a directive prologues, we're good to go
                                    if (!(constantWrapper is JsDirectivePrologue))
                                    {
                                        // make the statement a directive prologue instead of a constant wrapper
                                        statement = new JsDirectivePrologue(constantWrapper.Value.ToString(), constantWrapper.Context, constantWrapper.Parser)
                                            {
                                                MayHaveIssues = constantWrapper.MayHaveIssues
                                            };
                                    }
                                }
                                else if (!m_newModule)
                                {
                                    // no longer considering constant wrappers
                                    possibleDirectivePrologue = false;
                                }
                            }
                            else if (m_newModule)
                            {
                                // we scanned into a new module -- we might find directive prologues again
                                possibleDirectivePrologue = true;
                            }

                            // add it to the body
                            body.Append(statement);
                        }
                        catch (RecoveryTokenException exc)
                        {
                            if (exc._partiallyComputedNode != null)
                            {
                                body.Append(exc._partiallyComputedNode);
                            }
                            if (IndexOfToken(NoSkipTokenSet.s_StartStatementNoSkipTokenSet, exc) == -1)
                                throw;
                        }
                    }

                    // make sure any important comments before the closing brace are kept
                    AppendImportantComments(body);

                    body.Context.UpdateWith(m_currentToken);
                    fncCtx.UpdateWith(m_currentToken);
                }
                catch (EndOfStreamException)
                {
                    // if we get an EOF here, we never had a chance to find the closing curly-brace
                    fncCtx.HandleError(JsError.UnclosedFunction, true);
                }
                catch (RecoveryTokenException exc)
                {
                    if (IndexOfToken(NoSkipTokenSet.s_BlockNoSkipTokenSet, exc) == -1)
                    {
                        exc._partiallyComputedNode = new JsFunctionObject(fncCtx, this)
                            {
                                FunctionType = (inExpression ? JsFunctionType.Expression : JsFunctionType.Declaration),
                                IdContext = name.IfNotNull(n => n.Context),
                                Name = name.IfNotNull(n => n.Name),
                                ParameterDeclarations = formalParameters,
                                ParametersContext = paramsContext,
                                Body = body
                            };
                        throw;
                    }
                }
                finally
                {
                    m_blockType.RemoveAt(m_blockType.Count - 1);
                    m_noSkipTokenSet.Remove(NoSkipTokenSet.s_StartStatementNoSkipTokenSet);
                    m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockNoSkipTokenSet);
                }

                GetNextToken();
            }
            finally
            {
                // restore state
                m_blockType = blockType;
                m_labelTable = labelTable;
            }

            return new JsFunctionObject(fncCtx, this)
                {
                    FunctionType = functionType,
                    IdContext = name.IfNotNull(n => n.Context),
                    Name = name.IfNotNull(n => n.Name),
                    ParameterDeclarations = formalParameters,
                    ParametersContext = paramsContext,
                    Body = body
                };
        }
示例#8
0
        /// <summary>
        /// Parse the source code using the given settings, getting back an abstract syntax tree Block node as the root
        /// representing the list of statements in the source code.
        /// </summary>
        /// <param name="settings">code settings to use to process the source code</param>
        /// <returns>root Block node representing the top-level statements</returns>
        public JsBlock Parse(JsSettings settings)
        {
            // initialize the scanner with our settings
            // make sure the RawTokens setting is OFF or we won't be able to create our AST
            InitializeScanner(settings);

            // make sure we initialize the global scope's strict mode to our flag, whether or not it
            // is true. This means if the setting is false, we will RESET the flag to false if we are
            // reusing the scope and a previous Parse call had code that set it to strict with a
            // program directive.
            GlobalScope.UseStrict = m_settings.StrictMode;

            // make sure the global scope knows about our known global names
            GlobalScope.SetAssumedGlobals(m_settings);

            // start of a new module
            m_newModule = true;

            var timePoints = m_timingPoints = new long[9];
            var timeIndex = timePoints.Length;
            var stopWatch = new Stopwatch();
            stopWatch.Start();

            JsBlock scriptBlock = null;
            JsBlock returnBlock = null;
            try
            {
                switch (m_settings.SourceMode)
                {
                    case JsSourceMode.Program:
                        // simply parse a block of statements
                        returnBlock = scriptBlock = ParseStatements();
                        break;

                    case JsSourceMode.Expression:
                        // create a block, get the first token, add in the parse of a single expression,
                        // and we'll go fron there.
                        returnBlock = scriptBlock = new JsBlock(CurrentPositionContext(), this);
                        GetNextToken();
                        try
                        {
                            var expr = ParseExpression();
                            if (expr != null)
                            {
                                scriptBlock.Append(expr);
                                scriptBlock.UpdateWith(expr.Context);
                            }
                        }
                        catch (EndOfStreamException)
                        {
                            Debug.WriteLine("EOF");
                        }
                        break;

                    case JsSourceMode.EventHandler:
                        // we're going to create the global block, add in a function expression with a single
                        // parameter named "event", and then we're going to parse the input as the body of that
                        // function expression. We're going to resolve the global block, but only return the body
                        // of the function.
                        scriptBlock = new JsBlock(null, this);

                        var parameters = new JsAstNodeList(null, this);
                        parameters.Append(new JsParameterDeclaration(null, this)
                            {
                                Name = "event",
                                RenameNotAllowed = true
                            });

                        var funcExpression = new JsFunctionObject(null, this)
                            {
                                FunctionType = JsFunctionType.Expression,
                                ParameterDeclarations = parameters
                            };
                        scriptBlock.Append(funcExpression);
                        funcExpression.Body = returnBlock = ParseStatements();
                        break;

                    default:
                        Debug.Fail("Unexpected source mode enumeration");
                        return null;
                }
            }
            catch (RecoveryTokenException)
            {
                // this should never happen but let's make SURE we don't expose our
                // private exception object to the outside world
                m_currentToken.HandleError(JsError.ApplicationError, true);
            }

            timePoints[--timeIndex] = stopWatch.ElapsedTicks;

            if (scriptBlock != null)
            {
                // resolve everything
                JsResolutionVisitor.Apply(scriptBlock, GlobalScope, m_settings);
            }

            timePoints[--timeIndex] = stopWatch.ElapsedTicks;

            if (scriptBlock != null && Settings.MinifyCode && !Settings.PreprocessOnly)
            {
                // this visitor doesn't just reorder scopes. It also combines the adjacent var variables,
                // unnests blocks, identifies prologue directives, and sets the strict mode on scopes.
                JsReorderScopeVisitor.Apply(scriptBlock, this);
                timePoints[--timeIndex] = stopWatch.ElapsedTicks;

                // analyze the entire node tree (needed for hypercrunch)
                // root to leaf (top down)
                var analyzeVisitor = new JsAnalyzeNodeVisitor(this);
                scriptBlock.Accept(analyzeVisitor);
                timePoints[--timeIndex] = stopWatch.ElapsedTicks;

                // analyze the scope chain (also needed for hypercrunch)
                // root to leaf (top down)
                GlobalScope.AnalyzeScope();
                timePoints[--timeIndex] = stopWatch.ElapsedTicks;

                // if we want to crunch any names....
                if (m_settings.LocalRenaming != JsLocalRenaming.KeepAll
                    && m_settings.IsModificationAllowed(JsTreeModifications.LocalRenaming))
                {
                    // then do a top-down traversal of the scope tree. For each field that had not
                    // already been crunched (globals and outers will already be crunched), crunch
                    // the name with a crunch iterator that does not use any names in the verboten set.
                    GlobalScope.AutoRenameFields();
                }

                timePoints[--timeIndex] = stopWatch.ElapsedTicks;

                // if we want to evaluate literal expressions, do so now
                if (m_settings.EvalLiteralExpressions)
                {
                    var visitor = new JsEvaluateLiteralVisitor(this);
                    scriptBlock.Accept(visitor);
                }

                timePoints[--timeIndex] = stopWatch.ElapsedTicks;

                // make the final cleanup pass
                JsFinalPassVisitor.Apply(scriptBlock, this);
                timePoints[--timeIndex] = stopWatch.ElapsedTicks;

                // we want to walk all the scopes to make sure that any generated
                // variables that haven't been crunched have been assigned valid
                // variable names that don't collide with any existing variables.
                GlobalScope.ValidateGeneratedNames();
                timePoints[--timeIndex] = stopWatch.ElapsedTicks;
            }

            if (returnBlock != null && returnBlock.Parent != null)
            {
                returnBlock.Parent = null;
            }

            return returnBlock;
        }
示例#9
0
        //---------------------------------------------------------------------------------------
        // ParseBlock
        //
        //  Block :
        //    '{' OptionalStatements '}'
        //---------------------------------------------------------------------------------------
        JsBlock ParseBlock()
        {
            m_blockType.Add(BlockType.Block);

            // set the force-braces property to true because we are assuming this is only called
            // when we encounter a left-brace and we will want to keep it going forward. If we are optimizing
            // the code, we will reset these properties as we encounter them so that unneeded curly-braces
            // can be removed.
            JsBlock codeBlock = new JsBlock(m_currentToken.Clone(), this)
                {
                    ForceBraces = true
                };
            codeBlock.BraceOnNewLine = m_foundEndOfLine;
            GetNextToken();

            m_noSkipTokenSet.Add(NoSkipTokenSet.s_StartStatementNoSkipTokenSet);
            m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockNoSkipTokenSet);
            try
            {
                try
                {
                    while (JsToken.RightCurly != m_currentToken.Token)
                    {
                        try
                        {
                            // pass false because we really only want Statements, and FunctionDeclarations
                            // are technically not statements. We'll still handle them, but we'll issue a warning.
                            codeBlock.Append(ParseStatement(false));
                        }
                        catch (RecoveryTokenException exc)
                        {
                            if (exc._partiallyComputedNode != null)
                                codeBlock.Append(exc._partiallyComputedNode);
                            if (IndexOfToken(NoSkipTokenSet.s_StartStatementNoSkipTokenSet, exc) == -1)
                                throw;
                        }
                    }

                    // make sure any important comments before the closing brace are kept
                    AppendImportantComments(codeBlock);
                }
                catch (RecoveryTokenException exc)
                {
                    if (IndexOfToken(NoSkipTokenSet.s_BlockNoSkipTokenSet, exc) == -1)
                    {
                        exc._partiallyComputedNode = codeBlock;
                        throw;
                    }
                }
            }
            finally
            {
                m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockNoSkipTokenSet);
                m_noSkipTokenSet.Remove(NoSkipTokenSet.s_StartStatementNoSkipTokenSet);
                m_blockType.RemoveAt(m_blockType.Count - 1);
            }

            codeBlock.TerminatingContext = m_currentToken.Clone();
            // update the block context
            codeBlock.Context.UpdateWith(m_currentToken);
            GetNextToken();
            return codeBlock;
        }
        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;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }