public void Visit(CallNode node) { if (node != null) { if (node.InBrackets) { // if this is a member-bracket operation, then *we* don't need parens, but we shoul // recurse the function in case something in there does node.Function.Accept(this); } else if (!node.IsConstructor) { // we have parens for our call arguments, so we definitely // need to be wrapped and there's no need to recurse m_needsParens = true; } else { // we are a new-operator - if we have any arguments then we're good to go // because those arguments will be associated with us, not the outer new. // but if we don't have any arguments, we might need to be wrapped in parens // so any outer arguments don't get associated with us if (node.Arguments == null || node.Arguments.Count == 0) { m_needsParens = !m_outerHasNoArguments; } } } else { // shouldn't happen, but we're a call so let's wrap in parens m_needsParens = true; } }
public void Visit(CallNode node) { if (node != null) { DoesRequire = true; } }
public void Visit(CallNode node) { // if there's a function node, recurse into it if (node != null && node.Function != null) { node.Function.Accept(this); } }
public override void Visit(CallNode node) { base.Visit(node); if (ShouldInsertModulePathArgument(node)) { insertionIndex = node.Arguments[0].Context.StartPosition; } }
public override void Visit(CallNode node) { base.Visit(node); if (ShouldInsertModulePathArgument(node)) { var modulePathNode = new ConstantWrapper(modulePath, PrimitiveType.String, node.Context, node.Parser); node.Arguments.Insert(0, modulePathNode); } }
internal override void AnalyzeNode() { // check to see if this node is an argument to a RegExp constructor. // if it is, we'll want to not use certain string escapes AstNode previousNode = null; AstNode parentNode = Parent; while (parentNode != null) { // is this a call node and he previous node was one of the parameters? CallNode callNode = parentNode as CallNode; if (callNode != null && previousNode == callNode.Arguments) { // are we calling a simple lookup for "RegExp"? Lookup lookup = callNode.Function as Lookup; if (lookup != null && lookup.Name == "RegExp") { // we are -- so all string literals passed within this constructor should not use // standard string escape sequences m_isParameterToRegExp = true; // we can stop looking break; } } // next up the chain, keeping track of this current node as next iteration's "previous" node previousNode = parentNode; parentNode = parentNode.Parent; } // we only need to process the literals IF we are actually going to do // anything with them (combine duplicates). So if we aren't, don't call // AddLiteral because it hugely inflates the processing time of the application. if (Parser.Settings.CombineDuplicateLiterals) { // add this literal to the scope's literal collection. // HOWEVER, we do NOT want to add it for consideration of literal combination // if any scope above us is a with-scope -- otherwise the // variable we use to combine the literals might be confused with a // property on the with-object. // AND we don't want to do it if the scope is unknown, for the same reason. // we won't really know if the variable we create will interfere with the // scope resolution of any variables that me in the eval string. ActivationObject thisScope = Parser.ScopeStack.Peek(); if (thisScope.IsKnownAtCompileTime && !thisScope.IsInWithScope) { thisScope.AddLiteral(this, thisScope); } } // this node has no children, so don't bother calling the base }
public override AstNode Clone() { CallNode newCallNode = new CallNode( (Context == null ? null : Context.Clone()), Parser, (m_func == null ? null : m_func.Clone()), (m_args == null ? null : (AstNodeList)m_args.Clone()), m_inBrackets ); newCallNode.m_isConstructor = m_isConstructor; return(newCallNode); }
//code in parser relies on the member string (x.y.z...) being returned from here public override string ToCode(ToCodeFormat format) { // pass P to the root object so it knows we might want parentheses string rootCrunched = Root.ToCode(); // these tests are for items that DON'T need parens, and then we NOT the results. // non-numeric constant wrappers don't need parens (boolean, string, null). // numeric constant wrappers need parens IF there is no decimal point. // function expressions will take care of their own parens. bool needParen = !( (Root is Lookup) || (Root is Member) || (Root is CallNode) || (Root is ThisLiteral) || (Root is ArrayLiteral) || (Root is ObjectLiteral) || (Root is RegExpLiteral) || (Root is FunctionObject) || (Root is ConstantWrapper && !((ConstantWrapper)Root).IsNumericLiteral) || (Root is ConstantWrapper && ((ConstantWrapper)Root).IsNumericLiteral && rootCrunched.Contains(".")) ); // if the root is a constructor with no arguments, we'll need to wrap it in parens so the // member-dot comes out with the right precedence. // (don't bother checking if we already are already going to use parens) if (!needParen) { CallNode callNode = Root as CallNode; if (callNode != null && callNode.IsConstructor && (callNode.Arguments == null || callNode.Arguments.Count == 0)) { needParen = true; } } StringBuilder sb = new StringBuilder(); if (needParen) { sb.Append('('); sb.Append(rootCrunched); sb.Append(')'); } else { sb.Append(rootCrunched); } sb.Append('.'); sb.Append(Name); return(sb.ToString()); }
public override void Visit(CallNode node) { if (IsDefineFunction(node) && node.Arguments.Count > 0) { FoundModuleDefinition = true; // If first argument is a string, then it is the module path. var constWrapper = node.Arguments[0] as ConstantWrapper; if (constWrapper != null && constWrapper.PrimitiveType == PrimitiveType.String) { ModulePath = (string)constWrapper.Value; } return; } base.Visit(node); }
internal override string GetFunctionGuess(AstNode target) { // MSN VOODOO: treat the as and ns methods as special if the expression is the root, // the parent is the call, and there is one string parameter -- use the string parameter if (Root == target && (Name == "as" || Name == "ns")) { CallNode call = Parent as CallNode; if (call != null && call.Arguments.Count == 1) { ConstantWrapper firstParam = call.Arguments[0] as ConstantWrapper; if (firstParam != null) { return(firstParam.ToString()); } } } return(Name); }
private string GuessAtName() { AstNode parent = Parent; if (parent != null) { if (parent is AstNodeList) { // if the parent is an ASTList, then we're really interested // in our parent's parent (probably a call) parent = parent.Parent; } CallNode call = parent as CallNode; if (call != null && call.IsConstructor) { // if this function expression is the object of a new, then we want the parent parent = parent.Parent; } string guess = parent.GetFunctionGuess(this); if (guess != null && guess.Length > 0) { if (guess.StartsWith("\"", StringComparison.Ordinal) && guess.EndsWith("\"", StringComparison.Ordinal)) { // don't need to wrap it in quotes -- it already is return(guess); } // wrap the guessed name in quotes return(string.Format(CultureInfo.InvariantCulture, "\"{0}\"", guess)); } else { return(string.Format(CultureInfo.InvariantCulture, "anonymous_{0}", UniqueNumber)); } } return(string.Empty); }
internal override void AnalyzeNode() { if (Parser.Settings.StripDebugStatements && Parser.Settings.IsModificationAllowed(TreeModifications.StripDebugStatements)) { if (TrueBlock != null && TrueBlock.IsDebuggerStatement) { TrueBlock = null; } if (FalseBlock != null && FalseBlock.IsDebuggerStatement) { FalseBlock = null; } } // recurse.... base.AnalyzeNode(); // now check to see if the two branches are now empty. // if they are, null them out. if (TrueBlock != null && TrueBlock.Count == 0) { TrueBlock = null; } if (FalseBlock != null && FalseBlock.Count == 0) { FalseBlock = null; } // if there is no true branch but a false branch, then // put a not on the condition and move the false branch to the true branch. if (TrueBlock == null && FalseBlock != null && Parser.Settings.IsModificationAllowed(TreeModifications.IfConditionFalseToIfNotConditionTrue)) { // check to see if not-ing the condition produces a quick and easy // version first AstNode nottedCondition = Condition.LogicalNot(); if (nottedCondition != null) { // it does -- use it Condition = nottedCondition; } else { // it doesn't. Just wrap it. Condition = new NumericUnary( null, Parser, Condition, JSToken.LogicalNot ); } // don't forget to set the parent Condition.Parent = this; // and swap the branches TrueBlock = FalseBlock; FalseBlock = null; } else if (TrueBlock == null && FalseBlock == null && Parser.Settings.IsModificationAllowed(TreeModifications.IfEmptyToExpression)) { // NEITHER branches have anything now! // something we can do in the future: as long as the condition doesn't // contain calls or assignments, we should be able to completely delete // the statement altogether rather than changing it to an expression // statement on the condition. // I'm just not doing it yet because I don't // know what the effect will be on the iteration of block statements. // if we're on item, 5, for instance, and we delete it, will the next // item be item 6, or will it return the NEW item 5 (since the old item // 5 was deleted and everything shifted up)? // We don't know what it is and what the side-effects may be, so // just change this statement into an expression statement by replacing us with // the expression Parent.ReplaceChild(this, Condition); // no need to analyze -- we already recursed } // if this statement is now of the pattern "if (condtion) callNode" then // we can further reduce it by changing it to "condition && callNode". if (TrueBlock != null && FalseBlock == null && TrueBlock.Count == 1 && Parser.Settings.IsModificationAllowed(TreeModifications.IfConditionCallToConditionAndCall)) { // BUT we don't want to replace the statement if the true branch is a // call to an onXXXX method of an object. This is because of an IE bug // that throws an error if you pass any parameter to onclick or onfocus or // any of those event handlers directly from within an and-expression -- // although expression-statements seem to work just fine. CallNode callNode = TrueBlock[0] as CallNode; if (callNode != null) { Member callMember = callNode.Function as Member; if (callMember == null || !callMember.Name.StartsWith("on", StringComparison.Ordinal) || callNode.Arguments.Count == 0) { // we're good -- go ahead and replace it BinaryOperator binaryOp = new BinaryOperator( Context, Parser, Condition, TrueBlock, JSToken.LogicalAnd ); // we don't need to analyse this new node because we've already analyzed // the pieces parts as part of the if. And the AnalyzeNode for the BinaryOperator // doesn't really do anything else. Just replace our current node with this // new node Parent.ReplaceChild(this, binaryOp); } } } }
public void Visit(CallNode node) { // invalid! ignore IsValid = false; }
internal override void AnalyzeNode() { // see if this is a member (we'll need it for a couple checks) Member member = m_func as Member; if (Parser.Settings.StripDebugStatements && Parser.Settings.IsModificationAllowed(TreeModifications.StripDebugStatements)) { // if this is a member, and it's a debugger object, and it's a constructor.... if (member != null && member.IsDebuggerStatement && m_isConstructor) { // we need to replace our debugger object with a generic Object m_func = new Lookup("Object", m_func.Context, Parser); // and make sure the node list is empty if (m_args != null && m_args.Count > 0) { m_args = new AstNodeList(m_args.Context, Parser); } } } // if this is a constructor and we want to collapse // some of them to literals... if (m_isConstructor && 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 lookup = m_func as Lookup; if (lookup != null) { if (lookup.Name == "Object" && Parser.Settings.IsModificationAllowed(TreeModifications.NewObjectToObjectLiteral)) { // no arguments -- the Object constructor with no arguments is the exact same as an empty // object literal if (m_args == null || m_args.Count == 0) { // replace our node with an object literal ObjectLiteral objLiteral = new ObjectLiteral(Context, Parser, null, null); if (Parent.ReplaceChild(this, objLiteral)) { // and bail now. No need to recurse -- it's an empty literal return; } } else if (m_args.Count == 1) { // one argument // check to see if it's an object literal. ObjectLiteral objectLiteral = m_args[0] as ObjectLiteral; 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 Parent.ReplaceChild(this, objectLiteral); // don't forget to recurse the object now objectLiteral.AnalyzeNode(); // 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" && Parser.Settings.IsModificationAllowed(TreeModifications.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. ConstantWrapper constWrapper = (m_args != null && m_args.Count == 1 ? m_args[0] as ConstantWrapper : 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 (m_args == null || m_args.Count != 1 || (constWrapper != null && !constWrapper.IsNumericLiteral)) { // create the new array literal object ArrayLiteral arrayLiteral = new ArrayLiteral(Context, Parser, m_args); // replace ourself within our parent if (Parent.ReplaceChild(this, arrayLiteral)) { // recurse arrayLiteral.AnalyzeNode(); // 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] ResourceStrings resourceStrings = Parser.ResourceStrings; if (m_inBrackets && resourceStrings != null && resourceStrings.Count > 0) { // see if the root object is a lookup that corresponds to the // global value (not a local field) for our resource object // (same name) Lookup rootLookup = m_func as Lookup; if (rootLookup != null && rootLookup.LocalField == null && string.CompareOrdinal(rootLookup.Name, resourceStrings.Name) == 0) { // 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 (m_args.Count == 1) { // must be a constant wrapper ConstantWrapper argConstant = m_args[0] as ConstantWrapper; if (argConstant != null) { string resourceName = argConstant.Value.ToString(); // get the localized string from the resources object ConstantWrapper resourceLiteral = new ConstantWrapper( resourceStrings[resourceName], PrimitiveType.String, Context, Parser); // replace this node with localized string, analyze it, and bail // so we don't anaylze the tree we just replaced Parent.ReplaceChild(this, resourceLiteral); resourceLiteral.AnalyzeNode(); return; } else { // error! must be a constant 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) 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 (m_inBrackets && m_args != null) { // see if there is a single, constant argument string argText = m_args.SingleConstantArgument; if (argText != null) { // see if we want to replace the name string newName; if (Parser.Settings.HasRenamePairs && Parser.Settings.ManualRenamesProperties && Parser.Settings.IsModificationAllowed(TreeModifications.PropertyRenaming) && !string.IsNullOrEmpty(newName = 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 (Parser.Settings.IsModificationAllowed(TreeModifications.BracketMemberToDotMember) && JSScanner.IsSafeIdentifier(newName) && !JSScanner.IsKeyword(newName)) { // 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) Member replacementMember = new Member(Context, Parser, m_func, argText); Parent.ReplaceChild(this, replacementMember); // this analyze call will convert the old-name member to the newName value replacementMember.AnalyzeNode(); 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. m_args.ReplaceChild(m_args[0], new ConstantWrapper(newName, PrimitiveType.String, m_args[0].Context, Parser)); } } else if (Parser.Settings.IsModificationAllowed(TreeModifications.BracketMemberToDotMember) && JSScanner.IsSafeIdentifier(argText) && !JSScanner.IsKeyword(argText)) { // not a replacement, but the string literal is a safe identifier. So we will // replace this call node with a Member-dot operation Member replacementMember = new Member(Context, Parser, m_func, argText); Parent.ReplaceChild(this, replacementMember); replacementMember.AnalyzeNode(); return; } } } // call the base class to recurse base.AnalyzeNode(); // call this AFTER recursing to give the fields a chance to resolve, because we only // want to make this replacement if we are working on the global Date object. if (!m_inBrackets && !m_isConstructor && (m_args == null || m_args.Count == 0) && member != null && string.CompareOrdinal(member.Name, "getTime") == 0 && Parser.Settings.IsModificationAllowed(TreeModifications.DateGetTimeToUnaryPlus)) { // this is not a constructor and it's not a brackets call, and there are no arguments. // if the function is a member operation to "getTime" and the object of the member is a // constructor call to the global "Date" object (not a local), then we want to replace the call // with a unary plus on the Date constructor. Converting to numeric type is the same as // calling getTime, so it's the equivalent with much fewer bytes. CallNode dateConstructor = member.Root as CallNode; if (dateConstructor != null && dateConstructor.IsConstructor) { // lookup for the predifined (not local) "Date" field Lookup lookup = dateConstructor.Function as Lookup; if (lookup != null && string.CompareOrdinal(lookup.Name, "Date") == 0 && lookup.LocalField == null) { // this is in the pattern: (new Date()).getTime() // we want to replace it with +new Date // use the same date constructor node as the operand NumericUnary unary = new NumericUnary(Context, Parser, dateConstructor, JSToken.Plus); // replace us (the call to the getTime method) with this unary operator Parent.ReplaceChild(this, unary); // don't need to AnalyzeNode on the unary operator. The operand has already // been analyzed when we recursed, and the unary operator wouldn't do anything // special anyway (since the operand is not a numeric constant) } } } else if (Parser.Settings.EvalTreatment != EvalTreatment.Ignore) { // 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 != null && string.CompareOrdinal(member.Name, "eval") == 0) { if (member.LeftHandSide.IsWindowLookup) { // this is a call to window.eval() // mark this scope as unknown so we don't crunch out locals // we might reference in the eval at runtime ScopeStack.Peek().IsKnownAtCompileTime = false; } } else { CallNode callNode = m_func as CallNode; if (callNode != null && callNode.InBrackets && callNode.LeftHandSide.IsWindowLookup && callNode.Arguments.IsSingleConstantArgument("eval")) { // this is a call to window["eval"] // mark this scope as unknown so we don't crunch out locals // we might reference in the eval at runtime ScopeStack.Peek().IsKnownAtCompileTime = false; } } } /* REVIEW: may be too late. lookups may alread have been analyzed and * found undefined * // check to see if this is an assignment to a window["prop"] structure * BinaryOperator binaryOp = Parent as BinaryOperator; * if (binaryOp != null && binaryOp.IsAssign * && m_inBrackets * && m_func.IsWindowLookup * && m_args != null) * { * // and IF the property is a non-empty constant that isn't currently * // a global field... * string propertyName = m_args.SingleConstantArgument; * if (!string.IsNullOrEmpty(propertyName) * && Parser.GlobalScope[propertyName] == null) * { * // we want to also add it to the global fields so it's not undefined * Parser.GlobalScope.DeclareField(propertyName, null, 0); * } * } */ }
public override string ToCode(ToCodeFormat format) { StringBuilder sb = new StringBuilder(); // we will only add a space if we KNOW we might need one bool needsSpace = false; // normal processing... if (m_isConstructor) { sb.Append("new"); // the new operator might need to be followed by a space, depending // on what will actually follow needsSpace = true; } // list of items that DON'T need parens. // lookup, call or member don't need parens. All other items // are lower-precedence and therefore need to be wrapped in // parentheses to keep the right order. // function objects take care of their own parentheses. CallNode funcCall = m_func as CallNode; bool encloseInParens = !( (m_func is Lookup) || (m_func is Member) || (funcCall != null) || (m_func is ThisLiteral) || (m_func is FunctionObject) ); // because if the new-operator associates to the right and the ()-operator associates // to the left, we need to be careful that we don't change the precedence order when the // function of a new operator is itself a call. In that case, the call will have it's own // parameters (and therefore parentheses) that will need to be associated with the call // and NOT the new -- the call will need to be surrounded with parens to keep that association. if (m_isConstructor && funcCall != null && !funcCall.InBrackets) { encloseInParens = true; } // if the root is a constructor with no arguments, we'll need to wrap it in parens so the // member-dot comes out with the right precedence. // (don't bother checking if we already are already going to use parens) if (!encloseInParens && funcCall != null && funcCall.IsConstructor && (funcCall.Arguments == null || funcCall.Arguments.Count == 0)) { encloseInParens = true; } if (encloseInParens) { // we're adding a parenthesis, so no -- we won't need to // add a space needsSpace = false; sb.Append('('); } string functionString = m_func.ToCode(); // if we still think we might need a space, check the function we just // formatted. If it starts with an identifier part, then we need the space. if (needsSpace && JSScanner.StartsWithIdentifierPart(functionString)) { sb.Append(' '); } sb.Append(functionString); if (encloseInParens) { sb.Append(')'); } // if this isn't a constructor, or if it is and there are parameters, // then we want to output the parameters. But if this is a constructor with // no parameters, we can skip the whole empty-argument-parens thing altogether. if (!m_isConstructor || (m_args != null && m_args.Count > 0)) { sb.Append((m_inBrackets ? '[' : '(')); if (m_args != null) { sb.Append(m_args.ToCode(ToCodeFormat.Commas)); } sb.Append((m_inBrackets ? ']' : ')')); } return(sb.ToString()); }
bool ShouldInsertModulePathArgument(CallNode node) { return IsDefineFunction(node) && DefineCallIsAnonymous(node); }
bool IsDefineFunction(CallNode node) { return node.Function.ToCode() == "define"; }
//--------------------------------------------------------------------------------------- // 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 AstNode MemberExpression(AstNode expression, List<Context> newContexts) { for (; ; ) { m_noSkipTokenSet.Add(NoSkipTokenSet.s_MemberExprNoSkipTokenSet); try { switch (m_currentToken.Token) { case JSToken.LeftParenthesis: AstNodeList args = null; RecoveryTokenException callError = null; m_noSkipTokenSet.Add(NoSkipTokenSet.s_ParenToken); try { args = ParseExpressionList(JSToken.RightParenthesis); } catch (RecoveryTokenException exc) { args = (AstNodeList)exc._partiallyComputedNode; if (IndexOfToken(NoSkipTokenSet.s_ParenToken, exc) == -1) callError = exc; // thrown later on } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_ParenToken); } expression = new CallNode(expression.Context.CombineWith(args.Context), this, expression, args, false); if (null != newContexts && newContexts.Count > 0) { (newContexts[newContexts.Count - 1]).UpdateWith(expression.Context); if (!(expression is CallNode)) expression = new CallNode(newContexts[newContexts.Count - 1], this, expression, new AstNodeList(CurrentPositionContext(), this), false); else expression.Context = newContexts[newContexts.Count - 1]; ((CallNode)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 AstNodeList(m_currentToken.Clone(), this); AstNode 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 CallNode(expression.Context.CombineWith(m_currentToken.Clone()), this, expression, (AstNodeList)exc._partiallyComputedNode, true); } else { exc._partiallyComputedNode = expression; } throw; } else args = (AstNodeList)exc._partiallyComputedNode; } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BracketToken); } expression = new CallNode(expression.Context.CombineWith(m_currentToken.Clone()), this, expression, args, 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: ConstantWrapper id = null; 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 ConstantWrapper(identifier, PrimitiveType.String, m_currentToken.Clone(), this); } else if (JSScanner.IsValidIdentifier(m_currentToken.Code)) { // it must be a keyword, because it can't technically be an indentifier, // but it IS a valid identifier format. Throw the error but still // create the constant wrapper so we can output it as-is ReportError(JSError.NoIdentifier, m_currentToken.Clone(), true); id = new ConstantWrapper(m_currentToken.Code, PrimitiveType.String, m_currentToken.Clone(), this); } else { ReportError(JSError.NoIdentifier); SkipTokensAndThrow(expression); } } else { id = new ConstantWrapper(m_scanner.GetIdentifier(), PrimitiveType.String, m_currentToken.Clone(), this); } GetNextToken(); expression = new Member(expression.Context.CombineWith(id.Context), this, expression, id.Context.Code, id.Context); break; default: if (null != newContexts) { while (newContexts.Count > 0) { (newContexts[newContexts.Count - 1]).UpdateWith(expression.Context); expression = new CallNode(newContexts[newContexts.Count - 1], this, expression, new AstNodeList(CurrentPositionContext(), this), false); ((CallNode)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 override void Visit(CallNode node) { if (node != null) { // see if this is a member (we'll need it for a couple checks) Member member = node.Function as Member; if (m_parser.Settings.StripDebugStatements && m_parser.Settings.IsModificationAllowed(TreeModifications.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 need to replace our debugger object with a generic Object node.ReplaceChild(node.Function, new Lookup("Object", node.Function.Context, m_parser)); // and make sure the node list is empty if (node.Arguments != null && node.Arguments.Count > 0) { node.ReplaceChild(node.Arguments, new AstNodeList(node.Arguments.Context, m_parser)); } } } // 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 lookup = node.Function as Lookup; if (lookup != null) { if (lookup.Name == "Object" && m_parser.Settings.IsModificationAllowed(TreeModifications.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 ObjectLiteral objLiteral = new ObjectLiteral(node.Context, m_parser, null, null); 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. ObjectLiteral objectLiteral = node.Arguments[0] as ObjectLiteral; 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(TreeModifications.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. ConstantWrapper constWrapper = (node.Arguments != null && node.Arguments.Count == 1 ? node.Arguments[0] as ConstantWrapper : 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 ArrayLiteral arrayLiteral = new ArrayLiteral(node.Context, m_parser, 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] ResourceStrings resourceStrings = m_parser.ResourceStrings; if (node.InBrackets && resourceStrings != null && resourceStrings.Count > 0) { // see if the root object is a lookup that corresponds to the // global value (not a local field) for our resource object // (same name) Lookup rootLookup = node.Function as Lookup; if (rootLookup != null && rootLookup.LocalField == null && string.CompareOrdinal(rootLookup.Name, resourceStrings.Name) == 0) { // 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 ConstantWrapper argConstant = node.Arguments[0] as ConstantWrapper; if (argConstant != null) { string resourceName = argConstant.Value.ToString(); // get the localized string from the resources object ConstantWrapper resourceLiteral = new ConstantWrapper( resourceStrings[resourceName], PrimitiveType.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(TreeModifications.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(TreeModifications.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) Member replacementMember = new Member(node.Context, m_parser, node.Function, argText, 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.ReplaceChild(node.Arguments[0], new ConstantWrapper(newName, PrimitiveType.String, node.Arguments[0].Context, m_parser)); } } else if (m_parser.Settings.IsModificationAllowed(TreeModifications.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 Member replacementMember = new Member(node.Context, m_parser, node.Function, argText, 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 Member; // call this AFTER recursing to give the fields a chance to resolve, because we only // want to make this replacement if we are working on the global Date object. if (!node.InBrackets && !node.IsConstructor && (node.Arguments == null || node.Arguments.Count == 0) && member != null && string.CompareOrdinal(member.Name, "getTime") == 0 && m_parser.Settings.IsModificationAllowed(TreeModifications.DateGetTimeToUnaryPlus)) { // this is not a constructor and it's not a brackets call, and there are no arguments. // if the function is a member operation to "getTime" and the object of the member is a // constructor call to the global "Date" object (not a local), then we want to replace the call // with a unary plus on the Date constructor. Converting to numeric type is the same as // calling getTime, so it's the equivalent with much fewer bytes. CallNode dateConstructor = member.Root as CallNode; if (dateConstructor != null && dateConstructor.IsConstructor) { // lookup for the predifined (not local) "Date" field Lookup lookup = dateConstructor.Function as Lookup; if (lookup != null && string.CompareOrdinal(lookup.Name, "Date") == 0 && lookup.LocalField == null) { // this is in the pattern: (new Date()).getTime() // we want to replace it with +new Date // use the same date constructor node as the operand NumericUnary unary = new NumericUnary(node.Context, m_parser, dateConstructor, JSToken.Plus); // replace us (the call to the getTime method) with this unary operator node.Parent.ReplaceChild(node, unary); // don't need to recurse on the unary operator. The operand has already // been analyzed when we recursed, and the unary operator wouldn't do anything // special anyway (since the operand is not a numeric constant) } } } else { var isEval = false; var lookup = node.Function as Lookup; if (lookup != null && string.CompareOrdinal(lookup.Name, "eval") == 0 && lookup.VariableField is JSPredefinedField) { // 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.LeftHandSide.IsWindowLookup) { // this is a call to window.eval() isEval = true; } } else { CallNode callNode = node.Function as CallNode; if (callNode != null && callNode.InBrackets && callNode.LeftHandSide.IsWindowLookup && callNode.Arguments.IsSingleConstantArgument("eval")) { // this is a call to window["eval"] isEval = true; } } if (isEval) { if (m_parser.Settings.EvalTreatment != EvalTreatment.Ignore) { // mark this scope as unknown so we don't crunch out locals // we might reference in the eval at runtime ScopeStack.Peek().IsKnownAtCompileTime = false; } } } } }
public void Visit(CallNode node) { ReportError(node); }
bool DefineCallIsAnonymous(CallNode node) { // e.g. define( [...], function() {} ) // or define( function() {} ) return node.Arguments.Count < 3; }
public override void Visit(CallNode node) { // same logic for most nodes TypicalHandler(node); }