public void Visit(JsMember node)
 {
     // if there's a root node, recurse into it
     if (node != null && node.Root != null)
     {
         node.Root.Accept(this);
     }
 }
Esempio n. 2
0
 public virtual void Visit(JsMember node)
 {
     if (node != null)
     {
         if (node.Root != null)
         {
             node.Root.Accept(this);
         }
     }
 }
        /// <summary>
        /// If the new literal is a string literal, then we need to check to see if our
        /// parent is a CallNode. If it is, and if the string literal can be an identifier,
        /// we'll replace it with a Member-Dot operation.
        /// </summary>
        /// <param name="newLiteral">newLiteral we intend to replace this binaryop node with</param>
        /// <returns>true if we replaced the parent callnode with a member-dot operation</returns>
        /// <param name="node"></param>
        private bool ReplaceMemberBracketWithDot(JsBinaryOperator node, JsConstantWrapper newLiteral)
        {
            if (newLiteral.IsStringLiteral)
            {
                // see if this newly-combined string is the sole argument to a
                // call-brackets node. If it is and the combined string is a valid
                // identifier (and not a keyword), then we can replace the call
                // with a member operator.
                // remember that the parent of the argument won't be the call node -- it
                // will be the ast node list representing the arguments, whose parent will
                // be the node list.
                JsCallNode parentCall = (node.Parent is JsAstNodeList ? node.Parent.Parent as JsCallNode : null);
                if (parentCall != null && parentCall.InBrackets)
                {
                    // get the newly-combined string
                    string combinedString = newLiteral.ToString();

                    // see if this new string is the target of a replacement operation
                    string newName;
                    if (m_parser.Settings.HasRenamePairs && m_parser.Settings.ManualRenamesProperties
                        && m_parser.Settings.IsModificationAllowed(JsTreeModifications.PropertyRenaming)
                        && !string.IsNullOrEmpty(newName = m_parser.Settings.GetNewName(combinedString)))
                    {
                        // yes, it is. Now see if the new name is safe to be converted to a dot-operation.
                        if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.BracketMemberToDotMember)
                            && JsScanner.IsSafeIdentifier(newName)
                            && !JsScanner.IsKeyword(newName, parentCall.EnclosingScope.UseStrict))
                        {
                            // we want to replace the call with operator with a new member dot operation, and
                            // since we won't be analyzing it (we're past the analyze phase, we're going to need
                            // to use the new string value
                            JsMember replacementMember = new JsMember(parentCall.Context, m_parser)
                                {
                                    Root = parentCall.Function,
                                    Name = newName,
                                    NameContext = parentCall.Arguments[0].Context
                                };
                            parentCall.Parent.ReplaceChild(parentCall, replacementMember);
                            return true;
                        }
                        else
                        {
                            // nope, can't be changed to a dot-operator for whatever reason.
                            // just replace the value on this new literal. The old operation will
                            // get replaced with this new literal
                            newLiteral.Value = newName;

                            // and make sure it's type is string
                            newLiteral.PrimitiveType = JsPrimitiveType.String;
                        }
                    }
                    else if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.BracketMemberToDotMember))
                    {
                        // our parent is a call-bracket -- now we just need to see if the newly-combined
                        // string can be an identifier
                        if (JsScanner.IsSafeIdentifier(combinedString) && !JsScanner.IsKeyword(combinedString, parentCall.EnclosingScope.UseStrict))
                        {
                            // yes -- replace the parent call with a new member node using the newly-combined string
                            JsMember replacementMember = new JsMember(parentCall.Context, m_parser)
                                {
                                    Root = parentCall.Function,
                                    Name = combinedString,
                                    NameContext = parentCall.Arguments[0].Context
                                };
                            parentCall.Parent.ReplaceChild(parentCall, replacementMember);
                            return true;
                        }
                    }
                }
            }
            return false;
        }
        public override void Visit(JsMember node)
        {
            if (node != null)
            {
                // depth-first
                base.Visit(node);

                if (string.CompareOrdinal(node.Name, "length") == 0
                    && m_parser.Settings.IsModificationAllowed(JsTreeModifications.EvaluateLiteralLengths))
                {
                    // if we create a constant, we'll replace the current node with it
                    JsConstantWrapper length = null;

                    JsArrayLiteral arrayLiteral;
                    var constantWrapper = node.Root as JsConstantWrapper;
                    if (constantWrapper != null)
                    {
                        if (constantWrapper.PrimitiveType == JsPrimitiveType.String && !constantWrapper.MayHaveIssues)
                        {
                            length = new JsConstantWrapper(constantWrapper.ToString().Length, JsPrimitiveType.Number, node.Context, node.Parser);
                        }
                    }
                    else if ((arrayLiteral = node.Root as JsArrayLiteral) != null && !arrayLiteral.MayHaveIssues)
                    {
                        // get the count of items in the array literal, create a constant wrapper from it, and
                        // replace this node with it
                        length = new JsConstantWrapper(arrayLiteral.Elements.Count, JsPrimitiveType.Number, node.Context, node.Parser);
                    }

                    if (length != null)
                    {
                        node.Parent.ReplaceChild(node, length);
                    }
                }
            }
        }
