public override JsVariableField this[string name] { get { // check the name table JsVariableField variableField = base[name]; // not found so far, check the global properties if (variableField == null) { variableField = ResolveFromCollection(name, m_globalProperties, JsFieldType.Predefined, false); } // not found so far, check the global properties if (variableField == null) { variableField = ResolveFromCollection(name, m_globalFunctions, JsFieldType.Predefined, true); } // if not found so far, check to see if this value is provided in our "assumed" // global list specified on the command line if (variableField == null) { variableField = ResolveFromCollection(name, m_assumedGlobals, JsFieldType.Global, false); } return(variableField); } }
public override JsVariableField CreateInnerField(JsVariableField outerField) { return outerField.IfNotNull(o => { // blindly create an inner reference field for with scopes, no matter what it // is. globals and predefined values can be hijacked by object properties in // this scope. var withField = AddField(CreateField(outerField)); // and we need to make sure that any field that may be referenced from inside // a with-statement does not get automatically renamed outerField.CanCrunch = false; // if the outer field is an undefined global, then we want to flag it with a // special attribute that tells us that it might not actually be an undefined global, // because it might just be a property reference on the with-object. if (outerField.FieldType == JsFieldType.UndefinedGlobal) { do { outerField.Attributes |= FieldAttributes.RTSpecialName; } while ((outerField = outerField.OuterField) != null); } return withField; }); }
private void DefineParameters() { if (FunctionObject.ParameterDeclarations != null) { // for each parameter... foreach (JsParameterDeclaration parameter in FunctionObject.ParameterDeclarations) { // see if it's already defined var argumentField = this[parameter.Name]; if (argumentField == null) { // not already defined -- create a field now argumentField = new JsVariableField(JsFieldType.Argument, parameter.Name, 0, null) { Position = parameter.Position, OriginalContext = parameter.Context, CanCrunch = !parameter.RenameNotAllowed }; this.AddField(argumentField); } // make the parameter reference the field and the field reference // the parameter as its declaration parameter.VariableField = argumentField; argumentField.Declarations.Add(parameter); } } }
private void UnreferencedArgument(JsVariableField variableField) { // unreferenced argument. We only want to throw a warning if there are no referenced arguments // AFTER this unreferenced argument. Also, we're assuming that if this is an argument field, // this scope MUST be a function scope. var functionScope = this as JsFunctionScope; if (functionScope != null) { if (functionScope.FunctionObject.IfNotNull(func => func.IsArgumentTrimmable(variableField))) { // if we are planning on removing unreferenced function parameters, mark it as removed // so we don't waste a perfectly good auto-rename name on it later. if (m_settings.RemoveUnneededCode && m_settings.IsModificationAllowed(JsTreeModifications.RemoveUnusedParameters)) { variableField.WasRemoved = true; } variableField.OriginalContext.HandleError( JsError.ArgumentNotReferenced, false); } } }
public override JsVariableField CreateInnerField(JsVariableField outerField) { return(outerField.IfNotNull(o => { // blindly create an inner reference field for with scopes, no matter what it // is. globals and predefined values can be hijacked by object properties in // this scope. var withField = AddField(CreateField(outerField)); // and we need to make sure that any field that may be referenced from inside // a with-statement does not get automatically renamed outerField.CanCrunch = false; // if the outer field is an undefined global, then we want to flag it with a // special attribute that tells us that it might not actually be an undefined global, // because it might just be a property reference on the with-object. if (outerField.FieldType == JsFieldType.UndefinedGlobal) { do { outerField.Attributes |= FieldAttributes.RTSpecialName; } while ((outerField = outerField.OuterField) != null); } return withField; })); }
/// <summary> /// returns true if the fields point to the same ultimate reference object. /// Needs to walk up the outer-reference chain for each field in order to /// find the ultimate reference /// </summary> /// <param name="otherField"></param> /// <returns></returns> public bool IsSameField(JsVariableField otherField) { // shortcuts -- if they are already the same object, we're done; // and if the other field is null, then we are NOT the same object. if (this == otherField) { return(true); } else if (otherField == null) { return(false); } // get the ultimate field for this field var thisOuter = OuterField != null ? OuterField : this; while (thisOuter.OuterField != null) { thisOuter = thisOuter.OuterField; } // get the ultimate field for the other field var otherOuter = otherField.OuterField != null ? otherField.OuterField : otherField; while (otherOuter.OuterField != null) { otherOuter = otherOuter.OuterField; } // now that we have the same outer fields, check to see if they are the same return(thisOuter == otherOuter); }
internal bool IsArgumentTrimmable(JsVariableField targetArgumentField) { // walk backward until we either find the given argument field or the // first parameter that is referenced. // If we find the argument field, then we can trim it because there are no // referenced parameters after it. // if we find a referenced argument, then the parameter is not trimmable. JsVariableField argumentField = null; if (ParameterDeclarations != null) { for (int index = ParameterDeclarations.Count - 1; index >= 0; --index) { // better be a parameter declaration argumentField = (ParameterDeclarations[index] as JsParameterDeclaration).IfNotNull(p => p.VariableField); if (argumentField != null && (argumentField == targetArgumentField || argumentField.IsReferenced)) { break; } } } // if the argument field we landed on is the same as the target argument field, // then we found the target argument BEFORE we found a referenced parameter. Therefore // the argument can be trimmed. return(argumentField == targetArgumentField); }
private static void SingleReferenceVariableField(JsVariableField variableField) { // local fields that don't reference an outer field, have only one refcount // and one declaration if (variableField.FieldType == JsFieldType.Local && variableField.OuterField == null && variableField.Declarations.Count == 1) { // there should only be one, it should be a vardecl, and // either no initializer or a constant initializer var varDecl = variableField.OnlyDeclaration as JsVariableDeclaration; if (varDecl != null && varDecl.Initializer != null && varDecl.Initializer.IsConstant) { // there should only be one var reference = variableField.OnlyReference; if (reference != null) { // if the reference is not being assigned to, it is not an outer reference // (meaning the lookup is in the same scope as the declaration), and the // lookup is after the declaration if (!reference.IsAssignment && reference.VariableField != null && reference.VariableField.OuterField == null && reference.VariableField.CanCrunch && varDecl.Index < reference.Index && !IsIterativeReference(varDecl.Initializer, reference)) { // so we have a declaration assigning a constant value, and only one // reference reading that value. replace the reference with the constant // and get rid of the declaration. // transform: var lookup=constant;lookup ==> constant // remove the vardecl var declaration = varDecl.Parent as JsDeclaration; if (declaration != null) { // replace the reference with the constant variableField.References.Remove(reference); var refNode = reference as JsAstNode; refNode.Parent.IfNotNull(p => p.ReplaceChild(refNode, varDecl.Initializer)); // we're also going to remove the declaration itself variableField.Declarations.Remove(varDecl); variableField.WasRemoved = true; // remove the vardecl from the declaration list // and if the declaration is now empty, remove it, too declaration.Remove(varDecl); if (declaration.Count == 0) { declaration.Parent.IfNotNull(p => p.ReplaceChild(declaration, null)); } } } } } } }
private static void ResolveGhostedCatchParameter(JsActivationObject scope, JsParameterDeclaration catchParameter) { // check to see if the name exists in the outer variable scope. var ghostField = scope[catchParameter.Name]; if (ghostField == null) { // set up a ghost field to keep track of the relationship ghostField = new JsVariableField(JsFieldType.GhostCatch, catchParameter.Name, 0, null) { OriginalContext = catchParameter.Context }; scope.AddField(ghostField); } else if (ghostField.FieldType == JsFieldType.GhostCatch) { // there is, but it's another ghost catch variable. That's fine; just use it. // don't even flag it as ambiguous because if someone is later referencing the error variable // used in a couple catch variables, we'll say something then because other browsers will have that // variable undefined or from an outer scope. } else { // there is, and it's NOT another ghosted catch variable. Possible naming // collision in IE -- if an error happens, it will clobber the existing field's value, // although that MAY be the intention; we don't know for sure. But it IS a cross- // browser behavior difference. ghostField.IsAmbiguous = true; if (ghostField.OuterField != null) { // and to make matters worse, it's actually bound to an OUTER field // in modern browsers, but will bind to this catch variable in older // versions of IE! Definitely a cross-browser difference! // throw a cross-browser issue error. catchParameter.Context.HandleError(JsError.AmbiguousCatchVar); } } // link them so they all keep the same name going forward // (since they are named the same in the sources) catchParameter.VariableField.OuterField = ghostField; // TODO: this really should be a LIST of ghosted fields, since multiple // elements can ghost to the same field. ghostField.GhostedField = catchParameter.VariableField; // if the actual field has references, we want to bubble those up // since we're now linking those fields if (catchParameter.VariableField.RefCount > 0) { // add the catch parameter's references to the ghost field ghostField.AddReferences(catchParameter.VariableField.References); } }
internal JsVariableField AddField(JsVariableField variableField) { // add it to our name table NameTable[variableField.Name] = variableField; // set the owning scope to this is we are the outer field, or the outer field's // owning scope if this is an inner field variableField.OwningScope = variableField.OuterField == null ? this : variableField.OuterField.OwningScope; return(variableField); }
private static void MakeExpectedGlobal(JsVariableField varField) { // to make this an expected global, we're going to change the type of this field, // then just keep walking up the outer field references doing the same do { varField.FieldType = JsFieldType.Global; varField = varField.OuterField; }while (varField != null); }
private JsVariableField ResolveFromCollection(string name, HashSet <string> collection, JsFieldType fieldType, bool isFunction) { if (collection.Contains(name)) { var variableField = new JsVariableField(fieldType, name, 0, null); variableField.IsFunction = isFunction; return(AddField(variableField)); } return(null); }
public virtual JsVariableField CreateInnerField(JsVariableField outerField) { JsVariableField innerField = null; if (outerField != null) { // create a new inner field to be added to our scope innerField = CreateField(outerField); AddField(innerField); } return(innerField); }
private void UnreferencedVariableField(JsVariableField variableField) { // see if the value is a function var functionObject = variableField.FieldValue as JsFunctionObject; if (functionObject != null) { UnreferencedFunction(variableField, functionObject); } else if (variableField.FieldType == JsFieldType.Argument) { UnreferencedArgument(variableField); } else if (!variableField.WasRemoved) { UnreferencedVariable(variableField); } }
public override bool IsEquivalentTo(JsAstNode otherNode) { JsVariableField otherField = null; JsLookup otherLookup; var otherVarDecl = otherNode as JsVariableDeclaration; if (otherVarDecl != null) { otherField = otherVarDecl.VariableField; } else if ((otherLookup = otherNode as JsLookup) != null) { otherField = otherLookup.VariableField; } // if we get here, we're not equivalent return(this.VariableField != null && this.VariableField.IsSameField(otherField)); }
internal JsVariableField(JsFieldType fieldType, JsVariableField outerField) { if (outerField == null) { throw new ArgumentNullException("outerField"); } m_referenceTable = new HashSet<IJsNameReference>(); m_declarationTable = new HashSet<IJsNameDeclaration>(); // set values based on the outer field OuterField = outerField; Name = outerField.Name; Attributes = outerField.Attributes; FieldValue = outerField.FieldValue; IsGenerated = outerField.IsGenerated; // and set some other fields on our object based on the type we are SetFieldsBasedOnType(fieldType); }
internal JsVariableField(JsFieldType fieldType, JsVariableField outerField) { if (outerField == null) { throw new ArgumentNullException("outerField"); } m_referenceTable = new HashSet <IJsNameReference>(); m_declarationTable = new HashSet <IJsNameDeclaration>(); // set values based on the outer field OuterField = outerField; Name = outerField.Name; Attributes = outerField.Attributes; FieldValue = outerField.FieldValue; IsGenerated = outerField.IsGenerated; // and set some other fields on our object based on the type we are SetFieldsBasedOnType(fieldType); }
private void UnreferencedFunction(JsVariableField variableField, JsFunctionObject functionObject) { // if there is no name, then ignore this declaration because it's malformed. // (won't be a function expression because those are automatically referenced). // also ignore ghosted function fields. if (functionObject.Name != null && variableField.FieldType != JsFieldType.GhostFunction) { // if the function name isn't a simple identifier, then leave it there and mark it as // not renamable because it's probably one of those darn IE-extension event handlers or something. if (JsScanner.IsValidIdentifier(functionObject.Name)) { // unreferenced function declaration. fire a warning. var ctx = functionObject.IdContext ?? variableField.OriginalContext; ctx.HandleError(JsError.FunctionNotReferenced, false); // hide it from the output if our settings say we can. // we don't want to delete it, per se, because we still want it to // show up in the scope report so the user can see that it was unreachable // in case they are wondering where it went. // ES6 has the notion of block-scoped function declarations. ES5 says functions can't // be defined inside blocks -- only at the root level of the global scope or function scopes. // so if this is a block scope, don't hide the function, even if it is unreferenced because // of the cross-browser difference. if (this.IsKnownAtCompileTime && m_settings.MinifyCode && m_settings.RemoveUnneededCode && !(this is JsBlockScope)) { functionObject.HideFromOutput = true; } } else { // not a valid identifier name for this function. Don't rename it because it's // malformed and we don't want to mess up the developer's intent. variableField.CanCrunch = false; } } }
internal bool IsArgumentTrimmable(JsVariableField targetArgumentField) { // walk backward until we either find the given argument field or the // first parameter that is referenced. // If we find the argument field, then we can trim it because there are no // referenced parameters after it. // if we find a referenced argument, then the parameter is not trimmable. JsVariableField argumentField = null; if (ParameterDeclarations != null) { for (int index = ParameterDeclarations.Count - 1; index >= 0; --index) { // better be a parameter declaration argumentField = (ParameterDeclarations[index] as JsParameterDeclaration).IfNotNull(p => p.VariableField); if (argumentField != null && (argumentField == targetArgumentField || argumentField.IsReferenced)) { break; } } } // if the argument field we landed on is the same as the target argument field, // then we found the target argument BEFORE we found a referenced parameter. Therefore // the argument can be trimmed. return (argumentField == targetArgumentField); }
public override JsVariableField CreateField(JsVariableField outerField) { // when we create a field inside a with-scope, it's ALWAYS a with-field, no matter // what type the outer reference is. return(new JsVariableField(JsFieldType.WithField, outerField)); }
private void DefineField(IJsNameDeclaration nameDecl, JsFunctionObject fieldValue) { var field = this[nameDecl.Name]; if (nameDecl is JsParameterDeclaration) { // function parameters are handled separately, so if this is a parameter declaration, // then it must be a catch variable. if (field == null) { // no collision - create the catch-error field field = new JsVariableField(JsFieldType.CatchError, nameDecl.Name, 0, null) { OriginalContext = nameDecl.NameContext, IsDeclared = true }; this.AddField(field); } else { // it's an error to declare anything in the catch scope with the same name as the // error variable field.OriginalContext.HandleError(JsError.DuplicateCatch, true); } } else { if (field == null) { // could be global or local depending on the scope, so let the scope create it. field = this.CreateField(nameDecl.Name, null, 0); field.OriginalContext = nameDecl.NameContext; field.IsDeclared = true; field.IsFunction = (nameDecl is JsFunctionObject); field.FieldValue = fieldValue; // if this field is a constant, mark it now var lexDeclaration = nameDecl.Parent as JsLexicalDeclaration; field.InitializationOnly = nameDecl.Parent is JsConstStatement || (lexDeclaration != null && lexDeclaration.StatementToken == JsToken.Const); this.AddField(field); } else { // already defined! // if this is a lexical declaration, then it's an error because we have two // lexical declarations with the same name in the same scope. if (nameDecl.Parent is JsLexicalDeclaration) { nameDecl.NameContext.HandleError(JsError.DuplicateLexicalDeclaration, true); } if (nameDecl.Initializer != null) { // if this is an initialized declaration, then the var part is // superfluous and the "initializer" is really a lookup assignment. // So bump up the ref-count for those cases. var nameReference = nameDecl as IJsNameReference; if (nameReference != null) { field.AddReference(nameReference); } } // don't clobber an existing field value with null. For instance, the last // function declaration is the winner, so always set the value if we have something, // but a var following a function shouldn't reset it to null. if (fieldValue != null) { field.FieldValue = fieldValue; } } } nameDecl.VariableField = field; field.Declarations.Add(nameDecl); // if this scope is within a with-statement, or if the declaration was flagged // as not being renamable, then mark the field as not crunchable if (IsInWithScope || nameDecl.RenameNotAllowed) { field.CanCrunch = false; } }
public override JsVariableField CreateField(JsVariableField outerField) { // should NEVER try to create an inner field in a global scope throw new NotImplementedException(); }
public virtual JsVariableField CreateInnerField(JsVariableField outerField) { JsVariableField innerField = null; if (outerField != null) { // create a new inner field to be added to our scope innerField = CreateField(outerField); AddField(innerField); } return innerField; }
public void SetCatchVariable(JsVariableField field) { CatchParameter.VariableField = field; }
private static void MakeExpectedGlobal(JsVariableField varField) { // to make this an expected global, we're going to change the type of this field, // then just keep walking up the outer field references doing the same do { varField.FieldType = JsFieldType.Global; varField = varField.OuterField; } while (varField != null); }
public override JsVariableField CreateField(JsVariableField outerField) { // when we create a field inside a with-scope, it's ALWAYS a with-field, no matter // what type the outer reference is. return new JsVariableField(JsFieldType.WithField, outerField); }
private void UnreferencedVariable(JsVariableField variableField) { var throwWarning = true; // not a function, not an argument, not a catch-arg, not a global. // not referenced. If there's a single definition, and it either has no // initializer or the initializer is constant, get rid of it. // (unless we aren't removing unneeded code, or the scope is unknown) if (variableField.Declarations.Count == 1 && this.IsKnownAtCompileTime) { var varDecl = variableField.OnlyDeclaration as JsVariableDeclaration; if (varDecl != null) { var declaration = varDecl.Parent as JsDeclaration; if (declaration != null && (varDecl.Initializer == null || varDecl.Initializer.IsConstant)) { // if the decl parent is a for-in and the decl is the variable part // of the statement, then just leave it alone. Don't even throw a warning var forInStatement = declaration.Parent as JsForIn; if (forInStatement != null && declaration == forInStatement.Variable) { // just leave it alone, and don't even throw a warning for it. // TODO: try to reuse some pre-existing variable, or maybe replace // this vardecl with a ref to an unused parameter if this is inside // a function. throwWarning = false; } else if (m_settings.RemoveUnneededCode && m_settings.IsModificationAllowed(JsTreeModifications.RemoveUnusedVariables)) { variableField.Declarations.Remove(varDecl); // don't "remove" the field if it's a ghost to another field if (variableField.GhostedField == null) { variableField.WasRemoved = true; } // remove the vardecl from the declaration list, and if the // declaration list is now empty, remove it, too declaration.Remove(varDecl); if (declaration.Count == 0) { declaration.Parent.ReplaceChild(declaration, null); } } } else if (varDecl.Parent is JsForIn) { // then this is okay throwWarning = false; } } } if (throwWarning && variableField.HasNoReferences) { // not referenced -- throw a warning, assuming it hasn't been "removed" // via an optimization or something. variableField.OriginalContext.HandleError( JsError.VariableDefinedNotReferenced, false); } }
public virtual JsVariableField CreateField(JsVariableField outerField) { // use the same type as the outer field by default return(outerField.IfNotNull(o => new JsVariableField(o.FieldType, o))); }
private static void ResolveGhostedFunctions(JsActivationObject scope, JsFunctionObject funcObject) { var functionField = funcObject.VariableField; // let's check on ghosted names in the outer variable scope var ghostField = scope[funcObject.Name]; if (ghostField == null) { // nothing; good to go. Add a ghosted field to keep track of it. ghostField = new JsVariableField(JsFieldType.GhostFunction, funcObject.Name, 0, funcObject) { OriginalContext = functionField.OriginalContext, CanCrunch = funcObject.VariableField.IfNotNull(v => v.CanCrunch) }; scope.AddField(ghostField); } else if (ghostField.FieldType == JsFieldType.GhostFunction) { // there is, but it's another ghosted function expression. // what if a lookup is resolved to this field later? We probably still need to // at least flag it as ambiguous. We will only need to throw an error, though, // if someone actually references the outer ghost variable. ghostField.IsAmbiguous = true; } else { // something already exists. Could be a naming collision for IE or at least a // a cross-browser behavior difference if it's not coded properly. // mark this field as a function, even if it wasn't before ghostField.IsFunction = true; if (ghostField.OuterField != null) { // if the pre-existing field we are ghosting is a reference to // an OUTER field, then we actually have a problem that creates a BIG // difference between older IE browsers and everything else. // modern browsers will have the link to the outer field, but older // IE browsers will link to this function expression! // fire a cross-browser error warning ghostField.IsAmbiguous = true; funcObject.IdContext.HandleError(JsError.AmbiguousNamedFunctionExpression); } else if (ghostField.IsReferenced) { // if the ghosted field isn't even referenced, then who cares? // but it is referenced. Let's see if it matters. // something like: var nm = function nm() {} // is TOTALLY cool common cross-browser syntax. var parentVarDecl = funcObject.Parent as JsVariableDeclaration; if (parentVarDecl == null || parentVarDecl.Name != funcObject.Name) { // see if it's a simple assignment. // something like: var nm; nm = function nm(){}, // would also be cool, although less-common than the vardecl version. JsLookup lookup; var parentAssignment = funcObject.Parent as JsBinaryOperator; if (parentAssignment == null || parentAssignment.OperatorToken != JsToken.Assign || parentAssignment.Operand2 != funcObject || (lookup = parentAssignment.Operand1 as JsLookup) == null || lookup.Name != funcObject.Name) { // something else. Flag it as ambiguous. ghostField.IsAmbiguous = true; } } } } // link them so they all keep the same name going forward // (since they are named the same in the sources) functionField.OuterField = ghostField; // TODO: this really should be a LIST of ghosted fields, since multiple // elements can ghost to the same field. ghostField.GhostedField = functionField; // if the actual field has references, we want to bubble those up // since we're now linking those fields if (functionField.RefCount > 0) { // add the function's references to the ghost field ghostField.AddReferences(functionField.References); } }
public virtual JsVariableField CreateField(JsVariableField outerField) { // use the same type as the outer field by default return outerField.IfNotNull(o => new JsVariableField(o.FieldType, o)); }
internal JsVariableField AddField(JsVariableField variableField) { // add it to our name table NameTable[variableField.Name] = variableField; // set the owning scope to this is we are the outer field, or the outer field's // owning scope if this is an inner field variableField.OwningScope = variableField.OuterField == null ? this : variableField.OuterField.OwningScope; return variableField; }
/// <summary> /// returns true if the fields point to the same ultimate reference object. /// Needs to walk up the outer-reference chain for each field in order to /// find the ultimate reference /// </summary> /// <param name="otherField"></param> /// <returns></returns> public bool IsSameField(JsVariableField otherField) { // shortcuts -- if they are already the same object, we're done; // and if the other field is null, then we are NOT the same object. if (this == otherField) { return true; } else if (otherField == null) { return false; } // get the ultimate field for this field var thisOuter = OuterField != null ? OuterField : this; while (thisOuter.OuterField != null) { thisOuter = thisOuter.OuterField; } // get the ultimate field for the other field var otherOuter = otherField.OuterField != null ? otherField.OuterField : otherField; while (otherOuter.OuterField != null) { otherOuter = otherOuter.OuterField; } // now that we have the same outer fields, check to see if they are the same return thisOuter == otherOuter; }
private static bool ContainsReference(JsAstNode node, JsVariableField targetField) { // if this is a lookup to the target field, return true and be done var lookup = node as JsLookup; if (lookup != null) { if (lookup.VariableField != null) { // see if the fields are the same return lookup.VariableField == targetField; } else { // no variable field -- match the name, just in case return string.CompareOrdinal(lookup.Name, targetField.Name) == 0; } } // recurse through each child (if any). If any one returns true, // then stop processing and return true. foreach (var child in node.Children) { if (ContainsReference(child, targetField)) { return true; } } // if we get here, there were no matches return false; }