public void Visit(JsArrayLiteral node) { if (node != null) { if (node.Elements != null) { node.Elements.Accept(this); } node.Index = NextOrderIndex; } }
public void Visit(JsArrayLiteral node) { if (node != null) { m_writer.Write('['); if (node.Elements != null) { node.Elements.Accept(this); } m_writer.Write(']'); } }
public void Visit(JsArrayLiteral node) { // starts with a '[', so we don't care }
public void Visit(JsArrayLiteral node) { // not applicable; terminate }
public void Visit(JsArrayLiteral node) { // don't recurse; we don't need parens around this }
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(JsArrayLiteral node) { var isNoIn = m_noIn; m_noIn = false; if (node != null) { var symbol = StartSymbol(node); OutputPossibleLineBreak('['); MarkSegment(node, null, node.Context); SetContextOutputPosition(node.Context); m_startOfStatement = false; if (node.Elements.Count > 0) { Indent(); JsAstNode element = null; for (var ndx = 0; ndx < node.Elements.Count; ++ndx) { if (ndx > 0) { OutputPossibleLineBreak(','); MarkSegment(node, null, element.IfNotNull(e => e.TerminatingContext)); if (m_settings.OutputMode == MinifierOutputMode.MultipleLines) { OutputPossibleLineBreak(' '); } } element = node.Elements[ndx]; if (element != null) { AcceptNodeWithParens(element, element.Precedence == JsOperatorPrecedence.Comma); } } Unindent(); } Output(']'); MarkSegment(node, null, node.Context); EndSymbol(symbol); } m_noIn = isNoIn; }
public override void Visit(JsArrayLiteral node) { // same logic for most nodes TypicalHandler(node); }
private static bool OnlyHasConstantItems(JsArrayLiteral arrayLiteral) { var elementCount = arrayLiteral.Elements.Count; for (var ndx = 0; ndx < elementCount; ++ndx) { // if any one element isn't a constant or isn't safe for combination, then bail with false var constantWrapper = arrayLiteral.Elements[ndx] as JsConstantWrapper; if (constantWrapper == null || !constantWrapper.IsOkayToCombine) { return false; } } // if we get here, they were all constant return true; }
private static string ComputeJoin(JsArrayLiteral arrayLiteral, JsConstantWrapper separatorNode) { // if the separator node is null, then the separator is a single comma character. // otherwise it's just the string value of the separator. var separator = separatorNode == null ? "," : separatorNode.ToString(); var sb = new StringBuilder(); for (var ndx = 0; ndx < arrayLiteral.Elements.Count; ++ndx) { // add the separator between items (if we have one) if (ndx > 0 && !string.IsNullOrEmpty(separator)) { sb.Append(separator); } // the element is a constant wrapper (we wouldn't get this far if it wasn't), // but we've overloaded the virtual ToString method on ConstantWrappers to convert the // constant value to a string value. sb.Append(arrayLiteral.Elements[ndx].ToString()); } return sb.ToString(); }
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; } } } }