in AjaxMinDll/JavaScript/AnalyzeNodeVisitor.cs [1595:1911]
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;
Lookup lookup;
// if this is a constructor and we want to collapse
// some of them to literals...
if (node.IsConstructor)
{
// array function as new operands will probably be wrapped in parens
var funcObject = node.Function as FunctionObject;
if (funcObject != null || (funcObject = (node.Function as GroupingOperator).IfNotNull(g => g.Operand as FunctionObject)) != null)
{
if (funcObject.FunctionType == FunctionType.ArrowFunction)
{
// arrow functions can't be constructors, and we explicitly have one here.
// throw an error
node.Function.Context.HandleError(JSError.ArrowCannotBeConstructor, true);
}
}
else if (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 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
var objLiteral = new ObjectLiteral(node.Context);
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 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
var arrayLiteral = new ArrayLiteral(node.Context)
{
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 MatchPropertiesVisitor();
}
// 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
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);
// 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 ?? m_parser.GlobalScope).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)
{
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 ConstantWrapper(newName, PrimitiveType.String, node.Arguments[0].Context);
}
}
else if (m_parser.Settings.IsModificationAllowed(TreeModifications.BracketMemberToDotMember)
&& JSScanner.IsSafeIdentifier(argText)
&& !JSScanner.IsKeyword(argText, (node.EnclosingScope ?? m_parser.GlobalScope).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)
{
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);
if (node.Function != null && node.Function.IsDebugOnly)
{
// if the funtion is debug-only, then we are too!
node.IsDebugOnly = true;
// if if this node is a constructor call....
if (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.
node.Parent.ReplaceChild(node, new ObjectLiteral(node.Context)
{
IsDebugOnly = true
});
}
}
else
{
// might have changed
member = node.Function as Member;
lookup = node.Function as Lookup;
var isEval = false;
if (lookup != null
&& string.CompareOrdinal(lookup.Name, "eval") == 0
&& lookup.VariableField.FieldType == FieldType.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
{
CallNode callNode = node.Function as CallNode;
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 != EvalTreatment.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;
}
}
}
}
}