示例#1
0
 public virtual void Visit(JsObjectLiteral node)
 {
     if (node != null)
     {
         if (node.Properties != null)
         {
             node.Properties.Accept(this);
         }
     }
 }
        public void Visit(JsObjectLiteral node)
        {
            if (node != null)
            {
                m_writer.Write('{');
                if (node.Properties != null)
                {
                    node.Properties.Accept(this);
                }

                m_writer.Write('}');
            }
        }
 public virtual void Visit(JsObjectLiteral node)
 {
     if (node != null)
     {
         if (node.Properties != null)
         {
             node.Properties.Accept(this);
         }
     }
 }
 public void Visit(JsObjectLiteral node)
 {
     if (node != null)
     {
         node.Properties.Accept(this);
         node.Index = NextOrderIndex;
     }
 }
示例#5
0
        private JsAstNode ParseLeftHandSideExpression(bool isMinus)
        {
            JsAstNode ast = null;
            bool skipToken = true;
            List<JsContext> newContexts = null;

            TryItAgain:

            // new expression
            while (JsToken.New == m_currentToken.Token)
            {
                if (null == newContexts)
                    newContexts = new List<JsContext>(4);
                newContexts.Add(m_currentToken.Clone());
                GetNextToken();
            }
            JsToken token = m_currentToken.Token;
            switch (token)
            {
                // primary expression
                case JsToken.Identifier:
                    ast = new JsLookup(m_currentToken.Clone(), this)
                        {
                            Name = m_scanner.Identifier
                        };
                    break;

                case JsToken.ConditionalCommentStart:
                    // skip past the start to the next token
                    GetNextToken();
                    if (m_currentToken.Token == JsToken.ConditionalCompilationVariable)
                    {
                        // we have /*@id
                        ast = new JsConstantWrapperPP(m_currentToken.Clone(), this)
                            {
                                VarName = m_currentToken.Code,
                                ForceComments = true
                            };

                        GetNextToken();

                        if (m_currentToken.Token == JsToken.ConditionalCommentEnd)
                        {
                            // skip past the closing comment
                            GetNextToken();
                        }
                        else
                        {
                            // we ONLY support /*@id@*/ in expressions right now. If there's not
                            // a closing comment after the ID, then we don't support it.
                            // throw an error, skip to the end of the comment, then ignore it and start
                            // looking for the next token.
                            CCTooComplicated(null);
                            goto TryItAgain;
                        }
                    }
                    else if (m_currentToken.Token == JsToken.ConditionalCommentEnd)
                    {
                        // empty conditional comment! Ignore.
                        GetNextToken();
                        goto TryItAgain;
                    }
                    else
                    {
                        // we DON'T have "/*@IDENT". We only support "/*@IDENT @*/", so since this isn't
                        // and id, throw the error, skip to the end of the comment, and ignore it
                        // by looping back and looking for the NEXT token.
                        m_currentToken.HandleError(JsError.ConditionalCompilationTooComplex);

                        // skip to end of conditional comment
                        while (m_currentToken.Token != JsToken.EndOfFile && m_currentToken.Token != JsToken.ConditionalCommentEnd)
                        {
                            GetNextToken();
                        }
                        GetNextToken();
                        goto TryItAgain;
                    }
                    break;

                case JsToken.This:
                    ast = new JsThisLiteral(m_currentToken.Clone(), this);
                    break;

                case JsToken.StringLiteral:
                    ast = new JsConstantWrapper(m_scanner.StringLiteralValue, JsPrimitiveType.String, m_currentToken.Clone(), this)
                        {
                            MayHaveIssues = m_scanner.LiteralHasIssues
                        };
                    break;

                case JsToken.IntegerLiteral:
                case JsToken.NumericLiteral:
                    {
                        JsContext numericContext = m_currentToken.Clone();
                        double doubleValue;
                        if (ConvertNumericLiteralToDouble(m_currentToken.Code, (token == JsToken.IntegerLiteral), out doubleValue))
                        {
                            // conversion worked fine
                            // check for some boundary conditions
                            var mayHaveIssues = m_scanner.LiteralHasIssues;
                            if (doubleValue == double.MaxValue)
                            {
                                ReportError(JsError.NumericMaximum, numericContext, true);
                            }
                            else if (isMinus && -doubleValue == double.MinValue)
                            {
                                ReportError(JsError.NumericMinimum, numericContext, true);
                            }

                            // create the constant wrapper from the value
                            ast = new JsConstantWrapper(doubleValue, JsPrimitiveType.Number, numericContext, this)
                                {
                                    MayHaveIssues = mayHaveIssues
                                };
                        }
                        else
                        {
                            // if we went overflow or are not a number, then we will use the "Other"
                            // primitive type so we don't try doing any numeric calcs with it.
                            if (double.IsInfinity(doubleValue))
                            {
                                // overflow
                                // and if we ARE an overflow, report it
                                ReportError(JsError.NumericOverflow, numericContext, true);
                            }

                            // regardless, we're going to create a special constant wrapper
                            // that simply echos the input as-is
                            ast = new JsConstantWrapper(m_currentToken.Code, JsPrimitiveType.Other, numericContext, this)
                            {
                                MayHaveIssues = true
                            };
                        }
                        break;
                    }

                case JsToken.True:
                    ast = new JsConstantWrapper(true, JsPrimitiveType.Boolean, m_currentToken.Clone(), this);
                    break;

                case JsToken.False:
                    ast = new JsConstantWrapper(false, JsPrimitiveType.Boolean, m_currentToken.Clone(), this);
                    break;

                case JsToken.Null:
                    ast = new JsConstantWrapper(null, JsPrimitiveType.Null, m_currentToken.Clone(), this);
                    break;

                case JsToken.ConditionalCompilationVariable:
                    ast = new JsConstantWrapperPP(m_currentToken.Clone(), this)
                        {
                            VarName = m_currentToken.Code,
                            ForceComments = false
                        };
                    break;

                case JsToken.DivideAssign:
                // normally this token is not allowed on the left-hand side of an expression.
                // BUT, this might be the start of a regular expression that begins with an equals sign!
                // we need to test to see if we can parse a regular expression, and if not, THEN
                // we can fail the parse.

                case JsToken.Divide:
                    // could it be a regexp?
                    ast = ScanRegularExpression();
                    if (ast != null)
                    {
                        // yup -- we're done here
                        break;
                    }

                    // nope -- go to the default branch
                    goto default;

                // expression
                case JsToken.LeftParenthesis:
                    {
                        var groupingOp = new JsGroupingOperator(m_currentToken.Clone(), this);
                        ast = groupingOp;
                        GetNextToken();
                        m_noSkipTokenSet.Add(NoSkipTokenSet.s_ParenExpressionNoSkipToken);
                        try
                        {
                            // parse an expression
                            groupingOp.Operand = ParseExpression();
                            if (JsToken.RightParenthesis != m_currentToken.Token)
                            {
                                ReportError(JsError.NoRightParenthesis);
                            }
                            else
                            {
                                // add the closing paren to the expression context
                                ast.Context.UpdateWith(m_currentToken);
                            }
                        }
                        catch (RecoveryTokenException exc)
                        {
                            if (IndexOfToken(NoSkipTokenSet.s_ParenExpressionNoSkipToken, exc) == -1)
                                throw;
                            else
                                groupingOp.Operand = exc._partiallyComputedNode;
                        }
                        finally
                        {
                            m_noSkipTokenSet.Remove(NoSkipTokenSet.s_ParenExpressionNoSkipToken);
                        }
                    }
                    break;

                // array initializer
                case JsToken.LeftBracket:
                    JsContext listCtx = m_currentToken.Clone();
                    GetNextToken();
                    JsAstNodeList list = new JsAstNodeList(CurrentPositionContext(), this);
                    var hasTrailingCommas = false;
                    while (JsToken.RightBracket != m_currentToken.Token)
                    {
                        if (JsToken.Comma != m_currentToken.Token)
                        {
                            m_noSkipTokenSet.Add(NoSkipTokenSet.s_ArrayInitNoSkipTokenSet);
                            try
                            {
                                var expression = ParseExpression(true);
                                list.Append(expression);
                                if (JsToken.Comma != m_currentToken.Token)
                                {
                                    if (JsToken.RightBracket != m_currentToken.Token)
                                    {
                                        ReportError(JsError.NoRightBracket);
                                    }

                                    break;
                                }
                                else
                                {
                                    // we have a comma -- skip it after adding it as a terminator
                                    // on the previous expression
                                    var commaContext = m_currentToken.Clone();
                                    expression.IfNotNull(e => e.TerminatingContext = commaContext);
                                    GetNextToken();

                                    // if the next token is the closing brackets, then we need to
                                    // add a missing value to the array because we end in a comma and
                                    // we need to keep it for cross-platform compat.
                                    // TECHNICALLY, that puts an extra item into the array for most modern browsers, but not ALL.
                                    if (m_currentToken.Token == JsToken.RightBracket)
                                    {
                                        hasTrailingCommas = true;
                                        list.Append(new JsConstantWrapper(JsMissing.Value, JsPrimitiveType.Other, m_currentToken.Clone(), this));

                                        // throw a cross-browser warning about trailing commas
                                        commaContext.HandleError(JsError.ArrayLiteralTrailingComma);
                                        break;
                                    }
                                }
                            }
                            catch (RecoveryTokenException exc)
                            {
                                if (exc._partiallyComputedNode != null)
                                    list.Append(exc._partiallyComputedNode);
                                if (IndexOfToken(NoSkipTokenSet.s_ArrayInitNoSkipTokenSet, exc) == -1)
                                {
                                    listCtx.UpdateWith(CurrentPositionContext());
                                    exc._partiallyComputedNode = new JsArrayLiteral(listCtx, this)
                                        {
                                            Elements = list,
                                            MayHaveIssues = true
                                        };
                                    throw;
                                }
                                else
                                {
                                    if (JsToken.RightBracket == m_currentToken.Token)
                                        break;
                                }
                            }
                            finally
                            {
                                m_noSkipTokenSet.Remove(NoSkipTokenSet.s_ArrayInitNoSkipTokenSet);
                            }
                        }
                        else
                        {
                            // comma -- missing array item in the list
                            var commaContext = m_currentToken.Clone();
                            list.Append(new JsConstantWrapper(JsMissing.Value, JsPrimitiveType.Other, m_currentToken.Clone(), this)
                                {
                                    TerminatingContext = commaContext
                                });

                            // skip over the comma
                            GetNextToken();

                            // if the next token is the closing brace, then we end with a comma -- and we need to
                            // add ANOTHER missing value to make sure this last comma doesn't get left off.
                            // TECHNICALLY, that puts an extra item into the array for most modern browsers, but not ALL.
                            if (m_currentToken.Token == JsToken.RightBracket)
                            {
                                hasTrailingCommas = true;
                                list.Append(new JsConstantWrapper(JsMissing.Value, JsPrimitiveType.Other, m_currentToken.Clone(), this));

                                // throw a cross-browser warning about trailing commas
                                commaContext.HandleError(JsError.ArrayLiteralTrailingComma);
                                break;
                            }
                        }
                    }

                    listCtx.UpdateWith(m_currentToken);
                    ast = new JsArrayLiteral(listCtx, this)
                        {
                            Elements = list,
                            MayHaveIssues = hasTrailingCommas
                        };
                    break;

                // object initializer
                case JsToken.LeftCurly:
                    JsContext objCtx = m_currentToken.Clone();
                    GetNextToken();

                    var propertyList = new JsAstNodeList(CurrentPositionContext(), this);

                    if (JsToken.RightCurly != m_currentToken.Token)
                    {
                        for (; ; )
                        {
                            JsObjectLiteralField field = null;
                            JsAstNode value = null;
                            bool getterSetter = false;
                            string ident;

                            switch (m_currentToken.Token)
                            {
                                case JsToken.Identifier:
                                    field = new JsObjectLiteralField(m_scanner.Identifier, JsPrimitiveType.String, m_currentToken.Clone(), this);
                                    break;

                                case JsToken.StringLiteral:
                                    field = new JsObjectLiteralField(m_scanner.StringLiteralValue, JsPrimitiveType.String, m_currentToken.Clone(), this)
                                        {
                                            MayHaveIssues = m_scanner.LiteralHasIssues
                                        };
                                    break;

                                case JsToken.IntegerLiteral:
                                case JsToken.NumericLiteral:
                                    {
                                        double doubleValue;
                                        if (ConvertNumericLiteralToDouble(m_currentToken.Code, (m_currentToken.Token == JsToken.IntegerLiteral), out doubleValue))
                                        {
                                            // conversion worked fine
                                            field = new JsObjectLiteralField(
                                              doubleValue,
                                              JsPrimitiveType.Number,
                                              m_currentToken.Clone(),
                                              this
                                              );
                                        }
                                        else
                                        {
                                            // something went wrong and we're not sure the string representation in the source is
                                            // going to convert to a numeric value well
                                            if (double.IsInfinity(doubleValue))
                                            {
                                                ReportError(JsError.NumericOverflow, m_currentToken.Clone(), true);
                                            }

                                            // use the source as the field name, not the numeric value
                                            field = new JsObjectLiteralField(
                                                m_currentToken.Code,
                                                JsPrimitiveType.Other,
                                                m_currentToken.Clone(),
                                                this);
                                        }
                                        break;
                                    }

                                case JsToken.Get:
                                case JsToken.Set:
                                    if (PeekToken() == JsToken.Colon)
                                    {
                                        // the field is either "get" or "set" and isn't the special Mozilla getter/setter
                                        field = new JsObjectLiteralField(m_currentToken.Code, JsPrimitiveType.String, m_currentToken.Clone(), this);
                                    }
                                    else
                                    {
                                        // ecma-script get/set property construct
                                        getterSetter = true;
                                        bool isGet = (m_currentToken.Token == JsToken.Get);
                                        value = ParseFunction(
                                          (JsToken.Get == m_currentToken.Token ? JsFunctionType.Getter : JsFunctionType.Setter),
                                          m_currentToken.Clone()
                                          );
                                        JsFunctionObject funcExpr = value as JsFunctionObject;
                                        if (funcExpr != null)
                                        {
                                            // getter/setter is just the literal name with a get/set flag
                                            field = new JsGetterSetter(
                                              funcExpr.Name,
                                              isGet,
                                              funcExpr.IdContext.Clone(),
                                              this
                                              );
                                        }
                                        else
                                        {
                                            ReportError(JsError.FunctionExpressionExpected);
                                        }
                                    }
                                    break;

                                default:
                                    // NOT: identifier token, string, number, or getter/setter.
                                    // see if it's a token that COULD be an identifierName.
                                    ident = m_scanner.Identifier;
                                    if (JsScanner.IsValidIdentifier(ident))
                                    {
                                        // BY THE SPEC, if it's a valid identifierName -- which includes reserved words -- then it's
                                        // okay for object literal syntax. However, reserved words here won't work in all browsers,
                                        // so if it is a reserved word, let's throw a low-sev cross-browser warning on the code.
                                        if (JsKeyword.CanBeIdentifier(m_currentToken.Token) == null)
                                        {
                                            ReportError(JsError.ObjectLiteralKeyword, m_currentToken.Clone(), true);
                                        }

                                        field = new JsObjectLiteralField(ident, JsPrimitiveType.String, m_currentToken.Clone(), this);
                                    }
                                    else
                                    {
                                        // throw an error but use it anyway, since that's what the developer has going on
                                        ReportError(JsError.NoMemberIdentifier, m_currentToken.Clone(), true);
                                        field = new JsObjectLiteralField(m_currentToken.Code, JsPrimitiveType.String, m_currentToken.Clone(), this);
                                    }
                                    break;
                            }

                            if (field != null)
                            {
                                if (!getterSetter)
                                {
                                    GetNextToken();
                                }

                                m_noSkipTokenSet.Add(NoSkipTokenSet.s_ObjectInitNoSkipTokenSet);
                                try
                                {
                                    if (!getterSetter)
                                    {
                                        // get the value
                                        if (JsToken.Colon != m_currentToken.Token)
                                        {
                                            ReportError(JsError.NoColon, true);
                                            value = ParseExpression(true);
                                        }
                                        else
                                        {
                                            field.ColonContext = m_currentToken.Clone();
                                            GetNextToken();
                                            value = ParseExpression(true);
                                        }
                                    }

                                    // put the pair into the list of fields
                                    var propCtx = field.Context.Clone().CombineWith(value.IfNotNull(v => v.Context));
                                    var property = new JsObjectLiteralProperty(propCtx, this)
                                        {
                                            Name = field,
                                            Value = value
                                        };

                                    propertyList.Append(property);

                                    if (JsToken.RightCurly == m_currentToken.Token)
                                    {
                                        break;
                                    }
                                    else
                                    {
                                        if (JsToken.Comma == m_currentToken.Token)
                                        {
                                            // skip the comma after adding it to the property as a terminating context
                                            property.IfNotNull(p => p.TerminatingContext = m_currentToken.Clone());
                                            GetNextToken();

                                            // if the next token is the right-curly brace, then we ended
                                            // the list with a comma, which is perfectly fine
                                            if (m_currentToken.Token == JsToken.RightCurly)
                                            {
                                                break;
                                            }
                                        }
                                        else
                                        {
                                            if (m_foundEndOfLine)
                                            {
                                                ReportError(JsError.NoRightCurly);
                                            }
                                            else
                                                ReportError(JsError.NoComma, true);
                                            SkipTokensAndThrow();
                                        }
                                    }
                                }
                                catch (RecoveryTokenException exc)
                                {
                                    if (exc._partiallyComputedNode != null)
                                    {
                                        // the problem was in ParseExpression trying to determine value
                                        value = exc._partiallyComputedNode;

                                        var propCtx = field.Context.Clone().CombineWith(value.IfNotNull(v => v.Context));
                                        var property = new JsObjectLiteralProperty(propCtx, this)
                                        {
                                            Name = field,
                                            Value = value
                                        };

                                        propertyList.Append(property);
                                    }

                                    if (IndexOfToken(NoSkipTokenSet.s_ObjectInitNoSkipTokenSet, exc) == -1)
                                    {
                                        exc._partiallyComputedNode = new JsObjectLiteral(objCtx, this)
                                            {
                                                Properties = propertyList
                                            };
                                        throw;
                                    }
                                    else
                                    {
                                        if (JsToken.Comma == m_currentToken.Token)
                                            GetNextToken();
                                        if (JsToken.RightCurly == m_currentToken.Token)
                                            break;
                                    }
                                }
                                finally
                                {
                                    m_noSkipTokenSet.Remove(NoSkipTokenSet.s_ObjectInitNoSkipTokenSet);
                                }
                            }
                        }
                    }
                    objCtx.UpdateWith(m_currentToken);
                    ast = new JsObjectLiteral(objCtx, this)
                        {
                            Properties = propertyList
                        };
                    break;

                // function expression
                case JsToken.Function:
                    ast = ParseFunction(JsFunctionType.Expression, m_currentToken.Clone());
                    skipToken = false;
                    break;

                case JsToken.AspNetBlock:
                    ast = new JsAspNetBlockNode(m_currentToken.Clone(), this)
                        {
                            AspNetBlockText = m_currentToken.Code
                        };
                    break;

                default:
                    string identifier = JsKeyword.CanBeIdentifier(m_currentToken.Token);
                    if (null != identifier)
                    {
                        ast = new JsLookup(m_currentToken.Clone(), this)
                            {
                                Name = identifier
                            };
                    }
                    else
                    {
                        ReportError(JsError.ExpressionExpected);
                        SkipTokensAndThrow();
                    }
                    break;
            }

            // can be a CallExpression, that is, followed by '.' or '(' or '['
            if (skipToken)
                GetNextToken();

            return MemberExpression(ast, newContexts);
        }
 public void Visit(JsObjectLiteral node)
 {
     // we're good
 }
 public void Visit(JsObjectLiteral node)
 {
     // not applicable; terminate
 }
        public void Visit(JsObjectLiteral node)
        {
            if (node != null)
            {
                var symbol = StartSymbol(node);

                var isNoIn = m_noIn;
                m_noIn = false;

                // if start of statement, need to enclose in parens
                var encloseInParens = m_startOfStatement;
                if (encloseInParens)
                {
                    OutputPossibleLineBreak('(');
                }

                OutputPossibleLineBreak('{');
                MarkSegment(node, null, node.Context);
                SetContextOutputPosition(node.Context);

                m_startOfStatement = false;
                Indent();

                var count = node.Properties.IfNotNull(p => p.Count);
                if (count > 1)
                {
                    NewLine();
                }

                // output each key/value pair
                if (node.Properties != null)
                {
                    node.Properties.Accept(this);
                }

                Unindent();
                if (count > 1)
                {
                    NewLine();
                }

                Output('}');
                MarkSegment(node, null, node.Context);
                if (encloseInParens)
                {
                    Output(')');
                }

                m_noIn = isNoIn;

                EndSymbol(symbol);
            }
        }
 public override void Visit(JsObjectLiteral node)
 {
     // same logic for most nodes
     TypicalHandler(node);
 }
        public override void Visit(JsObjectLiteral node)
        {
            if (node != null)
            {
                // recurse
                base.Visit(node);

                if (m_scopeStack.Peek().UseStrict)
                {
                    // now strict-mode checks
                    // go through all property names and make sure there are no duplicates.
                    // use a map to remember which ones we already have and of what type.
                    var nameMap = new Dictionary<string, string>();
                    foreach (var propertyNode in node.Properties)
                    {
                        var property = propertyNode as JsObjectLiteralProperty;
                        if (property != null)
                        {
                            var propertyType = GetPropertyType(property.Value as JsFunctionObject);

                            // key name is the name plus the type. Can't just use the name because
                            // get and set will both have the same name (but different types)
                            var keyName = property.Name + propertyType;

                            string mappedType;
                            if (propertyType == "data")
                            {
                                // can't have another data, get, or set
                                if (nameMap.TryGetValue(keyName, out mappedType)
                                    || nameMap.TryGetValue(property.Name + "get", out mappedType)
                                    || nameMap.TryGetValue(property.Name + "set", out mappedType))
                                {
                                    // throw the error
                                    property.Name.Context.HandleError(JsError.StrictModeDuplicateProperty, true);

                                    // if the mapped type isn't data, then we can add this data name/type to the map
                                    // because that means the first tryget failed and we don't have a data already
                                    if (mappedType != propertyType)
                                    {
                                        nameMap.Add(keyName, propertyType);
                                    }
                                }
                                else
                                {
                                    // not in the map at all. Add it now.
                                    nameMap.Add(keyName, propertyType);
                                }
                            }
                            else
                            {
                                // get can have a set, but can't have a data or another get
                                // set can have a get, but can't have a data or another set
                                if (nameMap.TryGetValue(keyName, out mappedType)
                                    || nameMap.TryGetValue(property.Name + "data", out mappedType))
                                {
                                    // throw the error
                                    property.Name.Context.HandleError(JsError.StrictModeDuplicateProperty, true);

                                    // if the mapped type isn't data, then we can add this data name/type to the map
                                    if (mappedType != propertyType)
                                    {
                                        nameMap.Add(keyName, propertyType);
                                    }
                                }
                                else
                                {
                                    // not in the map at all - add it now
                                    nameMap.Add(keyName, propertyType);
                                }
                            }
                        }
                    }
                }
            }
        }
        public override void Visit(JsCallNode node)
        {
            if (node != null)
            {
                // see if this is a member (we'll need it for a couple checks)
                JsMember member = node.Function as JsMember;
                JsLookup lookup;

                if (m_parser.Settings.StripDebugStatements
                    && m_parser.Settings.IsModificationAllowed(JsTreeModifications.StripDebugStatements))
                {
                    // if this is a member, and it's a debugger object, and it's a constructor....
                    if (member != null && member.IsDebuggerStatement && node.IsConstructor)
                    {
                        // we have "new root.func(...)", root.func is a debug namespace, and we
                        // are stripping debug namespaces. Replace the new-operator with an
                        // empty object literal and bail.
                        node.Parent.ReplaceChild(node, new JsObjectLiteral(node.Context, node.Parser));
                        return;
                    }
                }

                // if this is a constructor and we want to collapse
                // some of them to literals...
                if (node.IsConstructor && m_parser.Settings.CollapseToLiteral)
                {
                    // see if this is a lookup, and if so, if it's pointing to one
                    // of the two constructors we want to collapse
                    lookup = node.Function as JsLookup;
                    if (lookup != null)
                    {
                        if (lookup.Name == "Object"
                            && m_parser.Settings.IsModificationAllowed(JsTreeModifications.NewObjectToObjectLiteral))
                        {
                            // no arguments -- the Object constructor with no arguments is the exact same as an empty
                            // object literal
                            if (node.Arguments == null || node.Arguments.Count == 0)
                            {
                                // replace our node with an object literal
                                var objLiteral = new JsObjectLiteral(node.Context, m_parser);
                                if (node.Parent.ReplaceChild(node, objLiteral))
                                {
                                    // and bail now. No need to recurse -- it's an empty literal
                                    return;
                                }
                            }
                            else if (node.Arguments.Count == 1)
                            {
                                // one argument
                                // check to see if it's an object literal.
                                var objectLiteral = node.Arguments[0] as JsObjectLiteral;
                                if (objectLiteral != null)
                                {
                                    // the Object constructor with an argument that is a JavaScript object merely returns the
                                    // argument. Since the argument is an object literal, it is by definition a JavaScript object
                                    // and therefore we can replace the constructor call with the object literal
                                    node.Parent.ReplaceChild(node, objectLiteral);

                                    // don't forget to recurse the object now
                                    objectLiteral.Accept(this);

                                    // and then bail -- we don't want to process this call
                                    // operation any more; we've gotten rid of it
                                    return;
                                }
                            }
                        }
                        else if (lookup.Name == "Array"
                            && m_parser.Settings.IsModificationAllowed(JsTreeModifications.NewArrayToArrayLiteral))
                        {
                            // Array is trickier.
                            // If there are no arguments, then just use [].
                            // if there are multiple arguments, then use [arg0,arg1...argN].
                            // but if there is one argument and it's numeric, we can't crunch it.
                            // also can't crunch if it's a function call or a member or something, since we won't
                            // KNOW whether or not it's numeric.
                            //
                            // so first see if it even is a single-argument constant wrapper.
                            JsConstantWrapper constWrapper = (node.Arguments != null && node.Arguments.Count == 1
                                ? node.Arguments[0] as JsConstantWrapper
                                : null);

                            // if the argument count is not one, then we crunch.
                            // if the argument count IS one, we only crunch if we have a constant wrapper,
                            // AND it's not numeric.
                            if (node.Arguments == null
                              || node.Arguments.Count != 1
                              || (constWrapper != null && !constWrapper.IsNumericLiteral))
                            {
                                // create the new array literal object
                                var arrayLiteral = new JsArrayLiteral(node.Context, m_parser)
                                    {
                                        Elements = node.Arguments
                                    };

                                // replace ourself within our parent
                                if (node.Parent.ReplaceChild(node, arrayLiteral))
                                {
                                    // recurse
                                    arrayLiteral.Accept(this);
                                    // and bail -- we don't want to recurse this node any more
                                    return;
                                }
                            }
                        }
                    }
                }

                // if we are replacing resource references with strings generated from resource files
                // and this is a brackets call: lookup[args]
                var resourceList = m_parser.Settings.ResourceStrings;
                if (node.InBrackets && resourceList.Count > 0)
                {
                    // if we don't have a match visitor, create it now
                    if (m_matchVisitor == null)
                    {
                        m_matchVisitor = new JsMatchPropertiesVisitor();
                    }

                    // check each resource strings object to see if we have a match.
                    // Walk the list BACKWARDS so that later resource string definitions supercede previous ones.
                    for (var ndx = resourceList.Count - 1; ndx >= 0; --ndx)
                    {
                        var resourceStrings = resourceList[ndx];

                        // check to see if the resource strings name matches the function
                        if (resourceStrings != null && m_matchVisitor.Match(node.Function, resourceStrings.Name))
                        {
                            // we're going to replace this node with a string constant wrapper
                            // but first we need to make sure that this is a valid lookup.
                            // if the parameter contains anything that would vary at run-time,
                            // then we need to throw an error.
                            // the parser will always have either one or zero nodes in the arguments
                            // arg list. We're not interested in zero args, so just make sure there is one
                            if (node.Arguments.Count == 1)
                            {
                                // must be a constant wrapper
                                JsConstantWrapper argConstant = node.Arguments[0] as JsConstantWrapper;
                                if (argConstant != null)
                                {
                                    string resourceName = argConstant.Value.ToString();

                                    // get the localized string from the resources object
                                    JsConstantWrapper resourceLiteral = new JsConstantWrapper(
                                        resourceStrings[resourceName],
                                        JsPrimitiveType.String,
                                        node.Context,
                                        m_parser);

                                    // replace this node with localized string, analyze it, and bail
                                    // so we don't anaylze the tree we just replaced
                                    node.Parent.ReplaceChild(node, resourceLiteral);
                                    resourceLiteral.Accept(this);
                                    return;
                                }
                                else
                                {
                                    // error! must be a constant
                                    node.Context.HandleError(
                                        JsError.ResourceReferenceMustBeConstant,
                                        true);
                                }
                            }
                            else
                            {
                                // error! can only be a single constant argument to the string resource object.
                                // the parser will only have zero or one arguments, so this must be zero
                                // (since the parser won't pass multiple args to a [] operator)
                                node.Context.HandleError(
                                    JsError.ResourceReferenceMustBeConstant,
                                    true);
                            }
                        }
                    }
                }

                // and finally, if this is a backets call and the argument is a constantwrapper that can
                // be an identifier, just change us to a member node:  obj["prop"] to obj.prop.
                // but ONLY if the string value is "safe" to be an identifier. Even though the ECMA-262
                // spec says certain Unicode categories are okay, in practice the various major browsers
                // all seem to have problems with certain characters in identifiers. Rather than risking
                // some browsers breaking when we change this syntax, don't do it for those "danger" categories.
                if (node.InBrackets && node.Arguments != null)
                {
                    // see if there is a single, constant argument
                    string argText = node.Arguments.SingleConstantArgument;
                    if (argText != null)
                    {
                        // see if we want to replace the name
                        string newName;
                        if (m_parser.Settings.HasRenamePairs && m_parser.Settings.ManualRenamesProperties
                            && m_parser.Settings.IsModificationAllowed(JsTreeModifications.PropertyRenaming)
                            && !string.IsNullOrEmpty(newName = m_parser.Settings.GetNewName(argText)))
                        {
                            // yes -- we are going to replace the name, either as a string literal, or by converting
                            // to a member-dot operation.
                            // See if we can't turn it into a dot-operator. If we can't, then we just want to replace the operator with
                            // a new constant wrapper. Otherwise we'll just replace the operator with a new constant wrapper.
                            if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.BracketMemberToDotMember)
                                && JsScanner.IsSafeIdentifier(newName)
                                && !JsScanner.IsKeyword(newName, node.EnclosingScope.UseStrict))
                            {
                                // the new name is safe to convert to a member-dot operator.
                                // but we don't want to convert the node to the NEW name, because we still need to Analyze the
                                // new member node -- and it might convert the new name to something else. So instead we're
                                // just going to convert this existing string to a member node WITH THE OLD STRING,
                                // and THEN analyze it (which will convert the old string to newName)
                                JsMember replacementMember = new JsMember(node.Context, m_parser)
                                    {
                                        Root = node.Function,
                                        Name = argText,
                                        NameContext = node.Arguments[0].Context
                                    };
                                node.Parent.ReplaceChild(node, replacementMember);

                                // this analyze call will convert the old-name member to the newName value
                                replacementMember.Accept(this);
                                return;
                            }
                            else
                            {
                                // nope; can't convert to a dot-operator.
                                // we're just going to replace the first argument with a new string literal
                                // and continue along our merry way.
                                node.Arguments[0] = new JsConstantWrapper(newName, JsPrimitiveType.String, node.Arguments[0].Context, m_parser);
                            }
                        }
                        else if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.BracketMemberToDotMember)
                            && JsScanner.IsSafeIdentifier(argText)
                            && !JsScanner.IsKeyword(argText, node.EnclosingScope.UseStrict))
                        {
                            // not a replacement, but the string literal is a safe identifier. So we will
                            // replace this call node with a Member-dot operation
                            JsMember replacementMember = new JsMember(node.Context, m_parser)
                                {
                                    Root = node.Function,
                                    Name = argText,
                                    NameContext = node.Arguments[0].Context
                                };
                            node.Parent.ReplaceChild(node, replacementMember);
                            replacementMember.Accept(this);
                            return;
                        }
                    }
                }

                // call the base class to recurse
                base.Visit(node);

                // might have changed
                member = node.Function as JsMember;
                lookup = node.Function as JsLookup;

                var isEval = false;
                if (lookup != null
                    && string.CompareOrdinal(lookup.Name, "eval") == 0
                    && lookup.VariableField.FieldType == JsFieldType.Predefined)
                {
                    // call to predefined eval function
                    isEval = true;
                }
                else if (member != null && string.CompareOrdinal(member.Name, "eval") == 0)
                {
                    // if this is a window.eval call, then we need to mark this scope as unknown just as
                    // we would if this was a regular eval call.
                    // (unless, of course, the parser settings say evals are safe)
                    // call AFTER recursing so we know the left-hand side properties have had a chance to
                    // lookup their fields to see if they are local or global
                    if (member.Root.IsWindowLookup)
                    {
                        // this is a call to window.eval()
                        isEval = true;
                    }
                }
                else
                {
                    JsCallNode callNode = node.Function as JsCallNode;
                    if (callNode != null
                        && callNode.InBrackets
                        && callNode.Function.IsWindowLookup
                        && callNode.Arguments.IsSingleConstantArgument("eval"))
                    {
                        // this is a call to window["eval"]
                        isEval = true;
                    }
                }

                if (isEval)
                {
                    if (m_parser.Settings.EvalTreatment != JsEvalTreatment.Ignore)
                    {
                        // mark this scope as unknown so we don't crunch out locals
                        // we might reference in the eval at runtime
                        m_scopeStack.Peek().IsKnownAtCompileTime = false;
                    }
                }
            }
        }