blob: e337d99edb5234e455aa2f5f13a7066f5479b56e [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.groovy.formatter.processors;
import com.intellij.formatting.Spacing;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.psi.formatter.FormatterUtil;
import com.intellij.psi.impl.source.SourceTreeToPsiMap;
import com.intellij.psi.impl.source.tree.CompositeElement;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.GroovyLanguage;
import org.jetbrains.plugins.groovy.codeStyle.GroovyCodeStyleSettings;
import org.jetbrains.plugins.groovy.formatter.FormattingContext;
import org.jetbrains.plugins.groovy.formatter.GeeseUtil;
import org.jetbrains.plugins.groovy.formatter.blocks.GroovyBlock;
import org.jetbrains.plugins.groovy.formatter.blocks.ParameterListBlock;
import org.jetbrains.plugins.groovy.formatter.models.spacing.SpacingTokens;
import org.jetbrains.plugins.groovy.lang.groovydoc.lexer.GroovyDocTokenTypes;
import org.jetbrains.plugins.groovy.lang.groovydoc.parser.GroovyDocElementTypes;
import org.jetbrains.plugins.groovy.lang.groovydoc.psi.api.*;
import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
import org.jetbrains.plugins.groovy.lang.lexer.TokenSets;
import org.jetbrains.plugins.groovy.lang.parser.GroovyElementTypes;
import org.jetbrains.plugins.groovy.lang.psi.GroovyElementVisitor;
import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrListOrMap;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotation;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationArgumentList;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationArrayInitializer;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.*;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrNamedArgument;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrSpreadArgument;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrOpenBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.clauses.GrForInClause;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.*;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrStringInjection;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrIndexProperty;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrAnonymousClassDefinition;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinitionBody;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrAnnotationMethod;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrEnumConstant;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.GrTopStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrArrayTypeElement;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeArgumentList;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeParameterList;
import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil;
/**
* @author ilyas
*/
public class GroovySpacingProcessor extends GroovyElementVisitor {
private PsiElement myParent;
private final GroovyCodeStyleSettings myGroovySettings;
private final CommonCodeStyleSettings mySettings;
private Spacing myResult;
private ASTNode myChild1;
private ASTNode myChild2;
private IElementType myType1;
private IElementType myType2;
public GroovySpacingProcessor(GroovyBlock block1, GroovyBlock block2, FormattingContext context) {
mySettings = context.getSettings();
myGroovySettings = context.getGroovySettings();
final ASTNode node = block2.getNode();
if (init(node)) return;
if (manageComments()) return;
if (manageMethodParameterList(block2)) return;
if (myParent instanceof GroovyPsiElement) {
((GroovyPsiElement) myParent).accept(this);
}
}
private boolean init(ASTNode node) {
_init(node);
if (myChild1 == null || myChild2 == null) {
return true;
}
PsiElement psi1 = myChild1.getPsi();
PsiElement psi2 = myChild2.getPsi();
if (psi1 == null || psi2 == null) {
return true;
}
if (psi1.getLanguage() != GroovyLanguage.INSTANCE || psi2.getLanguage() != GroovyLanguage.INSTANCE) {
return true;
}
return false;
}
private void _init(@Nullable final ASTNode child) {
if (child == null) return;
ASTNode treePrev = child.getTreePrev();
while (treePrev != null && isWhiteSpace(treePrev)) {
treePrev = treePrev.getTreePrev();
}
if (treePrev == null) {
_init(child.getTreeParent());
}
else {
myChild2 = child;
myType2 = myChild2.getElementType();
myChild1 = treePrev;
myType1 = myChild1.getElementType();
final CompositeElement parent = (CompositeElement)treePrev.getTreeParent();
myParent = SourceTreeToPsiMap.treeElementToPsi(parent);
}
}
private boolean manageMethodParameterList(GroovyBlock block2) {
if (block2 instanceof ParameterListBlock) {
createSpaceInCode(mySettings.SPACE_BEFORE_METHOD_PARENTHESES);
return true;
}
return false;
}
private boolean manageComments() {
if (mySettings.KEEP_FIRST_COLUMN_COMMENT && TokenSets.COMMENT_SET.contains(myType2)) {
if (!isAfterElementOrSemi(GroovyElementTypes.IMPORT_STATEMENT)) {
myResult = Spacing.createKeepingFirstColumnSpacing(0, Integer.MAX_VALUE, true, 1);
return true;
}
return false;
}
ASTNode prev = FormatterUtil.getPreviousNonWhitespaceLeaf(myChild2);
if (prev != null && prev.getElementType() == GroovyTokenTypes.mNLS) {
prev = FormatterUtil.getPreviousNonWhitespaceLeaf(prev);
}
if (prev != null && prev.getElementType() == GroovyTokenTypes.mSL_COMMENT) {
myResult = Spacing.createSpacing(0, Integer.MAX_VALUE, 1, mySettings.KEEP_LINE_BREAKS, keepBlankLines());
return true;
}
return false;
}
@Override
public void visitLiteralExpression(GrLiteral literal) {
createSpaceInCode(false);
}
@Override
public void visitGStringInjection(GrStringInjection injection) {
createSpaceInCode(false);
}
@Override
public void visitLabeledStatement(GrLabeledStatement labeledStatement) {
if (myType1 == GroovyTokenTypes.mCOLON) {
if (myGroovySettings.INDENT_LABEL_BLOCKS && !(myType2 == GroovyElementTypes.LITERAL)) {
createLF(true);
}
else {
createSpaceInCode(true);
}
}
}
@Override
public void visitAnnotation(GrAnnotation annotation) {
if (myType2 == GroovyElementTypes.ANNOTATION_ARGUMENTS) {
createSpaceInCode(mySettings.SPACE_BEFORE_ANOTATION_PARAMETER_LIST);
}
}
@Override
public void visitAnnotationArgumentList(GrAnnotationArgumentList annotationArgumentList) {
if (myType1 == GroovyTokenTypes.mLPAREN || myType2 == GroovyTokenTypes.mRPAREN) {
createSpaceInCode(mySettings.SPACE_WITHIN_ANNOTATION_PARENTHESES);
}
}
@Override
public void visitArgumentList(GrArgumentList list) {
if (myType1 == GroovyTokenTypes.mLBRACK || myType2 == GroovyTokenTypes.mRBRACK) {
createSpaceInCode(mySettings.SPACE_WITHIN_BRACKETS);
}
else {
processParentheses(GroovyTokenTypes.mLPAREN,
GroovyTokenTypes.mRPAREN,
mySettings.SPACE_WITHIN_METHOD_CALL_PARENTHESES,
mySettings.SPACE_WITHIN_EMPTY_METHOD_CALL_PARENTHESES,
mySettings.CALL_PARAMETERS_LPAREN_ON_NEXT_LINE,
mySettings.CALL_PARAMETERS_RPAREN_ON_NEXT_LINE);
}
}
private void createDependentLFSpacing(final boolean isLineFeed, final boolean isSpace, @NotNull final TextRange range) {
if (isLineFeed) {
myResult = Spacing.createDependentLFSpacing(isSpace ? 1 : 0, isSpace ? 1 : 0, range, mySettings.KEEP_LINE_BREAKS, keepBlankLines());
}
else {
createSpaceInCode(isSpace);
}
}
@Override
public void visitConditionalExpression(GrConditionalExpression expression) {
if (myType2 == GroovyTokenTypes.mQUESTION) {
createSpaceInCode(mySettings.SPACE_BEFORE_QUEST);
}
else if (myType1 == GroovyTokenTypes.mQUESTION) {
createSpaceInCode(mySettings.SPACE_AFTER_QUEST);
}
else if (myType2 == GroovyTokenTypes.mCOLON) {
createSpaceInCode(mySettings.SPACE_BEFORE_COLON);
}
else if (myType1 == GroovyTokenTypes.mCOLON) {
createSpaceInCode(mySettings.SPACE_AFTER_COLON);
}
}
@Override
public void visitElvisExpression(GrElvisExpression expression) {
if (myType1 == GroovyTokenTypes.mELVIS) {
createSpaceInCode(mySettings.SPACE_AFTER_COLON);
}
else if (myType2 == GroovyTokenTypes.mELVIS) {
createSpaceInCode(mySettings.SPACE_BEFORE_QUEST);
}
}
@Override
public void visitMethodCallExpression(GrMethodCallExpression methodCallExpression) {
if (myType2 == GroovyElementTypes.ARGUMENTS) {
manageSpaceBeforeCallLParenth();
}
else if (myType2 == GroovyElementTypes.CLOSABLE_BLOCK) {
createSpaceInCode(myGroovySettings.SPACE_BEFORE_CLOSURE_LBRACE);
}
}
private void manageSpaceBeforeCallLParenth() {
createSpaceInCode(mySettings.SPACE_BEFORE_METHOD_CALL_PARENTHESES);
}
@Override
public void visitApplicationStatement(GrApplicationStatement applicationStatement) {
if (myType2 == GroovyElementTypes.ARGUMENTS) manageSpaceBeforeCallLParenth();
}
@Override
public void visitIndexProperty(GrIndexProperty expression) {
if (myType2 == GroovyElementTypes.ARGUMENTS) manageSpaceBeforeCallLParenth();
}
@Override
public void visitConstructorInvocation(GrConstructorInvocation invocation) {
if (myType2 == GroovyElementTypes.ARGUMENTS) manageSpaceBeforeCallLParenth();
}
@Override
public void visitNewExpression(GrNewExpression newExpression) {
if (myType1 == GroovyTokenTypes.kNEW) {
createSpaceInCode(true);
}
else if (myType2 == GroovyElementTypes.ARGUMENTS) {
manageSpaceBeforeCallLParenth();
}
else if (myType2 == GroovyElementTypes.ARRAY_DECLARATOR) {
createSpaceInCode(false);
}
}
@Override
public void visitArrayDeclaration(GrArrayDeclaration arrayDeclaration) {
createSpaceInCode(false);
}
@Override
public void visitArrayTypeElement(GrArrayTypeElement typeElement) {
createSpaceInCode(false);
}
private void manageSpaceInTuple() {
if (myType1 == GroovyTokenTypes.mLPAREN || myType2 == GroovyTokenTypes.mRPAREN) {
createSpaceInCode(myGroovySettings.SPACE_WITHIN_TUPLE_EXPRESSION);
}
}
@Override
public void visitEnumConstant(GrEnumConstant enumConstant) {
manageSpaceBeforeCallLParenth();
}
@Override
public void visitVariableDeclaration(GrVariableDeclaration variableDeclaration) {
manageSpaceInTuple();
}
@Override
public void visitTupleExpression(GrTupleExpression tupleExpression) {
manageSpaceInTuple();
}
@Override
public void visitSpreadArgument(GrSpreadArgument spreadArgument) {
if (myType1 == GroovyTokenTypes.mSTAR) {
createSpaceInCode(mySettings.SPACE_AROUND_UNARY_OPERATOR);
}
}
@Override
public void visitFile(GroovyFileBase file) {
if (isAfterElementOrSemi(GroovyElementTypes.PACKAGE_DEFINITION)) {
myResult = Spacing.createSpacing(0, 0, mySettings.BLANK_LINES_AFTER_PACKAGE + 1, mySettings.KEEP_LINE_BREAKS, keepBlankLines());
}
else if (myType2 == GroovyElementTypes.PACKAGE_DEFINITION) {
myResult = Spacing.createSpacing(0, 0, mySettings.BLANK_LINES_BEFORE_PACKAGE + 1, mySettings.KEEP_LINE_BREAKS, keepBlankLines());
}
else if (isLeftOrRight(TokenSets.TYPE_DEFINITIONS)) {
if (myType1 == GroovyDocElementTypes.GROOVY_DOC_COMMENT) {
createLF(true);
}
else {
myResult = Spacing.createSpacing(0, 0, mySettings.BLANK_LINES_AROUND_CLASS + 1, mySettings.KEEP_LINE_BREAKS, keepBlankLines());
}
}
else if (isAfterElementOrSemi(GroovyElementTypes.IMPORT_STATEMENT) && myType2 != GroovyElementTypes.IMPORT_STATEMENT) { //after imports
myResult = Spacing.createSpacing(0, 0, mySettings.BLANK_LINES_AFTER_IMPORTS + 1, mySettings.KEEP_LINE_BREAKS, keepBlankLines());
}
else if (myType1 != GroovyElementTypes.IMPORT_STATEMENT && !isSemiAfter(GroovyElementTypes.IMPORT_STATEMENT) && myType2 ==
GroovyElementTypes.IMPORT_STATEMENT) { //before imports
myResult = Spacing.createSpacing(0, 0, mySettings.BLANK_LINES_BEFORE_IMPORTS, mySettings.KEEP_LINE_BREAKS, keepBlankLines());
}
else if (isAfterElementOrSemi(GroovyElementTypes.IMPORT_STATEMENT) && myType2 == GroovyElementTypes.IMPORT_STATEMENT) {
myResult = Spacing.createSpacing(0, 0, 1, mySettings.KEEP_LINE_BREAKS, keepBlankLines());
}
else {
processClassMembers(null);
}
}
private boolean isAfterElementOrSemi(final IElementType elementType) {
return myType1 == elementType && myType2 != GroovyTokenTypes.mSEMI || isSemiAfter(elementType);
}
private boolean isSemiAfter(@NotNull IElementType statement) {
return myType1 == GroovyTokenTypes.mSEMI && getStatementTypeBySemi(myChild1) == statement;
}
private boolean isSemiAfter(@NotNull TokenSet set) {
return myType1 == GroovyTokenTypes.mSEMI && set.contains(getStatementTypeBySemi(myChild1));
}
@Nullable
private static IElementType getStatementTypeBySemi(@NotNull ASTNode semi) {
final GrTopStatement statement = getStatementBySemicolon(semi.getPsi());
if (statement == null) return null;
return statement.getNode().getElementType();
}
@Nullable
private static GrTopStatement getStatementBySemicolon(@NotNull PsiElement semi) {
PsiElement prev = semi.getPrevSibling();
while (prev != null &&
TokenSets.WHITE_SPACES_OR_COMMENTS.contains(prev.getNode().getElementType()) &&
prev.getText().indexOf('\n') < 0) {
prev = prev.getPrevSibling();
}
if (prev instanceof GrTopStatement) return (GrTopStatement)prev;
return null;
}
@Override
public void visitClosure(GrClosableBlock closure) {
ASTNode rBraceAtTheEnd = GeeseUtil.getClosureRBraceAtTheEnd(myChild1);
if (myGroovySettings.USE_FLYING_GEESE_BRACES && myType2 == GroovyTokenTypes.mRCURLY && rBraceAtTheEnd != null) {
String text = rBraceAtTheEnd.getTreeParent().getText();
if (text.indexOf('\n') < 0) {
/* the case:
foo {
bar {print x}<we are here>}
*/
myResult = Spacing.createSpacing(1, 1, 1, false, 1);
}
else {
myResult = Spacing.createSpacing(0, 0, 0, true, 100, 0);
}
}
else if (myType1 == GroovyTokenTypes.mLCURLY && myType2 == GroovyTokenTypes.mRCURLY) { //empty closure
createSpaceInCode(false);
}
else if (myType1 == GroovyTokenTypes.mLCURLY && myType2 != GroovyElementTypes.PARAMETERS_LIST && myType2 !=
GroovyTokenTypes.mCLOSABLE_BLOCK_OP || myType2 ==
GroovyTokenTypes.mRCURLY) { //spaces between statements
boolean spacesWithinBraces = closure.getParent() instanceof GrStringInjection
? myGroovySettings.SPACE_WITHIN_GSTRING_INJECTION_BRACES
: mySettings.SPACE_WITHIN_BRACES;
createDependentLFSpacing(true, spacesWithinBraces, closure.getTextRange());
}
else if (myType1 == GroovyTokenTypes.mCLOSABLE_BLOCK_OP) {
myResult = GroovySpacingProcessorBasic.createDependentSpacingForClosure(mySettings, myGroovySettings, closure, true);
}
else if (myType1 == GroovyTokenTypes.mLCURLY && (myType2 == GroovyElementTypes.PARAMETERS_LIST || myType2 ==
GroovyTokenTypes.mCLOSABLE_BLOCK_OP)) {
boolean spacesWithinBraces = closure.getParent() instanceof GrStringInjection
? myGroovySettings.SPACE_WITHIN_GSTRING_INJECTION_BRACES
: mySettings.SPACE_WITHIN_BRACES;
createSpaceInCode(spacesWithinBraces);
}
}
@Override
public void visitOpenBlock(GrOpenBlock block) {
boolean isMethod = block.getParent() instanceof GrMethod;
boolean keepInOneLine = isMethod ? mySettings.KEEP_SIMPLE_METHODS_IN_ONE_LINE : mySettings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE;
if (myType1 == GroovyTokenTypes.mLCURLY && myType2 == GroovyTokenTypes.mRCURLY) {
createLF(!keepInOneLine);
}
else if (myType1 == GroovyTokenTypes.mLCURLY) {
if (keepInOneLine) {
createDependentLFSpacing(true, mySettings.SPACE_WITHIN_BRACES, block.getTextRange());
}
else {
int lineFeedsCount = isMethod ? mySettings.BLANK_LINES_BEFORE_METHOD_BODY + 1 : 1;
myResult = Spacing.createSpacing(0, 0, lineFeedsCount, mySettings.KEEP_LINE_BREAKS, keepBlankLines());
}
}
else if (myType2 == GroovyTokenTypes.mRCURLY) {
if (keepInOneLine) {
createDependentLFSpacing(true, mySettings.SPACE_WITHIN_BRACES, block.getTextRange());
}
else {
createLF(true);
}
}
}
@Override
public void visitTypeDefinition(GrTypeDefinition typeDefinition) {
if (myType2 == GroovyElementTypes.CLASS_BODY) {
if (typeDefinition instanceof GrAnonymousClassDefinition) {
createSpaceProperty(mySettings.SPACE_BEFORE_CLASS_LBRACE, true, 100); //don't manually remove line feeds because this line is ambiguous
}
else {
PsiElement nameIdentifier = typeDefinition.getNameIdentifierGroovy();
int dependenceStart = nameIdentifier.getTextRange().getStartOffset();
final TextRange range = new TextRange(dependenceStart, myChild1.getTextRange().getEndOffset());
createSpaceBeforeLBrace(mySettings.SPACE_BEFORE_CLASS_LBRACE, mySettings.CLASS_BRACE_STYLE, range, false);
}
}
else if (myType2 == GroovyElementTypes.TYPE_PARAMETER_LIST) {
createSpaceInCode(false);
}
else if (myType2 == GroovyElementTypes.ARGUMENTS) {
manageSpaceBeforeCallLParenth();
}
}
@Override
public void visitTypeDefinitionBody(GrTypeDefinitionBody typeDefinitionBody) {
if (myType1 == GroovyTokenTypes.mLCURLY && myType2 == GroovyTokenTypes.mRCURLY) {
if (mySettings.KEEP_SIMPLE_CLASSES_IN_ONE_LINE) {
createSpaceInCode(false);
}
else {
createLF(true);
}
}
else if (myType1 == GroovyTokenTypes.mLCURLY) {
myResult = Spacing.createSpacing(0, 0, mySettings.BLANK_LINES_AFTER_CLASS_HEADER + 1, mySettings.KEEP_LINE_BREAKS, keepBlankLines());
}
else if (myType2 == GroovyTokenTypes.mRCURLY) {
createLF(true);
}
else {
processClassMembers(typeDefinitionBody);
}
}
private void processClassMembers(@Nullable GrTypeDefinitionBody typeDefinitionBody) {
final boolean isInterface = typeDefinitionBody != null && ((GrTypeDefinition)typeDefinitionBody.getParent()).isInterface();
if (myType2 == GroovyTokenTypes.mSEMI) return;
if (typeDefinitionBody != null) { //check variable definitions only inside class body
if ((myType1 == GroovyElementTypes.VARIABLE_DEFINITION || isSemiAfter(GroovyElementTypes.VARIABLE_DEFINITION)) && TokenSets.METHOD_DEFS.contains(myType2)) {
final int minBlankLines = Math.max(
isInterface ? mySettings.BLANK_LINES_AROUND_METHOD_IN_INTERFACE : mySettings.BLANK_LINES_AROUND_METHOD,
isInterface ? mySettings.BLANK_LINES_AROUND_FIELD_IN_INTERFACE : mySettings.BLANK_LINES_AROUND_FIELD
);
myResult = Spacing.createSpacing(0, 0, minBlankLines + 1, mySettings.KEEP_LINE_BREAKS, keepBlankLines());
return;
}
else if (myType1 == GroovyElementTypes.VARIABLE_DEFINITION || isSemiAfter(GroovyElementTypes.VARIABLE_DEFINITION) || myType2 ==
GroovyElementTypes.VARIABLE_DEFINITION) {
final int minBlankLines =
isInterface ? mySettings.BLANK_LINES_AROUND_FIELD_IN_INTERFACE : mySettings.BLANK_LINES_AROUND_FIELD;
myResult = Spacing.createSpacing(0, 0, minBlankLines + 1, mySettings.KEEP_LINE_BREAKS, keepBlankLines());
return;
}
}
if (TokenSets.METHOD_DEFS.contains(myType1) || isSemiAfter(TokenSets.METHOD_DEFS) || TokenSets.METHOD_DEFS.contains((myType2))) {
if (myType1 == GroovyDocElementTypes.GROOVY_DOC_COMMENT) {
createLF(true);
return;
}
else {
final int minBlankLines = isInterface ? mySettings.BLANK_LINES_AROUND_METHOD_IN_INTERFACE : mySettings.BLANK_LINES_AROUND_METHOD;
myResult = Spacing.createSpacing(0, 0, minBlankLines + 1, mySettings.KEEP_LINE_BREAKS, keepBlankLines());
return;
}
}
if (TokenSets.TYPE_DEFINITIONS.contains(myType1) || isSemiAfter(TokenSets.TYPE_DEFINITIONS) || TokenSets.TYPE_DEFINITIONS.contains((myType2)) ) {
if (myType1 == GroovyDocElementTypes.GROOVY_DOC_COMMENT) {
createLF(true);
return;
}
else {
final int minBlankLines = mySettings.BLANK_LINES_AROUND_CLASS;
myResult = Spacing.createSpacing(0, 0, minBlankLines + 1, mySettings.KEEP_LINE_BREAKS, keepBlankLines());
return;
}
}
}
@Override
public void visitTypeArgumentList(GrTypeArgumentList typeArgumentList) {
if (myType1 == GroovyTokenTypes.mLT || myType2 == GroovyTokenTypes.mGT) {
createSpaceInCode(false);
}
}
@Override
public void visitTypeParameterList(GrTypeParameterList list) {
if (myType1 == GroovyTokenTypes.mCOMMA) {
createSpaceInCode(mySettings.SPACE_AFTER_COMMA_IN_TYPE_ARGUMENTS);
}
else if (myType1 == GroovyTokenTypes.mLT && myType2 == GroovyElementTypes.TYPE_PARAMETER ||
myType1 == GroovyElementTypes.TYPE_PARAMETER && myType2 == GroovyTokenTypes.mGT) {
createSpaceInCode(false);
}
}
@Override
public void visitForInClause(GrForInClause forInClause) {
if (myType1 == GroovyElementTypes.PARAMETER && myType2 == GroovyTokenTypes.mCOLON) {
createSpaceInCode(true);
}
}
@Override
public void visitCastExpression(GrTypeCastExpression typeCastExpression) {
if (SpacingTokens.LEFT_BRACES.contains(myType1) || SpacingTokens.RIGHT_BRACES.contains(myType2)) {
createSpaceInCode(mySettings.SPACE_WITHIN_CAST_PARENTHESES);
}
else if (myType1 == GroovyTokenTypes.mRPAREN) {
createSpaceInCode(mySettings.SPACE_AFTER_TYPE_CAST);
}
}
@Override
public void visitMethod(GrMethod method) {
if (myType1 == GroovyTokenTypes.mRPAREN && myType2 == GroovyElementTypes.THROW_CLAUSE) {
if (mySettings.THROWS_KEYWORD_WRAP == CommonCodeStyleSettings.WRAP_ALWAYS) {
createLF(true);
}
else {
createSpaceInCode(true);
}
}
else if (isOpenBlock(myType2)) {
PsiElement methodName = method.getNameIdentifier();
int dependencyStart = methodName == null ? myParent.getTextRange().getStartOffset() : methodName.getTextRange().getStartOffset();
createSpaceBeforeLBrace(mySettings.SPACE_BEFORE_METHOD_LBRACE, mySettings.METHOD_BRACE_STYLE,
new TextRange(dependencyStart, myChild1.getTextRange().getEndOffset()),
mySettings.KEEP_SIMPLE_METHODS_IN_ONE_LINE);
}
else if (myType2 == GroovyElementTypes.TYPE_PARAMETER_LIST) {
createSpaceInCode(true);
}
else {
processParentheses(GroovyTokenTypes.mLPAREN,
GroovyTokenTypes.mRPAREN,
mySettings.SPACE_WITHIN_METHOD_PARENTHESES,
mySettings.SPACE_WITHIN_EMPTY_METHOD_PARENTHESES,
mySettings.METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE,
mySettings.METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE);
}
}
private boolean processParentheses(@NotNull IElementType left,
@NotNull IElementType right,
@NotNull Boolean spaceWithin,
@Nullable Boolean spaceWithinEmpty,
@Nullable Boolean leftLF,
@Nullable Boolean rightLF) {
if (myType1 == left && myType2 == right && spaceWithinEmpty != null) {
createSpaceInCode(spaceWithinEmpty);
return true;
}
else if (myType1 == left) {
final ASTNode rparenth = findFrom(myChild1, right, true);
if (rparenth == null || leftLF == null) {
createSpaceInCode(spaceWithin);
}
else {
final TextRange range = new TextRange(myChild1.getStartOffset(), rparenth.getTextRange().getEndOffset());
createDependentLFSpacing(leftLF, spaceWithin, range);
}
return true;
}
else if (myType2 == right) {
final ASTNode lparenth = findFrom(myChild1, left, false);
if (lparenth == null || rightLF == null) {
createSpaceInCode(spaceWithin);
}
else {
final TextRange range = new TextRange(lparenth.getStartOffset(), myChild2.getTextRange().getEndOffset());
createDependentLFSpacing(rightLF, spaceWithin, range);
}
return true;
}
else {
return false;
}
}
@Override
public void visitAnnotationMethod(GrAnnotationMethod annotationMethod) {
if (myType2 == GroovyTokenTypes.kDEFAULT) {
createSpaceInCode(true);
}
else {
super.visitAnnotationMethod(annotationMethod);
}
}
@Override
public void visitDocMethodReference(GrDocMethodReference reference) {
visitDocMember();
}
@Override
public void visitDocFieldReference(GrDocFieldReference reference) {
visitDocMember();
}
private void visitDocMember() {
createSpaceProperty(false, false, 0);
}
@Override
public void visitDocMethodParameterList(GrDocMethodParams params) {
if (myType1 == GroovyDocTokenTypes.mGDOC_TAG_VALUE_LPAREN || myType2 == GroovyDocTokenTypes.mGDOC_TAG_VALUE_RPAREN || myType2 ==
GroovyDocTokenTypes.mGDOC_TAG_VALUE_COMMA) {
createSpaceProperty(false, false, 0);
}
else {
createSpaceInCode(true);
}
}
@Override
public void visitDocMethodParameter(GrDocMethodParameter parameter) {
if (myChild1.getTreePrev() == null) {
createSpaceInCode(true);
}
}
@Override
public void visitWhileStatement(GrWhileStatement statement) {
if (myType2 == GroovyTokenTypes.mLPAREN) {
createSpaceInCode(mySettings.SPACE_BEFORE_WHILE_PARENTHESES);
}
else if (myType1 == GroovyTokenTypes.mLPAREN || myType2 == GroovyTokenTypes.mRPAREN) {
createSpaceInCode(mySettings.SPACE_WITHIN_WHILE_PARENTHESES);
}
else if (myChild2.getPsi() instanceof GrBlockStatement) {
createSpaceBeforeLBrace(mySettings.SPACE_BEFORE_WHILE_LBRACE, mySettings.BRACE_STYLE,
new TextRange(myParent.getTextRange().getStartOffset(), myChild1.getTextRange().getEndOffset()),
mySettings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE);
}
else {
createSpacingBeforeElementInsideControlStatement();
}
}
@Override
public void visitCatchClause(GrCatchClause catchClause) {
if (isOpenBlock(myType2)) {
createSpaceBeforeLBrace(mySettings.SPACE_BEFORE_CATCH_LBRACE, mySettings.BRACE_STYLE, null,
mySettings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE);
}
else if (myType2 == GroovyTokenTypes.mLPAREN) {
createSpaceInCode(mySettings.SPACE_BEFORE_CATCH_PARENTHESES);
}
else if (myType1 == GroovyTokenTypes.mLPAREN || myType2 == GroovyTokenTypes.mRPAREN) {
createSpaceInCode(mySettings.SPACE_WITHIN_CATCH_PARENTHESES);
}
}
@Override
public void visitFinallyClause(GrFinallyClause catchClause) {
if (isOpenBlock(myType2)) {
createSpaceBeforeLBrace(mySettings.SPACE_BEFORE_FINALLY_LBRACE, mySettings.BRACE_STYLE, null,
mySettings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE);
}
}
@Override
public void visitTryStatement(GrTryCatchStatement tryCatchStatement) {
if (myType2 == GroovyElementTypes.FINALLY_CLAUSE) {
processTryOnNewLineCondition(mySettings.FINALLY_ON_NEW_LINE, mySettings.SPACE_BEFORE_FINALLY_KEYWORD);
}
else if (isOpenBlock(myType2)) {
createSpaceBeforeLBrace(mySettings.SPACE_BEFORE_TRY_LBRACE, mySettings.BRACE_STYLE, null, mySettings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE);
}
else if (myType2 == GroovyElementTypes.CATCH_CLAUSE) {
processTryOnNewLineCondition(mySettings.CATCH_ON_NEW_LINE, mySettings.SPACE_BEFORE_CATCH_KEYWORD);
}
}
@Override
public void visitAssignmentExpression(GrAssignmentExpression expression) {
if (TokenSets.ASSIGN_OP_SET.contains(myType1) || TokenSets.ASSIGN_OP_SET.contains(myType2)) {
createSpaceInCode(mySettings.SPACE_AROUND_ASSIGNMENT_OPERATORS);
}
}
@Override
public void visitBinaryExpression(GrBinaryExpression expression) {
@SuppressWarnings("SimplifiableConditionalExpression" )
boolean spaceAround = isLeftOrRight(SpacingTokens.LOGICAL_OPERATORS) ? mySettings.SPACE_AROUND_LOGICAL_OPERATORS :
isLeftOrRight(SpacingTokens.EQUALITY_OPERATORS) ? mySettings.SPACE_AROUND_EQUALITY_OPERATORS :
isLeftOrRight(SpacingTokens.RELATIONAL_OPERATORS) ? mySettings.SPACE_AROUND_RELATIONAL_OPERATORS :
isLeftOrRight(SpacingTokens.BITWISE_OPERATORS) ? mySettings.SPACE_AROUND_BITWISE_OPERATORS :
isLeftOrRight(SpacingTokens.ADDITIVE_OPERATORS) ? mySettings.SPACE_AROUND_ADDITIVE_OPERATORS :
isLeftOrRight(SpacingTokens.MULTIPLICATIVE_OPERATORS) ? mySettings.SPACE_AROUND_MULTIPLICATIVE_OPERATORS :
isLeftOrRight(SpacingTokens.SHIFT_OPERATORS) ? mySettings.SPACE_AROUND_SHIFT_OPERATORS :
isLeftOrRight(SpacingTokens.REGEX_OPERATORS) ? myGroovySettings.SPACE_AROUND_REGEX_OPERATORS :
isLeftOrRight(GroovyTokenTypes.kIN);
if (TokenSets.BINARY_OP_SET.contains(myType2)) {
createDependentLFSpacing(mySettings.BINARY_OPERATION_SIGN_ON_NEXT_LINE, spaceAround, expression.getTextRange());
}
else {
createSpaceInCode(spaceAround);
}
}
private boolean isLeftOrRight(TokenSet operators) {
return operators.contains(myType1) || operators.contains(myType2);
}
private boolean isLeftOrRight(IElementType type) {
return myType1 == type || myType2 == type;
}
@Override
public void visitUnaryExpression(GrUnaryExpression expression) {
if (!expression.isPostfix() && expression.getOperationToken() == myChild1 ||
expression.isPostfix() && expression.getOperationToken() == myChild2) {
createSpaceInCode(mySettings.SPACE_AROUND_UNARY_OPERATOR);
}
}
@Override
public void visitSwitchStatement(GrSwitchStatement switchStatement) {
if (myType1 == GroovyTokenTypes.kSWITCH && myType2 == GroovyTokenTypes.mLPAREN) {
createSpaceInCode(mySettings.SPACE_BEFORE_SWITCH_PARENTHESES);
}
else if (myType1 == GroovyTokenTypes.mLPAREN || myType2 == GroovyTokenTypes.mRPAREN) {
createSpaceInCode(mySettings.SPACE_WITHIN_SWITCH_PARENTHESES);
}
else if (myType2 == GroovyTokenTypes.mLCURLY) {
createSpaceBeforeLBrace(mySettings.SPACE_BEFORE_SWITCH_LBRACE, mySettings.BRACE_STYLE, null,
mySettings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE);
}
else if (myType1 == GroovyTokenTypes.mLCURLY || myType2 == GroovyTokenTypes.mRCURLY) {
createLF(true);
}
}
@Override
public void visitSynchronizedStatement(GrSynchronizedStatement synchronizedStatement) {
if (myType1 == GroovyTokenTypes.kSYNCHRONIZED || myType2 == GroovyTokenTypes.mLPAREN) {
createSpaceInCode(mySettings.SPACE_BEFORE_SYNCHRONIZED_PARENTHESES);
} else if (myType1 == GroovyTokenTypes.mLPAREN || myType2 == GroovyTokenTypes.mRPAREN) {
createSpaceInCode(mySettings.SPACE_WITHIN_SYNCHRONIZED_PARENTHESES);
} else if (isOpenBlock(myType2)) {
createSpaceBeforeLBrace(mySettings.SPACE_BEFORE_SYNCHRONIZED_LBRACE, mySettings.BRACE_STYLE, null,
mySettings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE);
}
}
@Override
public void visitDocComment(GrDocComment comment) {
if (myType1 == GroovyDocElementTypes.GDOC_TAG &&
myType2 == GroovyDocElementTypes.GDOC_TAG &&
mySettings.getRootSettings().JD_LEADING_ASTERISKS_ARE_ENABLED) {
IElementType type = myChild1.getLastChildNode().getElementType();
if (type == GroovyDocTokenTypes.mGDOC_ASTERISKS) {
createSpaceInCode(true);
}
}
if (myType1 == GroovyDocTokenTypes.mGDOC_COMMENT_START && myType2 == GroovyDocTokenTypes.mGDOC_COMMENT_DATA ||
myType1 == GroovyDocTokenTypes.mGDOC_COMMENT_DATA && myType2 == GroovyDocTokenTypes.mGDOC_COMMENT_END ||
myType1 == GroovyDocTokenTypes.mGDOC_ASTERISKS && myType2 == GroovyDocTokenTypes.mGDOC_COMMENT_END) {
createLazySpace();
}
}
private void createLazySpace() {
myResult = Spacing.createSpacing(0, Integer.MAX_VALUE, 0, mySettings.KEEP_LINE_BREAKS, keepBlankLines());
}
@Override
public void visitDocTag(GrDocTag docTag) {
if (myType1 == GroovyDocTokenTypes.mGDOC_INLINE_TAG_START || myType2 == GroovyDocTokenTypes.mGDOC_INLINE_TAG_END) {
createSpaceInCode(false);
}
}
@Override
public void visitNamedArgument(GrNamedArgument argument) {
if (myType1 == GroovyTokenTypes.mCOLON) {
createSpaceInCode(myGroovySettings.SPACE_IN_NAMED_ARGUMENT);
}
}
@Override
public void visitListOrMap(GrListOrMap listOrMap) {
if (myType1 == GroovyTokenTypes.mLBRACK || myType2 == GroovyTokenTypes.mRBRACK) {
createSpaceInCode(myGroovySettings.SPACE_WITHIN_LIST_OR_MAP);
}
}
@Override
public void visitParenthesizedExpression(GrParenthesizedExpression expression) {
processParentheses(GroovyTokenTypes.mLPAREN, GroovyTokenTypes.mRPAREN, mySettings.SPACE_WITHIN_PARENTHESES, null, mySettings.PARENTHESES_EXPRESSION_LPAREN_WRAP,
mySettings.PARENTHESES_EXPRESSION_RPAREN_WRAP);
}
@Override
public void visitAnnotationArrayInitializer(GrAnnotationArrayInitializer arrayInitializer) {
if (myType1 == GroovyTokenTypes.mLBRACK || myType2 == GroovyTokenTypes.mRBRACK) {
createSpaceInCode(mySettings.SPACE_WITHIN_BRACKETS);
}
}
@Override
public void visitIfStatement(GrIfStatement ifStatement) {
if (myType2 == GroovyTokenTypes.kELSE) {
if (!isOpenBlock(myType1) && myType1 != GroovyElementTypes.BLOCK_STATEMENT) {
createSpaceInCode(true);
}
else {
if (mySettings.ELSE_ON_NEW_LINE) {
createLF(true);
}
else {
createSpaceProperty(mySettings.SPACE_BEFORE_ELSE_KEYWORD, false, 0);
}
}
}
else if (myType1 == GroovyTokenTypes.kELSE) {
if (myType2 == GroovyElementTypes.IF_STATEMENT) {
if (mySettings.SPECIAL_ELSE_IF_TREATMENT) {
createSpaceProperty(true, false, 0);
}
else {
createLF(true);
}
}
else {
if (myType2 == GroovyElementTypes.BLOCK_STATEMENT || isOpenBlock(myType2)) {
createSpaceBeforeLBrace(mySettings.SPACE_BEFORE_ELSE_LBRACE, mySettings.BRACE_STYLE, null,
mySettings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE);
}
else {
createSpacingBeforeElementInsideControlStatement();
}
}
}
else if (myType2 == GroovyElementTypes.BLOCK_STATEMENT || isOpenBlock(myType2)) {
boolean space = myChild2.getPsi() == ((GrIfStatement)myParent).getElseBranch()
? mySettings.SPACE_BEFORE_ELSE_LBRACE
: mySettings.SPACE_BEFORE_IF_LBRACE;
createSpaceBeforeLBrace(space, mySettings.BRACE_STYLE,
new TextRange(myParent.getTextRange().getStartOffset(), myChild1.getTextRange().getEndOffset()),
mySettings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE);
}
else if (myType2 == GroovyTokenTypes.mLPAREN) {
createSpaceInCode(mySettings.SPACE_BEFORE_IF_PARENTHESES);
}
else if (myType1 == GroovyTokenTypes.mLPAREN || myType2 == GroovyTokenTypes.mRPAREN) {
createSpaceInCode(mySettings.SPACE_WITHIN_IF_PARENTHESES);
}
else if (((GrIfStatement)myParent).getThenBranch() == myChild2.getPsi()) {
createSpacingBeforeElementInsideControlStatement();
}
}
@Override
public void visitForStatement(GrForStatement forStatement) {
if (myType2 == GroovyTokenTypes.mLPAREN) {
createSpaceInCode(mySettings.SPACE_BEFORE_FOR_PARENTHESES);
}
else if (myType2 == GroovyElementTypes.BLOCK_STATEMENT || isOpenBlock(myType2)) {
if (myType2 == GroovyElementTypes.BLOCK_STATEMENT) {
createSpaceBeforeLBrace(mySettings.SPACE_BEFORE_FOR_LBRACE, mySettings.BRACE_STYLE,
new TextRange(myParent.getTextRange().getStartOffset(), myChild1.getTextRange().getEndOffset()),
mySettings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE);
}
else if (mySettings.KEEP_CONTROL_STATEMENT_IN_ONE_LINE) {
createDependentLFSpacing(true, true, myParent.getTextRange());
}
else {
createLF(true);
}
}
else if (myType1 == GroovyTokenTypes.mRPAREN) {
createSpacingBeforeElementInsideControlStatement();
}
else {
processParentheses(GroovyTokenTypes.mLPAREN,
GroovyTokenTypes.mRPAREN,
mySettings.SPACE_WITHIN_FOR_PARENTHESES,
null,
mySettings.FOR_STATEMENT_LPAREN_ON_NEXT_LINE,
mySettings.FOR_STATEMENT_RPAREN_ON_NEXT_LINE);
}
}
private static boolean isOpenBlock(IElementType type) {
return type == GroovyElementTypes.OPEN_BLOCK || type == GroovyElementTypes.CONSTRUCTOR_BODY;
}
@Nullable
private static ASTNode findFrom(ASTNode current, final IElementType expected, boolean forward) {
while (current != null) {
if (current.getElementType() == expected) return current;
current = forward ? current.getTreeNext() : current.getTreePrev();
}
return null;
}
private void processTryOnNewLineCondition(boolean onNewLine, boolean spaceIfNotNewLine) {
if (onNewLine) {
if (mySettings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE) {
myResult = Spacing.createDependentLFSpacing(0, 1, myParent.getTextRange(), mySettings.KEEP_LINE_BREAKS, keepBlankLines());
}
else {
createLF(true);
}
}
else {
createSpaceInCode(spaceIfNotNewLine);
}
}
private void createSpacingBeforeElementInsideControlStatement() {
if (mySettings.KEEP_CONTROL_STATEMENT_IN_ONE_LINE && myType1 != GroovyTokenTypes.mSL_COMMENT) {
createSpaceInCode(true);
}
else {
createLF(true);
}
}
public Spacing getSpacing() {
return myResult;
}
private void createSpaceInCode(final boolean space) {
createSpaceProperty(space, mySettings.KEEP_LINE_BREAKS, keepBlankLines());
}
private void createSpaceProperty(boolean space, boolean keepLineBreaks, final int keepBlankLines) {
myResult = Spacing.createSpacing(space ? 1 : 0, space ? 1 : 0, 0, keepLineBreaks, keepBlankLines);
}
private void createSpaceBeforeLBrace(final boolean spaceBeforeLbrace,
int braceStyle,
@Nullable TextRange dependantRange,
boolean keepOneLine) {
if (dependantRange != null && braceStyle == CommonCodeStyleSettings.NEXT_LINE_IF_WRAPPED) {
int space = spaceBeforeLbrace ? 1 : 0;
myResult = createNonLFSpace(space, dependantRange, false);
}
else if (braceStyle == CommonCodeStyleSettings.END_OF_LINE || braceStyle == CommonCodeStyleSettings.NEXT_LINE_IF_WRAPPED) {
int space = spaceBeforeLbrace ? 1 : 0;
myResult = createNonLFSpace(space, null, false);
}
else if (keepOneLine) {
int space = spaceBeforeLbrace ? 1 : 0;
myResult = Spacing.createDependentLFSpacing(space, space, myParent.getTextRange(), mySettings.KEEP_LINE_BREAKS,
keepBlankLines());
}
else {
createLF(true);
}
}
private void createLF(final boolean lf) {
myResult = Spacing.createSpacing(0, 0, lf ? 1 : 0, mySettings.KEEP_LINE_BREAKS, keepBlankLines());
}
private Spacing createNonLFSpace(int spaces, @Nullable final TextRange dependantRange, final boolean keepLineBreaks) {
final ASTNode prev = FormatterUtil.getPreviousNonWhitespaceLeaf(myChild2);
if (prev != null && prev.getElementType() == GroovyTokenTypes.mSL_COMMENT) {
return Spacing.createSpacing(0, Integer.MAX_VALUE, 1, keepLineBreaks, keepBlankLines());
}
else if (dependantRange != null) {
return Spacing.createDependentLFSpacing(spaces, spaces, dependantRange, keepLineBreaks, keepBlankLines());
}
else {
return Spacing.createSpacing(spaces, spaces, 0, keepLineBreaks, keepBlankLines());
}
}
private int keepBlankLines() {
if (myType2 == GroovyTokenTypes.mRCURLY) {
return mySettings.KEEP_BLANK_LINES_BEFORE_RBRACE;
}
else if (myParent instanceof GrTypeDefinitionBody) {
return mySettings.KEEP_BLANK_LINES_IN_DECLARATIONS;
}
else {
return mySettings.KEEP_BLANK_LINES_IN_CODE;
}
}
static boolean isWhiteSpace(final ASTNode node) {
return node != null && (PsiImplUtil.isWhiteSpaceOrNls(node) || node.getTextLength() == 0);
}
}