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;
            });
        }
 public override JsVariableField CreateField(JsVariableField outerField)
 {
     // should NEVER try to create an inner field in a global scope
     throw new NotImplementedException();
 }
 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);
     }
 }
        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);
            }
        }
        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;
                }
            }
        }
        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);
                }
            }
        }
        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);
                }
            }
        }
        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;
        }
        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 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));
 }
        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 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 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;
        }
        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));
                                }
                            }
                        }
                    }
                }
            }
        }
Esempio n. 17
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);
 }
        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;
            }
        }
Esempio n. 19
0
 public void SetCatchVariable(JsVariableField field)
 {
     CatchParameter.VariableField = field;
 }
        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;
        }