private static int RelocateFunction(JsBlock block, int insertAt, JsFunctionObject funcDecl)
        {
            if (block[insertAt] != funcDecl)
            {
                // technically function declarations can only be direct children of the program or a function block.
                // and since we are passing in such a block, the parent of the function declaration better be that
                // block. If it isn't, we don't want to move it because it's not in an allowed place, and different
                // browsers treat that situation differently. Some browsers would process such funcdecls as if
                // they were a direct child of the main block. Others will treat it like a function expression with
                // an external name, and only assign the function to the name if that line of code is actually
                // executed. So since there's a difference, just leave them as-is and only move valid funcdecls.
                if (funcDecl.Parent == block)
                {
                    // remove the function from it's parent, which will take it away from where it is right now.
                    funcDecl.Parent.ReplaceChild(funcDecl, null);

                    // now insert it into the block at the new location, incrementing the location so the next function
                    // will be inserted after it. It is important that they be in the same order as the source, or the semantics
                    // will change when there are functions with the same name.
                    block.Insert(insertAt++, funcDecl);
                }
            }
            else
            {
                // we're already in the right place. Just increment the pointer to move to the next position
                // for next time
                ++insertAt;
            }

            // return the new position
            return(insertAt);
        }
 public void Visit(JsFunctionObject node)
 {
     // this shouldn't be called for anything but a function expression,
     // which is definitely NOT safe to start a statement off because it would
     // then be interpreted as a function *declaration*.
     Debug.Assert(node == null || node.FunctionType == JsFunctionType.Expression);
     m_isSafe = false;
 }
Ejemplo n.º 3
0
        internal JsFunctionScope(JsActivationObject parent, bool isExpression, JsSettings settings, JsFunctionObject funcObj)
            : base(parent, settings)
        {
            m_refScopes = new HashSet<JsActivationObject>();
            if (isExpression)
            {
                // parent scopes automatically reference enclosed function expressions
                AddReference(Parent);
            }

            FunctionObject = funcObj;
        }
Ejemplo n.º 4
0
        internal JsFunctionScope(JsActivationObject parent, bool isExpression, JsSettings settings, JsFunctionObject funcObj)
            : base(parent, settings)
        {
            m_refScopes = new HashSet <JsActivationObject>();
            if (isExpression)
            {
                // parent scopes automatically reference enclosed function expressions
                AddReference(Parent);
            }

            FunctionObject = funcObj;
        }
        public override void Visit(JsFunctionObject node)
        {
            if (node != null)
            {
                // if we are reordering ANYTHING, then we need to do the reordering on a scope level.
                // so if that's the case, we need to create a list of all the child functions and NOT
                // recurse at this point. Then we'll reorder, then we'll use the lists to recurse.
                // BUT if we are not reordering anything, no sense making the lists and recursing later.
                // if that's the case, we can just recurse now and not have to worry about anything later.
                if (m_moveVarStatements || m_moveFunctionDecls)
                {
                    // add the node to the appropriate list: either function expression or function declaration.
                    // assume if it's not a function declaration, it must be an expression since the other types
                    // are not declaration (getter, setter) and we want to treat declarations special.
                    // if the conditional comment level isn't zero, then something funky is going on with
                    // the conditional-compilation statements, and we don't want to move the declarations, so
                    // don't add them to the declaration list. But we still want to recurse them, so add them
                    // to the expression list (which get recursed but not moved).
                    if (node.FunctionType == JsFunctionType.Declaration && m_conditionalCommentLevel == 0)
                    {
                        if (m_functionDeclarations == null)
                        {
                            m_functionDeclarations = new List <JsFunctionObject>();
                        }

                        m_functionDeclarations.Add(node);
                    }
                    else
                    {
                        if (m_functionExpressions == null)
                        {
                            m_functionExpressions = new List <JsFunctionObject>();
                        }

                        m_functionExpressions.Add(node);
                    }

                    // BUT DO NOT RECURSE!!!!
                    // we only want the functions and variables in THIS scope, not child function scopes.
                    //base.Visit(node);
                }
                else
                {
                    // we're not reordering, so just recurse now to save the hassle
                    base.Visit(node);
                }
            }
        }
        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 override void Visit(JsFunctionObject node)
        {
            if (node != null)
            {
                // if we are reordering ANYTHING, then we need to do the reordering on a scope level.
                // so if that's the case, we need to create a list of all the child functions and NOT
                // recurse at this point. Then we'll reorder, then we'll use the lists to recurse.
                // BUT if we are not reordering anything, no sense making the lists and recursing later.
                // if that's the case, we can just recurse now and not have to worry about anything later.
                if (m_moveVarStatements || m_moveFunctionDecls)
                {
                    // add the node to the appropriate list: either function expression or function declaration.
                    // assume if it's not a function declaration, it must be an expression since the other types
                    // are not declaration (getter, setter) and we want to treat declarations special.
                    // if the conditional comment level isn't zero, then something funky is going on with
                    // the conditional-compilation statements, and we don't want to move the declarations, so
                    // don't add them to the declaration list. But we still want to recurse them, so add them
                    // to the expression list (which get recursed but not moved).
                    if (node.FunctionType == JsFunctionType.Declaration && m_conditionalCommentLevel == 0)
                    {
                        if (m_functionDeclarations == null)
                        {
                            m_functionDeclarations = new List<JsFunctionObject>();
                        }

                        m_functionDeclarations.Add(node);
                    }
                    else
                    {
                        if (m_functionExpressions == null)
                        {
                            m_functionExpressions = new List<JsFunctionObject>();
                        }

                        m_functionExpressions.Add(node);
                    }

                    // BUT DO NOT RECURSE!!!!
                    // we only want the functions and variables in THIS scope, not child function scopes.
                    //base.Visit(node);
                }
                else
                {
                    // we're not reordering, so just recurse now to save the hassle
                    base.Visit(node);
                }
            }
        }
 public void Visit(JsFunctionObject node)
 {
     // invalid! ignore
     IsValid = false;
 }
 public void Visit(JsFunctionObject node)
 {
     // not applicable; terminate
 }
