public void Visit(JsForNode node) { if (node != null) { node.Index = NextOrderIndex; if (node.Initializer != null) { // if the variable portion of the for-in statement is a lexical // declaration, then we will create the block node for its body right now // and add the declaration. This will prevent the body from both creating // a new lexical scope and from deleting an empty one. var lexDeclaration = node.Initializer as JsLexicalDeclaration; if (lexDeclaration != null) { // create the scope on the block node.BlockScope = new JsBlockScope(CurrentLexicalScope, node.Context, m_settings) { IsInWithScope = m_withDepth > 0 }; m_lexicalStack.Push(node.BlockScope); } } try { if (node.Initializer != null) { node.Initializer.Accept(this); } if (node.Condition != null) { node.Condition.Accept(this); } if (node.Body != null) { node.Body.Accept(this); } if (node.Incrementer != null) { node.Incrementer.Accept(this); } } finally { if (node.BlockScope != null) { Debug.Assert(CurrentLexicalScope == node.BlockScope); m_lexicalStack.Pop(); } } } }
public void Visit(JsForNode node) { // not applicable; terminate }
public void Visit(JsForNode node) { // invalid! ignore IsValid = false; }
public void Visit(JsForNode node) { Debug.Fail("shouldn't get here"); }
public override void Visit(JsBlock node) { if (node != null) { // if this block has a block scope, then look at the lexically-declared names (if any) // and throw an error if any are defined as var's within this scope (ES6 rules). // if this is the body of a function object, use the function scope. JsActivationObject lexicalScope = node.BlockScope; if (lexicalScope == null) { var functionObject = node.Parent as JsFunctionObject; if (functionObject != null) { lexicalScope = functionObject.FunctionScope; } } if (lexicalScope != null) { foreach (var lexDecl in lexicalScope.LexicallyDeclaredNames) { var varDecl = lexicalScope.VarDeclaredName(lexDecl.Name); if (varDecl != null) { // collision. // if the lexical declaration is a let or const declaration (as opposed to a function declaration), // then force the warning to an error. This is so the function declaration will remain a warning if // it collides with a var. varDecl.NameContext.HandleError(JsError.DuplicateLexicalDeclaration, lexDecl is JsLexicalDeclaration); // mark them both a no-rename to preserve the collision in the output lexDecl.VariableField.IfNotNull(v => v.CanCrunch = false); varDecl.VariableField.IfNotNull(v => v.CanCrunch = false); } } } // we might things differently if these statements are the body collection for a function // because we can assume the implicit return statement at the end of it bool isFunctionLevel = (node.Parent is JsFunctionObject); // if we want to remove debug statements... if (m_parser.Settings.StripDebugStatements && m_parser.Settings.IsModificationAllowed(JsTreeModifications.StripDebugStatements)) { // do it now before we try doing other things StripDebugStatements(node); } // analyze all the statements in our block and recurse them if (node.BlockScope != null) { m_scopeStack.Push(node.BlockScope); } try { // don't call the base class to recurse -- let's walk the block // backwards in case any of the children opt to delete themselves. for (var ndx = node.Count - 1; ndx >= 0; --ndx) { node[ndx].Accept(this); } } finally { if (node.BlockScope != null) { m_scopeStack.Pop(); } } if (m_parser.Settings.RemoveUnneededCode) { // go forward, and check the count each iteration because we might be ADDING statements to the block. // let's look at all our if-statements. If a true-clause ends in a return, then we don't // need the else-clause; we can pull its statements out and stick them after the if-statement. // also, if we encounter a return-, break- or continue-statement, we can axe everything after it for (var ndx = 0; ndx < node.Count; ++ndx) { // see if it's an if-statement with both a true and a false block var ifNode = node[ndx] as JsIfNode; if (ifNode != null && ifNode.TrueBlock != null && ifNode.TrueBlock.Count > 0 && ifNode.FalseBlock != null) { // now check to see if the true block ends in a return statement if (ifNode.TrueBlock[ifNode.TrueBlock.Count - 1] is JsReturnNode) { // transform: if(cond){statements1;return}else{statements2} to if(cond){statements1;return}statements2 // it does. insert all the false-block statements after the if-statement node.InsertRange(ndx + 1, ifNode.FalseBlock.Children); // and then remove the false block altogether ifNode.FalseBlock = null; } } else if (node[ndx] is JsReturnNode || node[ndx] is JsBreak || node[ndx] is JsContinueNode || node[ndx] is JsThrowNode) { // we have an exit node -- no statments afterwards will be executed, so clear them out. // transform: {...;return;...} to {...;return} // transform: {...;break;...} to {...;break} // transform: {...;continue;...} to {...;continue} // transform: {...;throw;...} to {...;throw} // we've found an exit statement, and it's not the last statement in the function. // walk the rest of the statements and delete anything that isn't a function declaration // or a var- or const-statement. for (var ndxRemove = node.Count - 1; ndxRemove > ndx; --ndxRemove) { var funcObject = node[ndxRemove] as JsFunctionObject; if (funcObject == null || funcObject.FunctionType != JsFunctionType.Declaration) { // if it's a const-statement, leave it. // we COULD check to see if the constant is referenced anywhere and delete // any that aren't. Maybe later. // we also don't want to do like the var-statements and remove the initializers. // Not sure if any browsers would fail a const WITHOUT an initializer. if (!(node[ndxRemove] is JsConstStatement)) { var varStatement = node[ndxRemove] as JsVar; if (varStatement != null) { // var statements can't be removed, but any initializers should // be deleted since they won't get executed. for (var ndxDecl = 0; ndxDecl < varStatement.Count; ++ndxDecl) { if (varStatement[ndxDecl].Initializer != null) { varStatement[ndxDecl].Initializer = null; } } } else { // not a function declaration, and not a var statement -- get rid of it JsDetachReferences.Apply(node[ndxRemove]); node.RemoveAt(ndxRemove); } } } } } } } // now check the last statement -- if it's an if-statement where the true-block is a single return // and there is no false block, convert this one statement to a conditional. We might back it out later // if we don't combine the conditional with other stuff. // but we can only do this if we're at the functional level because of the implied return at the end // of that block. if (isFunctionLevel && node.Count > 0 && m_parser.Settings.IsModificationAllowed(JsTreeModifications.IfConditionReturnToCondition)) { JsReturnNode returnNode; var ifNode = FindLastStatement(node) as JsIfNode; if (ifNode != null && ifNode.FalseBlock == null && ifNode.TrueBlock.Count == 1 && (returnNode = ifNode.TrueBlock[0] as JsReturnNode) != null) { // if the return node doesn't have an operand, then we can just replace the if-statement with its conditional if (returnNode.Operand == null) { // if the condition is a constant, then eliminate it altogether if (ifNode.Condition.IsConstant) { // delete the node altogether. Because the condition is a constant, // there is no else-block, and the if-block only contains a return // with no expression, we don't have anything to detach. node.ReplaceChild(ifNode, null); } else { // transform: {...;if(cond)return;} to {...;cond;} node.ReplaceChild(ifNode, ifNode.Condition); } } else if (returnNode.Operand.IsExpression) { // this is a strategic replacement that might pay off later. And if // it doesn't, we'll eventually back it out after all the other stuff // if applied on top of it. // transform: if(cond)return expr;} to return cond?expr:void 0} var conditional = new JsConditional(null, m_parser) { Condition = ifNode.Condition, TrueExpression = returnNode.Operand, FalseExpression = CreateVoidNode() }; // replace the if-statement with the new return node node.ReplaceChild(ifNode, new JsReturnNode(ifNode.Context, m_parser) { Operand = conditional }); Optimize(conditional); } } } // now walk through and combine adjacent expression statements, and adjacent var-for statements // and adjecent expression-return statements if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.CombineAdjacentExpressionStatements)) { CombineExpressions(node); } // check to see if we want to combine a preceding var with a for-statement if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.MoveVarIntoFor)) { // look at the statements in the block. // walk BACKWARDS down the list because we'll be removing items when we encounter // var statements that can be moved inside a for statement's initializer // we also don't need to check the first one, since there is nothing before it. for (int ndx = node.Count - 1; ndx > 0; --ndx) { // see if the previous statement is a var statement // (we've already combined adjacent var-statements) JsForNode forNode; JsWhileNode whileNode; var previousVar = node[ndx - 1] as JsVar; if (previousVar != null && (forNode = node[ndx] as JsForNode) != null) { // BUT if the var statement has any initializers containing an in-operator, first check // to see if we haven't killed that move before we try moving it. Opera 11 seems to have // an issue with that syntax, even if properly parenthesized. if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.MoveInExpressionsIntoForStatement) || !previousVar.ContainsInOperator) { // and see if the forNode's initializer is empty if (forNode.Initializer != null) { // not empty -- see if it is a Var node JsVar varInitializer = forNode.Initializer as JsVar; if (varInitializer != null) { // transform: var decls1;for(var decls2;...) to for(var decls1,decls2;...) // we want to PREPEND the initializers in the previous var-statement // to our for-statement's initializer var-statement list varInitializer.InsertAt(0, previousVar); // then remove the previous var statement node.RemoveAt(ndx - 1); // this will bump the for node up one position in the list, so the next iteration // will be right back on this node in case there are other var statements we need // to combine } else { // we want to see if the initializer expression is a series of one or more // simple assignments to variables that are in the previous var statement. // if all the expressions are assignments to variables that are defined in the // previous var statement, then we can just move the var statement into the // for statement. var binaryOp = forNode.Initializer as JsBinaryOperator; if (binaryOp != null && AreAssignmentsInVar(binaryOp, previousVar)) { // transform: var decls;for(expr1;...) to for(var decls,expr1;...) // WHERE expr1 only consists of assignments to variables that are declared // in that previous var-statement. // TODO: we *could* also do it is the expr1 assignments are to lookups that are // defined in THIS scope (not any outer scopes), because it wouldn't hurt to have // then in a var statement again. // create a list and fill it with all the var-decls created from the assignment // operators in the expression var varDecls = new List<JsVariableDeclaration>(); ConvertAssignmentsToVarDecls(binaryOp, varDecls, m_parser); // then go through and append each one to the var statement before us foreach (var varDecl in varDecls) { previousVar.Append(varDecl); } // move the previous var-statement into our initializer forNode.Initializer = previousVar; // and remove the previous var-statement from the list. node.RemoveAt(ndx - 1); // this will bump the for node up one position in the list, so the next iteration // will be right back on this node, but the initializer will not be null } } } else { // transform: var decls;for(;...) to for(var decls;...) // if it's empty, then we're free to add the previous var statement // to this for statement's initializer. remove it from it's current // position and add it as the initializer node.RemoveAt(ndx - 1); forNode.Initializer = previousVar; // this will bump the for node up one position in the list, so the next iteration // will be right back on this node, but the initializer will not be null } } } else if (previousVar != null && (whileNode = node[ndx] as JsWhileNode) != null && m_parser.Settings.IsModificationAllowed(JsTreeModifications.ChangeWhileToFor)) { // transform: var ...;while(cond)... => for(var ...;cond;)... node[ndx] = new JsForNode(null, m_parser) { Initializer = previousVar, Condition = whileNode.Condition, Body = whileNode.Body }; node.RemoveAt(ndx - 1); } } } // see if the last statement is a return statement JsReturnNode lastReturn; if ((lastReturn = FindLastStatement(node) as JsReturnNode) != null) { // set this flag to true if we end up adding an expression to the block. // before exiting, we'll go through and combine adjacent expressions again if this // flag has been set to true. bool changedStatementToExpression = false; // get the index of the statement before the last return // (skip over function decls and importand comments) var indexPrevious = PreviousStatementIndex(node, lastReturn); // just out of curiosity, let's see if we fit a common pattern: // var name=expr;return name; // or // const name=expr;return name; // if so, we can cut out the var and simply return the expression JsLookup lookup; if ((lookup = lastReturn.Operand as JsLookup) != null && indexPrevious >= 0) { // use the base class for both the var- and const-statements so we will // pick them both up at the same time var varStatement = node[indexPrevious] as JsDeclaration; if (varStatement != null) { // if the last vardecl in the var statement matches the return lookup, and no // other references exist for this field (refcount == 1)... JsVariableDeclaration varDecl; if ((varDecl = varStatement[varStatement.Count - 1]).Initializer != null && varDecl.IsEquivalentTo(lookup) && varDecl.VariableField.RefCount == 1) { // clean up the field's references because we're removing both the lookup reference // in the return statement and the vardecl. varDecl.VariableField.References.Remove(lookup); varDecl.VariableField.Declarations.Remove(varDecl); if (varStatement.Count == 1) { // transform: ...;var name=expr;return name} to ...;return expr} // there's only one vardecl in the var, so get rid of the entire statement lastReturn.Operand = varDecl.Initializer; node.RemoveAt(indexPrevious); } else { // multiple vardecls are in the statement; we only need to get rid of the last one lastReturn.Operand = varDecl.Initializer; varStatement[varStatement.Count - 1] = null; } } } } // check to see if we can combine the return statement with a previous if-statement // into a simple return-conditional. The true statement needs to have no false block, // and only one statement in the true block. JsConditional conditional; JsIfNode previousIf; while (indexPrevious >= 0 && lastReturn != null && (previousIf = node[indexPrevious] as JsIfNode) != null && previousIf.TrueBlock != null && previousIf.TrueBlock.Count == 1 && previousIf.FalseBlock == null) { // assume no change is made for this loop bool somethingChanged = false; // and that one true-block statement needs to be a return statement var previousReturn = previousIf.TrueBlock[0] as JsReturnNode; if (previousReturn != null) { if (lastReturn.Operand == null) { if (previousReturn.Operand == null) { // IF we are at the function level, then the block ends in an implicit return (undefined) // and we can change this if to just the condition. If we aren't at the function level, // then we have to leave the return, but we can replace the if with just the condition. if (!isFunctionLevel) { // not at the function level, so the return must stay. if (previousIf.Condition.IsConstant) { // transform: if(cond)return;return} to return} node.RemoveAt(indexPrevious); somethingChanged = true; } else { // transform: if(cond)return;return} to cond;return} node[indexPrevious] = previousIf.Condition; } } else if (previousIf.Condition.IsConstant) { // transform: remove if(cond)return;return} because cond is a constant node.ReplaceChild(lastReturn, null); node.RemoveAt(indexPrevious); somethingChanged = true; } else { // transform: if(cond)return;return} to cond} // replace the final return with just the condition, then remove the previous if if (node.ReplaceChild(lastReturn, previousIf.Condition)) { node.RemoveAt(indexPrevious); somethingChanged = true; } } } else { // transform: if(cond)return expr;return} to return cond?expr:void 0 conditional = new JsConditional(null, m_parser) { Condition = previousIf.Condition, TrueExpression = previousReturn.Operand, FalseExpression = CreateVoidNode() }; // replace the final return with the new return, then delete the previous if-statement if (node.ReplaceChild(lastReturn, new JsReturnNode(null, m_parser) { Operand = conditional })) { node.RemoveAt(indexPrevious); Optimize(conditional); somethingChanged = true; } } } else { if (previousReturn.Operand == null) { // transform: if(cond)return;return expr} to return cond?void 0:expr conditional = new JsConditional(null, m_parser) { Condition = previousIf.Condition, TrueExpression = CreateVoidNode(), FalseExpression = lastReturn.Operand }; // replace the final return with the new return, then delete the previous if-statement if (node.ReplaceChild(lastReturn, new JsReturnNode(null, m_parser) { Operand = conditional })) { node.RemoveAt(indexPrevious); Optimize(conditional); somethingChanged = true; } } else if (previousReturn.Operand.IsEquivalentTo(lastReturn.Operand)) { if (previousIf.Condition.IsConstant) { // the condition is constant, and the returns return the same thing. // get rid of the if statement altogether. // transform: if(cond)return expr;return expr} to return expr} JsDetachReferences.Apply(previousReturn.Operand); node.RemoveAt(indexPrevious); somethingChanged = true; } else { // transform: if(cond)return expr;return expr} to return cond,expr} // create a new binary op with the condition and the final-return operand, // replace the operand on the final-return with the new binary operator, // and then delete the previous if-statement JsDetachReferences.Apply(previousReturn.Operand); lastReturn.Operand = JsCommaOperator.CombineWithComma(null, m_parser, previousIf.Condition, lastReturn.Operand); node.RemoveAt(indexPrevious); somethingChanged = true; } } else { // transform: if(cond)return expr1;return expr2} to return cond?expr1:expr2} // create a new conditional with the condition and the return operands, // replace the operand on the final-return with the new conditional operator, // and then delete the previous if-statement // transform: if(cond)return expr1;return expr2} to return cond?expr1:expr2} conditional = new JsConditional(null, m_parser) { Condition = previousIf.Condition, TrueExpression = previousReturn.Operand, FalseExpression = lastReturn.Operand }; // replace the operand on the final-return with the new conditional operator, // and then delete the previous if-statement lastReturn.Operand = conditional; node.RemoveAt(indexPrevious); Optimize(conditional); somethingChanged = true; } } } if (!somethingChanged) { // nothing changed -- break out of the loop break; } else { // set the flag that indicates something changed in at least one of these loops changedStatementToExpression = true; // and since we changed something, we need to bump the index down one // AFTER we grab the last return node (which has slipped into the same position // as the previous node) lastReturn = node[indexPrevious--] as JsReturnNode; } } // if we added any more expressions since we ran our expression-combination logic, // run it again. if (changedStatementToExpression && m_parser.Settings.IsModificationAllowed(JsTreeModifications.CombineAdjacentExpressionStatements)) { CombineExpressions(node); } // and FINALLY, we want to see if what we did previously didn't pan out and we end // in something like return cond?expr:void 0, in which case we want to change it // back to a simple if(condition)return expr; (saves four bytes). // see if the last statement is a return statement that returns a conditional if (lastReturn != null && (conditional = lastReturn.Operand as JsConditional) != null) { var unaryOperator = conditional.FalseExpression as JsUnaryOperator; if (unaryOperator != null && unaryOperator.OperatorToken == JsToken.Void && unaryOperator.Operand is JsConstantWrapper) { unaryOperator = conditional.TrueExpression as JsUnaryOperator; if (unaryOperator != null && unaryOperator.OperatorToken == JsToken.Void) { if (isFunctionLevel) { // transform: ...;return cond?void 0:void 0} to ...;cond} // function level ends in an implicit "return void 0" node.ReplaceChild(lastReturn, conditional.Condition); } else { // transform: ...;return cond?void 0:void 0} to ...;cond;return} // non-function level doesn't end in an implicit return, // so we need to break them out into two statements node.ReplaceChild(lastReturn, conditional.Condition); node.Append(new JsReturnNode(null, m_parser)); } } else if (isFunctionLevel) { // transform: ...;return cond?expr:void 0} to ...;if(cond)return expr} // (only works at the function-level because of the implicit return statement) var ifNode = new JsIfNode(lastReturn.Context, m_parser) { Condition = conditional.Condition, TrueBlock = JsAstNode.ForceToBlock(new JsReturnNode(null, m_parser) { Operand = conditional.TrueExpression }) }; node.ReplaceChild(lastReturn, ifNode); } } else if (isFunctionLevel) { unaryOperator = conditional.TrueExpression as JsUnaryOperator; if (unaryOperator != null && unaryOperator.OperatorToken == JsToken.Void && unaryOperator.Operand is JsConstantWrapper) { // transform: ...;return cond?void 0;expr} to ...;if(!cond)return expr} // (only works at the function level because of the implicit return) // get the logical-not of the conditional var logicalNot = new JsLogicalNot(conditional.Condition, m_parser); logicalNot.Apply(); // create a new if-node based on the condition, with the branches swapped // (true-expression goes to false-branch, false-expression goes to true-branch var ifNode = new JsIfNode(lastReturn.Context, m_parser) { Condition = conditional.Condition, TrueBlock = JsAstNode.ForceToBlock(new JsReturnNode(null, m_parser) { Operand = conditional.FalseExpression }) }; node.ReplaceChild(lastReturn, ifNode); } } } } if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.CombineEquivalentIfReturns)) { // walk backwards looking for if(cond1)return expr1;if(cond2)return expr2; // (backwards, because we'll be combining those into one statement, reducing the number of statements. // don't go all the way to zero, because each loop will compare the statement to the PREVIOUS // statement, and the first statement (index==0) has no previous statement. for (var ndx = node.Count - 1; ndx > 0; --ndx) { // see if the current statement is an if-statement with no else block, and a true // block that contains a single return-statement WITH an expression. JsAstNode currentExpr = null; JsAstNode condition2; if (IsIfReturnExpr(node[ndx], out condition2, ref currentExpr) != null) { // see if the previous statement is also the same pattern, but with // the equivalent expression as its return operand JsAstNode condition1; var matchedExpression = currentExpr; var ifNode = IsIfReturnExpr(node[ndx - 1], out condition1, ref matchedExpression); if (ifNode != null) { // it is a match! // let's combine them -- we'll add the current condition to the // previous condition with a logical-or and delete the current statement. // transform: if(cond1)return expr;if(cond2)return expr; to if(cond1||cond2)return expr; ifNode.Condition = new JsBinaryOperator(null, m_parser) { Operand1 = condition1, Operand2 = condition2, OperatorToken = JsToken.LogicalOr, TerminatingContext = ifNode.TerminatingContext ?? node.TerminatingContext }; JsDetachReferences.Apply(currentExpr); node.RemoveAt(ndx); } } } } if (isFunctionLevel && m_parser.Settings.IsModificationAllowed(JsTreeModifications.InvertIfReturn)) { // walk backwards looking for if (cond) return; whenever we encounter that statement, // we can change it to if (!cond) and put all subsequent statements in the block inside the // if's true-block. for (var ndx = node.Count - 1; ndx >= 0; --ndx) { var ifNode = node[ndx] as JsIfNode; if (ifNode != null && ifNode.FalseBlock == null && ifNode.TrueBlock != null && ifNode.TrueBlock.Count == 1) { var returnNode = ifNode.TrueBlock[0] as JsReturnNode; if (returnNode != null && returnNode.Operand == null) { // we have if(cond)return; // logical-not the condition, remove the return statement, // and move all subsequent sibling statements inside the if-statement. JsLogicalNot.Apply(ifNode.Condition, m_parser); ifNode.TrueBlock.Clear(); var ndxMove = ndx + 1; if (node.Count == ndxMove + 1) { // there's only one statement after our if-node. // see if it's ALSO an if-node with no else block. var secondIfNode = node[ndxMove] as JsIfNode; if (secondIfNode != null && (secondIfNode.FalseBlock == null || secondIfNode.FalseBlock.Count == 0)) { // it is! // transform: if(cond1)return;if(cond2){...} => if(!cond1&&cond2){...} // (the cond1 is already inverted at this point) // combine cond2 with cond1 via a logical-and, // move all secondIf statements inside the if-node, // remove the secondIf node. node.RemoveAt(ndxMove); ifNode.Condition = new JsBinaryOperator(null, m_parser) { Operand1 = ifNode.Condition, Operand2 = secondIfNode.Condition, OperatorToken = JsToken.LogicalAnd }; ifNode.TrueBlock = secondIfNode.TrueBlock; } else if (node[ndxMove].IsExpression && m_parser.Settings.IsModificationAllowed(JsTreeModifications.IfConditionCallToConditionAndCall)) { // now we have if(cond)expr; optimize that! var expression = node[ndxMove]; node.RemoveAt(ndxMove); IfConditionExpressionToExpression(ifNode, expression); } } // just move all the following statements inside the if-statement while (node.Count > ndxMove) { var movedNode = node[ndxMove]; node.RemoveAt(ndxMove); ifNode.TrueBlock.Append(movedNode); } } } } } else { var isIteratorBlock = node.Parent is JsForNode || node.Parent is JsForIn || node.Parent is JsWhileNode || node.Parent is JsDoWhile; if (isIteratorBlock && m_parser.Settings.IsModificationAllowed(JsTreeModifications.InvertIfContinue)) { // walk backwards looking for if (cond) continue; whenever we encounter that statement, // we can change it to if (!cond) and put all subsequent statements in the block inside the // if's true-block. for (var ndx = node.Count - 1; ndx >= 0; --ndx) { var ifNode = node[ndx] as JsIfNode; if (ifNode != null && ifNode.FalseBlock == null && ifNode.TrueBlock != null && ifNode.TrueBlock.Count == 1) { var continueNode = ifNode.TrueBlock[0] as JsContinueNode; // if there's no label, then we're good. Otherwise we can only make this optimization // if the label refers to the parent iterator node. if (continueNode != null && (string.IsNullOrEmpty(continueNode.Label) || (LabelMatchesParent(continueNode.Label, node.Parent)))) { // if this is the last statement, then we don't really need the if at all // and can just replace it with its condition if (ndx < node.Count - 1) { // we have if(cond)continue;st1;...stn; // logical-not the condition, remove the continue statement, // and move all subsequent sibling statements inside the if-statement. JsLogicalNot.Apply(ifNode.Condition, m_parser); ifNode.TrueBlock.Clear(); // TODO: if we removed a labeled continue, do we need to fix up some label references? var ndxMove = ndx + 1; if (node.Count == ndxMove + 1) { // there's only one statement after our if-node. // see if it's ALSO an if-node with no else block. var secondIfNode = node[ndxMove] as JsIfNode; if (secondIfNode != null && (secondIfNode.FalseBlock == null || secondIfNode.FalseBlock.Count == 0)) { // it is! // transform: if(cond1)continue;if(cond2){...} => if(!cond1&&cond2){...} // (the cond1 is already inverted at this point) // combine cond2 with cond1 via a logical-and, // move all secondIf statements inside the if-node, // remove the secondIf node. ifNode.Condition = new JsBinaryOperator(null, m_parser) { Operand1 = ifNode.Condition, Operand2 = secondIfNode.Condition, OperatorToken = JsToken.LogicalAnd }; ifNode.TrueBlock = secondIfNode.TrueBlock; node.RemoveAt(ndxMove); } else if (node[ndxMove].IsExpression && m_parser.Settings.IsModificationAllowed(JsTreeModifications.IfConditionCallToConditionAndCall)) { // now we have if(cond)expr; optimize that! var expression = node[ndxMove]; node.RemoveAt(ndxMove); IfConditionExpressionToExpression(ifNode, expression); } } // just move all the following statements inside the if-statement while (node.Count > ndxMove) { var movedNode = node[ndxMove]; node.RemoveAt(ndxMove); ifNode.TrueBlock.Append(movedNode); } } else { // we have if(cond)continue} -- nothing after the if. // the loop is going to continue anyway, so replace the if-statement // with the condition and be done if (ifNode.Condition.IsConstant) { // consition is constant -- get rid of the if-statement altogether node.RemoveAt(ndx); } else { // condition isn't constant node[ndx] = ifNode.Condition; } } } } } } } } }
public void Visit(JsForNode node) { if (node != null) { var symbol = StartSymbol(node); Output("for"); MarkSegment(node, null, node.Context); SetContextOutputPosition(node.Context); if (m_settings.OutputMode == MinifierOutputMode.MultipleLines) { OutputPossibleLineBreak(' '); } OutputPossibleLineBreak('('); m_startOfStatement = false; if (node.Initializer != null) { // we have a no-in scenario for the initializer m_noIn = true; node.Initializer.Accept(this); m_noIn = false; } // NEVER do without these semicolons OutputPossibleLineBreak(';'); MarkSegment(node, null, node.Separator1Context ?? node.Context); if (m_settings.OutputMode == MinifierOutputMode.MultipleLines) { OutputPossibleLineBreak(' '); } if (node.Condition != null) { node.Condition.Accept(this); } OutputPossibleLineBreak(';'); MarkSegment(node, null, node.Separator2Context ?? node.Context); if (m_settings.OutputMode == MinifierOutputMode.MultipleLines) { OutputPossibleLineBreak(' '); } if (node.Incrementer != null) { node.Incrementer.Accept(this); } OutputPossibleLineBreak(')'); MarkSegment(node, null, node.Context); OutputBlock(node.Body); EndSymbol(symbol); } }
private void DoForNode(JsForNode node) { if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.EvaluateNumericExpressions)) { JsConstantWrapper constantCondition = node.Condition as JsConstantWrapper; if (constantCondition != null) { try { // if condition is always false, change it to a zero (only one byte) // and if it is always true, remove it (default behavior) if (constantCondition.ToBoolean()) { // always true -- don't need a condition at all node.Condition = null; } else if (constantCondition.IsNotOneOrPositiveZero) { // always false and it's not already a zero. Make it so (only one byte) node.Condition = new JsConstantWrapper(0, JsPrimitiveType.Number, node.Condition.Context, m_parser); } } catch (InvalidCastException) { // ignore any invalid cast exceptions } } } }
private void DoWhileNode(JsWhileNode node) { // see if the condition is a constant if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.EvaluateNumericExpressions)) { var constantCondition = node.Condition as JsConstantWrapper; if (constantCondition != null) { // TODO: (someday) we'd RATHER eliminate the statement altogether if the condition is always false, // but we'd need to make sure var'd variables and declared functions are properly handled. try { bool isTrue = constantCondition.ToBoolean(); if (isTrue) { // the condition is always true, so we should change it to 1 if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.ChangeWhileToFor)) { // the condition is always true; we should change it to a for(;;) statement. // less bytes than while(1) // check to see if we want to combine a preceding var with a for-statement JsAstNode initializer = null; if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.MoveVarIntoFor)) { // if the previous statement is a var, we can move it to the initializer // and save even more bytes. The parent should always be a block. If not, // then assume there is no previous. var parentBlock = node.Parent as JsBlock; if (parentBlock != null) { int whileIndex = parentBlock.IndexOf(node); if (whileIndex > 0) { var previousVar = parentBlock[whileIndex - 1] as JsVar; if (previousVar != null) { initializer = previousVar; parentBlock.RemoveAt(whileIndex - 1); } } } } // create the for using our body and replace ourselves with it var forNode = new JsForNode(node.Context, m_parser) { Initializer = initializer, Body = node.Body }; node.Parent.ReplaceChild(node, forNode); } else { // the condition is always true, so we can replace the condition // with a 1 -- only one byte node.Condition = new JsConstantWrapper(1, JsPrimitiveType.Number, null, m_parser); } } else if (constantCondition.IsNotOneOrPositiveZero) { // the condition is always false, so we can replace the condition // with a zero -- only one byte node.Condition = new JsConstantWrapper(0, JsPrimitiveType.Number, null, m_parser); } } catch (InvalidCastException) { // ignore any invalid cast exceptions } } } }
public override void Visit(JsForNode node) { if (node != null) { // depth-first base.Visit(node); DoForNode(node); } }
private void CombineWithPreviousExpression(JsBlock node, int ndx) { JsIfNode ifNode; JsForNode forNode; JsWhileNode whileNode; JsReturnNode returnNode; if (node[ndx].IsExpression) { CombineTwoExpressions(node, ndx); } else if ((returnNode = node[ndx] as JsReturnNode) != null) { CombineReturnWithExpression(node, ndx, returnNode); } else if ((forNode = node[ndx] as JsForNode) != null) { CombineForNodeWithExpression(node, ndx, forNode); } else if ((ifNode = node[ndx] as JsIfNode) != null) { // transform: expr;if(cond)... => if(expr,cond)... // combine the previous expression with the if-condition via comma, then delete // the previous statement. ifNode.Condition = JsCommaOperator.CombineWithComma(null, m_parser, node[ndx - 1], ifNode.Condition); node.RemoveAt(ndx - 1); } else if ((whileNode = node[ndx] as JsWhileNode) != null && m_parser.Settings.IsModificationAllowed(JsTreeModifications.ChangeWhileToFor)) { // transform: expr;while(cond)... => for(expr;cond;)... // zero-sum, and maybe a little worse for performance because of the nop iterator, // but combines two statements into one, which may have savings later on. var initializer = node[ndx - 1]; node[ndx] = new JsForNode(null, m_parser) { Initializer = initializer, Condition = whileNode.Condition, Body = whileNode.Body }; node.RemoveAt(ndx - 1); } }
private void CombineForNodeWithExpression(JsBlock node, int ndx, JsForNode forNode) { // if we aren't allowing in-operators to be moved into for-statements, then // first check to see if that previous expression statement is free of in-operators // before trying to move it. if (m_parser.Settings.IsModificationAllowed(JsTreeModifications.MoveInExpressionsIntoForStatement) || !node[ndx - 1].ContainsInOperator) { if (forNode.Initializer == null) { // transform: expr1;for(;...) to for(expr1;...) // simply move the previous expression to the for-statement's initializer forNode.Initializer = node[ndx - 1]; node[ndx - 1] = null; } else if (forNode.Initializer.IsExpression) { // transform: expr1;for(expr2;...) to for(expr1,expr2;...) var binOp = JsCommaOperator.CombineWithComma(null, m_parser, node[ndx - 1], forNode.Initializer); // replace the initializer with the new binary operator and remove the previous node forNode.Initializer = binOp; node[ndx - 1] = null; } } }
public void Visit(JsForNode node) { // starts with a 'for', so we don't care }
private JsAstNode ParseForStatement() { m_blockType.Add(BlockType.Loop); JsAstNode forNode = null; try { JsContext forCtx = m_currentToken.Clone(); GetNextToken(); if (JsToken.LeftParenthesis != m_currentToken.Token) { ReportError(JsError.NoLeftParenthesis); } GetNextToken(); bool isForIn = false, recoveryInForIn = false; JsAstNode lhs = null, initializer = null, condOrColl = null, increment = null; JsContext operatorContext = null; JsContext separator1Context = null; JsContext separator2Context = null; try { if (JsToken.Var == m_currentToken.Token || JsToken.Let == m_currentToken.Token || JsToken.Const == m_currentToken.Token) { isForIn = true; JsDeclaration declaration; if (m_currentToken.Token == JsToken.Var) { declaration = new JsVar(m_currentToken.Clone(), this); } else { declaration = new JsLexicalDeclaration(m_currentToken.Clone(), this) { StatementToken = m_currentToken.Token }; } declaration.Append(ParseIdentifierInitializer(JsToken.In)); // a list of variable initializers is allowed only in a for(;;) while (JsToken.Comma == m_currentToken.Token) { isForIn = false; declaration.Append(ParseIdentifierInitializer(JsToken.In)); //initializer = new Comma(initializer.context.CombineWith(var.context), initializer, var); } initializer = declaration; // if it could still be a for..in, now it's time to get the 'in' // TODO: for ES6 might be 'of' if (isForIn) { if (JsToken.In == m_currentToken.Token || (m_currentToken.Token == JsToken.Identifier && string.CompareOrdinal(m_currentToken.Code, "of") == 0)) { operatorContext = m_currentToken.Clone(); GetNextToken(); condOrColl = ParseExpression(); } else { isForIn = false; } } } else { if (JsToken.Semicolon != m_currentToken.Token) { bool isLHS; initializer = ParseUnaryExpression(out isLHS, false); if (isLHS && (JsToken.In == m_currentToken.Token || (m_currentToken.Token == JsToken.Identifier && string.CompareOrdinal(m_currentToken.Code, "of") == 0))) { isForIn = true; operatorContext = m_currentToken.Clone(); lhs = initializer; initializer = null; GetNextToken(); m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet); try { condOrColl = ParseExpression(); } catch (RecoveryTokenException exc) { if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) == -1) { exc._partiallyComputedNode = null; throw; } else { if (exc._partiallyComputedNode == null) condOrColl = new JsConstantWrapper(true, JsPrimitiveType.Boolean, CurrentPositionContext(), this); // what could we put here? else condOrColl = exc._partiallyComputedNode; } if (exc._token == JsToken.RightParenthesis) { GetNextToken(); recoveryInForIn = true; } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet); } } else { initializer = ParseExpression(initializer, false, isLHS, JsToken.In); } } } } catch (RecoveryTokenException exc) { // error is too early abort for exc._partiallyComputedNode = null; throw; } // at this point we know whether or not is a for..in if (isForIn) { if (!recoveryInForIn) { if (JsToken.RightParenthesis != m_currentToken.Token) ReportError(JsError.NoRightParenthesis); forCtx.UpdateWith(m_currentToken); GetNextToken(); } JsAstNode body = null; // if the statements aren't withing curly-braces, throw a possible error if (JsToken.LeftCurly != m_currentToken.Token) { ReportError(JsError.StatementBlockExpected, CurrentPositionContext(), true); } try { // parse a Statement, not a SourceElement // and ignore any important comments that spring up right here. body = ParseStatement(false, true); } catch (RecoveryTokenException exc) { if (exc._partiallyComputedNode == null) body = new JsBlock(CurrentPositionContext(), this); else body = exc._partiallyComputedNode; exc._partiallyComputedNode = new JsForIn(forCtx, this) { Variable = (lhs != null ? lhs : initializer), OperatorContext = operatorContext, Collection = condOrColl, Body = JsAstNode.ForceToBlock(body), }; throw; } // for (a in b) // lhs = a, initializer = null // for (var a in b) // lhs = null, initializer = var a forNode = new JsForIn(forCtx, this) { Variable = (lhs != null ? lhs : initializer), OperatorContext = operatorContext, Collection = condOrColl, Body = JsAstNode.ForceToBlock(body), }; } else { m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet); try { if (JsToken.Semicolon == m_currentToken.Token) { separator1Context = m_currentToken.Clone(); } else { ReportError(JsError.NoSemicolon); if (JsToken.Colon == m_currentToken.Token) { m_noSkipTokenSet.Add(NoSkipTokenSet.s_VariableDeclNoSkipTokenSet); try { SkipTokensAndThrow(); } catch (RecoveryTokenException) { if (JsToken.Semicolon == m_currentToken.Token) { m_useCurrentForNext = false; } else { throw; } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_VariableDeclNoSkipTokenSet); } } } GetNextToken(); if (JsToken.Semicolon != m_currentToken.Token) { condOrColl = ParseExpression(); if (JsToken.Semicolon != m_currentToken.Token) { ReportError(JsError.NoSemicolon); } } separator2Context = m_currentToken.Clone(); GetNextToken(); if (JsToken.RightParenthesis != m_currentToken.Token) { increment = ParseExpression(); } if (JsToken.RightParenthesis != m_currentToken.Token) { ReportError(JsError.NoRightParenthesis); } forCtx.UpdateWith(m_currentToken); GetNextToken(); } catch (RecoveryTokenException exc) { if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) == -1) { exc._partiallyComputedNode = null; throw; } else { // discard any partial info, just genrate empty condition and increment and keep going exc._partiallyComputedNode = null; if (condOrColl == null) condOrColl = new JsConstantWrapper(true, JsPrimitiveType.Boolean, CurrentPositionContext(), this); } if (exc._token == JsToken.RightParenthesis) { GetNextToken(); } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet); } // if this is an assignment, throw a warning in case the developer // meant to use == instead of = // but no warning if the condition is wrapped in parens. var binOp = condOrColl as JsBinaryOperator; if (binOp != null && binOp.OperatorToken == JsToken.Assign) { condOrColl.Context.HandleError(JsError.SuspectAssignment); } JsAstNode body = null; // if the statements aren't withing curly-braces, throw a possible error if (JsToken.LeftCurly != m_currentToken.Token) { ReportError(JsError.StatementBlockExpected, CurrentPositionContext(), true); } try { // parse a Statement, not a SourceElement // and ignore any important comments that spring up right here. body = ParseStatement(false, true); } catch (RecoveryTokenException exc) { if (exc._partiallyComputedNode == null) body = new JsBlock(CurrentPositionContext(), this); else body = exc._partiallyComputedNode; exc._partiallyComputedNode = new JsForNode(forCtx, this) { Initializer = initializer, Separator1Context = separator1Context, Condition = condOrColl, Separator2Context = separator2Context, Incrementer = increment, Body = JsAstNode.ForceToBlock(body) }; throw; } forNode = new JsForNode(forCtx, this) { Initializer = initializer, Separator1Context = separator1Context, Condition = condOrColl, Separator2Context = separator2Context, Incrementer = increment, Body = JsAstNode.ForceToBlock(body) }; } } finally { m_blockType.RemoveAt(m_blockType.Count - 1); } return forNode; }
public override void Visit(JsForNode node) { if (node != null) { // if this for-statement has it's own lexical scope, then it's an error // if the any of the field names declared in this scope is also defined inside the body. if (node.BlockScope != null) { foreach (var field in node.BlockScope.LexicallyDeclaredNames) { // if the block has a lexical scope, check it for conflicts if (node.Body != null && node.Body.BlockScope != null) { var lexDecl = node.Body.BlockScope.LexicallyDeclaredName(field.Name); if (lexDecl != null) { // report the error (lex/const/funcdecl collision) lexDecl.NameContext.HandleError(JsError.DuplicateLexicalDeclaration, true); // link the inner one to the outer one so any renaming stays in sync. if (lexDecl.VariableField != null) { lexDecl.VariableField.OuterField = field.VariableField; if (field.VariableField != null && !lexDecl.VariableField.CanCrunch) { field.VariableField.CanCrunch = false; } } } } // check to make sure there are no var-decl'd names with the same name. Those will // get carried up to this scope so we don't need to check the block scope (if any) var varDecl = node.BlockScope.VarDeclaredName(field.Name); if (varDecl != null) { // report the error (lex/const collides with var) or warning (funcdecl collides with var) varDecl.NameContext.HandleError(JsError.DuplicateLexicalDeclaration, field is JsLexicalDeclaration); // and mark them both as no-rename varDecl.VariableField.IfNotNull(v => v.CanCrunch = false); field.VariableField.IfNotNull(v => v.CanCrunch = false); } } } // if we are stripping debugger statements and the body is // just a debugger statement, replace it with a null // (but only if the body doesn't have its own lexical scope) if (m_parser.Settings.StripDebugStatements && m_parser.Settings.IsModificationAllowed(JsTreeModifications.StripDebugStatements) && node.Body != null && node.Body.IsDebuggerStatement && node.Body.BlockScope == null) { node.Body = null; } // recurse base.Visit(node); // if the body is now empty (and doesn't have its own lexical scope), make it null if (node.Body != null && node.Body.Count == 0 && node.Body.BlockScope == null) { node.Body = null; } } }