/**
* Aptana Studio
* Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the GNU Public License (GPL) v3 (with exceptions).
* Please see the license.html included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
package com.aptana.editor.php.formatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jface.text.IRegion;
import org2.eclipse.php.core.compiler.PHPFlags;
import org2.eclipse.php.internal.core.ast.nodes.ASTError;
import org2.eclipse.php.internal.core.ast.nodes.ASTNode;
import org2.eclipse.php.internal.core.ast.nodes.ArrayAccess;
import org2.eclipse.php.internal.core.ast.nodes.ArrayCreation;
import org2.eclipse.php.internal.core.ast.nodes.ArrayElement;
import org2.eclipse.php.internal.core.ast.nodes.Assignment;
import org2.eclipse.php.internal.core.ast.nodes.BackTickExpression;
import org2.eclipse.php.internal.core.ast.nodes.Block;
import org2.eclipse.php.internal.core.ast.nodes.BreakStatement;
import org2.eclipse.php.internal.core.ast.nodes.CastExpression;
import org2.eclipse.php.internal.core.ast.nodes.CatchClause;
import org2.eclipse.php.internal.core.ast.nodes.ChainingInstanceCall;
import org2.eclipse.php.internal.core.ast.nodes.ClassDeclaration;
import org2.eclipse.php.internal.core.ast.nodes.ClassInstanceCreation;
import org2.eclipse.php.internal.core.ast.nodes.ClassName;
import org2.eclipse.php.internal.core.ast.nodes.CloneExpression;
import org2.eclipse.php.internal.core.ast.nodes.Comment;
import org2.eclipse.php.internal.core.ast.nodes.ConditionalExpression;
import org2.eclipse.php.internal.core.ast.nodes.ConstantDeclaration;
import org2.eclipse.php.internal.core.ast.nodes.ContinueStatement;
import org2.eclipse.php.internal.core.ast.nodes.DeclareStatement;
import org2.eclipse.php.internal.core.ast.nodes.DereferenceNode;
import org2.eclipse.php.internal.core.ast.nodes.DoStatement;
import org2.eclipse.php.internal.core.ast.nodes.EchoStatement;
import org2.eclipse.php.internal.core.ast.nodes.EmptyStatement;
import org2.eclipse.php.internal.core.ast.nodes.Expression;
import org2.eclipse.php.internal.core.ast.nodes.ExpressionStatement;
import org2.eclipse.php.internal.core.ast.nodes.FieldAccess;
import org2.eclipse.php.internal.core.ast.nodes.FieldsDeclaration;
import org2.eclipse.php.internal.core.ast.nodes.ForEachStatement;
import org2.eclipse.php.internal.core.ast.nodes.ForStatement;
import org2.eclipse.php.internal.core.ast.nodes.FormalParameter;
import org2.eclipse.php.internal.core.ast.nodes.FullyQualifiedTraitMethodReference;
import org2.eclipse.php.internal.core.ast.nodes.FunctionDeclaration;
import org2.eclipse.php.internal.core.ast.nodes.FunctionInvocation;
import org2.eclipse.php.internal.core.ast.nodes.FunctionName;
import org2.eclipse.php.internal.core.ast.nodes.GlobalStatement;
import org2.eclipse.php.internal.core.ast.nodes.GotoLabel;
import org2.eclipse.php.internal.core.ast.nodes.GotoStatement;
import org2.eclipse.php.internal.core.ast.nodes.Identifier;
import org2.eclipse.php.internal.core.ast.nodes.IfStatement;
import org2.eclipse.php.internal.core.ast.nodes.IgnoreError;
import org2.eclipse.php.internal.core.ast.nodes.InLineHtml;
import org2.eclipse.php.internal.core.ast.nodes.Include;
import org2.eclipse.php.internal.core.ast.nodes.InfixExpression;
import org2.eclipse.php.internal.core.ast.nodes.InstanceOfExpression;
import org2.eclipse.php.internal.core.ast.nodes.InterfaceDeclaration;
import org2.eclipse.php.internal.core.ast.nodes.LambdaFunctionDeclaration;
import org2.eclipse.php.internal.core.ast.nodes.ListVariable;
import org2.eclipse.php.internal.core.ast.nodes.MethodDeclaration;
import org2.eclipse.php.internal.core.ast.nodes.MethodInvocation;
import org2.eclipse.php.internal.core.ast.nodes.NamespaceDeclaration;
import org2.eclipse.php.internal.core.ast.nodes.NamespaceName;
import org2.eclipse.php.internal.core.ast.nodes.PHPArrayDereferenceList;
import org2.eclipse.php.internal.core.ast.nodes.ParenthesisExpression;
import org2.eclipse.php.internal.core.ast.nodes.PostfixExpression;
import org2.eclipse.php.internal.core.ast.nodes.PrefixExpression;
import org2.eclipse.php.internal.core.ast.nodes.Quote;
import org2.eclipse.php.internal.core.ast.nodes.Reference;
import org2.eclipse.php.internal.core.ast.nodes.ReflectionVariable;
import org2.eclipse.php.internal.core.ast.nodes.ReturnStatement;
import org2.eclipse.php.internal.core.ast.nodes.Scalar;
import org2.eclipse.php.internal.core.ast.nodes.Statement;
import org2.eclipse.php.internal.core.ast.nodes.StaticConstantAccess;
import org2.eclipse.php.internal.core.ast.nodes.StaticFieldAccess;
import org2.eclipse.php.internal.core.ast.nodes.StaticMethodInvocation;
import org2.eclipse.php.internal.core.ast.nodes.StaticStatement;
import org2.eclipse.php.internal.core.ast.nodes.SwitchCase;
import org2.eclipse.php.internal.core.ast.nodes.SwitchStatement;
import org2.eclipse.php.internal.core.ast.nodes.ThrowStatement;
import org2.eclipse.php.internal.core.ast.nodes.TraitAlias;
import org2.eclipse.php.internal.core.ast.nodes.TraitDeclaration;
import org2.eclipse.php.internal.core.ast.nodes.TraitPrecedence;
import org2.eclipse.php.internal.core.ast.nodes.TraitStatement;
import org2.eclipse.php.internal.core.ast.nodes.TraitUseStatement;
import org2.eclipse.php.internal.core.ast.nodes.TryStatement;
import org2.eclipse.php.internal.core.ast.nodes.TypeDeclaration;
import org2.eclipse.php.internal.core.ast.nodes.UnaryOperation;
import org2.eclipse.php.internal.core.ast.nodes.UseStatement;
import org2.eclipse.php.internal.core.ast.nodes.UseStatementPart;
import org2.eclipse.php.internal.core.ast.nodes.Variable;
import org2.eclipse.php.internal.core.ast.nodes.VariableBase;
import org2.eclipse.php.internal.core.ast.nodes.WhileStatement;
import org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor;
import com.aptana.core.util.StringUtil;
import com.aptana.editor.php.formatter.nodes.FormatterPHPArrayElementNode;
import com.aptana.editor.php.formatter.nodes.FormatterPHPBlockNode;
import com.aptana.editor.php.formatter.nodes.FormatterPHPBreakNode;
import com.aptana.editor.php.formatter.nodes.FormatterPHPCaseBodyNode;
import com.aptana.editor.php.formatter.nodes.FormatterPHPCaseColonNode;
import com.aptana.editor.php.formatter.nodes.FormatterPHPDeclarationNode;
import com.aptana.editor.php.formatter.nodes.FormatterPHPElseIfNode;
import com.aptana.editor.php.formatter.nodes.FormatterPHPElseNode;
import com.aptana.editor.php.formatter.nodes.FormatterPHPExcludedTextNode;
import com.aptana.editor.php.formatter.nodes.FormatterPHPExpressionWrapperNode;
import com.aptana.editor.php.formatter.nodes.FormatterPHPFunctionBodyNode;
import com.aptana.editor.php.formatter.nodes.FormatterPHPFunctionInvocationNode;
import com.aptana.editor.php.formatter.nodes.FormatterPHPHeredocNode;
import com.aptana.editor.php.formatter.nodes.FormatterPHPIfNode;
import com.aptana.editor.php.formatter.nodes.FormatterPHPImplicitBlockNode;
import com.aptana.editor.php.formatter.nodes.FormatterPHPKeywordNode;
import com.aptana.editor.php.formatter.nodes.FormatterPHPLineStartingNode;
import com.aptana.editor.php.formatter.nodes.FormatterPHPNamespaceBlockNode;
import com.aptana.editor.php.formatter.nodes.FormatterPHPNonBlockedWhileNode;
import com.aptana.editor.php.formatter.nodes.FormatterPHPOperatorNode;
import com.aptana.editor.php.formatter.nodes.FormatterPHPParenthesesNode;
import com.aptana.editor.php.formatter.nodes.FormatterPHPPunctuationNode;
import com.aptana.editor.php.formatter.nodes.FormatterPHPSwitchNode;
import com.aptana.editor.php.formatter.nodes.FormatterPHPTextNode;
import com.aptana.editor.php.formatter.nodes.FormatterPHPTraitPrecedenceWrapperNode;
import com.aptana.editor.php.formatter.nodes.FormatterPHPTypeBodyNode;
import com.aptana.editor.php.internal.indexer.PHPDocUtils;
import com.aptana.formatter.FormatterDocument;
import com.aptana.formatter.FormatterUtils;
import com.aptana.formatter.nodes.AbstractFormatterNodeBuilder;
import com.aptana.formatter.nodes.IFormatterContainerNode;
import com.aptana.formatter.nodes.NodeTypes.TypeBracket;
import com.aptana.formatter.nodes.NodeTypes.TypeOperator;
import com.aptana.formatter.nodes.NodeTypes.TypePunctuation;
/**
* A PHP formatter node builder.
*
* @author Shalom Gibly <sgibly@aptana.com>
*/
public class PHPFormatterVisitor extends AbstractVisitor
{
// Match words in a string
private static final Pattern WORD_PATTERN = Pattern.compile("\\w+"); //$NON-NLS-1$
private static final Pattern LINE_SPLIT_PATTERN = Pattern.compile("\r?\n|\r"); //$NON-NLS-1$
public static final String INVOCATION_ARROW = "->"; //$NON-NLS-1$
public static final String STATIC_INVOCATION = "::"; //$NON-NLS-1$
private static final char[] SEMICOLON_AND_COLON = new char[] { ';', ',' };
private static final char[] SEMICOLON = new char[] { ';' };
private FormatterDocument document;
private PHPFormatterNodeBuilder builder;
private Set<Integer> multiLinecommentsEndOffsets;
private Set<Integer> singleLinecommentsEndOffsets;
private List<IRegion> onOffRegions;
private List<Comment> comments;
/**
* @param builder
* @param document
* @param comments
*/
public PHPFormatterVisitor(FormatterDocument document, PHPFormatterNodeBuilder builder, List<Comment> comments)
{
this.document = document;
this.builder = builder;
processComments(comments);
}
/**
* Collect the comments ending offsets (including the white-spaces that appear after them). Also, in case a
* formatter on/off tag was set, process the comments content to exclude some of the source from formatting.
*
* @param comments
*/
private void processComments(List<Comment> comments)
{
this.comments = comments;
multiLinecommentsEndOffsets = new HashSet<Integer>();
singleLinecommentsEndOffsets = new HashSet<Integer>();
if (comments == null)
{
return;
}
boolean onOffEnabled = document.getBoolean(PHPFormatterConstants.FORMATTER_OFF_ON_ENABLED);
LinkedHashMap<Integer, String> commentsMap = onOffEnabled ? new LinkedHashMap<Integer, String>(comments.size())
: null;
for (Comment comment : comments)
{
int commentType = comment.getCommentType();
int end = comment.getEnd();
if (commentType == Comment.TYPE_SINGLE_LINE)
{
singleLinecommentsEndOffsets.add(builder.getNextNonWhiteCharOffset(document, end));
}
else if (commentType == Comment.TYPE_MULTILINE || commentType == Comment.TYPE_PHPDOC)
{
multiLinecommentsEndOffsets.add(builder.getNextNonWhiteCharOffset(document, end));
}
// Add to the map of comments when the On-Off is enabled.
if (onOffEnabled)
{
int start = comment.getStart();
String commentStr = document.get(start, end);
commentsMap.put(start, commentStr);
}
}
// Generate the On-Off regions
if (onOffEnabled && !commentsMap.isEmpty())
{
Pattern onPattern = Pattern.compile(Pattern.quote(document.getString(PHPFormatterConstants.FORMATTER_ON)));
Pattern offPattern = Pattern
.compile(Pattern.quote(document.getString(PHPFormatterConstants.FORMATTER_OFF)));
onOffRegions = FormatterUtils.resolveOnOffRegions(commentsMap, onPattern, offPattern,
document.getLength() - 1);
}
}
/**
* Returns the On-Off formatting regions, as detected from the comments.<br>
* In case the formatter preferences have this option disabled, or in case there are no On-Off regions, the returned
* list is <code>null</code>.
*
* @return A {@link List} that hold the regions that should be skipped when formatting the source; Null, in case
* there are no on-off regions.
*/
public List<IRegion> getOnOffRegions()
{
return onOffRegions;
}
/**
* Returns true if there is a any type of comment right before the given element.<br>
* There should be only whitespaces between the given offset and the comment.
*
* @param offset
* @return True, if the given offset is right after a comment.
*/
private boolean hasAnyCommentBefore(int offset)
{
return multiLinecommentsEndOffsets.contains(offset) || singleLinecommentsEndOffsets.contains(offset);
}
/**
* Returns true if there is a multi-line comment right before the given element.<br>
* There should be only whitespaces between the given offset and the comment.
*
* @param offset
* @return True, if the given offset is right after a comment.
*/
private boolean hasMultiLineCommentBefore(int offset)
{
return multiLinecommentsEndOffsets.contains(offset);
}
/**
* Returns true if there is a single-line comment right before the given element.<br>
* There should be only whitespaces between the given offset and the comment.
*
* @param offset
* @return True, if the given offset is right after a comment.
*/
private boolean hasSingleLineCommentBefore(int offset)
{
return singleLinecommentsEndOffsets.contains(offset);
}
/*
* (non-Javadoc)
* @see
* org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.IfStatement
* )
*/
@Override
public boolean visit(IfStatement ifStatement)
{
Statement falseStatement = ifStatement.getFalseStatement();
Statement trueStatement = ifStatement.getTrueStatement();
boolean isEmptyFalseBlock = (falseStatement == null);
boolean hasTrueBlock = (trueStatement.getType() == ASTNode.BLOCK);
boolean hasFalseBlock = (!isEmptyFalseBlock && falseStatement.getType() == ASTNode.BLOCK);
// First, construct the if condition node
int start = ifStatement.getStart();
FormatterPHPIfNode conditionNode = new FormatterPHPIfNode(document, hasTrueBlock, ifStatement);
int startLength = (document.charAt(start) == 'e') ? 6 : 2;
// If the expression starts with an 'e', it's an "elseif" expression. Otherwise, it's an "if" expression.
conditionNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, start, start + startLength));
builder.push(conditionNode);
// push the condition elements that appear in parentheses
pushNodeInParentheses('(', ')', start + startLength, trueStatement.getStart(), ifStatement.getCondition(),
TypeBracket.CONDITIONAL_PARENTHESIS);
// Construct the 'true' part of the 'if' and visit its children
if (hasTrueBlock)
{
visitBlockNode((Block) trueStatement, ifStatement, isEmptyFalseBlock);
}
else
{
// Wrap with an empty block node and visit the children
wrapInImplicitBlock(trueStatement, false);
}
builder.checkedPop(conditionNode, trueStatement.getEnd());
if (!isEmptyFalseBlock)
{
// Construct the 'false' part if exist.
// Note that the PHP parser does not provide us with the start offset of the 'else' keyword, so we need
// to locate it in between the end of the 'true' block and the begin of the 'false' block.
// However, in case we have an 'elseif' case, the offset of the false block points to the start of the
// 'elseif' word.
int trueBlockEnd = trueStatement.getEnd();
int falseBlockStart = falseStatement.getStart();
String segment = (trueBlockEnd != falseBlockStart) ? document.get(trueBlockEnd, falseBlockStart)
: StringUtil.EMPTY;
int elsePos = segment.toLowerCase().indexOf("else"); //$NON-NLS-1$
boolean isElseIf = (falseStatement.getType() == ASTNode.IF_STATEMENT);
boolean isConnectedElsif = (isElseIf && elsePos < 0);
FormatterPHPElseNode elseNode = null;
if (!isConnectedElsif)
{
int elseBlockStart = elsePos + trueBlockEnd;
int elseBlockDeclarationEnd = elseBlockStart + 4; // +4 for the keyword 'else'
elseNode = new FormatterPHPElseNode(document, hasFalseBlock, isElseIf, hasTrueBlock,
hasAnyCommentBefore(elseBlockStart));
elseNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, elseBlockStart,
elseBlockDeclarationEnd));
builder.push(elseNode);
}
if (!isConnectedElsif && hasFalseBlock)
{
visitBlockNode((Block) falseStatement, ifStatement, true);
}
else
{
if (isElseIf)
{
// Wrap the incoming 'if' with an Else-If node that will allow us later to break it and indent
// it.
FormatterPHPElseIfNode elseIfNode = new FormatterPHPElseIfNode(document,
hasAnyCommentBefore(falseBlockStart));
elseIfNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, falseBlockStart,
falseBlockStart));
builder.push(elseIfNode);
falseStatement.accept(this);
int falseBlockEnd = falseStatement.getEnd();
builder.checkedPop(elseIfNode, falseBlockEnd);
int end = elseIfNode.getEndOffset();
elseIfNode.setEnd(AbstractFormatterNodeBuilder.createTextNode(document, end, end));
}
else
{
// Wrap with an empty block node and visit the children
wrapInImplicitBlock(falseStatement, false);
}
}
if (elseNode != null)
{
builder.checkedPop(elseNode, falseStatement.getEnd());
}
}
return false;
}
/*
* (non-Javadoc)
* @see
* org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.ArrayAccess
* )
*/
@Override
public boolean visit(ArrayAccess arrayAccess)
{
Expression index = arrayAccess.getIndex();
VariableBase name = arrayAccess.getName();
name.accept(this);
if (arrayAccess.getArrayType() == ArrayAccess.VARIABLE_HASHTABLE)
{
// push a curly brackets and visit the index
pushNodeInParentheses('{', '}', name.getEnd(), arrayAccess.getEnd(), index, TypeBracket.ARRAY_CURLY);
}
else
{
// push a square brackets and visit the index
pushNodeInParentheses('[', ']', name.getEnd(), arrayAccess.getEnd(), index, TypeBracket.ARRAY_SQUARE);
}
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* ArrayCreation )
*/
@Override
public boolean visit(ArrayCreation arrayCreation)
{
// PHP 5.4 introduced a short-syntax for array creations.
// for example: $args = ['superman', 'foo' => 'bar', 'batman', 'spiderman'];
boolean isShortSyntax = !arrayCreation.isHasArrayKey();
// we need to make sure we do not add a new line in front of the 'array' in some cases,
// therefore, we push a common declaration. We set the 'hasBlockedBody' to true to avoid
// indentation.
int declarationEndOffset = arrayCreation.getStart();
if (!isShortSyntax)
{
declarationEndOffset += 5;
visitCommonDeclaration(arrayCreation, declarationEndOffset, true);
}
List<ArrayElement> elements = arrayCreation.elements();
// It's possible to have an extra comma at the end of the array creation. This comma is not
// included in the elements given to us by the arrayCreation so we have to look for it by passing 'true'
// as the value of 'lookForExtraComma'.
TypeBracket bracketType = isShortSyntax ? TypeBracket.ARRAY_SQUARE : TypeBracket.ARRAY_PARENTHESIS;
pushParametersInParentheses(declarationEndOffset, arrayCreation.getEnd(), elements,
TypePunctuation.ARRAY_COMMA, true, bracketType, false);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* ArrayElement )
*/
@Override
public boolean visit(ArrayElement arrayElement)
{
Expression key = arrayElement.getKey();
Expression value = arrayElement.getValue();
List<ASTNode> leftNodes = new ArrayList<ASTNode>(1);
List<ASTNode> rightNodes = null;
if (key == null)
{
leftNodes.add(value);
}
else
{
leftNodes.add(key);
rightNodes = new ArrayList<ASTNode>(1);
rightNodes.add(value);
}
ArrayCreation parent = (ArrayCreation) arrayElement.getParent();
boolean hasSingleElement = parent.elements().size() == 1;
FormatterPHPArrayElementNode arrayElementNode = new FormatterPHPArrayElementNode(document, hasSingleElement,
hasAnyCommentBefore(arrayElement.getStart()));
arrayElementNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, arrayElement.getStart(),
arrayElement.getStart()));
builder.push(arrayElementNode);
visitNodeLists(leftNodes, rightNodes, TypeOperator.KEY_VALUE, null);
arrayElementNode.setEnd(AbstractFormatterNodeBuilder.createTextNode(document, arrayElement.getEnd(),
arrayElement.getEnd()));
builder.checkedPop(arrayElementNode, -1);
return false;
}
/*
* (non-Javadoc)
* @see
* org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.Assignment
* )
*/
@Override
public boolean visit(Assignment assignment)
{
VariableBase leftHandSide = assignment.getLeftHandSide();
Expression rightHandSide = assignment.getRightHandSide();
String operationString = assignment.getOperationString();
visitLeftRightExpression(assignment, leftHandSide, rightHandSide, operationString);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* FormalParameter)
*/
@Override
public boolean visit(FormalParameter formalParameter)
{
Expression parameterName = formalParameter.getParameterName();
Expression parameterType = formalParameter.getParameterType();
if (parameterType != null)
{
if (parameterType.getType() == ASTNode.IDENTIFIER)
{
visit((Identifier) parameterType);
}
else if (parameterType.getType() == ASTNode.NAMESPACE_NAME)
{
visit((NamespaceName) parameterType);
}
visitTextNode(parameterName, true, 1);
}
else
{
parameterName.accept(this);
}
Expression defaultValue = formalParameter.getDefaultValue();
if (defaultValue != null)
{
// locate the '=' operator and push it before visiting the defaultValue
int assignmentOffset = builder.getNextNonWhiteCharOffset(document, parameterName.getEnd());
pushTypeOperator(TypeOperator.ASSIGNMENT, assignmentOffset, false);
visitTextNode(defaultValue, true, 0);
}
return false;
}
/*
* (non-Javadoc)
* @see
* org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.ASTError
* )
*/
@Override
public boolean visit(ASTError astError)
{
builder.setHasErrors(true);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* BackTickExpression)
*/
@Override
public boolean visit(BackTickExpression backTickExpression)
{
visitTextNode(backTickExpression, true, 0);
return false;
}
/*
* (non-Javadoc)
* @see
* org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.Block)
*/
@Override
public boolean visit(Block block)
{
// the default visit for a block assumes that there is an open char for that block, but not necessarily a
// closing char. See also visitBlockNode() for other block visiting options.
FormatterPHPBlockNode blockNode = new FormatterPHPBlockNode(document,
block.getParent().getType() == ASTNode.PROGRAM);
blockNode
.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, block.getStart(), block.getStart() + 1));
builder.push(blockNode);
block.childrenAccept(this);
int end = block.getEnd();
builder.checkedPop(blockNode, end - 1);
if (block.isCurly())
{
int endWithSemicolon = locateCharMatchInLine(end, SEMICOLON_AND_COLON, document, false);
blockNode.setEnd(AbstractFormatterNodeBuilder.createTextNode(document, end - 1, endWithSemicolon));
}
else
{
blockNode.setEnd(AbstractFormatterNodeBuilder.createTextNode(document, end, end));
}
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* BreakStatement)
*/
@Override
public boolean visit(BreakStatement breakStatement)
{
Expression expression = breakStatement.getExpression();
int start = breakStatement.getStart();
int end = breakStatement.getEnd();
if (expression == null)
{
FormatterPHPBreakNode breakNode = new FormatterPHPBreakNode(document, breakStatement.getParent());
breakNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, start, start + 5));
builder.push(breakNode);
builder.checkedPop(breakNode, -1);
}
else
{
// treat it as we treat the 'continue' statement
// push the 'break' keyword.
pushKeyword(start, 5, true, false);
// visit the break expression
expression.accept(this);
}
findAndPushPunctuationNode(TypePunctuation.SEMICOLON, end - 1, false, true);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* ClassDeclaration)
*/
@Override
public boolean visit(ClassDeclaration classDeclaration)
{
visitTypeDeclaration(classDeclaration);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* ClassInstanceCreation)
*/
@Override
public boolean visit(ClassInstanceCreation classInstanceCreation)
{
ClassName className = classInstanceCreation.getClassName();
int creationEnd = classInstanceCreation.getEnd();
boolean hasParentheses = creationEnd != className.getEnd();
// push the 'new' keyword. We push it as a text node and not as a keyword to handle cases
// were we have a reference preceding the new-instance creation ('&new MyClass')
int start = classInstanceCreation.getStart();
visitTextNode(start, start + 3, true, 0);
className.accept(this);
if (hasParentheses)
{
// create a constructor node
List<Expression> ctorParams = classInstanceCreation.ctorParams();
pushParametersInParentheses(className.getEnd(), classInstanceCreation.getEnd(), ctorParams,
TypePunctuation.COMMA, false, TypeBracket.DECLARATION_PARENTHESIS, false);
}
// PHP 5.4 - chainingInstanceCall
ChainingInstanceCall chainingInstanceCall = classInstanceCreation.getChainingInstanceCall();
if (chainingInstanceCall != null)
{
// This is the only place that we can call the visit for the ChainingInstanceCall.
// It's not being called by the regular AST visitor, so we need to call it here.
chainingInstanceCall.accept(this);
}
return false;
}
/*
* (non-Javadoc)
* @see
* org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.ClassName
* )
*/
@Override
public boolean visit(ClassName className)
{
visitTextNode(className, true, 1);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* CloneExpression)
*/
@Override
public boolean visit(CloneExpression cloneExpression)
{
// push the 'clone' as invocation.
int cloneStart = cloneExpression.getStart();
pushFunctionInvocationName(cloneExpression, cloneStart, cloneStart + 5);
// push the expression as if it's in a parentheses expression
List<ASTNode> expressionInList = new ArrayList<ASTNode>(1);
expressionInList.add(cloneExpression.getExpression());
pushParametersInParentheses(cloneStart + 5, cloneExpression.getEnd(), expressionInList, TypePunctuation.COMMA,
false, TypeBracket.INVOCATION_PARENTHESIS, true);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* ConditionalExpression)
*/
@Override
public boolean visit(ConditionalExpression conditionalExpression)
{
// (APSTUD-5303) Since PHP 5.3, it is possible to leave out the middle part of the ternary operator.
// Expression expr1 ?: expr3 returns expr1 if expr1 evaluates to TRUE, and expr3 otherwise.
Expression condition = conditionalExpression.getCondition();
condition.accept(this);
Expression ifTrue = conditionalExpression.getIfTrue();
Expression ifFalse = conditionalExpression.getIfFalse();
// push the conditional operator
int startLookup;
int endLookup;
if (ifTrue != null)
{
startLookup = ifTrue.getStart();
endLookup = ifTrue.getEnd();
}
else
{
startLookup = ifFalse.getStart();
endLookup = condition.getEnd();
}
int conditionalOpOffset = condition.getEnd() + document.get(condition.getEnd(), startLookup).indexOf('?');
pushTypeOperator(TypeOperator.CONDITIONAL, conditionalOpOffset, false);
// visit the true part
if (ifTrue != null)
{
ifTrue.accept(this);
}
// push the colon separator
int colonOffset = endLookup + document.get(endLookup, ifFalse.getStart()).indexOf(':');
pushTypeOperator(TypeOperator.CONDITIONAL_COLON, colonOffset, false);
// visit the false part
ifFalse.accept(this);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* ConstantDeclaration)
*/
@Override
public boolean visit(ConstantDeclaration classConstantDeclaration)
{
// push the 'const' keyword.
pushKeyword(classConstantDeclaration.getStart(), 5, true, false);
// Push the declarations. Each has an assignment char and they are separated by commas.
List<? extends ASTNode> leftNodes = classConstantDeclaration.names();
List<? extends ASTNode> rightNodes = classConstantDeclaration.initializers();
visitNodeLists(leftNodes, rightNodes, TypeOperator.ASSIGNMENT, TypePunctuation.COMMA);
// locate the semicolon at the end of the expression. If exists, push it as a node.
int end = rightNodes.get(rightNodes.size() - 1).getEnd();
findAndPushPunctuationNode(TypePunctuation.SEMICOLON, end, false, true);
return false;
}
/**
* This method is used to visit and push nodes that are separated with some delimiter, and potentially have
* operators between them.<br>
* For example, const A='a', B='b';
*
* @param leftNodes
* A list of ASTNodes.
* @param rightNodes
* A list of ASTNodes that are pairing with the leftNodes. In case there are no pairs, this list may be
* null. However, if there are pairs (such as assignments), the size of this group must match the size of
* the left group.
* @param pairsOperator
* The operator {@link TypeOperator} that should appear between the left and the right pair, when there
* are pairs. May be null only when the rightNodes are null.
* @param pairsSeparator
* A separator that appears between the leftNodes. If there are pairs, the separator appears between one
* pair to the other (may only be null in case a separator is not needed, e.g. we have only one
* item/pair)
*/
private void visitNodeLists(List<? extends ASTNode> leftNodes, List<? extends ASTNode> rightNodes,
TypeOperator pairsOperator, TypePunctuation pairsSeparator)
{
// push the expressions one at a time, with comma nodes between them.
int leftSize = leftNodes.size();
for (int i = 0; i < leftSize; i++)
{
ASTNode left = leftNodes.get(i);
ASTNode right = (rightNodes != null) ? rightNodes.get(i) : null;
left.accept(this);
if (right != null && pairsOperator != null)
{
int startIndex = left.getEnd();
String text = document.get(startIndex, right.getStart());
String typeStr = pairsOperator.toString();
startIndex += text.indexOf(typeStr);
pushTypeOperator(pairsOperator, startIndex, false);
right.accept(this);
}
// add a separator if needed
if (pairsSeparator != null && i + 1 < leftNodes.size())
{
int startIndex = left.getEnd();
String text = document.get(startIndex, leftNodes.get(i + 1).getStart());
String separatorStr = pairsSeparator.toString();
startIndex += text.indexOf(separatorStr);
pushTypePunctuation(pairsSeparator, startIndex);
}
}
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* ContinueStatement)
*/
@Override
public boolean visit(ContinueStatement continueStatement)
{
// push the 'continue' keyword.
int continueStart = continueStatement.getStart();
int end = continueStatement.getEnd() - 1;
Expression expression = continueStatement.getExpression();
pushKeyword(continueStart, 8, true, expression == null);
// visit the continue expression, if exists
if (expression != null)
{
expression.accept(this);
}
findAndPushPunctuationNode(TypePunctuation.SEMICOLON, end, false, true);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* DeclareStatement)
*/
@Override
public boolean visit(DeclareStatement declareStatement)
{
Statement body = declareStatement.getBody();
List<Identifier> directiveNames = declareStatement.directiveNames();
List<Expression> directiveValues = declareStatement.directiveValues();
// push the declare keyword as a function invocation
int start = declareStatement.getStart();
pushFunctionInvocationName(declareStatement, start, start + 7);
// push the parentheses with the names and the values that we have inside
int openParen = PHPFormatterNodeBuilder.locateCharForward(document, '(', start + 7, comments);
int closeParen = PHPFormatterNodeBuilder.locateCharBackward(document, ')', (body != null) ? body.getStart()
: declareStatement.getEnd(), comments);
FormatterPHPParenthesesNode parenthesesNode = new FormatterPHPParenthesesNode(document,
TypeBracket.DECLARATION_PARENTHESIS);
parenthesesNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, openParen, openParen + 1));
builder.push(parenthesesNode);
// push the list of names and values
visitNodeLists(directiveNames, directiveValues, TypeOperator.ASSIGNMENT, TypePunctuation.COMMA);
builder.checkedPop(parenthesesNode, -1);
parenthesesNode.setEnd(AbstractFormatterNodeBuilder.createTextNode(document, closeParen, closeParen + 1));
if (body != null)
{
body.accept(this);
}
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* EchoStatement )
*/
@Override
public boolean visit(EchoStatement echoStatement)
{
// push the 'echo' invocation.
int echoStart = echoStatement.getStart();
pushFunctionInvocationName(echoStatement, echoStart, echoStart + 4);
// push the expressions one at a time, with comma nodes between them.
List<Expression> expressions = echoStatement.expressions();
pushParametersInParentheses(echoStart + 4, echoStatement.getEnd(), expressions, TypePunctuation.COMMA, false,
TypeBracket.INVOCATION_PARENTHESIS, true);
// locate the semicolon at the end of the expression. If exists, push it as a node.
int end = Math.max(echoStatement.getEnd() - 1, expressions.get(expressions.size() - 1).getEnd());
findAndPushPunctuationNode(TypePunctuation.SEMICOLON, end, false, true);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* EmptyStatement)
*/
@Override
public boolean visit(EmptyStatement emptyStatement)
{
visitTextNode(emptyStatement, true, 0);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* ExpressionStatement)
*/
@Override
public boolean visit(ExpressionStatement expressionStatement)
{
int expressionEnd = expressionStatement.getEnd();
boolean endsWithSemicolon = document.charAt(expressionEnd - 1) == ';';
FormatterPHPExpressionWrapperNode expressionNode = new FormatterPHPExpressionWrapperNode(document);
int start = expressionStatement.getStart();
int end = expressionEnd;
if (endsWithSemicolon)
{
end--;
}
expressionNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, start, start));
builder.push(expressionNode);
expressionStatement.childrenAccept(this);
expressionNode.setEnd(AbstractFormatterNodeBuilder.createTextNode(document, end, end));
builder.checkedPop(expressionNode, -1);
// push a semicolon if we have one
if (endsWithSemicolon)
{
findAndPushPunctuationNode(TypePunctuation.SEMICOLON, end, false, true);
}
return false;
}
/*
* (non-Javadoc)
* @see
* org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.FieldAccess
* )
*/
@Override
public boolean visit(FieldAccess fieldAccess)
{
VariableBase dispatcher = fieldAccess.getDispatcher();
VariableBase member = fieldAccess.getMember();
visitLeftRightExpression(fieldAccess, dispatcher, member, INVOCATION_ARROW);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* ForStatement )
*/
@Override
public boolean visit(ForStatement forStatement)
{
List<Expression> initializers = forStatement.initializers();
List<Expression> conditions = forStatement.conditions();
List<Expression> updaters = forStatement.updaters();
Statement body = forStatement.getBody();
// visit the 'for' keyword
int declarationEndOffset = forStatement.getStart() + 3;
visitCommonDeclaration(forStatement, declarationEndOffset, true);
// visit the elements in the parentheses
int expressionEndOffset = (body != null) ? body.getStart() : forStatement.getEnd();
int openParen = PHPFormatterNodeBuilder.locateCharForward(document, '(', declarationEndOffset, comments);
int closeParen = PHPFormatterNodeBuilder.locateCharBackward(document, ')', expressionEndOffset, comments);
FormatterPHPParenthesesNode parenthesesNode = new FormatterPHPParenthesesNode(document,
TypeBracket.LOOP_PARENTHESIS);
parenthesesNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, openParen, openParen + 1));
builder.push(parenthesesNode);
// visit the initializers, the conditions and the updaters.
// between them, push the semicolons
visitNodeLists(initializers, null, null, TypePunctuation.COMMA);
int semicolonOffset = PHPFormatterNodeBuilder.locateCharForward(document, ';', declarationEndOffset, comments);
pushTypePunctuation(TypePunctuation.FOR_SEMICOLON, semicolonOffset);
visitNodeLists(conditions, null, null, TypePunctuation.COMMA);
semicolonOffset = PHPFormatterNodeBuilder.locateCharForward(document, ';', semicolonOffset + 1, comments);
pushTypePunctuation(TypePunctuation.FOR_SEMICOLON, semicolonOffset);
visitNodeLists(updaters, null, null, TypePunctuation.COMMA);
// close the parentheses node.
builder.checkedPop(parenthesesNode, -1);
parenthesesNode.setEnd(AbstractFormatterNodeBuilder.createTextNode(document, closeParen, closeParen + 1));
// in case we have a 'body', visit it.
commonVisitBlockBody(forStatement, body);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* ForEachStatement)
*/
@Override
public boolean visit(ForEachStatement forEachStatement)
{
Expression expression = forEachStatement.getExpression();
Expression key = forEachStatement.getKey(); // the 'key' is optional
Expression value = forEachStatement.getValue();
Statement body = forEachStatement.getStatement();
// visit the 'foreach' keyword
int declarationEndOffset = forEachStatement.getStart() + 7;
visitCommonDeclaration(forEachStatement, declarationEndOffset, true);
// visit the elements in the parentheses
int expressionEndOffset = (body != null) ? body.getStart() : forEachStatement.getEnd();
int openParen = PHPFormatterNodeBuilder.locateCharForward(document, '(', declarationEndOffset, comments);
int closeParen = PHPFormatterNodeBuilder.locateCharBackward(document, ')', expressionEndOffset, comments);
FormatterPHPParenthesesNode parenthesesNode = new FormatterPHPParenthesesNode(document,
TypeBracket.LOOP_PARENTHESIS);
parenthesesNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, openParen, openParen + 1));
builder.push(parenthesesNode);
// push the expression
visitTextNode(expression, true, 0);
// add the 'as' node (it's between the expression and the key/value)
int endLookupForAs = (key != null) ? key.getStart() : value.getStart();
String txt = document.get(expression.getEnd(), endLookupForAs);
int asStart = expression.getEnd() + txt.toLowerCase().indexOf("as"); //$NON-NLS-1$
visitTextNode(asStart, asStart + 2, true, 1, 1);
// push the key and the value.
if (key != null)
{
visitLeftRightExpression(null, key, value, TypeOperator.KEY_VALUE.toString());
}
else
{
// push only the value as a text node
visitTextNode(value, true, 1);
}
// close the parentheses node.
builder.checkedPop(parenthesesNode, -1);
parenthesesNode.setEnd(AbstractFormatterNodeBuilder.createTextNode(document, closeParen, closeParen + 1));
// in case we have a 'body', visit it.
commonVisitBlockBody(forEachStatement, body);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* WhileStatement)
*/
@Override
public boolean visit(WhileStatement whileStatement)
{
visitCommonLoopBlock(whileStatement, whileStatement.getStart() + 5, whileStatement.getBody(),
whileStatement.getCondition());
return false;
}
/*
* (non-Javadoc)
* @see
* org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.DoStatement
* )
*/
@Override
public boolean visit(DoStatement doStatement)
{
Statement body = doStatement.getBody();
// First, push the 'do' declaration node and its body
visitCommonLoopBlock(doStatement, doStatement.getStart() + 2, body, null);
// now deal with the 'while' condition part. we need to include the word 'while' that appears
// somewhere between the block-end and the condition start.
// We wrap this node as a begin-end node that will hold the condition internals as children
FormatterPHPNonBlockedWhileNode whileNode = new FormatterPHPNonBlockedWhileNode(document);
// Search for the exact 'while' start offset
int whileBeginOffset = PHPFormatterNodeBuilder.locateCharForward(document, 'w', body.getEnd(), comments);
int conditionEnd = locateCharMatchInLine(doStatement.getEnd(), SEMICOLON, document, true);
whileNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, whileBeginOffset, conditionEnd));
builder.push(whileNode);
builder.checkedPop(whileNode, -1);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* FunctionDeclaration)
*/
@Override
public boolean visit(FunctionDeclaration functionDeclaration)
{
visitFunctionDeclaration(functionDeclaration, functionDeclaration.getFunctionName(),
functionDeclaration.formalParameters(), null, functionDeclaration.getBody());
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* FunctionInvocation)
*/
@Override
public boolean visit(FunctionInvocation functionInvocation)
{
visitFunctionInvocation(functionInvocation);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* GlobalStatement)
*/
@Override
public boolean visit(GlobalStatement globalStatement)
{
pushKeyword(globalStatement.getStart(), 6, true, false);
List<Variable> variables = globalStatement.variables();
visitNodeLists(variables, null, null, TypePunctuation.COMMA);
// we also need to push the semicolon for the global
findAndPushPunctuationNode(TypePunctuation.SEMICOLON, globalStatement.getEnd() - 1, false, true);
return false;
}
/*
* (non-Javadoc)
* @see
* org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.GotoLabel
* )
*/
@Override
public boolean visit(GotoLabel gotoLabel)
{
// To Goto label is setting the end by including the spaces between the last non-white char
// and the terminating colon. We trim those spaces in the formatting.
FormatterPHPLineStartingNode lineStartingNode = new FormatterPHPLineStartingNode(document);
int start = gotoLabel.getStart();
int end = gotoLabel.getEnd();
int trimmedLength = document.get(start, end - 1).trim().length();
int labelEnd = end - (end - start - trimmedLength);
lineStartingNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, start, labelEnd));
builder.push(lineStartingNode);
builder.checkedPop(lineStartingNode, -1);
findAndPushPunctuationNode(TypePunctuation.GOTO_COLON, end - 1, false, true);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* GotoStatement )
*/
@Override
public boolean visit(GotoStatement gotoStatement)
{
Identifier label = gotoStatement.getLabel();
pushKeyword(gotoStatement.getStart(), 4, true, false);
label.accept(this);
findAndPushPunctuationNode(TypePunctuation.SEMICOLON, gotoStatement.getEnd() - 1, false, true);
return false;
}
/*
* (non-Javadoc)
* @see
* org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.Identifier
* )
*/
@Override
public boolean visit(Identifier identifier)
{
visitTextNode(identifier, true, 0);
return false;
}
/*
* (non-Javadoc)
* @see
* org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.IgnoreError
* )
*/
@Override
public boolean visit(IgnoreError ignoreError)
{
// push the first sign ('@') as a simple text node.
int start = ignoreError.getStart();
int end = start + 1;
visitTextNode(start, end, true, 0);
// visit the expression
ignoreError.getExpression().accept(this);
return false;
}
/*
* (non-Javadoc)
* @see
* org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.Include
* )
*/
@Override
public boolean visit(Include include)
{
// push the 'include' keyword.
int includeStart = include.getStart();
int keywordLength = Include.getType(include.getIncludeType()).length();
boolean firstInLine = include.getParent().getType() != ASTNode.IGNORE_ERROR;
pushKeyword(includeStart, keywordLength, firstInLine, false, true);
// visit the include expression.
Expression expression = include.getExpression();
expression.accept(this);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* InfixExpression)
*/
@Override
public boolean visit(InfixExpression infixExpression)
{
ASTNode left = infixExpression.getLeft();
ASTNode right = infixExpression.getRight();
// Instead of calling InfixExpression.getOperator(infixExpression.getOperator()), we grab the operator
// from the document as is. This way, we will be able to handle case-insensitive operators, as well as 'synonym'
// operators such as <> and !=.
// However, in case the length of the original operator is smaller, we use it anyway (see #APSTUD-4356 as a
// reason for that)
String operatorStr = InfixExpression.getOperator(infixExpression.getOperator());
String operatorStringAsIs = document.get(left.getEnd(), right.getStart()).trim();
if (operatorStr.length() < operatorStringAsIs.length())
{
operatorStringAsIs = operatorStr;
}
visitLeftRightExpression(infixExpression, left, right, operatorStringAsIs);
return false;
}
/*
* (non-Javadoc)
* @see
* org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.InLineHtml
* )
*/
@Override
public boolean visit(InLineHtml inLineHtml)
{
visitTextNode(inLineHtml, false, 0);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* InstanceOfExpression)
*/
@Override
public boolean visit(InstanceOfExpression instanceOfExpression)
{
Expression expression = instanceOfExpression.getExpression();
ClassName className = instanceOfExpression.getClassName();
// visit the left expression
expression.accept(this);
// locate the word 'instanceof' in the gap between the expression and the class name.
int exprEnd = expression.getEnd();
String txt = document.get(exprEnd, className.getStart());
int instanceOfStart = exprEnd + txt.toLowerCase().indexOf("instanceof"); //$NON-NLS-1$
visitTextNode(instanceOfStart, instanceOfStart + 10, true, 1);
// visit the right class name
className.accept(this);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* InterfaceDeclaration)
*/
@Override
public boolean visit(InterfaceDeclaration interfaceDeclaration)
{
visitTypeDeclaration(interfaceDeclaration);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* LambdaFunctionDeclaration)
*/
@Override
public boolean visit(LambdaFunctionDeclaration lambdaFunctionDeclaration)
{
if (lambdaFunctionDeclaration.isStatic())
{
// Unfortunately, the only way to get to the 'static' keyword is by traversing back and look for the 's'.
int lambdaStart = lambdaFunctionDeclaration.getStart();
int staticStart = PHPFormatterNodeBuilder.locateCharBackward(document, 's', lambdaStart);
if (lambdaStart != staticStart)
{
// push a 'static' keyword node
FormatterPHPKeywordNode staticNode = new FormatterPHPKeywordNode(document, false, false);
staticNode
.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, staticStart, staticStart + 6));
builder.push(staticNode);
builder.checkedPop(staticNode, -1);
}
}
visitFunctionDeclaration(lambdaFunctionDeclaration, null, lambdaFunctionDeclaration.formalParameters(),
lambdaFunctionDeclaration.lexicalVariables(), lambdaFunctionDeclaration.getBody());
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* ListVariable )
*/
@Override
public boolean visit(ListVariable listVariable)
{
List<VariableBase> variables = listVariable.variables();
int start = listVariable.getStart();
pushFunctionInvocationName(listVariable, start, start + 4);
pushParametersInParentheses(start + 4, listVariable.getEnd(), variables, TypePunctuation.COMMA, false,
TypeBracket.DECLARATION_PARENTHESIS, false);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* MethodDeclaration)
*/
@Override
public boolean visit(MethodDeclaration methodDeclaration)
{
FunctionDeclaration function = methodDeclaration.getFunction();
visitModifiers(methodDeclaration.getStart(), function.getStart());
function.accept(this);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* FieldsDeclaration)
*/
@Override
public boolean visit(FieldsDeclaration fieldsDeclaration)
{
// A class field declaration is treated in a similar way we treat a class method declaration
Variable[] variableNames = fieldsDeclaration.getVariableNames();
Variable firstVariable = variableNames[0];
visitModifiers(fieldsDeclaration.getStart(), firstVariable.getStart());
// visit the variables and their values
Expression[] initialValues = fieldsDeclaration.getInitialValues();
// visit the variables and their initial values
List<? extends ASTNode> variablesList = Arrays.asList(variableNames);
List<? extends ASTNode> valuesList = (initialValues != null) ? Arrays.asList(initialValues) : null;
visitNodeLists(variablesList, valuesList, TypeOperator.ASSIGNMENT, TypePunctuation.COMMA);
// locate the push the semicolon at the end
findAndPushPunctuationNode(TypePunctuation.SEMICOLON, fieldsDeclaration.getEnd() - 1, false, true);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* MethodInvocation)
*/
@Override
public boolean visit(MethodInvocation methodInvocation)
{
visitLeftRightExpression(methodInvocation, methodInvocation.getDispatcher(), methodInvocation.getMethod(),
INVOCATION_ARROW);
// note: we push the semicolon as part of the function-invocation that we have in this node.
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* StaticMethodInvocation)
*/
@Override
public boolean visit(StaticMethodInvocation staticMethodInvocation)
{
visitLeftRightExpression(staticMethodInvocation, staticMethodInvocation.getClassName(),
staticMethodInvocation.getMethod(), STATIC_INVOCATION);
// note: we push the semicolon as part of the function-invocation that we have in this node.
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* NamespaceDeclaration)
*/
@Override
public boolean visit(NamespaceDeclaration namespaceDeclaration)
{
int start = namespaceDeclaration.getStart();
pushKeyword(start, 9, true, false);
int end = start + 9;
NamespaceName namespaceName = namespaceDeclaration.getName();
if (namespaceName != null)
{
namespaceName.accept(this);
end = namespaceName.getEnd();
}
findAndPushPunctuationNode(TypePunctuation.SEMICOLON, end, false, true);
// visit the namespace body block. If this block is invisible one, wrap it in a special
// namespace block to allow indentation customization.
FormatterPHPNamespaceBlockNode bodyNode = new FormatterPHPNamespaceBlockNode(document);
Block body = namespaceDeclaration.getBody();
if (body.isCurly())
{
body.accept(this);
}
else
{
int bodyStart = body.getStart();
int bodyEnd = body.getEnd();
bodyNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, bodyStart, bodyStart));
builder.push(bodyNode);
body.childrenAccept(this);
bodyNode.setEnd(AbstractFormatterNodeBuilder.createTextNode(document, bodyEnd, bodyEnd));
builder.checkedPop(bodyNode, namespaceDeclaration.getEnd());
}
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* NamespaceName )
*/
@Override
public boolean visit(NamespaceName namespaceName)
{
List<Identifier> segments = namespaceName.segments();
int start = namespaceName.getStart();
if (namespaceName.isGlobal())
{
// look for the '\' that came before the name and push it separately.
start = PHPFormatterNodeBuilder.locateCharBackward(document, '\\', start, comments);
pushTypePunctuation(TypePunctuation.NAMESPACE_SEPARATOR, start);
}
// Push the rest of the segments as a list of nodes.
visitNodeLists(segments, null, null, TypePunctuation.NAMESPACE_SEPARATOR);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* ParenthesisExpression)
*/
@Override
public boolean visit(ParenthesisExpression parenthesisExpression)
{
FormatterPHPParenthesesNode parenthesesNode = new FormatterPHPParenthesesNode(document, TypeBracket.PARENTHESIS);
int start = parenthesisExpression.getStart();
parenthesesNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, start, start + 1));
builder.push(parenthesesNode);
parenthesisExpression.childrenAccept(this);
builder.checkedPop(parenthesesNode, -1);
int end = parenthesisExpression.getEnd();
parenthesesNode.setEnd(AbstractFormatterNodeBuilder.createTextNode(document, end - 1, end));
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* PostfixExpression)
*/
@Override
public boolean visit(PostfixExpression postfixExpression)
{
VariableBase var = postfixExpression.getVariable();
TypeOperator op;
if (postfixExpression.getOperator() == PostfixExpression.OP_INC)
{
op = TypeOperator.POSTFIX_INCREMENT;
}
else
{
op = TypeOperator.POSTFIX_DECREMENT;
}
var.accept(this);
int leftOffset = var.getEnd();
int operatorOffset = document.get(leftOffset, postfixExpression.getEnd()).indexOf(op.toString()) + leftOffset;
pushTypeOperator(op, operatorOffset, false);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* PrefixExpression)
*/
@Override
public boolean visit(PrefixExpression prefixExpression)
{
VariableBase var = prefixExpression.getVariable();
TypeOperator op;
if (prefixExpression.getOperator() == PrefixExpression.OP_INC)
{
op = TypeOperator.PREFIX_INCREMENT;
}
else
{
op = TypeOperator.PREFIX_DECREMENT;
}
int leftOffset = prefixExpression.getStart();
int operatorOffset = document.get(leftOffset, var.getStart()).indexOf(op.toString()) + leftOffset;
pushTypeOperator(op, operatorOffset, false);
var.accept(this);
return false;
}
/*
* (non-Javadoc)
* @see
* org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.Quote)
*/
@Override
public boolean visit(Quote quote)
{
int quoteType = quote.getQuoteType();
String quoteStr = document.get(quote.getStart(), quote.getEnd());
// Check for HEREDOC, NOWDOC and multi-lines strings.
if (quoteType == Quote.QT_HEREDOC || quoteType == Quote.QT_NOWDOC
|| LINE_SPLIT_PATTERN.split(quoteStr, 2).length == 2)
{
FormatterPHPHeredocNode heredocNode = new FormatterPHPHeredocNode(document, quote.getStart(),
quote.getEnd());
IFormatterContainerNode parentNode = builder.peek();
parentNode.addChild(heredocNode);
}
else
{
visitTextNode(quote, true, 0);
}
return false;
}
/*
* (non-Javadoc)
* @see
* org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.Reference
* )
*/
@Override
public boolean visit(Reference reference)
{
// push the first reference sign ('&') as a simple text node.
int start = reference.getStart();
int end = start + 1;
visitTextNode(start, end, true, 0);
// visit the referenced expression
reference.getExpression().accept(this);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* ReflectionVariable)
*/
@Override
public boolean visit(ReflectionVariable reflectionVariable)
{
// push the first dollar sign as a simple text node.
int start = reflectionVariable.getStart();
int end = start + 1;
visitTextNode(start, end, true, 0);
// visit the name variable
reflectionVariable.getName().accept(this);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* ReturnStatement)
*/
@Override
public boolean visit(ReturnStatement returnStatement)
{
// push the 'return' keyword.
int returnStart = returnStatement.getStart();
Expression expression = returnStatement.getExpression();
pushKeyword(returnStart, 6, true, expression == null);
// visit the return expression.
if (expression != null)
{
expression.accept(this);
}
// Check if the statement ends with a semicolon. If so, push it as a text node.
// push the ending semicolon
findAndPushPunctuationNode(TypePunctuation.SEMICOLON, returnStatement.getEnd() - 1, false, true);
return false;
}
/*
* (non-Javadoc)
* @see
* org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.Scalar)
*/
@Override
public boolean visit(Scalar scalar)
{
// In case the Scalar is a string, we need to check if the string spans across multiple lines. If this is the
// case, we have to exclude part of this scalar from the formatting. Otherwise, we'll change the content of that
// string.
if (scalar.getScalarType() == Scalar.TYPE_STRING)
{
String[] split = LINE_SPLIT_PATTERN.split(scalar.getStringValue(), 2);
if (split.length > 1)
{
// We have a multi-line string.
FormatterPHPExcludedTextNode heredocNode = new FormatterPHPExcludedTextNode(document, 0, 0);
heredocNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, scalar.getStart(),
scalar.getEnd()));
IFormatterContainerNode parentNode = builder.peek();
parentNode.addChild(heredocNode);
return false;
}
}
visitTextNode(scalar, true, 0);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* StaticConstantAccess)
*/
@Override
public boolean visit(StaticConstantAccess classConstantAccess)
{
visitLeftRightExpression(classConstantAccess, classConstantAccess.getClassName(),
classConstantAccess.getConstant(), TypeOperator.STATIC_INVOCATION.toString());
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* StaticFieldAccess)
*/
@Override
public boolean visit(StaticFieldAccess staticFieldAccess)
{
visitLeftRightExpression(staticFieldAccess, staticFieldAccess.getClassName(), staticFieldAccess.getField(),
TypeOperator.STATIC_INVOCATION.toString());
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* StaticStatement)
*/
@Override
public boolean visit(StaticStatement staticStatement)
{
pushKeyword(staticStatement.getStart(), 6, true, false);
List<Expression> expressions = staticStatement.expressions();
visitNodeLists(expressions, null, null, TypePunctuation.COMMA);
// push the ending semicolon
findAndPushPunctuationNode(TypePunctuation.SEMICOLON, staticStatement.getEnd() - 1, false, true);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* SwitchStatement)
*/
@Override
public boolean visit(SwitchStatement switchStatement)
{
Block body = switchStatement.getBody();
// In case the body is not curly, we are dealing with an alternative syntax (e.g. colon and 'endswitch' instead
// of curly open and close for the body).
boolean isAlternativeSyntax = !body.isCurly();
// Push the switch-case declaration node
FormatterPHPDeclarationNode switchNode = new FormatterPHPDeclarationNode(document, true, switchStatement);
int rightParenthesis = PHPFormatterNodeBuilder.locateCharBackward(document, ')', body.getStart(), comments);
switchNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, switchStatement.getStart(),
rightParenthesis + 1));
builder.push(switchNode);
builder.checkedPop(switchNode, -1);
// push a switch-case body node
int blockStart = body.getStart();
FormatterPHPSwitchNode blockNode = new FormatterPHPSwitchNode(document);
blockNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, blockStart, blockStart + 1));
builder.push(blockNode);
// visit the children under that block node
body.childrenAccept(this);
int endingOffset = switchStatement.getEnd();
endingOffset--;
if (isAlternativeSyntax)
{
// deduct the 'endswitch' length.
// we already removed 1 offset above, so we remove the extra 8.
endingOffset -= 8;
}
// APSTUD-3382 - Check if we have a comment right before the end of this switch.
if (hasAnyCommentBefore(endingOffset))
{
Comment comment = PHPDocUtils.getCommentByType(comments, endingOffset, document.getText(), -1);
if (comment != null)
{
// push a text node for that comment
blockNode.addChild(AbstractFormatterNodeBuilder.createTextNode(document, comment.getStart(),
comment.getEnd()));
}
}
blockNode.setEnd(AbstractFormatterNodeBuilder.createTextNode(document, endingOffset, endingOffset + 1));
// pop the block node
builder.checkedPop(blockNode, -1);
return false;
}
/*
* (non-Javadoc)
* @see
* org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.SwitchCase
* )
*/
@Override
public boolean visit(SwitchCase switchCase)
{
List<Statement> actions = switchCase.actions();
boolean hasBlockedChild = (actions.size() == 1 && actions.get(0).getType() == ASTNode.BLOCK);
// compute the colon position
int endCaseOffset = (switchCase.isDefault()) ? switchCase.getStart() + 7 : switchCase.getValue().getEnd();
int colonOffset = PHPFormatterNodeBuilder.locateCharForward(document, ':', endCaseOffset, comments);
// push the case/default node till the colon.
// We create a begin-end node that will hold a case-colon node as an inner child to manage its spacing.
FormatterPHPExpressionWrapperNode caseNode = new FormatterPHPExpressionWrapperNode(document);
// get the value-end offset. In case it's a 'default' case, set the end at the end offset of the word 'default'
int valueEnd = switchCase.isDefault() ? switchCase.getStart() + 7 : switchCase.getValue().getEnd();
caseNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, switchCase.getStart(), valueEnd));
caseNode.setEnd(AbstractFormatterNodeBuilder.createTextNode(document, colonOffset + 1, colonOffset + 1));
builder.push(caseNode);
// push the colon node
FormatterPHPCaseColonNode caseColonNode = new FormatterPHPCaseColonNode(document, hasBlockedChild);
caseColonNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, colonOffset, colonOffset + 1));
builder.push(caseColonNode);
builder.checkedPop(caseColonNode, -1);
builder.checkedPop(caseNode, -1);
// push the case/default content
FormatterPHPCaseBodyNode caseBodyNode = new FormatterPHPCaseBodyNode(document, hasBlockedChild, hasBlockedChild
&& hasAnyCommentBefore(actions.get(0).getStart()));
if (hasBlockedChild)
{
Block body = (Block) actions.get(0);
// we have a 'case' with a curly-block
caseBodyNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, body.getStart(),
body.getStart() + 1));
builder.push(caseBodyNode);
body.childrenAccept(this);
int endingOffset = body.getEnd() - 1;
builder.checkedPop(caseBodyNode, endingOffset);
int end = locateCharMatchInLine(endingOffset + 1, SEMICOLON_AND_COLON, document, false);
caseBodyNode.setEnd(AbstractFormatterNodeBuilder.createTextNode(document, endingOffset, end));
}
else
{
if (!actions.isEmpty())
{
int start = actions.get(0).getStart();
if (hasAnyCommentBefore(start))
{
start = caseColonNode.getEndOffset();
}
int end = actions.get(actions.size() - 1).getEnd();
caseBodyNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, start, start));
builder.push(caseBodyNode);
for (Statement st : actions)
{
st.accept(this);
}
builder.checkedPop(caseBodyNode, switchCase.getEnd());
caseBodyNode.setEnd(AbstractFormatterNodeBuilder.createTextNode(document, end, end));
}
}
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* CastExpression)
*/
@Override
public boolean visit(CastExpression castExpression)
{
Expression expression = castExpression.getExpression();
// push the parentheses with the case type inside them
int castCloserOffset = PHPFormatterNodeBuilder.locateCharBackward(document, ')', expression.getStart(),
comments);
visitTextNode(castExpression.getStart(), castCloserOffset, true, 0);
// push the expression
expression.accept(this);
return false;
}
/*
* (non-Javadoc)
* @see
* org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.CatchClause
* )
*/
@Override
public boolean visit(CatchClause catchClause)
{
int declarationEnd = catchClause.getClassName().getEnd();
declarationEnd = PHPFormatterNodeBuilder.locateCharForward(document, ')', declarationEnd, comments) + 1;
visitCommonDeclaration(catchClause, declarationEnd, true);
visitBlockNode(catchClause.getBody(), catchClause, true);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* ThrowStatement)
*/
@Override
public boolean visit(ThrowStatement throwStatement)
{
pushKeyword(throwStatement.getStart(), 5, true, false);
Expression expression = throwStatement.getExpression();
if (expression instanceof ParenthesisExpression)
{
expression.accept(this);
}
else
{
// Unlike PHP 5.3, the PHP 5.4 parser does not give us a ParenthesisExpression when there is an expression
// like "throw (new Exception());"
// We need to manually check for parenthesis that wrap the expression.
String text = document.get(throwStatement.getStart() + 5, expression.getStart());
if (text.trim().startsWith("(")) { //$NON-NLS-1$
pushNodeInParentheses('(', ')', throwStatement.getStart() + 5, expression.getEnd(), expression,
TypeBracket.PARENTHESIS);
}
else
{
expression.accept(this);
}
}
findAndPushPunctuationNode(TypePunctuation.SEMICOLON, expression.getEnd(), false, true);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* TryStatement )
*/
@Override
public boolean visit(TryStatement tryStatement)
{
visitCommonDeclaration(tryStatement, tryStatement.getStart() + 3, true);
visitBlockNode(tryStatement.getBody(), tryStatement, true);
List<CatchClause> catchClauses = tryStatement.catchClauses();
for (CatchClause catchClause : catchClauses)
{
catchClause.accept(this);
}
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* UnaryOperation)
*/
@Override
public boolean visit(UnaryOperation unaryOperation)
{
Expression expression = unaryOperation.getExpression();
String operationString = unaryOperation.getOperationString();
TypeOperator typeOperator = TypeOperator.getTypeOperator(operationString);
pushTypeOperator(typeOperator, unaryOperation.getStart(), true);
expression.accept(this);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* UseStatement )
*/
@Override
public boolean visit(UseStatement useStatement)
{
pushKeyword(useStatement.getStart(), 3, true, false);
List<UseStatementPart> parts = useStatement.parts();
visitNodeLists(parts, null, null, TypePunctuation.COMMA);
findAndPushPunctuationNode(TypePunctuation.SEMICOLON, useStatement.getEnd() - 1, false, true);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* UseStatementPart)
*/
@Override
public boolean visit(UseStatementPart useStatementPart)
{
NamespaceName namespaceName = useStatementPart.getName();
Identifier alias = useStatementPart.getAlias();
// visit the namespace name
namespaceName.accept(this);
// in case it has an alias, add the 'as' node and then visit the alias name.
if (alias != null)
{
String text = document.get(namespaceName.getEnd(), alias.getStart());
int asOffset = text.toLowerCase().indexOf("as"); //$NON-NLS-1$
asOffset += namespaceName.getEnd();
pushKeyword(asOffset, 2, false, false);
alias.accept(this);
}
return false;
}
/*
* (non-Javadoc)
* @see
* org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.Variable
* )
*/
@Override
public boolean visit(Variable variable)
{
visitTextNode(variable, true, 0);
return false;
}
// ### PHP 5.4 nodes ### //
/**
* ChainingInstanceCall visit. For example: <code>$X = (new foo)->setX(20)->getX();</code>
*
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* ChainingInstanceCall)
*/
@Override
public boolean visit(ChainingInstanceCall chainingCall)
{
// We skip this one. It's being covered by the method/function visits.
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* FullyQualifiedTraitMethodReference)
*/
@Override
public boolean visit(FullyQualifiedTraitMethodReference node)
{
// e.g. B::foo in a trait 'use' expression.
visitLeftRightExpression(node, node.getClassName(), node.getFunctionName(),
TypeOperator.STATIC_INVOCATION.toString());
return false;
}
/**
* PHPArrayDereferenceList visit. For example:
*
* <pre>
* function cars() {
* return ['Honda', 'Toyota', 'Lotus'];
* }
* echo cars()[2]; // Outputs: Lotus
* </pre>
*
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* PHPArrayDereferenceList)
*/
@Override
public boolean visit(PHPArrayDereferenceList dereferenceList)
{
// Drill down to the DereferenceNode visit...
return super.visit(dereferenceList);
}
/**
* DereferenceNode visit for the square brackets we have at the PHPArrayDereferenceList expression.
*
* @see PHPArrayDereferenceList
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* DereferenceNode)
*/
@Override
public boolean visit(DereferenceNode dereferenceNode)
{
pushNodeInParentheses('[', ']', dereferenceNode.getStart(), dereferenceNode.getEnd(),
dereferenceNode.getName(), TypeBracket.ARRAY_SQUARE);
return false;
}
/*
* (non-Javadoc)
* @see
* org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.TraitAlias
* )
*/
@Override
public boolean visit(TraitAlias node)
{
// @formatter:off
// For example, each line in this block is a TraitAliasStatement node:
// use A {
// B::foo as C;
// myFunc as protected;
// sayHello as private myPrivateHello;
// }
// @formatter:on
// Wrap this one with a node
FormatterPHPTraitPrecedenceWrapperNode wrapperNode = new FormatterPHPTraitPrecedenceWrapperNode(document);
int start = node.getStart();
int end = node.getEnd();
wrapperNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, start, start));
builder.push(wrapperNode);
// Visit the method
Expression traitMethod = node.getTraitMethod();
traitMethod.accept(this);
// push the 'as' keyword. Start by looking for the functionName and a possible modifier. Note that in case the
// modifer is 'public', we have to check for an actual 'public' keyword. The default modifier is 'public',
// however, it's not neccessary to include the keyword in the PHP code.
String modifier = PHPFlags.toString(node.getModifier());
Identifier functionName = node.getFunctionName();
int traitMethodEndOffset = traitMethod.getEnd();
String txt = document.get(traitMethodEndOffset,
functionName != null ? functionName.getStart() : node.getModifierOffset());
int asStart = traitMethodEndOffset + txt.toLowerCase().indexOf("as"); //$NON-NLS-1$
visitTextNode(asStart, asStart + 2, true, 1, 1);
// Visit any modifier we have
int modifierOffset = node.getModifierOffset();
if (txt.indexOf(modifier) > -1 || functionName == null)
{
visitTextNode(modifierOffset, modifierOffset + modifier.length(), true, 1, functionName != null ? 1 : 0);
}
// Visit the function name or push the modifier string
if (functionName != null)
{
functionName.accept(this);
}
// Close the wrapper
wrapperNode.setEnd(AbstractFormatterNodeBuilder.createTextNode(document, end, end));
builder.checkedPop(wrapperNode, -1);
// Push a semicolon and make sure it's a line-terminating one.
findAndPushPunctuationNode(TypePunctuation.SEMICOLON, node.getEnd() - 1, false, true);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* TraitDeclaration)
*/
@Override
public boolean visit(TraitDeclaration traitDeclaration)
{
visitTypeDeclaration(traitDeclaration);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* TraitPrecedence)
*/
@Override
public boolean visit(TraitPrecedence node)
{
// @formatter:off
// For example, each line in this block is a TraitPrecedence node:
// use A {
// B::foo insteadof A, B, C;
// A::fii insteadof D;
// }
// @formatter:on
// Wrap this one with a node
FormatterPHPTraitPrecedenceWrapperNode wrapperNode = new FormatterPHPTraitPrecedenceWrapperNode(document);
int start = node.getStart();
int end = node.getEnd();
wrapperNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, start, start));
builder.push(wrapperNode);
// Visit the fully-qualified trait reference that appears before the 'insteadof' keyword.
FullyQualifiedTraitMethodReference methodReference = node.getMethodReference();
methodReference.accept(this);
// push the 'insteadof' keyword.
List<NamespaceName> trList = node.getTrList();
int exprEnd = methodReference.getEnd();
String txt = document.get(exprEnd, trList.get(0).getStart());
int insteadofStart = exprEnd + txt.toLowerCase().indexOf("insteadof"); //$NON-NLS-1$
visitTextNode(insteadofStart, insteadofStart + 9, true, 1, 1);
// Visit the list of trait names that appear after the 'insteadof' keyword.
visitNodeLists(trList, null, null, TypePunctuation.COMMA);
// Close the wrapper
wrapperNode.setEnd(AbstractFormatterNodeBuilder.createTextNode(document, end, end));
builder.checkedPop(wrapperNode, -1);
// Push a semicolon and make sure it's a line-terminating one.
findAndPushPunctuationNode(TypePunctuation.SEMICOLON, trList.get(trList.size() - 1).getEnd() - 1, false, true);
return false;
}
/*
* (non-Javadoc)
* @see org2.eclipse.php.internal.core.ast.visitor.AbstractVisitor#visit(org2.eclipse.php.internal.core.ast.nodes.
* TraitUseStatement)
*/
@Override
public boolean visit(TraitUseStatement traitUse)
{
pushKeyword(traitUse.getStart(), 3, true, false);
// Push the trait list
List<NamespaceName> traitList = traitUse.getTraitList();
visitNodeLists(traitList, null, null, TypePunctuation.COMMA);
// This will visit the NamespaceName nodes that exist as the TraitUseStatement children.
// visit any 'conflict-resolution' block (e.g. B::foo insteadof A;)
List<TraitStatement> tsList = traitUse.getTsList();
if (tsList != null)
{
// look for curly brackets. Note that there is no easy way to tell if the list is empty because it does not
// exist, or it's empty because we have an empty block.
int lastTraitListOffset = traitList.get(traitList.size() - 1).getEnd() - 1;
int openCurlyOffset = PHPFormatterNodeBuilder.locateCharForward(document, '{', lastTraitListOffset,
comments);
FormatterPHPBlockNode blockNode = null;
if (openCurlyOffset != lastTraitListOffset && openCurlyOffset < traitUse.getEnd())
{
blockNode = new FormatterPHPBlockNode(document, false);
blockNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, openCurlyOffset,
openCurlyOffset + 1));
builder.push(blockNode);
}
if (!tsList.isEmpty())
{
for (TraitStatement statement : tsList)
{
statement.accept(this);
}
}
if (blockNode != null)
{
// locate the closin curly and push it.
int closeCurlyOffset = PHPFormatterNodeBuilder.locateCharBackward(document, '}', traitUse.getEnd(),
comments);
blockNode.setEnd(AbstractFormatterNodeBuilder.createTextNode(document, closeCurlyOffset,
closeCurlyOffset + 1));
builder.checkedPop(blockNode, traitUse.getEnd());
}
}
return false;
}
// ###################### Helper Methods ###################### //
/**
* Visit and push a class/interface declaration
*
* @param typeDeclaration
*/
private void visitTypeDeclaration(TypeDeclaration typeDeclaration)
{
// Locate the end offset of the declaration (before the block starts)
Block body = typeDeclaration.getBody();
int declarationBeginEnd = body.getStart() - 1;
List<Identifier> interfaces = typeDeclaration.interfaces();
if (interfaces != null && !interfaces.isEmpty())
{
declarationBeginEnd = interfaces.get(interfaces.size() - 1).getEnd();
}
else if (typeDeclaration.getType() == ASTNode.CLASS_DECLARATION
&& ((ClassDeclaration) typeDeclaration).getSuperClass() != null)
{
declarationBeginEnd = ((ClassDeclaration) typeDeclaration).getSuperClass().getEnd();
}
else
{
declarationBeginEnd = typeDeclaration.getName().getEnd();
}
FormatterPHPDeclarationNode typeNode = new FormatterPHPDeclarationNode(document, true, typeDeclaration);
typeNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, typeDeclaration.getStart(),
declarationBeginEnd));
builder.push(typeNode);
builder.checkedPop(typeNode, -1);
// add the class body
FormatterPHPTypeBodyNode typeBodyNode = new FormatterPHPTypeBodyNode(document,
hasAnyCommentBefore(body.getStart()));
typeBodyNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, body.getStart(),
body.getStart() + 1));
builder.push(typeBodyNode);
body.childrenAccept(this);
int end = body.getEnd();
builder.checkedPop(typeBodyNode, end - 1);
typeBodyNode.setEnd(AbstractFormatterNodeBuilder.createTextNode(document, end - 1, end));
}
/**
* A visit for a function invocation. This visit can be performed in numerous occasions, so we have it as a separate
* method that will be called in those occasions.
*
* @param functionInvocation
*/
private void visitFunctionInvocation(FunctionInvocation functionInvocation)
{
FunctionName functionName = functionInvocation.getFunctionName();
// push the function's name
pushFunctionInvocationName(functionInvocation, functionName.getStart(), functionName.getEnd());
// push the parenthesis and the parameters (if exist)
List<Expression> invocationParameters = functionInvocation.parameters();
pushParametersInParentheses(functionName.getEnd(), functionInvocation.getEnd(), invocationParameters,
TypePunctuation.COMMA, false, TypeBracket.INVOCATION_PARENTHESIS, false);
// PHP 5.4
PHPArrayDereferenceList arrayDereferenceList = functionInvocation.getArrayDereferenceList();
if (arrayDereferenceList != null)
{
arrayDereferenceList.accept(this);
}
}
/**
* Push the name part of a function invocation.
*
* @param invocationNode
* @param nameStart
* @param nameEnd
*/
private void pushFunctionInvocationName(ASTNode invocationNode, int nameStart, int nameEnd)
{
FormatterPHPFunctionInvocationNode node = new FormatterPHPFunctionInvocationNode(document, invocationNode);
node.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, nameStart, nameEnd));
builder.push(node);
builder.checkedPop(node, -1);
}
/**
* Push a FormatterPHPParenthesesNode that contains a parameters array. <br>
* Each parameter in the parameters list is expected to be separated from the others with a comma.
*
* @param declarationEndOffset
* @param expressionEndOffset
* @param parameters
* @param punctuationType
* A {@link TypePunctuation}. Usually this should be a COMMA, but it can also be something else to
* provide special formatting styles.
* @param lookForExtraComma
* Indicate that the parameters list may end with an extra comma that is not included in them. This
* function will look for that comma if the value is true and will add it as a punctuation node in case
* it was found.
* @param bracketsType
* The type of parentheses to push.
* @param needsMatchingBracketVerification
* Indicate that a brackets check needs to be done in order to determine if a parentheses node is to be
* added. This is needed in some special cases where the brackets are optional (like with 'echo'
* statements).
*/
private void pushParametersInParentheses(int declarationEndOffset, int expressionEndOffset,
List<? extends ASTNode> parameters, TypePunctuation punctuationType, boolean lookForExtraComma,
TypeBracket bracketsType, boolean needsMatchingBracketVerification)
{
// in some cases, we get a ParethesisExpression inside a single parameter.
// for those cases, we skip the parentheses node push and go straight to the
// push of the ParethesisExpression, which should handle the rest.
boolean pushParenthesisNode = parameters.size() != 1 || parameters.size() == 1
&& parameters.get(0).getType() != ASTNode.PARENTHESIS_EXPRESSION;
int openParenOffset = builder.getNextNonWhiteCharOffset(document, declarationEndOffset);
if (needsMatchingBracketVerification && pushParenthesisNode)
{
// This check will handle different cases, like:
// echo 1;
// echo (new IDE) -> first();
// echo ('hello');
if (openParenOffset > -1 && document.charAt(openParenOffset) == bracketsType.getLeft().charAt(0))
{
// We make sure that the expression starts and ends with a matching parentheses that wraps it.
pushParenthesisNode = isWrappedInMatchingBrackets(bracketsType, declarationEndOffset,
expressionEndOffset, document);
}
}
FormatterPHPParenthesesNode parenthesesNode = null;
if (pushParenthesisNode)
{
if (bracketsType.getLeft().charAt(0) == document.charAt(openParenOffset))
{
parenthesesNode = new FormatterPHPParenthesesNode(document, false, parameters.size(), bracketsType);
parenthesesNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, openParenOffset,
openParenOffset + 1));
}
else
{
parenthesesNode = new FormatterPHPParenthesesNode(document, true, parameters.size(), bracketsType);
parenthesesNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, openParenOffset,
openParenOffset));
}
builder.push(parenthesesNode);
}
if (parameters != null && parameters.size() > 0)
{
visitNodeLists(parameters, null, null, punctuationType);
if (lookForExtraComma)
{
// Look ahead to find any extra comma that we may have. If found, push it as a punctuation node.
int lastParamEnd = parameters.get(parameters.size() - 1).getEnd();
int nextNonWhitespace = builder.getNextNonWhiteCharOffset(document, lastParamEnd);
if (document.charAt(nextNonWhitespace) == ',')
{
pushTypePunctuation(punctuationType, nextNonWhitespace);
}
}
}
if (pushParenthesisNode)
{
int closeParenStart = expressionEndOffset;
int closeParenEnd = expressionEndOffset;
if (!parenthesesNode.isAsWrapper())
{
closeParenStart = PHPFormatterNodeBuilder.locateCharBackward(document, bracketsType.getRight()
.charAt(0), expressionEndOffset - 1, comments);
closeParenEnd = closeParenStart + 1;
}
if (hasSingleLineCommentBefore(closeParenStart))
{
// Make sure that the closing pair will not get pushed up when there is a comment line right before it.
parenthesesNode.setNewLineBeforeClosing(true);
builder.checkedPop(parenthesesNode, closeParenStart);
}
else if (hasMultiLineCommentBefore(closeParenStart))
{
builder.checkedPop(parenthesesNode, closeParenStart);
}
else
{
builder.checkedPop(parenthesesNode, -1);
}
parenthesesNode.setEnd(AbstractFormatterNodeBuilder
.createTextNode(document, closeParenStart, closeParenEnd));
}
}
/**
* Push a FormatterPHPParenthesesNode that contains an ASTNode (expression). <br>
*
* @param openChar
* The parentheses open char (e.g. '(', '[' etc.)
* @param closeChar
* The parentheses close char (e.g. ')', ']' etc.)
* @param declarationEndOffset
* @param expressionEndOffset
* @param node
*/
private void pushNodeInParentheses(char openChar, char closeChar, int declarationEndOffset,
int expressionEndOffset, ASTNode node, TypeBracket type)
{
int openParen = PHPFormatterNodeBuilder.locateCharForward(document, openChar, declarationEndOffset, comments);
int closeParen = PHPFormatterNodeBuilder.locateCharBackward(document, closeChar, expressionEndOffset, comments);
FormatterPHPParenthesesNode parenthesesNode = new FormatterPHPParenthesesNode(document, type);
parenthesesNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, openParen, openParen + 1));
builder.push(parenthesesNode);
if (node != null)
{
node.accept(this);
}
builder.checkedPop(parenthesesNode, -1);
parenthesesNode.setEnd(AbstractFormatterNodeBuilder.createTextNode(document, closeParen, closeParen + 1));
}
/**
* Visits and push a modifiers section. This section can appear before a method or a variable in a class, before
* class definitions etc.
*
* @param node
* The node that holds the modifier
* @param nextNode
* The next node that appears right after the modifiers.
*/
private void visitModifiers(int startOffset, int endOffset)
{
// The gap between the start and the function holds the modifiers (if exist).
// We create a node for each of these modifiers to remove any extra spaces they have between them.
String modifiers = document.get(startOffset, endOffset);
Matcher matcher = WORD_PATTERN.matcher(modifiers);
boolean isFirst = true;
while (matcher.find())
{
FormatterPHPKeywordNode modifierNode = new FormatterPHPKeywordNode(document, isFirst, false);
modifierNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, matcher.start() + startOffset,
matcher.end() + startOffset));
builder.push(modifierNode);
builder.checkedPop(modifierNode, -1);
isFirst = false;
}
if (isFirst)
{
// if we got to this point with the 'isFirst' as 'true', we know that the modifiers are empty.
// in this case, we need to push an empty modifiers node.
FormatterPHPKeywordNode emptyModifier = new FormatterPHPKeywordNode(document, isFirst, false);
emptyModifier.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, startOffset, startOffset));
builder.push(emptyModifier);
builder.checkedPop(emptyModifier, -1);
}
}
/**
* @param node
* @param declarationEndOffset
* @param body
*/
private void visitCommonLoopBlock(ASTNode node, int declarationEndOffset, Statement body, ASTNode condition)
{
visitCommonDeclaration(node, declarationEndOffset, true);
// if we have conditions, visit them as well
if (condition != null)
{
int conditionEnd = (body != null) ? body.getStart() : node.getEnd();
pushNodeInParentheses('(', ')', declarationEndOffset, conditionEnd, condition, TypeBracket.LOOP_PARENTHESIS);
}
// visit the body
commonVisitBlockBody(node, body);
}
/**
* A common visit for a body ASTNode, which can be a Block or a different statement that will be wrapped in an
* implicit block node.
*
* @param parent
* @param body
*/
private void commonVisitBlockBody(ASTNode parent, ASTNode body)
{
boolean hasBlockedBody = (body != null && body.getType() == ASTNode.BLOCK);
boolean emptyBody = (body != null && body.getType() == ASTNode.EMPTY_STATEMENT);
if (hasBlockedBody)
{
visitBlockNode((Block) body, parent, true);
}
else if (body != null)
{
if (!emptyBody)
{
wrapInImplicitBlock(body, true);
}
else
{
// create and push a special node that represents this empty statement.
// When visiting a loop, this statement will probably only holds a semicolon char, so we make sure
// we attach the char to the end of the previous node.
body.accept(this);
}
}
}
/**
* A simple visit and push of a node that pushes a PHP text node which consumes any white-spaces before that node by
* request.
*
* @param node
* @param consumePreviousWhitespaces
* @param spacesCountBefore
* @see #visitTextNode(int, int, boolean, int)
*/
private void visitTextNode(ASTNode node, boolean consumePreviousWhitespaces, int spacesCountBefore)
{
visitTextNode(node.getStart(), node.getEnd(), consumePreviousWhitespaces, spacesCountBefore);
}
/**
* A simple visit and push of a node that pushes a PHP text node which consumes any white-spaces before that node by
* request.
*
* @param startOffset
* @param endOffset
* @param consumePreviousWhitespaces
* @param spacesCountBefore
* @see #visitTextNode(ASTNode, boolean, int)
*/
private void visitTextNode(int startOffset, int endOffset, boolean consumePreviousWhitespaces, int spacesCountBefore)
{
visitTextNode(startOffset, endOffset, consumePreviousWhitespaces, spacesCountBefore, 0);
}
/**
* A simple visit and push of a node that pushes a PHP text node which consumes any white-spaces before that node by
* request.
*
* @param startOffset
* @param endOffset
* @param consumePreviousWhitespaces
* @param spacesCountBefore
* @param spacesCountAfter
*/
private void visitTextNode(int startOffset, int endOffset, boolean consumePreviousWhitespaces,
int spacesCountBefore, int spacesCountAfter)
{
FormatterPHPTextNode textNode = new FormatterPHPTextNode(document, consumePreviousWhitespaces,
spacesCountBefore, spacesCountAfter);
textNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, startOffset, endOffset));
builder.push(textNode);
builder.checkedPop(textNode, endOffset);
}
/**
* Visit and push a FormatterPHPBlockNode. <br>
* The given body can represent a curly-braces body, or even an alternative syntax body. This method will check the
* block to see if it's curly, and if not, it will try to match the alternative syntax closer according to the given
* parent node type.
*
* @param block
* The block
* @param parent
* The block's parent
* @see http://www.php.net/manual/en/control-structures.alternative-syntax.php
*/
private void visitBlockNode(Block block, ASTNode parent, boolean consumeEndingSemicolon)
{
boolean isAlternativeSyntaxBlock = !block.isCurly();
FormatterPHPBlockNode blockNode = new FormatterPHPBlockNode(document, hasAnyCommentBefore(block.getStart()));
blockNode
.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, block.getStart(), block.getStart() + 1));
builder.push(blockNode);
// visit the children
block.childrenAccept(this);
int end = block.getEnd();
int closingStartOffset = end;
if (!isAlternativeSyntaxBlock)
{
closingStartOffset--;
}
if (isAlternativeSyntaxBlock)
{
String alternativeSyntaxCloser = getAlternativeSyntaxCloser(parent);
int alternativeCloserLength = alternativeSyntaxCloser.length();
if (closingStartOffset - alternativeCloserLength >= 0
&& document.get(closingStartOffset - alternativeCloserLength, closingStartOffset).toLowerCase()
.equals(alternativeSyntaxCloser))
{
closingStartOffset -= alternativeCloserLength;
}
}
// pop the block node
builder.checkedPop(blockNode, Math.min(closingStartOffset, end));
blockNode.setEnd(AbstractFormatterNodeBuilder.createTextNode(document, closingStartOffset,
Math.max(closingStartOffset, end)));
}
/**
* Visit and push a function declaration. The declaration can be a 'regular' function or can be a lambda function.
*
* @param functionDeclaration
* @param functionName
* @param formalParameters
* @param lexicalParameters
* @param body
*/
private void visitFunctionDeclaration(ASTNode functionDeclaration, Identifier functionName,
List<FormalParameter> formalParameters, List<Expression> lexicalParameters, Block body)
{
// First, push the function declaration node
int declarationEnd = functionDeclaration.getStart() + 8;
visitCommonDeclaration(functionDeclaration, declarationEnd, true);
// push the function name node, if exists
if (functionName != null)
{
visitTextNode(functionName, true, 1);
declarationEnd = functionName.getEnd();
}
boolean hasLexicalParams = (lexicalParameters != null && !lexicalParameters.isEmpty());
int parametersEnd = (body != null) ? body.getStart() : functionDeclaration.getEnd();
if (hasLexicalParams)
{
int firstLexicalOffset = lexicalParameters.get(0).getStart();
// Search backward for the letter 'u' from the word 'use'
parametersEnd = PHPFormatterNodeBuilder.locateCharBackward(document, 'u', firstLexicalOffset, comments) - 1;
}
// push the function parameters
pushParametersInParentheses(declarationEnd, parametersEnd, formalParameters, TypePunctuation.COMMA, false,
TypeBracket.DECLARATION_PARENTHESIS, false);
// In case we have 'lexical' parameters, like we get with a lambda-function, we push them after pushing the
// 'use' keyword (for example: function($aaa) use ($bbb, $ccc)...)
if (hasLexicalParams)
{
// Locate and push the 'use'
int useKeywordStart = builder.getNextNonWhiteCharOffset(document, builder.peek().getEndOffset());
pushKeyword(useKeywordStart, 3, false, false);
// Push the lexical parameters
pushParametersInParentheses(useKeywordStart + 3, body.getStart(), lexicalParameters, TypePunctuation.COMMA,
false, TypeBracket.DECLARATION_PARENTHESIS, false);
}
// Then, push the body
if (body != null)
{
FormatterPHPFunctionBodyNode bodyNode = new FormatterPHPFunctionBodyNode(document,
hasAnyCommentBefore(body.getStart()));
bodyNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, body.getStart(),
body.getStart() + 1));
builder.push(bodyNode);
body.childrenAccept(this);
int bodyEnd = body.getEnd();
builder.checkedPop(bodyNode, bodyEnd - 1);
bodyNode.setEnd(AbstractFormatterNodeBuilder.createTextNode(document, bodyEnd - 1, bodyEnd));
}
}
/**
* Visit and push a common declaration part of an expression.s
*
* @param node
* @param declarationEndOffset
* @param hasBlockedBody
*/
private void visitCommonDeclaration(ASTNode node, int declarationEndOffset, boolean hasBlockedBody)
{
FormatterPHPDeclarationNode declarationNode = new FormatterPHPDeclarationNode(document, hasBlockedBody, node);
declarationNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, node.getStart(),
declarationEndOffset));
builder.push(declarationNode);
builder.checkedPop(declarationNode, -1);
}
/**
* Visit an expression with left node, right node and an operator in between.<br>
* Note that the left <b>or</b> the right may be null.
*
* @param left
* @param right
* @param operatorString
*/
private void visitLeftRightExpression(ASTNode parentNode, ASTNode left, ASTNode right, String operatorString)
{
int leftOffset;
int rightOffset;
if (left != null)
{
left.accept(this);
leftOffset = left.getEnd();
}
else
{
leftOffset = parentNode.getStart();
}
if (right != null)
{
rightOffset = right.getStart();
}
else
{
rightOffset = parentNode.getEnd();
}
int operatorOffset = document.get(leftOffset, rightOffset).indexOf(operatorString) + leftOffset;
TypeOperator typeOperator = TypeOperator.getTypeOperator(operatorString.toLowerCase());
pushTypeOperator(typeOperator, operatorOffset, false);
if (right != null)
{
right.accept(this);
}
}
private void pushTypeOperator(TypeOperator operator, int startOffset, boolean isUnary)
{
FormatterPHPOperatorNode node = new FormatterPHPOperatorNode(document, operator, isUnary);
node.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, startOffset, startOffset
+ operator.toString().length()));
builder.push(node);
builder.checkedPop(node, -1);
}
private void pushTypePunctuation(TypePunctuation punctuation, int startOffset)
{
FormatterPHPPunctuationNode node = new FormatterPHPPunctuationNode(document, punctuation);
node.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, startOffset, startOffset
+ punctuation.toString().length()));
builder.push(node);
builder.checkedPop(node, -1);
}
/**
* Returns the string value that represents the closing of an alternative syntax block. In case non exists, this
* method returns an empty string.
*
* @param parent
* @return The alternative syntax block-closing string.
*/
private String getAlternativeSyntaxCloser(ASTNode parent)
{
switch (parent.getType())
{
case ASTNode.IF_STATEMENT:
return "endif"; //$NON-NLS-1$
case ASTNode.WHILE_STATEMENT:
return "endwhile"; //$NON-NLS-1$
case ASTNode.FOR_EACH_STATEMENT:
return "endforeach"; //$NON-NLS-1$
case ASTNode.FOR_STATEMENT:
return "endfor"; //$NON-NLS-1$
case ASTNode.SWITCH_STATEMENT:
return "endswitch"; //$NON-NLS-1$
default:
return StringUtil.EMPTY;
}
}
/**
* Push a keyword (e.g. 'const', 'echo', 'private' etc.)
*
* @param start
* @param keywordLength
* @param isFirstInLine
* @param isLastInLine
*/
private void pushKeyword(int start, int keywordLength, boolean isFirstInLine, boolean isLastInLine)
{
pushKeyword(start, keywordLength, isFirstInLine, isLastInLine, false);
}
/**
* Push a keyword (e.g. 'const', 'echo', 'private' etc.)
*
* @param start
* @param keywordLength
* @param isFirstInLine
* @param isLastInLine
* @param consumeSpaces
* Consume any spaces before the keyword.
*/
private void pushKeyword(int start, int keywordLength, boolean isFirstInLine, boolean isLastInLine,
boolean consumeSpaces)
{
FormatterPHPKeywordNode keywordNode = new FormatterPHPKeywordNode(document, isFirstInLine, isLastInLine,
consumeSpaces);
keywordNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, start, start + keywordLength));
builder.push(keywordNode);
builder.checkedPop(keywordNode, -1);
}
/**
* Locate and push a punctuation char node.
*
* @param offsetToSearch
* - The offset that will be used as the start for the search of the semicolon.
* @param ignoreNonWhitespace
* indicate that a non-whitespace chars that appear before the semicolon will be ignored. If this flag is
* false, and a non-whitespace appear between the given offset and the semicolon, the method will
* <b>not</b> push a semicolon node.
* @param isLineTerminating
* Indicates that this punctuation node is a line terminating one.
*/
private void findAndPushPunctuationNode(TypePunctuation type, int offsetToSearch, boolean ignoreNonWhitespace,
boolean isLineTerminating)
{
char punctuationType = type.toString().charAt(0);
int punctuationOffset = PHPFormatterNodeBuilder.locateCharForward(document, punctuationType, offsetToSearch,
comments);
if (punctuationOffset != offsetToSearch || document.charAt(punctuationOffset) == punctuationType)
{
if (offsetToSearch + 1 < punctuationOffset)
{
// Check this when the punctuation type was found a few characters ahead (not off by one).
String segment = document.get(offsetToSearch, punctuationOffset);
if (!ignoreNonWhitespace && segment.trim().length() > 0)
{
return;
}
}
if (isLineTerminating)
{
// We need to make sure that the termination only happens when the line does not
// have a terminator already.
int lineEnd = locateWhitespaceLineEndingOffset(punctuationOffset + 1);
isLineTerminating = lineEnd < 0;
}
FormatterPHPPunctuationNode punctuationNode = new FormatterPHPPunctuationNode(document, type,
isLineTerminating);
punctuationNode.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, punctuationOffset,
punctuationOffset + 1));
builder.push(punctuationNode);
builder.checkedPop(punctuationNode, -1);
}
}
/**
* Wrap a given node in an implicit block node and visit the node to insert it as a child of that block.
*
* @param node
* The node to wrap and visit.
* @param indent
*/
private void wrapInImplicitBlock(ASTNode node, boolean indent)
{
FormatterPHPImplicitBlockNode emptyBlock = new FormatterPHPImplicitBlockNode(document, false, indent, 0);
int start = node.getStart();
int end = node.getEnd();
emptyBlock.setBegin(AbstractFormatterNodeBuilder.createTextNode(document, start, start));
builder.push(emptyBlock);
node.accept(this);
builder.checkedPop(emptyBlock, -1);
emptyBlock.setEnd(AbstractFormatterNodeBuilder.createTextNode(document, end, end));
}
/**
* Locate a line ending offset. The line should only contain whitespace characters.
*
* @return The line ending offset, or -1 in case not found.
*/
private int locateWhitespaceLineEndingOffset(int start)
{
int length = document.getLength();
for (int offset = start; offset < length; offset++)
{
char c = document.charAt(offset);
if (c == '\n' || c == '\r')
{
return offset;
}
if (!Character.isWhitespace(c))
{
return -1;
}
}
return -1;
}
/**
* Scan for a list of char terminator located at the <b>same line</b>. Return the given offset if non is found.<br>
* <b>See important note in the @return tag.</b>
*
* @param offset
* @param chars
* An array of chars to match
* @param document
* @param ignoreNonWhitespace
* In case this flag is false, any non-whitespace char that appear before we located a requested char
* will stop the search. In case it's true, the search will continue till the end of the line.
* @return The first match offset; The given offset if a match not found. <b>Note that the returned offset is in a
* +1 position from the real character offset. This is to ease the caller process of adapting it to the
* formatter-document's offsets.</b>
*/
private int locateCharMatchInLine(int offset, char[] chars, FormatterDocument document, boolean ignoreNonWhitespace)
{
int i = offset;
int size = document.getLength();
for (; i < size; i++)
{
char c = document.charAt(i);
for (char toMatch : chars)
{
if (c == toMatch)
{
return i + 1;
}
}
if (c == '\n' || c == '\r')
{
break;
}
if (!ignoreNonWhitespace && (c != ' ' || c != '\t'))
{
break;
}
}
return offset;
}
/**
* Check if the document range is wrapped in matching brackets. Not only that the brackets have to be balanced, they
* also need to wrap the range (excluding whitespaces).
*
* @param bracketsType
* @param startOffset
* @param endOffset
* @param document
* @return <code>true</code> iff the range is wrapped with the bracket-type open and close chars; <code>false</code>
* otherwise.
*/
private static boolean isWrappedInMatchingBrackets(TypeBracket bracketsType, int startOffset, int endOffset,
FormatterDocument document)
{
endOffset = Math.min(endOffset, document.getLength() - 1);
if (document.charAt(endOffset) == ';')
{
endOffset--;
}
char openChar = bracketsType.getLeft().charAt(0);
char closeChar = bracketsType.getRight().charAt(0);
Stack<Character> brackets = new Stack<Character>();
for (; startOffset <= endOffset; startOffset++)
{
char c = document.charAt(startOffset);
if (c == openChar)
{
brackets.push(c);
}
else if (c == closeChar)
{
if (brackets.isEmpty() || brackets.pop().charValue() != openChar)
{
return false;
}
}
else if (!Character.isWhitespace(c) && brackets.isEmpty())
{
return false;
}
}
return brackets.isEmpty();
}
}