Ejemplo n.º 10
0
 public void Visit(JsFunctionObject node)
 {
     // not applicable; terminate
 }
Ejemplo n.º 11
0
        /// <summary>
        /// Parse the source code using the given settings, getting back an abstract syntax tree Block node as the root
        /// representing the list of statements in the source code.
        /// </summary>
        /// <param name="settings">code settings to use to process the source code</param>
        /// <returns>root Block node representing the top-level statements</returns>
        public JsBlock Parse(JsSettings settings)
        {
            // initialize the scanner with our settings
            // make sure the RawTokens setting is OFF or we won't be able to create our AST
            InitializeScanner(settings);

            // make sure we initialize the global scope's strict mode to our flag, whether or not it
            // is true. This means if the setting is false, we will RESET the flag to false if we are
            // reusing the scope and a previous Parse call had code that set it to strict with a
            // program directive.
            GlobalScope.UseStrict = m_settings.StrictMode;

            // make sure the global scope knows about our known global names
            GlobalScope.SetAssumedGlobals(m_settings);

            // start of a new module
            m_newModule = true;

            var timePoints = m_timingPoints = new long[9];
            var timeIndex = timePoints.Length;
            var stopWatch = new Stopwatch();
            stopWatch.Start();

            JsBlock scriptBlock = null;
            JsBlock returnBlock = null;
            try
            {
                switch (m_settings.SourceMode)
                {
                    case JsSourceMode.Program:
                        // simply parse a block of statements
                        returnBlock = scriptBlock = ParseStatements();
                        break;

                    case JsSourceMode.Expression:
                        // create a block, get the first token, add in the parse of a single expression,
                        // and we'll go fron there.
                        returnBlock = scriptBlock = new JsBlock(CurrentPositionContext(), this);
                        GetNextToken();
                        try
                        {
                            var expr = ParseExpression();
                            if (expr != null)
                            {
                                scriptBlock.Append(expr);
                                scriptBlock.UpdateWith(expr.Context);
                            }
                        }
                        catch (EndOfStreamException)
                        {
                            Debug.WriteLine("EOF");
                        }
                        break;

                    case JsSourceMode.EventHandler:
                        // we're going to create the global block, add in a function expression with a single
                        // parameter named "event", and then we're going to parse the input as the body of that
                        // function expression. We're going to resolve the global block, but only return the body
                        // of the function.
                        scriptBlock = new JsBlock(null, this);

                        var parameters = new JsAstNodeList(null, this);
                        parameters.Append(new JsParameterDeclaration(null, this)
                            {
                                Name = "event",
                                RenameNotAllowed = true
                            });

                        var funcExpression = new JsFunctionObject(null, this)
                            {
                                FunctionType = JsFunctionType.Expression,
                                ParameterDeclarations = parameters
                            };
                        scriptBlock.Append(funcExpression);
                        funcExpression.Body = returnBlock = ParseStatements();
                        break;

                    default:
                        Debug.Fail("Unexpected source mode enumeration");
                        return null;
                }
            }
            catch (RecoveryTokenException)
            {
                // this should never happen but let's make SURE we don't expose our
                // private exception object to the outside world
                m_currentToken.HandleError(JsError.ApplicationError, true);
            }

            timePoints[--timeIndex] = stopWatch.ElapsedTicks;

            if (scriptBlock != null)
            {
                // resolve everything
                JsResolutionVisitor.Apply(scriptBlock, GlobalScope, m_settings);
            }

            timePoints[--timeIndex] = stopWatch.ElapsedTicks;

            if (scriptBlock != null && Settings.MinifyCode && !Settings.PreprocessOnly)
            {
                // this visitor doesn't just reorder scopes. It also combines the adjacent var variables,
                // unnests blocks, identifies prologue directives, and sets the strict mode on scopes.
                JsReorderScopeVisitor.Apply(scriptBlock, this);
                timePoints[--timeIndex] = stopWatch.ElapsedTicks;

                // analyze the entire node tree (needed for hypercrunch)
                // root to leaf (top down)
                var analyzeVisitor = new JsAnalyzeNodeVisitor(this);
                scriptBlock.Accept(analyzeVisitor);
                timePoints[--timeIndex] = stopWatch.ElapsedTicks;

                // analyze the scope chain (also needed for hypercrunch)
                // root to leaf (top down)
                GlobalScope.AnalyzeScope();
                timePoints[--timeIndex] = stopWatch.ElapsedTicks;

                // if we want to crunch any names....
                if (m_settings.LocalRenaming != JsLocalRenaming.KeepAll
                    && m_settings.IsModificationAllowed(JsTreeModifications.LocalRenaming))
                {
                    // then do a top-down traversal of the scope tree. For each field that had not
                    // already been crunched (globals and outers will already be crunched), crunch
                    // the name with a crunch iterator that does not use any names in the verboten set.
                    GlobalScope.AutoRenameFields();
                }

                timePoints[--timeIndex] = stopWatch.ElapsedTicks;

                // if we want to evaluate literal expressions, do so now
                if (m_settings.EvalLiteralExpressions)
                {
                    var visitor = new JsEvaluateLiteralVisitor(this);
                    scriptBlock.Accept(visitor);
                }

                timePoints[--timeIndex] = stopWatch.ElapsedTicks;

                // make the final cleanup pass
                JsFinalPassVisitor.Apply(scriptBlock, this);
                timePoints[--timeIndex] = stopWatch.ElapsedTicks;

                // we want to walk all the scopes to make sure that any generated
                // variables that haven't been crunched have been assigned valid
                // variable names that don't collide with any existing variables.
                GlobalScope.ValidateGeneratedNames();
                timePoints[--timeIndex] = stopWatch.ElapsedTicks;
            }

            if (returnBlock != null && returnBlock.Parent != null)
            {
                returnBlock.Parent = null;
            }

            return returnBlock;
        }
 public void Visit(JsFunctionObject node)
 {
     // we're good
 }
 private static string GetPropertyType(JsFunctionObject funcObj)
 {
     // should never be a function declaration....
     return funcObj == null || funcObj.FunctionType == JsFunctionType.Expression
         ? "data"
         : funcObj.FunctionType == JsFunctionType.Getter ? "get" : "set";
 }
