Пример #1
0
        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);
        }
Пример #2
0
        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);
                }
            }
        }
Пример #3
0
        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);
            }
        }
Пример #4
0
        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 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);
                }
            }
        }
Пример #6
0
        /// <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 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);
            }
        }
 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);
 }
        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);
        }
Пример #11
0
        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));
        }
Пример #15
0
        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;
                }
            }
        }
 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)));
 }
Пример #18
0
 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));
 }
Пример #19
0
 public override JsVariableField CreateField(JsVariableField outerField)
 {
     // should NEVER try to create an inner field in a global scope
     throw new NotImplementedException();
 }
        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;
            }
        }
        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);
            }
        }
        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);
            }
        }
Пример #23
0
 public void SetCatchVariable(JsVariableField field)
 {
     CatchParameter.VariableField = field;
 }