Esempio n. 5
0
 public override void Visit(JsMember node)
 {
     // same logic for most nodes
     TypicalHandler(node);
 }
 public virtual void Visit(JsMember node)
 {
     if (node != null)
     {
         if (node.Root != null)
         {
             node.Root.Accept(this);
         }
     }
 }
 public void Visit(JsMember node)
 {
     // invalid! ignore
     IsValid = false;
 }
 public void Visit(JsMember node)
 {
     // need to recurse the collection
     if (node != null)
     {
         node.Root.Accept(this);
     }
 }
 public void Visit(JsMember node)
 {
     // only interested if the index is greater than zero, since the zero-index
     // needs to be a lookup.
     if (node != null && m_index > 0)
     {
         // check the Name property against the current part
         if (string.CompareOrdinal(node.Name, m_parts[m_index--]) == 0)
         {
             // match! recurse the root after decrementing the index
             node.Root.Accept(this);
         }
     }
 }
Esempio n. 10
0
        //---------------------------------------------------------------------------------------
        // MemberExpression
        //
        // Accessor :
        //  <empty> |
        //  Arguments Accessor
        //  '[' Expression ']' Accessor |
        //  '.' Identifier Accessor |
        //
        //  Don't have this function throwing an exception without checking all the calling sites.
        //  There is state in instance variable that is saved on the calling stack in some function
        //  (i.e ParseFunction and ParseClass) and you don't want to blow up the stack
        //---------------------------------------------------------------------------------------
        private JsAstNode MemberExpression(JsAstNode expression, List<JsContext> newContexts)
        {
            for (; ; )
            {
                m_noSkipTokenSet.Add(NoSkipTokenSet.s_MemberExprNoSkipTokenSet);
                try
                {
                    switch (m_currentToken.Token)
                    {
                        case JsToken.LeftParenthesis:
                            JsAstNodeList args = null;
                            RecoveryTokenException callError = null;
                            m_noSkipTokenSet.Add(NoSkipTokenSet.s_ParenToken);
                            try
                            {
                                args = ParseExpressionList(JsToken.RightParenthesis);
                            }
                            catch (RecoveryTokenException exc)
                            {
                                args = (JsAstNodeList)exc._partiallyComputedNode;
                                if (IndexOfToken(NoSkipTokenSet.s_ParenToken, exc) == -1)
                                    callError = exc; // thrown later on
                            }
                            finally
                            {
                                m_noSkipTokenSet.Remove(NoSkipTokenSet.s_ParenToken);
                            }

                            expression = new JsCallNode(expression.Context.CombineWith(args.Context), this)
                                {
                                    Function = expression,
                                    Arguments = args,
                                    InBrackets = false
                                };

                            if (null != newContexts && newContexts.Count > 0)
                            {
                                (newContexts[newContexts.Count - 1]).UpdateWith(expression.Context);
                                if (!(expression is JsCallNode))
                                {
                                    expression = new JsCallNode(newContexts[newContexts.Count - 1], this)
                                        {
                                            Function = expression,
                                            Arguments = new JsAstNodeList(CurrentPositionContext(), this)
                                        };
                                }
                                else
                                {
                                    expression.Context = newContexts[newContexts.Count - 1];
                                }

                                ((JsCallNode)expression).IsConstructor = true;
                                newContexts.RemoveAt(newContexts.Count - 1);
                            }

                            if (callError != null)
                            {
                                callError._partiallyComputedNode = expression;
                                throw callError;
                            }

                            GetNextToken();
                            break;

                        case JsToken.LeftBracket:
                            m_noSkipTokenSet.Add(NoSkipTokenSet.s_BracketToken);
                            try
                            {
                                //
                                // ROTOR parses a[b,c] as a call to a, passing in the arguments b and c.
                                // the correct parse is a member lookup on a of c -- the "b,c" should be
                                // a single expression with a comma operator that evaluates b but only
                                // returns c.
                                // So we'll change the default behavior from parsing an expression list to
                                // parsing a single expression, but returning a single-item list (or an empty
                                // list if there is no expression) so the rest of the code will work.
                                //
                                //args = ParseExpressionList(JSToken.RightBracket);
                                GetNextToken();
                                args = new JsAstNodeList(CurrentPositionContext(), this);

                                JsAstNode accessor = ParseExpression();
                                if (accessor != null)
                                {
                                    args.Append(accessor);
                                }
                            }
                            catch (RecoveryTokenException exc)
                            {
                                if (IndexOfToken(NoSkipTokenSet.s_BracketToken, exc) == -1)
                                {
                                    if (exc._partiallyComputedNode != null)
                                    {
                                        exc._partiallyComputedNode =
                                           new JsCallNode(expression.Context.CombineWith(m_currentToken.Clone()), this)
                                            {
                                                Function = expression,
                                                Arguments = (JsAstNodeList)exc._partiallyComputedNode,
                                                InBrackets = true
                                            };
                                    }
                                    else
                                    {
                                        exc._partiallyComputedNode = expression;
                                    }
                                    throw;
                                }
                                else
                                    args = (JsAstNodeList)exc._partiallyComputedNode;
                            }
                            finally
                            {
                                m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BracketToken);
                            }
                            expression = new JsCallNode(expression.Context.CombineWith(m_currentToken.Clone()), this)
                                {
                                    Function = expression,
                                    Arguments = args,
                                    InBrackets = true
                                };

                            // there originally was code here in the ROTOR sources that checked the new context list and
                            // changed this member call to a constructor call, effectively combining the two. I believe they
                            // need to remain separate.

                            // remove the close bracket token
                            GetNextToken();
                            break;

                        case JsToken.AccessField:
                            JsConstantWrapper id = null;
                            JsContext nameContext = m_currentToken.Clone();
                            GetNextToken();
                            if (JsToken.Identifier != m_currentToken.Token)
                            {
                                string identifier = JsKeyword.CanBeIdentifier(m_currentToken.Token);
                                if (null != identifier)
                                {
                                    // don't report an error here -- it's actually okay to have a property name
                                    // that is a keyword which is okay to be an identifier. For instance,
                                    // jQuery has a commonly-used method named "get" to make an ajax request
                                    //ForceReportInfo(JSError.KeywordUsedAsIdentifier);
                                    id = new JsConstantWrapper(identifier, JsPrimitiveType.String, m_currentToken.Clone(), this);
                                }
                                else if (JsScanner.IsValidIdentifier(m_currentToken.Code))
                                {
                                    // it must be a keyword, because it can't technically be an identifier,
                                    // but it IS a valid identifier format. Throw a warning but still
                                    // create the constant wrapper so we can output it as-is
                                    ReportError(JsError.KeywordUsedAsIdentifier, m_currentToken.Clone(), true);
                                    id = new JsConstantWrapper(m_currentToken.Code, JsPrimitiveType.String, m_currentToken.Clone(), this);
                                }
                                else
                                {
                                    ReportError(JsError.NoIdentifier);
                                    SkipTokensAndThrow(expression);
                                }
                            }
                            else
                            {
                                id = new JsConstantWrapper(m_scanner.Identifier, JsPrimitiveType.String, m_currentToken.Clone(), this);
                            }
                            GetNextToken();
                            expression = new JsMember(expression.Context.CombineWith(id.Context), this)
                                {
                                    Root = expression,
                                    Name = id.Context.Code,
                                    NameContext = nameContext.CombineWith(id.Context)
                                };
                            break;
                        default:
                            if (null != newContexts)
                            {
                                while (newContexts.Count > 0)
                                {
                                    (newContexts[newContexts.Count - 1]).UpdateWith(expression.Context);
                                    expression = new JsCallNode(newContexts[newContexts.Count - 1], this)
                                        {
                                            Function = expression,
                                            Arguments = new JsAstNodeList(CurrentPositionContext(), this)
                                        };
                                    ((JsCallNode)expression).IsConstructor = true;
                                    newContexts.RemoveAt(newContexts.Count - 1);
                                }
                            }
                            return expression;
                    }
                }
                catch (RecoveryTokenException exc)
                {
                    if (IndexOfToken(NoSkipTokenSet.s_MemberExprNoSkipTokenSet, exc) != -1)
                        expression = exc._partiallyComputedNode;
                    else
                    {
                        Debug.Assert(exc._partiallyComputedNode == expression);
                        throw;
                    }
                }
                finally
                {
                    m_noSkipTokenSet.Remove(NoSkipTokenSet.s_MemberExprNoSkipTokenSet);
                }
            }
        }
        public void Visit(JsMember node)
        {
            if (node != null)
            {
                if (node.Root != null)
                {
                    node.Root.Accept(this);
                }

                node.Index = NextOrderIndex;
            }
        }
        public void Visit(JsMember node)
        {
            if (node != null)
            {
                var symbol = StartSymbol(node);

                var isNoIn = m_noIn;
                m_noIn = false;

                if (node.Root != null)
                {
                    var constantWrapper = node.Root as JsConstantWrapper;
                    if (constantWrapper != null
                        && (constantWrapper.IsFiniteNumericLiteral || constantWrapper.IsOtherDecimal))
                    {
                        // numeric constant wrapper that isn't NaN or Infinity - get the formatted text version.
                        // if the number has issues, then don't format it and just use the source.
                        string numericText;
                        if (constantWrapper.Context == null
                            || !constantWrapper.Context.HasCode
                            || (m_settings.IsModificationAllowed(JsTreeModifications.MinifyNumericLiterals) && !constantWrapper.MayHaveIssues))
                        {
                            // apply minification to the literal to get it as small as possible
                            numericText = NormalizeNumber(constantWrapper.ToNumber(), constantWrapper.Context);
                        }
                        else
                        {
                            // context is not null but we don't want to minify numeric literals.
                            // just use the original literal from the context.
                            numericText = constantWrapper.Context.Code;
                        }

                        // if the value is negative, we're going to need to wrap it in parens
                        if (numericText.StartsWith("-", StringComparison.Ordinal))
                        {
                            Output('(');
                            Output(numericText);
                            Output(')');
                        }
                        else
                        {
                            // if there is no decimal point in the number and no exponent, then we may need to add
                            // a decimal point to the end of the number so the member-dot operator doesn't get mistaken
                            // for the decimal point and generate a syntax error.
                            Output(numericText);
                            if (numericText.IndexOf('.') < 0
                                && numericText.IndexOf("e", StringComparison.OrdinalIgnoreCase) < 0)
                            {
                                // HOWEVER... octal literals don't need the dot. So if this number starts with zero and
                                // has more than one digit, we need to check for octal literals and 0xd+ 0bd+ and 0od+ literals,
                                // because THOSE don't need the extra dot, either.
                                bool addDecimalPoint = !numericText.StartsWith("0", StringComparison.Ordinal) || numericText.Length == 1;
                                if (!addDecimalPoint)
                                {
                                    // But we might also have a number that just starts with zero and is a regular decimal (like 0009).
                                    // if the second "digit" isn't a number, then we have 0x or 0b or 0o, so we don't have to do
                                    // any further tests -- we know we don't need the extra decimal point. Otherwise we need to
                                    // make sure this
                                    if (char.IsDigit(numericText[1]))
                                    {
                                        // the second character is a digit, so we know we aren't 0x, 0b, or 0o. But we start with
                                        // a zero -- so we need to test to see if this is an octal literal, because they do NOT need
                                        // the extra decimal point. But if it isn't an octal literal, we DO need it after all.
                                        for (var ndx = 1; ndx < numericText.Length; ++ndx)
                                        {
                                            if ('7' < numericText[ndx])
                                            {
                                                // NOT octal; we need the extra dot
                                                addDecimalPoint = true;
                                                break;
                                            }
                                        }
                                    }
                                }

                                if (addDecimalPoint)
                                {
                                    Output('.');
                                }
                            }
                        }
                    }
                    else
                    {
                        // not a numeric constant wrapper
                        var needsParens = node.Root.Precedence < node.Precedence;
                        if (!needsParens)
                        {
                            // if the root is a new operator with no arguments, then we need to wrap
                            var callNode = node.Root as JsCallNode;
                            if (callNode != null
                                && callNode.IsConstructor
                                && (callNode.Arguments == null || callNode.Arguments.Count == 0))
                            {
                                needsParens = true;
                            }
                        }

                        AcceptNodeWithParens(node.Root, needsParens);
                    }

                    SetContextOutputPosition(node.Context);
                }

                OutputPossibleLineBreak('.');
                MarkSegment(node, node.Name, node.NameContext);
                Output(node.Name);
                m_startOfStatement = false;
                m_noIn = isNoIn;

                EndSymbol(symbol);
            }
        }
        public override void Visit(JsMember node)
        {
            if (node != null)
            {
                // if we don't even have any resource strings, then there's nothing
                // we need to do and we can just perform the base operation
                var resourceList = m_parser.Settings.ResourceStrings;
                if (resourceList.Count > 0)
                {
                    // if we haven't created the match visitor yet, do so now
                    if (m_matchVisitor == null)
                    {
                        m_matchVisitor = new JsMatchPropertiesVisitor();
                    }

                    // walk the list BACKWARDS so that later resource strings supercede previous ones
                    for (var ndx = resourceList.Count - 1; ndx >= 0; --ndx)
                    {
                        var resourceStrings = resourceList[ndx];

                        // see if the resource string name matches the root
                        if (m_matchVisitor.Match(node.Root, resourceStrings.Name))
                        {
                            // it is -- we're going to replace this with a string value.
                            // if this member name is a string on the object, we'll replacve it with
                            // the literal. Otherwise we'll replace it with an empty string.
                            // see if the string resource contains this value
                            JsConstantWrapper stringLiteral = new JsConstantWrapper(
                                resourceStrings[node.Name] ?? string.Empty,
                                JsPrimitiveType.String,
                                node.Context,
                                m_parser
                                );

                            node.Parent.ReplaceChild(node, stringLiteral);

                            // analyze the literal
                            stringLiteral.Accept(this);
                            return;
                        }
                    }
                }

                // if we are replacing property names and we have something to replace
                if (m_parser.Settings.HasRenamePairs && m_parser.Settings.ManualRenamesProperties
                    && m_parser.Settings.IsModificationAllowed(JsTreeModifications.PropertyRenaming))
                {
                    // see if this name is a target for replacement
                    string newName = m_parser.Settings.GetNewName(node.Name);
                    if (!string.IsNullOrEmpty(newName))
                    {
                        // it is -- set the name to the new name
                        node.Name = newName;
                    }
                }

                // check the name of the member for reserved words that aren't allowed
                if (JsScanner.IsKeyword(node.Name, m_scopeStack.Peek().UseStrict))
                {
                    node.NameContext.HandleError(JsError.KeywordUsedAsIdentifier);
                }

                // recurse
                base.Visit(node);
            }
        }
        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;
                    }
                }
            }
        }