Ejemplo n.º 14
0
        /// <summary>
        /// Output everything for a function except the initial keyword
        /// </summary>
        /// <param name="node"></param>
        /// <param name="removeUnused"></param>
        private void OutputFunctionArgsAndBody(JsFunctionObject node, bool removeUnused)
        {
            if (node != null)
            {
                m_startOfStatement = false;
                if (node.ParameterDeclarations != null)
                {
                    Indent();
                    OutputPossibleLineBreak('(');
                    MarkSegment(node, null, node.ParametersContext);

                    // figure out the last referenced argument so we can skip
                    // any that aren't actually referenced
                    int lastRef = node.ParameterDeclarations.Count - 1;

                    // if we're not known at compile time, then we can't leave off unreferenced parameters
                    // (also don't leave things off if we're not hypercrunching)
                    // (also check the kill flag for removing unused parameters)
                    if (removeUnused)
                    {
                        while (lastRef >= 0)
                        {
                            // we want to loop backwards until we either find a parameter that is referenced.
                            // at that point, lastRef will be the index of the last referenced parameter so
                            // we can output from 0 to lastRef
                            var argumentField = (node.ParameterDeclarations[lastRef] as JsParameterDeclaration).IfNotNull(p => p.VariableField);
                            if (argumentField != null && !argumentField.IsReferenced)
                            {
                                --lastRef;
                            }
                            else
                            {
                                // found a referenced parameter, or something weird -- stop looking
                                break;
                            }
                        }
                    }

                    JsAstNode paramDecl = null;
                    for (var ndx = 0; ndx <= lastRef; ++ndx)
                    {
                        if (ndx > 0)
                        {
                            OutputPossibleLineBreak(',');
                            MarkSegment(node, null, paramDecl.IfNotNull(p => p.TerminatingContext) ?? node.ParametersContext);
                            if (m_settings.OutputMode == MinifierOutputMode.MultipleLines)
                            {
                                OutputPossibleLineBreak(' ');
                            }
                        }

                        paramDecl = node.ParameterDeclarations[ndx];
                        if (paramDecl != null)
                        {
                            paramDecl.Accept(this);
                        }
                    }

                    Unindent();
                    OutputPossibleLineBreak(')');
                    MarkSegment(node, null, node.ParametersContext);
                }

                if (node.Body == null || node.Body.Count == 0)
                {
                    Output("{}");
                    MarkSegment(node, null, node.Body.IfNotNull(b => b.Context));
                    BreakLine(false);
                }
                else
                {
                    if (m_settings.BlocksStartOnSameLine == MinifierBlockStart.NewLine
                        || (m_settings.BlocksStartOnSameLine == MinifierBlockStart.UseSource && node.Body.BraceOnNewLine))
                    {
                        NewLine();
                    }
                    else if (m_settings.OutputMode == MinifierOutputMode.MultipleLines)
                    {
                        OutputPossibleLineBreak(' ');
                    }

                    node.Body.Accept(this);
                }
            }
        }
        private static int RelocateFunction(JsBlock block, int insertAt, JsFunctionObject funcDecl)
        {
            if (block[insertAt] != funcDecl)
            {
                // technically function declarations can only be direct children of the program or a function block.
                // and since we are passing in such a block, the parent of the function declaration better be that
                // block. If it isn't, we don't want to move it because it's not in an allowed place, and different
                // browsers treat that situation differently. Some browsers would process such funcdecls as if
                // they were a direct child of the main block. Others will treat it like a function expression with
                // an external name, and only assign the function to the name if that line of code is actually
                // executed. So since there's a difference, just leave them as-is and only move valid funcdecls.
                if (funcDecl.Parent == block)
                {
                    // remove the function from it's parent, which will take it away from where it is right now.
                    funcDecl.Parent.ReplaceChild(funcDecl, null);

                    // now insert it into the block at the new location, incrementing the location so the next function
                    // will be inserted after it. It is important that they be in the same order as the source, or the semantics
                    // will change when there are functions with the same name.
                    block.Insert(insertAt++, funcDecl);
                }
            }
            else
            {
                // we're already in the right place. Just increment the pointer to move to the next position
                // for next time
                ++insertAt;
            }

            // return the new position
            return insertAt;
        }
        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;
            }
        }
