in AjaxMinDll/JavaScript/AnalyzeNodeVisitor.cs [626:1505]
public override void Visit(Block 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.
ActivationObject lexicalScope = null;
if (node.HasOwnScope)
{
lexicalScope = node.EnclosingScope as BlockScope;
var functionObject = node.Parent as FunctionObject;
if (functionObject != null)
{
lexicalScope = functionObject.EnclosingScope;
}
}
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.Context.HandleError(JSError.DuplicateLexicalDeclaration, lexDecl is LexicalDeclaration);
// 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 FunctionObject);
// analyze all the statements in our block and recurse them
if (node.HasOwnScope)
{
m_scopeStack.Push(node.EnclosingScope);
}
var oldStrictError = m_strictNameError;
try
{
m_strictNameError = JSError.StrictModeVariableName;
// 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);
// we can do this because we are walking backwards, and if the statement had deleted itself
// for whatever reason, the next one will slip into its place, and we've already seen it so
// it must not be debug only or we would've deleted it.
if (m_stripDebug && node.Count > ndx && node[ndx].IsDebugOnly)
{
node.RemoveAt(ndx);
}
}
}
finally
{
m_strictNameError = oldStrictError;
if (node.HasOwnScope)
{
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 IfNode;
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 ReturnNode)
{
// 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 ReturnNode
|| node[ndx] is Break
|| node[ndx] is ContinueNode
|| node[ndx] is ThrowNode)
{
// 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)
{
if (node[ndxRemove].IsDeclaration)
{
// we want to keep this statement because it's a "declaration."
// HOWEVER, if this is a var or a let, let's whack the initializer, since it will
// never get executed. Leave the initializers for const statements, even though
// they will never get run, since it would be a syntax error to not have one.
var declaration = node[ndxRemove] as Declaration;
if (declaration != null && declaration.StatementToken != JSToken.Const)
{
for (var ndxDecl = 0; ndxDecl < declaration.Count; ++ndxDecl)
{
if (declaration[ndxDecl].Initializer != null)
{
DetachReferences.Apply(declaration[ndxDecl].Initializer);
declaration[ndxDecl].Initializer = null;
}
}
}
}
else
{
// not a declaration -- remove it
DetachReferences.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(TreeModifications.IfConditionReturnToCondition))
{
ReturnNode returnNode;
var ifNode = FindLastStatement(node) as IfNode;
if (ifNode != null && ifNode.FalseBlock == null
&& ifNode.TrueBlock.Count == 1
&& (returnNode = ifNode.TrueBlock[0] as ReturnNode) != 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 Conditional(ifNode.Condition.Context.FlattenToStart())
{
Condition = ifNode.Condition,
TrueExpression = returnNode.Operand,
FalseExpression = CreateVoidNode(returnNode.Context.FlattenToStart())
};
// replace the if-statement with the new return node
node.ReplaceChild(ifNode, new ReturnNode(ifNode.Context)
{
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(TreeModifications.CombineAdjacentExpressionStatements))
{
CombineExpressions(node);
}
// check to see if we want to combine a preceding var with a for-statement
if (m_parser.Settings.IsModificationAllowed(TreeModifications.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)
ForNode forNode;
ForIn forInNode;
WhileNode whileNode;
var previousVar = node[ndx - 1] as Var;
if (previousVar != null && (forNode = node[ndx] as ForNode) != 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(TreeModifications.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
Var varInitializer = forNode.Initializer as Var;
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 BinaryOperator;
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
ConvertAssignmentsToVarDecls(binaryOp, previousVar, m_parser);
// 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 WhileNode) != null
&& m_parser.Settings.IsModificationAllowed(TreeModifications.ChangeWhileToFor))
{
// transform: var ...;while(cond)... => for(var ...;cond;)...
node[ndx] = new ForNode(whileNode.Context.FlattenToStart())
{
Initializer = previousVar,
Condition = whileNode.Condition,
Body = whileNode.Body
};
node.RemoveAt(ndx - 1);
}
else if (previousVar != null
&& (forInNode = node[ndx] as ForIn) != null)
{
// if the for-in's variable field is not a declaration, we should check to see
// if there is a single named reference, and whether the previous var's last declaration
// is for the same var. If so, we can move that declaration into the for-in.
if (!(forInNode.Variable is Declaration))
{
// now see if the LAST vardecl in the previous var statement is the
// same field(s) as the variable, and there was either no initializer
// or the initializer was a constant ('cause we're going to kill it).
var lastDecl = previousVar[previousVar.Count - 1];
if (lastDecl.IsEquivalentTo(forInNode.Variable)
&& (lastDecl.Initializer == null || lastDecl.Initializer.IsConstant))
{
// convert the reference inside the for-in to a declaration binding.
// set the previous decl's binding to this new binding, delete any
// initializer, and move the vardecl into the for-in.
// Be sure to clean up the var-statement if it's now empty.
var newBinding = BindingTransform.ToBinding(forInNode.Variable);
if (newBinding != null)
{
var newVarDecl = new VariableDeclaration(forInNode.Variable.Context.Clone())
{
Binding = newBinding,
};
var newVar = new Var(forInNode.Variable.Context.Clone());
newVar.Append(newVarDecl);
forInNode.Variable = newVar;
// clean up that last declaration, which might go all the way
// up to the statement if removing it leaves the statement empty.
// but ONLY remove declarations that are in the new for-binding.
var oldBindings = BindingsVisitor.Bindings(lastDecl.Binding);
foreach (var newName in BindingsVisitor.Bindings(newBinding))
{
foreach (var oldName in oldBindings)
{
if (oldName.IsEquivalentTo(newName))
{
ActivationObject.RemoveBinding(oldName);
break;
}
}
}
}
}
}
}
}
}
// see if the last statement is a return statement
ReturnNode lastReturn;
if ((lastReturn = FindLastStatement(node) as ReturnNode) != 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
Lookup lookup;
if ((lookup = lastReturn.Operand as Lookup) != 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 Declaration;
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)...
var varDecl = varStatement[varStatement.Count - 1];
if (varDecl.Initializer != null
&& varDecl.IsEquivalentTo(lookup))
{
// we only care about simple binding identifiers
var bindingIdentifier = varDecl.Binding as BindingIdentifier;
if (bindingIdentifier != null
&& bindingIdentifier.VariableField.IfNotNull(v => v.RefCount == 1))
{
// clean up the field's references because we're removing both the lookup reference
// in the return statement and the vardecl.
bindingIdentifier.VariableField.References.Remove(lookup);
bindingIdentifier.VariableField.Declarations.Remove(bindingIdentifier);
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.
Conditional conditional;
IfNode previousIf;
while (indexPrevious >= 0
&& lastReturn != null
&& (previousIf = node[indexPrevious] as IfNode) != 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 ReturnNode;
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 Conditional(previousIf.Condition.Context.FlattenToStart())
{
Condition = previousIf.Condition,
TrueExpression = previousReturn.Operand,
FalseExpression = CreateVoidNode(previousReturn.Context.FlattenToStart())
};
// replace the final return with the new return, then delete the previous if-statement
if (node.ReplaceChild(lastReturn, new ReturnNode(previousReturn.Context.FlattenToStart())
{
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 Conditional(previousIf.Condition.Context.FlattenToStart())
{
Condition = previousIf.Condition,
TrueExpression = CreateVoidNode(lastReturn.Context.FlattenToStart()),
FalseExpression = lastReturn.Operand
};
// replace the final return with the new return, then delete the previous if-statement
if (node.ReplaceChild(lastReturn, new ReturnNode(lastReturn.Context.FlattenToStart())
{
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}
DetachReferences.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
DetachReferences.Apply(previousReturn.Operand);
lastReturn.Operand = CommaOperator.CombineWithComma(previousIf.Condition.Context.FlattenToStart(), 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 Conditional(previousIf.Condition.Context.FlattenToStart())
{
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 ReturnNode;
}
}
// if we added any more expressions since we ran our expression-combination logic,
// run it again.
if (changedStatementToExpression
&& m_parser.Settings.IsModificationAllowed(TreeModifications.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 Conditional) != null)
{
var unaryOperator = conditional.FalseExpression as UnaryOperator;
if (unaryOperator != null
&& unaryOperator.OperatorToken == JSToken.Void
&& unaryOperator.Operand is ConstantWrapper)
{
unaryOperator = conditional.TrueExpression as UnaryOperator;
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 ReturnNode(lastReturn.Context.Clone()));
}
}
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 IfNode(lastReturn.Context)
{
Condition = conditional.Condition,
TrueBlock = AstNode.ForceToBlock(new ReturnNode(lastReturn.Context.Clone())
{
Operand = conditional.TrueExpression
})
};
node.ReplaceChild(lastReturn, ifNode);
}
}
else if (isFunctionLevel)
{
unaryOperator = conditional.TrueExpression as UnaryOperator;
if (unaryOperator != null
&& unaryOperator.OperatorToken == JSToken.Void
&& unaryOperator.Operand is ConstantWrapper)
{
// 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 LogicalNot(conditional.Condition, m_parser.Settings);
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 IfNode(lastReturn.Context)
{
Condition = conditional.Condition,
TrueBlock = AstNode.ForceToBlock(new ReturnNode(lastReturn.Context.Clone())
{
Operand = conditional.FalseExpression
})
};
node.ReplaceChild(lastReturn, ifNode);
}
}
}
}
if (m_parser.Settings.IsModificationAllowed(TreeModifications.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.
AstNode currentExpr = null;
AstNode 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
AstNode 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 BinaryOperator(condition1.Context.FlattenToStart())
{
Operand1 = condition1,
Operand2 = condition2,
OperatorToken = JSToken.LogicalOr,
TerminatingContext = ifNode.TerminatingContext ?? node.TerminatingContext
};
DetachReferences.Apply(currentExpr);
node.RemoveAt(ndx);
}
}
}
}
if (isFunctionLevel
&& m_parser.Settings.IsModificationAllowed(TreeModifications.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 IfNode;
if (ifNode != null
&& ifNode.FalseBlock == null
&& ifNode.TrueBlock != null
&& ifNode.TrueBlock.Count == 1)
{
var returnNode = ifNode.TrueBlock[0] as ReturnNode;
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.
LogicalNot.Apply(ifNode.Condition, m_parser.Settings);
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 IfNode;
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 BinaryOperator(ifNode.Condition.Context.FlattenToStart())
{
Operand1 = ifNode.Condition,
Operand2 = secondIfNode.Condition,
OperatorToken = JSToken.LogicalAnd
};
ifNode.TrueBlock = secondIfNode.TrueBlock;
}
else if (node[ndxMove].IsExpression
&& m_parser.Settings.IsModificationAllowed(TreeModifications.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 ForNode
|| node.Parent is ForIn
|| node.Parent is WhileNode
|| node.Parent is DoWhile;
if (isIteratorBlock
&& m_parser.Settings.IsModificationAllowed(TreeModifications.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 IfNode;
if (ifNode != null
&& ifNode.FalseBlock == null
&& ifNode.TrueBlock != null
&& ifNode.TrueBlock.Count == 1)
{
var continueNode = ifNode.TrueBlock[0] as ContinueNode;
// 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.
LogicalNot.Apply(ifNode.Condition, m_parser.Settings);
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 IfNode;
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 BinaryOperator(ifNode.Condition.Context.FlattenToStart())
{
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(TreeModifications.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;
}
}
}
}
}
}
}
}
}