public void Visit(JsMember node) { // need to recurse the collection if (node != null) { node.Root.Accept(this); } }
public void Visit(JsMember node) { // if there's a root node, recurse into it if (node != null && node.Root != null) { node.Root.Accept(this); } }
public void Visit(JsMember node) { if (node != null) { if (node.Root != null) { node.Root.Accept(this); } node.Index = NextOrderIndex; } }
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); } } }
public void Visit(JsMember node) { // invalid! ignore IsValid = false; }
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 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) { // same logic for most nodes TypicalHandler(node); }
/// <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); } } } }
//--------------------------------------------------------------------------------------- // 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 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; } } } }