Ejemplo n.º 17
0
        public void Visit(JsFunctionObject node)
        {
            if (node != null)
            {
                // it's a declaration; put the index to -1.
                node.Index = -1;

                // create a function scope, assign it to the function object,
                // and push it on the stack
                var parentScope = CurrentLexicalScope;
                if (node.FunctionType == JsFunctionType.Expression &&
                    !string.IsNullOrEmpty(node.Name))
                {
                    // function expressions have an intermediate scope between the parent and the
                    // function's scope that contains just the function name so the function can
                    // be self-referencing without the function expression polluting the parent scope.
                    // don't add the function name field yet, because it's not a decl per se.
                    parentScope = new JsFunctionScope(parentScope, true, m_settings, node)
                    {
                        IsInWithScope = m_withDepth > 0
                    };

                    // add this function object to the list of function objects the variable scope
                    // will need to ghost later
                    CurrentVariableScope.GhostedFunctions.Add(node);
                }

                node.FunctionScope = new JsFunctionScope(parentScope, node.FunctionType != JsFunctionType.Declaration, m_settings, node)
                {
                    IsInWithScope = m_withDepth > 0
                };
                m_lexicalStack.Push(node.FunctionScope);
                m_variableStack.Push(node.FunctionScope);

                var savedIndex = m_orderIndex;
                try
                {
                    // recurse into the function to handle it after saving the current index and resetting it
                    if (node.Body != null)
                    {
                        m_orderIndex = 0;
                        node.Body.Accept(this);
                    }
                }
                finally
                {
                    Debug.Assert(CurrentLexicalScope == node.FunctionScope);
                    m_lexicalStack.Pop();
                    m_variableStack.Pop();

                    m_orderIndex = savedIndex;
                }

                // nothing to add to the var-decl list.
                // but add the function name to the current lex-decl list
                // IF it is a declaration and it has a name (and it SHOULD unless there was an error)
                if (node.FunctionType == JsFunctionType.Declaration &&
                    !string.IsNullOrEmpty(node.Name))
                {
                    var lexicalScope = CurrentLexicalScope;
                    lexicalScope.LexicallyDeclaredNames.Add(node);

                    if (lexicalScope != CurrentVariableScope)
                    {
                        // the current lexical scope is the variable scope.
                        // this is ES6 syntax: a function declaration inside a block scope. Not allowed
                        // in ES5 code, so throw a warning and ghost this function in the outer variable scope
                        // to make sure that we don't generate any naming collisions.
                        node.NameContext.HandleError(JsError.MisplacedFunctionDeclaration, false);
                        CurrentVariableScope.GhostedFunctions.Add(node);
                    }
                }
            }
        }
