public static void Apply(JsBlock block, JsParser parser) { // create a new instance of the visitor and apply it to the block var visitor = new JsReorderScopeVisitor(parser); block.Accept(visitor); // if there were any module directive prologues we need to promote, do them first var insertAt = 0; if (visitor.m_moduleDirectives != null) { foreach (var directivePrologue in visitor.m_moduleDirectives) { insertAt = RelocateDirectivePrologue(block, insertAt, directivePrologue); } } // Make sure that we skip over any remaining comments and directive prologues. // we do NOT want to insert anything between the start of the scope and any directive prologues. while (insertAt < block.Count && (block[insertAt] is JsDirectivePrologue || block[insertAt] is JsImportantComment)) { ++insertAt; } // first, we want to move all function declarations to the top of this block if (visitor.m_functionDeclarations != null) { foreach (var funcDecl in visitor.m_functionDeclarations) { insertAt = RelocateFunction(block, insertAt, funcDecl); } } // special case: if there is only one var statement in the entire scope, // then just leave it alone because we will only add bytes by moving it around, // or be byte-neutral at best (no initializers and not in a for-statement). if (visitor.m_varStatements != null && visitor.m_varStatements.Count > 1) { // then we want to move all variable declarations after to the top (after the functions) foreach (var varStatement in visitor.m_varStatements) { insertAt = RelocateVar(block, insertAt, varStatement); } } // then we want to do the same thing for all child functions (declarations AND other) if (visitor.m_functionDeclarations != null) { foreach (var funcDecl in visitor.m_functionDeclarations) { Apply(funcDecl.Body, parser); } } if (visitor.m_functionExpressions != null) { foreach (var funcExpr in visitor.m_functionExpressions) { Apply(funcExpr.Body, parser); } } }
/// <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; }
private void OutputBlockWithBraces(JsBlock block) { if (m_settings.BlocksStartOnSameLine == MinifierBlockStart.NewLine || (m_settings.BlocksStartOnSameLine == MinifierBlockStart.UseSource && block.BraceOnNewLine)) { NewLine(); } else if (m_settings.OutputMode == MinifierOutputMode.MultipleLines) { OutputPossibleLineBreak(' '); } block.Accept(this); }