Ejemplo n.º 18
0
        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);
            }
        }
Ejemplo n.º 19
0
        public void Visit(JsFunctionObject node)
        {
            if (node != null)
            {
                var symbol = StartSymbol(node);

                var isNoIn = m_noIn;
                m_noIn = false;

                var encloseInParens = node.IsExpression && m_startOfStatement;
                if (encloseInParens)
                {
                    OutputPossibleLineBreak('(');
                }

                // get the function name we will use for symbol references.
                // use the function's real name if:
                //    1. there is one AND
                //      2a. the function is a declaration OR
                //      2b. the refcount is greater than zero OR
                //      2c. we aren't going to remove function expression names
                // otherwise use the name guess.
                var hasName = !node.Name.IsNullOrWhiteSpace()
                        && (!node.IsExpression
                        || node.RefCount > 0
                        || !m_settings.RemoveFunctionExpressionNames
                        || !m_settings.IsModificationAllowed(JsTreeModifications.RemoveFunctionExpressionNames));
                var fullFunctionName = hasName
                        ? node.Name
                        : node.NameGuess;

                Output("function");
                MarkSegment(node, fullFunctionName, node.Context);
                SetContextOutputPosition(node.Context);

                m_startOfStatement = false;
                bool isAnonymous = true;
                if (!node.Name.IsNullOrWhiteSpace())
                {
                    isAnonymous = false;
                    var minFunctionName = node.VariableField != null
                        ? node.VariableField.ToString()
                        : node.Name;
                    if (m_settings.SymbolsMap != null)
                    {
                        m_functionStack.Push(minFunctionName);
                    }

                    if (hasName)
                    {
                        // all identifier should be treated as if they start with a valid
                        // identifier character. That might not always be the case, like when
                        // we consider an ASP.NET block to output the start of an identifier.
                        // so let's FORCE the insert-space logic here.
                        if (JsScanner.IsValidIdentifierPart(m_lastCharacter))
                        {
                            Output(' ');
                        }

                        Output(minFunctionName);
                        MarkSegment(node, node.Name, node.IdContext);
                        SetContextOutputPosition(node.NameContext);
                    }
                }

                if (m_settings.SymbolsMap != null && isAnonymous)
                {
                    JsBinaryOperator binaryOperator = node.Parent as JsBinaryOperator;
                    if (binaryOperator != null && binaryOperator.Operand1 is JsLookup)
                    {
                        m_functionStack.Push("(anonymous) [{0}]".FormatInvariant(binaryOperator.Operand1));
                    }
                    else
                    {
                        m_functionStack.Push("(anonymous)");
                    }
                }

                OutputFunctionArgsAndBody(node, m_settings.RemoveUnneededCode
                    && node.EnclosingScope.IsKnownAtCompileTime
                    && m_settings.MinifyCode
                    && m_settings.IsModificationAllowed(JsTreeModifications.RemoveUnusedParameters));

                if (encloseInParens)
                {
                    OutputPossibleLineBreak(')');
                }

                m_noIn = isNoIn;

                EndSymbol(symbol);

                if (m_settings.SymbolsMap != null)
                {
                    m_functionStack.Pop();
                }
            }
        }
Ejemplo n.º 20
0
 public void Visit(JsFunctionObject node)
 {
     // invalid! ignore
     IsValid = false;
 }
        public void Visit(JsFunctionObject node)
        {
            if (node != null)
            {
                // it's a declaration; put the index to -1.
                node.Index = -1;

                // create a function scope, assign it to the function object,
                // and push it on the stack
                var parentScope = CurrentLexicalScope;
                if (node.FunctionType == JsFunctionType.Expression
                    && !string.IsNullOrEmpty(node.Name))
                {
                    // function expressions have an intermediate scope between the parent and the
                    // function's scope that contains just the function name so the function can
                    // be self-referencing without the function expression polluting the parent scope.
                    // don't add the function name field yet, because it's not a decl per se.
                    parentScope = new JsFunctionScope(parentScope, true, m_settings, node)
                    {
                        IsInWithScope = m_withDepth > 0
                    };

                    // add this function object to the list of function objects the variable scope
                    // will need to ghost later
                    CurrentVariableScope.GhostedFunctions.Add(node);
                }

                node.FunctionScope = new JsFunctionScope(parentScope, node.FunctionType != JsFunctionType.Declaration, m_settings, node)
                {
                    IsInWithScope = m_withDepth > 0
                };
                m_lexicalStack.Push(node.FunctionScope);
                m_variableStack.Push(node.FunctionScope);

                var savedIndex = m_orderIndex;
                try
                {
                    // recurse into the function to handle it after saving the current index and resetting it
                    if (node.Body != null)
                    {
                        m_orderIndex = 0;
                        node.Body.Accept(this);
                    }
                }
                finally
                {
                    Debug.Assert(CurrentLexicalScope == node.FunctionScope);
                    m_lexicalStack.Pop();
                    m_variableStack.Pop();

                    m_orderIndex = savedIndex;
                }

                // nothing to add to the var-decl list.
                // but add the function name to the current lex-decl list
                // IF it is a declaration and it has a name (and it SHOULD unless there was an error)
                if (node.FunctionType == JsFunctionType.Declaration &&
                    !string.IsNullOrEmpty(node.Name))
                {
                    var lexicalScope = CurrentLexicalScope;
                    lexicalScope.LexicallyDeclaredNames.Add(node);

                    if (lexicalScope != CurrentVariableScope)
                    {
                        // the current lexical scope is the variable scope.
                        // this is ES6 syntax: a function declaration inside a block scope. Not allowed
                        // in ES5 code, so throw a warning and ghost this function in the outer variable scope
                        // to make sure that we don't generate any naming collisions.
                        node.NameContext.HandleError(JsError.MisplacedFunctionDeclaration, false);
                        CurrentVariableScope.GhostedFunctions.Add(node);
                    }
                }
            }
        }
        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;
            }
        }
Ejemplo n.º 23
0
 public void Visit(JsFunctionObject node)
 {
     // we're good
 }
 public void Visit(JsFunctionObject node)
 {
     // this shouldn't be called for anything but a function expression,
     // which is definitely NOT safe to start a statement off because it would
     // then be interpreted as a function *declaration*.
     Debug.Assert(node == null || node.FunctionType == JsFunctionType.Expression);
     m_isSafe = 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 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 override void Visit(JsFunctionObject node)
        {
            if (node != null)
            {
                // get the name of this function, calculate something if it's anonymous or if
                // the name isn't actually referenced
                if (node.Name.IsNullOrWhiteSpace()
                    || (node.IsExpression
                        && node.RefCount == 0
                        && m_parser.Settings.RemoveFunctionExpressionNames
                        && m_parser.Settings.IsModificationAllowed(JsTreeModifications.RemoveFunctionExpressionNames)))
                {
                    node.NameGuess = GuessAtName(node);
                }

                // don't analyze the identifier or we'll add an extra reference to it.
                // and we don't need to analyze the parameters because they were fielded-up
                // back when the function object was created, too

                if (m_scopeStack.Peek().UseStrict)
                {
                    // if this is a function delcaration, it better be a source element.
                    // if not, we want to throw a warning that different browsers will treat this function declaration
                    // differently. Technically, this location is not allowed. IE and most other browsers will
                    // simply treat it like every other function declaration in this scope. Firefox, however, won't
                    // add this function declaration's name to the containing scope until the function declaration
                    // is actually "executed." So if you try to call it BEFORE, you will get a "not defined" error.

                    // TODO: take this out for now, because we will throw this error in the resolution process
                    // until we can get this worked out properly.
                    //if (!node.IsSourceElement && node.FunctionType == FunctionType.Declaration)
                    //{
                    //    (node.NameContext ?? node.Context).HandleError(JSError.MisplacedFunctionDeclaration, true);
                    //}

                    // we need to make sure the function isn't named "eval" or "arguments"
                    if (string.CompareOrdinal(node.Name, "eval") == 0
                        || string.CompareOrdinal(node.Name, "arguments") == 0)
                    {
                        if (node.IdContext != null)
                        {
                            node.IdContext.HandleError(JsError.StrictModeFunctionName, true);
                        }
                        else if (node.Context != null)
                        {
                            node.Context.HandleError(JsError.StrictModeFunctionName, true);
                        }
                    }

                    // we need to make sure:
                    //  1. there are no duplicate argument names, and
                    //  2. none of them are named "eval" or "arguments"
                    // create map that we'll use to determine if there are any dups
                    if (node.ParameterDeclarations != null
                        && node.ParameterDeclarations.Count > 0)
                    {
                        var parameterMap = new HashSet<string>();
                        foreach (var parameter in node.ParameterDeclarations)
                        {
                            // if it already exists in the map, then it's a dup
                            var parameterName = (parameter as JsParameterDeclaration).IfNotNull(p => p.Name);
                            if (parameterMap.Add(parameterName))
                            {
                                // now check to see if it's one of the two forbidden names
                                if (string.CompareOrdinal(parameterName, "eval") == 0
                                    || string.CompareOrdinal(parameterName, "arguments") == 0)
                                {
                                    parameter.Context.HandleError(JsError.StrictModeArgumentName, true);
                                }
                            }
                            else
                            {
                                // already exists -- throw an error
                                parameter.Context.HandleError(JsError.StrictModeDuplicateArgument, true);
                            }
                        }
                    }
                }
                else if (node.ParameterDeclarations != null
                    && node.ParameterDeclarations.Count > 0)
                {
                    // not strict
                    // if there are duplicate parameter names, throw a warning
                    var parameterMap = new HashSet<string>();
                    foreach (var parameter in node.ParameterDeclarations)
                    {
                        // if it already exists in the map, then it's a dup
                        var parameterName = (parameter as JsParameterDeclaration).IfNotNull(p => p.Name);
                        if (!parameterMap.Add(parameterName))
                        {
                            // already exists -- throw an error
                            parameter.Context.HandleError(JsError.DuplicateName, false);
                        }
                    }
                }

                if (node.Body != null)
                {
                    // push the stack and analyze the body
                    m_scopeStack.Push(node.FunctionScope);
                    try
                    {
                        // recurse the body
                        node.Body.Accept(this);
                    }
                    finally
                    {
                        m_scopeStack.Pop();
                    }
                }
            }
        }