package com.jetbrains.lang.dart.ide.formatter;
import com.intellij.formatting.Block;
import com.intellij.formatting.Spacing;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.psi.formatter.FormatterUtil;
import com.intellij.psi.formatter.common.AbstractBlock;
import com.intellij.psi.impl.source.tree.CompositeElement;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import com.intellij.util.containers.SortedList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import static com.jetbrains.lang.dart.DartTokenTypes.*;
import static com.jetbrains.lang.dart.DartTokenTypesSets.*;
public class DartSpacingProcessor {
private static final TokenSet TOKENS_WITH_SPACE_AFTER = TokenSet
.create(VAR, FINAL, STATIC, EXTERNAL, ABSTRACT, GET, SET, FACTORY, OPERATOR, PART, EXPORT, DEFERRED, AS, SHOW, HIDE, RETURN_TYPE,
COVARIANT);
private static final TokenSet KEYWORDS_WITH_SPACE_BEFORE =
TokenSet.create(GET, SET, EXTENDS, IMPLEMENTS, DEFERRED, AS, SHOW_COMBINATOR, HIDE_COMBINATOR, COVARIANT);
private static final TokenSet CASCADE_REFERENCE_EXPRESSION_SET = TokenSet.create(CASCADE_REFERENCE_EXPRESSION);
private static final TokenSet REFERENCE_EXPRESSION_SET = TokenSet.create(REFERENCE_EXPRESSION);
private static final TokenSet ID_SET = TokenSet.create(ID);
private static final TokenSet PREFIX_OPERATOR_SET = TokenSet.create(PREFIX_OPERATOR);
private static final TokenSet SIMPLE_LITERAL_SET =
TokenSet.create(STRING_LITERAL_EXPRESSION, NUMBER, TRUE, FALSE, NULL, THIS, LIST_LITERAL_EXPRESSION, MAP_LITERAL_EXPRESSION);
private static final TokenSet SKIP_COMMA = TokenSet.create(COMMA);
private static final TokenSet DIRECTIVE_GROUPS = TokenSet.create(IMPORT_STATEMENT, EXPORT_STATEMENT, PART_STATEMENT);
private final ASTNode myNode;
private final CommonCodeStyleSettings mySettings;
public DartSpacingProcessor(ASTNode node, CommonCodeStyleSettings settings) {
myNode = node;
mySettings = settings;
}
public Spacing getSpacing(final Block child1, final Block child2) {
if (!(child1 instanceof AbstractBlock) || !(child2 instanceof AbstractBlock)) {
return null;
}
final IElementType elementType = myNode.getElementType();
final IElementType parentType = myNode.getTreeParent() == null ? null : myNode.getTreeParent().getElementType();
final ASTNode node1 = ((AbstractBlock)child1).getNode();
final IElementType type1 = node1.getElementType();
final ASTNode node2 = ((AbstractBlock)child2).getNode();
final IElementType type2 = node2.getElementType();
if (type2 == SINGLE_LINE_COMMENT && !isDirectlyPrecededByNewline(node2)) {
// line comment after code on the same line: do not add line break here, it may be used to ignore warning
// but after '{' in class or function definition Dart Style inserts line break, so let's do the same
if (type1 != LBRACE || (elementType != CLASS_BODY && (!BLOCKS.contains(elementType) || parentType != FUNCTION_BODY))) {
return Spacing.createSpacing(1, 1, 0, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_CODE, 0);
}
}
if (elementType == DART_FILE && COMMENTS.contains(type1) && !COMMENTS.contains(type2)) {
final ASTNode prev = getPrevSiblingOnTheSameLineSkipCommentsAndWhitespace(((AbstractBlock)child1).getNode());
if (prev != null) {
final int lineBreaks = getMinLineBreaksBetweenTopLevelNodes(prev.getElementType(), type2);
return Spacing.createSpacing(0, 0, lineBreaks, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_CODE);
}
}
if (type1 == SINGLE_LINE_COMMENT) {
return Spacing.createSpacing(0, 0, 1, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_CODE);
}
if (type2 == SINGLE_LINE_DOC_COMMENT) {
int nsp = 2;
if (type1 == SINGLE_LINE_DOC_COMMENT || (elementType != DART_FILE && type1 == LBRACE)) nsp = 1;
return Spacing.createSpacing(0, 0, nsp, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_CODE);
}
if (DIRECTIVE_GROUPS.contains(type1)) {
if (type2 == MULTI_LINE_COMMENT) {
ASTNode next = FormatterUtil.getNextNonWhitespaceSibling(node2);
if (next != null &&
next.getElementType() == type1) {
boolean needsNewline = isEmbeddedComment(type2, child2) && !isDirectlyPrecededByNewline(next);
int space = needsNewline ? 0 : 1;
int newline = needsNewline ? 1 : 0;
return Spacing.createSpacing(0, space, newline, true, mySettings.KEEP_BLANK_LINES_IN_CODE);
}
}
if (type2 != IMPORT_STATEMENT && type2 != EXPORT_STATEMENT && !isEmbeddedComment(type2, child2)) {
int numNewlines = COMMENTS.contains(type2) && isBlankLineAfterComment(node2) ? 1 : 2;
return Spacing.createSpacing(0, 0, numNewlines, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_CODE);
}
}
if (type1 == LIBRARY_STATEMENT) {
int newlines = COMMENTS.contains(type2) && isBlankLineAfterComment(node2) ? 1 : 2;
return Spacing.createSpacing(0, 0, newlines, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_CODE);
}
if (elementType == LIBRARY_STATEMENT || parentType == LIBRARY_STATEMENT) {
if (isEmbeddedComment(type2, child2)) {
return Spacing.createSpacing(1, 1, 0, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_CODE);
}
if (type1 == MULTI_LINE_COMMENT && isEmbeddedComment(type1, child1)) {
return Spacing.createSpacing(1, 1, 0, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_CODE);
}
}
if (parentType == LIBRARY_STATEMENT) {
return noSpace();
}
if (SEMICOLON == type2) {
if (type1 == SEMICOLON && elementType == STATEMENTS) {
return addSingleSpaceIf(false, true); // Empty statement on new line.
}
return Spacing.createSpacing(0, 0, 0, true, mySettings.KEEP_BLANK_LINES_IN_CODE);
}
if (AT == type1) return Spacing.createSpacing(0, 0, 0, false, 0);
if (METADATA == type1) {
if (parentType == TYPE_PARAMETERS) {
// Metadata on type parameters must be inlined.
return Spacing.createSpacing(1, 1, 0, false, 0);
}
if (COMMENTS.contains(type2)) {
return Spacing.createSpacing(1, 1, 0, true, 0);
}
if (parentType == DART_FILE) {
// Metadata on top-level declarations must be on its own line.
return Spacing.createSpacing(0, 0, 1, false, 0); //false here
}
if (parentType == CLASS_MEMBERS || FUNCTION_DEFINITION.contains(parentType)) {
if (type2 == METADATA || FormatterUtil.isPrecededBy(node1, METADATA, WHITE_SPACE)) {
// Multiple metadata each goes on its own line.
return Spacing.createSpacing(0, 0, 1, false, 0);
}
// Metadata on constructors and methods may be inlined.
return Spacing.createSpacing(1, 1, 0, true, 0);
}
if (parentType == VAR_DECLARATION_LIST) {
if (myNode.getTreeParent().getTreeParent().getElementType() == STATEMENTS) {
// Metadata on local variables must be on its own line.
return Spacing.createSpacing(0, 0, 1, false, 0);
}
if (type2 == METADATA || FormatterUtil.isPrecededBy(node1, METADATA, WHITE_SPACE)) {
// Multiple metadata each goes on its own line.
return Spacing.createSpacing(0, 0, 1, false, 0);
}
// Metadata on fields may be inlined.
return Spacing.createSpacing(1, 1, 0, true, 0);
}
if (parentType == NORMAL_FORMAL_PARAMETER) {
// Metadata on parameter declarations must be inlined.
return Spacing.createSpacing(1, 1, 0, false, 0);
}
// Other metadata occurrences may be inlined.
return Spacing.createSpacing(1, 1, 0, true, 0);
}
if (FUNCTION_DEFINITION.contains(type2)) {
boolean needsBlank = needsBlankLineBeforeFunction(elementType);
if (needsBlank && !mySettings.KEEP_LINE_BREAKS) {
if (parentType == CLASS_BODY || elementType == DART_FILE) {
if (type1 == SEMICOLON || hasEmptyBlock(node1)) {
needsBlank = false;
}
}
}
final int lineFeeds = COMMENTS.contains(type1) || !needsBlank ? 1 : 2;
return Spacing.createSpacing(0, 0, lineFeeds, needsBlank, mySettings.KEEP_BLANK_LINES_IN_CODE);
}
if (DOC_COMMENT_CONTENTS.contains(type2)) {
return Spacing.createSpacing(0, Integer.MAX_VALUE, 0, true, mySettings.KEEP_BLANK_LINES_IN_CODE);
}
if (BLOCKS_EXT.contains(elementType)) {
boolean topLevel = elementType == DART_FILE || elementType == EMBEDDED_CONTENT;
int lineFeeds = 1;
int spaces = 0;
int blanks = mySettings.KEEP_BLANK_LINES_IN_CODE;
boolean keepBreaks = false;
if (!COMMENTS.contains(type1) && (elementType == CLASS_MEMBERS || topLevel && DECLARATIONS.contains(type2))) {
if (type1 == SEMICOLON && type2 == VAR_DECLARATION_LIST) {
final ASTNode node1TreePrev = node1.getTreePrev();
if (node1TreePrev == null || node1TreePrev.getElementType() != VAR_DECLARATION_LIST) {
lineFeeds = 2;
}
}
else {
if (type2 == VAR_DECLARATION_LIST && hasEmptyBlock(node1) ||
type1 == FUNCTION_TYPE_ALIAS && type2 == FUNCTION_TYPE_ALIAS) {
lineFeeds = 1;
}
else {
lineFeeds = 2;
}
}
}
else if (type1 == LBRACE && type2 == RBRACE) {
if (parentType == ON_PART && mySettings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE) {
lineFeeds = 0;
}
else if (parentType == FUNCTION_BODY) {
if ((myNode.getTreeParent().getTreeParent() != null) &&
(myNode.getTreeParent().getTreeParent().getElementType() == METHOD_DECLARATION) &&
mySettings.KEEP_SIMPLE_METHODS_IN_ONE_LINE) {
lineFeeds = 0; // Empty method.
keepBreaks = mySettings.KEEP_LINE_BREAKS;
blanks = keepBreaks ? mySettings.KEEP_BLANK_LINES_IN_CODE : 0;
}
else if (mySettings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE) {
lineFeeds = 0; // Empty function, either top-level or statement.
keepBreaks = mySettings.KEEP_LINE_BREAKS;
blanks = keepBreaks ? mySettings.KEEP_BLANK_LINES_IN_CODE : 0;
}
}
else if (parentType == IF_STATEMENT && mySettings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE) {
lineFeeds = 0;
}
else if (parentType == FOR_STATEMENT && mySettings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE) {
lineFeeds = 0;
}
else if (parentType == WHILE_STATEMENT && mySettings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE) {
lineFeeds = 0;
}
else if (parentType == DO_WHILE_STATEMENT && mySettings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE) {
lineFeeds = 0;
}
else if (parentType == TRY_STATEMENT && mySettings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE) {
lineFeeds = 0;
}
else if (parentType == FINALLY_PART && mySettings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE) {
lineFeeds = 0;
}
else if (parentType == FUNCTION_EXPRESSION_BODY && mySettings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE) {
lineFeeds = 0;
}
else if (parentType == STATEMENTS && mySettings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE) {
lineFeeds = 0;
}
}
else if (topLevel && COMMENTS.contains(type2)) {
lineFeeds = 0;
spaces = 1;
keepBreaks = true;
}
else if (type1 != LBRACE && isEmbeddedComment(type2, child2)) {
lineFeeds = 0;
spaces = 1;
keepBreaks = false;
}
else if ((type1 == LBRACE && type2 == STATEMENTS) || (type2 == RBRACE && type1 == STATEMENTS)) {
lineFeeds = 1;
keepBreaks = false;
blanks = 0;
}
else if (type1 == LBRACE && type2 == SINGLE_LINE_COMMENT) {
lineFeeds = 1;
keepBreaks = false;
blanks = 0;
}
else if (type1 == MULTI_LINE_COMMENT && type2 == STATEMENTS) {
spaces = 1;
lineFeeds = 0;
keepBreaks = true;
}
return Spacing.createSpacing(spaces, spaces, lineFeeds, keepBreaks, blanks);
}
if (elementType == STATEMENTS && (parentType == SWITCH_CASE || parentType == DEFAULT_CASE)) {
return Spacing.createSpacing(0, 0, 1, false, mySettings.KEEP_BLANK_LINES_IN_CODE);
}
if (!COMMENTS.contains(type2) && BLOCKS.contains(parentType)) {
return addLineBreak();
}
// Special checks for switch formatting according to dart_style, which conflicts with settings.
if (type2 == RBRACE && (type1 == SWITCH_CASE || type1 == DEFAULT_CASE)) {
// No blank line before closing brace in switch statement.
return Spacing.createSpacing(0, 0, 1, false, 0);
}
if (type1 == COLON && (elementType == SWITCH_CASE || elementType == DEFAULT_CASE)) {
// No blank line before first statement of a case.
return Spacing.createSpacing(0, 0, 1, false, 0);
}
if (elementType == SWITCH_STATEMENT && type1 == LBRACE) {
// No blank line before first case of a switch.
return Spacing.createSpacing(0, 0, 1, false, 0);
}
if (type1 == STATEMENTS || type2 == STATEMENTS) {
return addLineBreak();
}
if (type1 == CLASS_MEMBERS || type2 == CLASS_MEMBERS) {
if (type1 == MULTI_LINE_COMMENT) {
return addSingleSpaceIf(true, false);
}
else {
return addSingleSpaceIf(false, true);
}
}
if (type2 == LPAREN) {
if (elementType == IF_STATEMENT) {
return addSingleSpaceIf(mySettings.SPACE_BEFORE_IF_PARENTHESES);
}
else if (elementType == WHILE_STATEMENT || elementType == DO_WHILE_STATEMENT) {
return addSingleSpaceIf(mySettings.SPACE_BEFORE_WHILE_PARENTHESES);
}
else if (elementType == SWITCH_STATEMENT) {
return addSingleSpaceIf(mySettings.SPACE_BEFORE_SWITCH_PARENTHESES);
}
else if (elementType == ON_PART || elementType == CATCH_PART) {
return addSingleSpaceIf(mySettings.SPACE_BEFORE_CATCH_PARENTHESES);
}
}
if (elementType == IF_STATEMENT) {
if (type1 == RPAREN && mySettings.BRACE_STYLE == CommonCodeStyleSettings.END_OF_LINE) {
// Always have a single space following the closing paren of an if-condition.
int nsp = mySettings.SPACE_BEFORE_IF_LBRACE ? 1 : 0;
int lf = 0;
if (!BLOCKS.contains(type2) && mySettings.SPECIAL_ELSE_IF_TREATMENT) {
if (FormatterUtil.isFollowedBy(node2, ELSE, SEMICOLON)) lf = 1;
}
return Spacing.createSpacing(nsp, nsp, lf, !BLOCKS.contains(type2) && mySettings.KEEP_LINE_BREAKS, 0);
}
if (type1 == SEMICOLON && type2 == ELSE) {
// If the then-part is on the line with the condition put the else-part on the next line.
return Spacing.createSpacing(0, 0, 1, false, 0);
}
}
if (type2 == FOR_LOOP_PARTS_IN_BRACES) {
return addSingleSpaceIf(mySettings.SPACE_BEFORE_FOR_PARENTHESES);
}
if (type2 == FORMAL_PARAMETER_LIST && (FUNCTION_DEFINITION.contains(elementType) || elementType == FUNCTION_EXPRESSION)) {
return addSingleSpaceIf(mySettings.SPACE_BEFORE_METHOD_PARENTHESES);
}
if (elementType == DEFAULT_FORMAL_NAMED_PARAMETER && (type1 == EQ || type2 == EQ)) {
return addSingleSpaceIf(mySettings.SPACE_AROUND_ASSIGNMENT_OPERATORS);
}
if (type2 == ARGUMENTS && elementType == CALL_EXPRESSION) {
return addSingleSpaceIf(mySettings.SPACE_BEFORE_METHOD_CALL_PARENTHESES);
}
//
//Spacing before left braces
//
if (BLOCKS.contains(type2)) {
if (elementType == IF_STATEMENT && type1 != ELSE) {
return setBraceSpace(mySettings.SPACE_BEFORE_IF_LBRACE, mySettings.BRACE_STYLE, child1.getTextRange());
}
else if (elementType == IF_STATEMENT && type1 == ELSE) {
return setBraceSpace(mySettings.SPACE_BEFORE_ELSE_LBRACE, mySettings.BRACE_STYLE, child1.getTextRange());
}
else if (elementType == WHILE_STATEMENT || elementType == DO_WHILE_STATEMENT) {
return setBraceSpace(mySettings.SPACE_BEFORE_WHILE_LBRACE, mySettings.BRACE_STYLE, child1.getTextRange());
}
else if (elementType == FOR_STATEMENT) {
return setBraceSpace(mySettings.SPACE_BEFORE_FOR_LBRACE, mySettings.BRACE_STYLE, child1.getTextRange());
}
else if (elementType == TRY_STATEMENT) {
return setBraceSpace(mySettings.SPACE_BEFORE_TRY_LBRACE, mySettings.BRACE_STYLE, child1.getTextRange());
}
else if (elementType == ON_PART) {
return setBraceSpace(mySettings.SPACE_BEFORE_CATCH_LBRACE, mySettings.BRACE_STYLE, child1.getTextRange());
}
else if (elementType == FINALLY_PART) {
return setBraceSpace(mySettings.SPACE_BEFORE_FINALLY_LBRACE, mySettings.BRACE_STYLE, child1.getTextRange());
}
}
if (type2 == LBRACE && elementType == SWITCH_STATEMENT) {
return setBraceSpace(mySettings.SPACE_BEFORE_SWITCH_LBRACE, mySettings.BRACE_STYLE, child1.getTextRange());
}
if (FUNCTION_DEFINITION.contains(elementType) && type2 == FUNCTION_BODY) {
return setBraceSpace(mySettings.SPACE_BEFORE_METHOD_LBRACE, mySettings.METHOD_BRACE_STYLE, child1.getTextRange());
}
if (elementType == FUNCTION_EXPRESSION && type2 == FUNCTION_EXPRESSION_BODY) {
return setBraceSpace(mySettings.SPACE_BEFORE_METHOD_LBRACE, mySettings.METHOD_BRACE_STYLE, child1.getTextRange());
}
if (elementType == CLASS_DEFINITION) {
if (type2 == CLASS_BODY) {
return setBraceSpace(mySettings.SPACE_BEFORE_CLASS_LBRACE, mySettings.BRACE_STYLE, child1.getTextRange());
}
if (type2 == TYPE_PARAMETERS) {
return noSpace();
}
if (type2 == INTERFACES || type2 == MIXINS) {
ASTNode typeNameNode = FormatterUtil.getNextNonWhitespaceSibling(myNode.getFirstChildNode());
ASTNode bodyNode = myNode.getLastChildNode();
if (typeNameNode != null && bodyNode != null) {
// For some reason we need to start at the beginning of the type name, not the end.
TextRange range = TextRange.create(typeNameNode.getTextRange().getStartOffset(), bodyNode.getStartOffset());
return Spacing.createDependentLFSpacing(1, 1, range, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_CODE);
}
}
return Spacing.createSpacing(1, 1, 0, false, 0);
}
if (elementType == MIXIN_APPLICATION) {
return Spacing.createSpacing(1, 1, 0, false, 0);
}
if (elementType == ENUM_DEFINITION) {
if (mySettings.BRACE_STYLE == CommonCodeStyleSettings.END_OF_LINE) {
if (type1 == LBRACE && type2 == RBRACE) {
return noSpace();
}
if (type1 == LBRACE || type2 == RBRACE) {
return Spacing.createDependentLFSpacing(1, 1, textRangeFollowingMetadata(), false, 0);
}
if (type2 == ENUM_CONSTANT_DECLARATION) {
return Spacing.createDependentLFSpacing(1, 1, textRangeFollowingMetadata(), false, 0);
}
}
if (type2 == LBRACE) {
return setBraceSpace(mySettings.SPACE_BEFORE_CLASS_LBRACE, mySettings.BRACE_STYLE, child1.getTextRange());
}
}
if (type1 == LPAREN || type2 == RPAREN) {
if (elementType == IF_STATEMENT) {
return addSingleSpaceIf(mySettings.SPACE_WITHIN_IF_PARENTHESES);
}
else if (elementType == WHILE_STATEMENT || elementType == DO_WHILE_STATEMENT) {
return addSingleSpaceIf(mySettings.SPACE_WITHIN_WHILE_PARENTHESES);
}
else if (elementType == FOR_LOOP_PARTS_IN_BRACES) {
return addSingleSpaceIf(mySettings.SPACE_WITHIN_FOR_PARENTHESES);
}
else if (elementType == SWITCH_STATEMENT) {
return addSingleSpaceIf(mySettings.SPACE_WITHIN_SWITCH_PARENTHESES);
}
else if (elementType == CATCH_PART) {
return addSingleSpaceIf(mySettings.SPACE_WITHIN_CATCH_PARENTHESES);
}
else if (elementType == FORMAL_PARAMETER_LIST) {
final boolean newLineNeeded =
type1 == LPAREN ? mySettings.METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE : mySettings.METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE;
if (newLineNeeded || mySettings.SPACE_WITHIN_METHOD_PARENTHESES) {
return addSingleSpaceIf(mySettings.SPACE_WITHIN_METHOD_PARENTHESES, newLineNeeded);
}
return Spacing.createSpacing(0, 0, 0, false, 0);
}
else if (elementType == ARGUMENTS) {
final boolean newLineNeeded =
type1 == LPAREN ? mySettings.CALL_PARAMETERS_LPAREN_ON_NEXT_LINE : mySettings.CALL_PARAMETERS_RPAREN_ON_NEXT_LINE;
return addSingleSpaceIf(mySettings.SPACE_WITHIN_METHOD_CALL_PARENTHESES, newLineNeeded);
}
else if (mySettings.BINARY_OPERATION_WRAP != CommonCodeStyleSettings.DO_NOT_WRAP && elementType == PARENTHESIZED_EXPRESSION) {
final boolean newLineNeeded =
type1 == LPAREN ? mySettings.PARENTHESES_EXPRESSION_LPAREN_WRAP : mySettings.PARENTHESES_EXPRESSION_RPAREN_WRAP;
return addSingleSpaceIf(false, newLineNeeded);
}
}
if (elementType == TERNARY_EXPRESSION) {
if (type2 == QUEST) {
return addSingleSpaceIf(mySettings.SPACE_BEFORE_QUEST);
}
else if (type2 == COLON) {
return addSingleSpaceIf(mySettings.SPACE_BEFORE_COLON);
}
else if (type1 == QUEST) {
return addSingleSpaceIf(mySettings.SPACE_AFTER_QUEST);
}
else if (type1 == COLON) {
return addSingleSpaceIf(mySettings.SPACE_AFTER_COLON);
}
}
//
// Spacing around assignment operators (=, -=, etc.)
//
if (type1 == ASSIGNMENT_OPERATOR || type2 == ASSIGNMENT_OPERATOR) {
return addSingleSpaceIf(mySettings.SPACE_AROUND_ASSIGNMENT_OPERATORS);
}
if (type1 == EQ && elementType == VAR_INIT) {
return addSingleSpaceIf(mySettings.SPACE_AROUND_ASSIGNMENT_OPERATORS);
}
if (type2 == VAR_INIT) {
return addSingleSpaceIf(mySettings.SPACE_AROUND_ASSIGNMENT_OPERATORS);
}
//
// Spacing around logical operators (&&, OR, etc.)
//
if (LOGIC_OPERATORS.contains(type1) || LOGIC_OPERATORS.contains(type2)) {
return addSingleSpaceIf(mySettings.SPACE_AROUND_LOGICAL_OPERATORS);
}
//
// Spacing around equality operators (==, != etc.)
//
if (type1 == EQUALITY_OPERATOR || type2 == EQUALITY_OPERATOR) {
return addSingleSpaceIf(mySettings.SPACE_AROUND_EQUALITY_OPERATORS);
}
//
// Spacing around relational operators (<, <= etc.)
//
if (type1 == RELATIONAL_OPERATOR || type2 == RELATIONAL_OPERATOR) {
return addSingleSpaceIf(mySettings.SPACE_AROUND_RELATIONAL_OPERATORS);
}
//
// Spacing around bitwise operators ( &, |, ^, etc.)
//
if (BITWISE_OPERATORS.contains(type1) || BITWISE_OPERATORS.contains(type2)) {
return addSingleSpaceIf(mySettings.SPACE_AROUND_BITWISE_OPERATORS);
}
//
// Spacing around additive operators ( +, -, etc.)
//
if ((type1 == ADDITIVE_OPERATOR || type2 == ADDITIVE_OPERATOR) && elementType != PREFIX_EXPRESSION) {
return addSingleSpaceIf(mySettings.SPACE_AROUND_ADDITIVE_OPERATORS);
}
//
// Spacing around multiplicative operators ( *, /, %, etc.)
//
if (type1 == MULTIPLICATIVE_OPERATOR || type2 == MULTIPLICATIVE_OPERATOR) {
return addSingleSpaceIf(mySettings.SPACE_AROUND_MULTIPLICATIVE_OPERATORS);
}
//
// Spacing between successive unary operators ( -, + )
//
if (type1 == PREFIX_OPERATOR && type2 == PREFIX_EXPRESSION) {
ASTNode[] childs = node2.getChildren(PREFIX_OPERATOR_SET);
if (childs.length > 0) {
return addSingleSpaceIf(isSpaceNeededBetweenPrefixOps(node1, childs[0]));
}
}
//
// Spacing around unary operators ( NOT, ++, etc.)
//
if (type1 == PREFIX_OPERATOR || type2 == PREFIX_OPERATOR) {
return addSingleSpaceIf(mySettings.SPACE_AROUND_UNARY_OPERATOR);
}
//
// Spacing around shift operators ( <<, >>, >>>, etc.)
//
if (type1 == SHIFT_OPERATOR || type2 == SHIFT_OPERATOR) {
return addSingleSpaceIf(mySettings.SPACE_AROUND_SHIFT_OPERATORS);
}
//
//Spacing before keyword (else, catch, etc)
//
if (type2 == ELSE) {
return addSingleSpaceIf(mySettings.SPACE_BEFORE_ELSE_KEYWORD, mySettings.ELSE_ON_NEW_LINE);
}
if (type2 == WHILE) {
return addSingleSpaceIf(mySettings.SPACE_BEFORE_WHILE_KEYWORD, mySettings.WHILE_ON_NEW_LINE);
}
if (type2 == ON_PART) {
return addSingleSpaceIf(mySettings.SPACE_BEFORE_CATCH_KEYWORD, mySettings.CATCH_ON_NEW_LINE);
}
if (type2 == FINALLY_PART) {
return addSingleSpaceIf(mySettings.SPACE_BEFORE_FINALLY_KEYWORD, mySettings.CATCH_ON_NEW_LINE);
}
//
//Other
//
if (type1 == ELSE) {
if (type2 == IF_STATEMENT) {
return Spacing.createSpacing(1, 1, mySettings.SPECIAL_ELSE_IF_TREATMENT ? 0 : 1, false, mySettings.KEEP_BLANK_LINES_IN_CODE);
}
if (type2 != LBRACE) {
// Keep single-statement else-part on same line?
int lf = mySettings.SPECIAL_ELSE_IF_TREATMENT ? 1 : 0;
return Spacing.createSpacing(1, 1, lf, !BLOCKS.contains(type2) && mySettings.KEEP_LINE_BREAKS, 0);
}
}
if (type1 == LBRACE && type2 == RBRACE) {
// Empty class.
if (elementType == CLASS_BODY && mySettings.KEEP_SIMPLE_CLASSES_IN_ONE_LINE) return noSpace();
// Empty MAP_LITERAL_EXPRESSION or LIST_LITERAL_EXPRESSION.
if (mySettings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE) return noSpace();
}
boolean isBraces = type1 == LBRACE || type2 == RBRACE;
if ((isBraces &&
elementType != OPTIONAL_FORMAL_PARAMETERS &&
elementType != OPTIONAL_PARAMETER_TYPES &&
elementType != MAP_LITERAL_EXPRESSION) ||
BLOCKS_EXT.contains(type1) ||
FUNCTION_DEFINITION.contains(type1)) {
return addLineBreak();
}
if (COMMENTS.contains(type1)) {
if (isBraces || type2 == SEMICOLON) {
return addLineBreak();
}
if (parentType == DART_FILE &&
FUNCTION_DEFINITION.contains(elementType) &&
!(type1 == MULTI_LINE_COMMENT && type2 == COMPONENT_NAME)) {
return addLineBreak();
}
if (type2 == RBRACKET && elementType != OPTIONAL_FORMAL_PARAMETERS) {
return addLineBreak();
}
if (type2 == ARGUMENT_LIST || type2 == COMPONENT_NAME) {
if (type1 == MULTI_LINE_COMMENT && isEmbeddedComment(type1, child1)) {
if (!hasNewlineInText(node1)) {
return addSingleSpaceIf(true);
}
}
return addLineBreak();
}
}
if ((elementType == INTERFACES || elementType == MIXINS) && type2 == TYPE_LIST) {
return Spacing
.createDependentLFSpacing(1, 1, myNode.getTextRange(), mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_CODE);
}
if (elementType == TYPE_LIST && type2 == TYPE) {
return Spacing.createDependentLFSpacing(1, 1, myNode.getTreeParent().getTextRange(), mySettings.KEEP_LINE_BREAKS,
mySettings.KEEP_BLANK_LINES_IN_CODE);
}
if (type1 == LBRACKET && type2 == RBRACKET) {
return noSpace();
}
if (type1 == COMMA && (elementType == FORMAL_PARAMETER_LIST || elementType == ARGUMENT_LIST)) {
return addSingleSpaceIf(mySettings.SPACE_AFTER_COMMA);
}
if (type1 == COMMA) {
if (type2 == RBRACKET) {
TextRange range = myNode.getTextRange();
return Spacing.createDependentLFSpacing(0, 0, range, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_CODE);
}
return addSingleSpaceIf(mySettings.SPACE_AFTER_COMMA && type2 != RBRACE && type2 != RBRACKET);
}
if (type2 == COMMA) {
return addSingleSpaceIf(mySettings.SPACE_BEFORE_COMMA);
}
//todo: customize in settings
if (type1 == EXPRESSION_BODY_DEF) { // =>
if (type2 == STRING_LITERAL_EXPRESSION) {
// We might want to add a check that the string contains a newline as in regression/0000/0036.unit
return addSingleSpaceIf(true);
}
TextRange range = node2.getTextRange();
return Spacing.createDependentLFSpacing(1, 1, range, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_CODE);
}
if (type2 == EXPRESSION_BODY_DEF) {
return addSingleSpaceIf(true);
}
if (type1 == FOR_LOOP_PARTS_IN_BRACES && !BLOCKS_EXT.contains(type2)) {
return addLineBreak();
}
if (type1 == IF_STATEMENT ||
type1 == SWITCH_STATEMENT ||
type1 == TRY_STATEMENT ||
type1 == DO_WHILE_STATEMENT ||
type1 == FOR_STATEMENT ||
type1 == SWITCH_CASE ||
type1 == DEFAULT_CASE ||
type1 == WHILE_STATEMENT) {
return addLineBreak();
}
if (COMMENTS.contains(type2)) {
int forceSpace = 1;
if (type2 == MULTI_LINE_COMMENT && (type1 == COMPONENT_NAME || type1 == TYPE || type1 == VAR || type1 == RETURN_TYPE)) {
forceSpace = 0;
}
return Spacing.createSpacing(forceSpace, 1, 0, true, mySettings.KEEP_BLANK_LINES_IN_CODE);
}
if (TOKENS_WITH_SPACE_AFTER.contains(type1) || KEYWORDS_WITH_SPACE_BEFORE.contains(type2)) {
return addSingleSpaceIf(true);
}
if (elementType == FOR_LOOP_PARTS && type1 == SEMICOLON) {
return addSingleSpaceIf(true);
}
if (elementType == VALUE_EXPRESSION && type2 == CASCADE_REFERENCE_EXPRESSION) {
if (type1 == CASCADE_REFERENCE_EXPRESSION) {
if (cascadesAreSameMethod(((AbstractBlock)child1).getNode(), ((AbstractBlock)child2).getNode())) {
return Spacing.createSpacing(0, 0, 0, false, 0);
}
}
else if (type1 == REFERENCE_EXPRESSION || isSimpleLiteral(type1)) {
CompositeElement elem = (CompositeElement)myNode;
ASTNode[] childs = elem.getChildren(CASCADE_REFERENCE_EXPRESSION_SET);
if (childs.length == 1) {
return Spacing.createDependentLFSpacing(0, 0, myNode.getTextRange(), true, 0);
}
if (allCascadesAreSameMethod(childs)) {
return Spacing.createSpacing(0, 0, 0, false, 0);
}
}
else if (type1 == NEW_EXPRESSION && parentType == ARGUMENT_LIST) {
return Spacing.createDependentLFSpacing(0, 0, myNode.getTextRange(), true, 0);
}
return addLineBreak();
}
if (type1 == CLOSING_QUOTE && type2 == OPEN_QUOTE && elementType == STRING_LITERAL_EXPRESSION) {
ASTNode sib = node1;
int preserveNewline = 0;
// Adjacent strings on the same line should not be split.
while ((sib = sib.getTreeNext()) != null) {
// Comments are handled elsewhere.
// TODO Create a test for this loop after adjacent-string wrapping is implemented.
if (sib.getElementType() == WHITE_SPACE) {
String ws = sib.getText();
if (ws.contains("\n")) {
preserveNewline++;
break;
}
continue;
}
break;
}
// Adjacent strings on separate lines should not include blank lines.
return Spacing.createSpacing(0, 1, preserveNewline, true, 0);
}
// Put the constructor colon on the next line unless only one initializer.
if (type2 == INITIALIZERS) {
if (hasMultipleInitializers(node2)) {
return addSingleSpaceIf(false, true);
}
else {
return addSingleSpaceIf(true, false);
}
}
if (elementType == LIST_LITERAL_EXPRESSION && type2 == RBRACKET) {
return Spacing.createDependentLFSpacing(0, 0, node1.getTextRange(), mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_CODE);
}
if (elementType == NAMED_ARGUMENT || elementType == DEFAULT_FORMAL_NAMED_PARAMETER || elementType == MAP_LITERAL_ENTRY) {
if (type1 == COLON) {
return addSingleSpaceIf(true);
}
if (type2 == COLON) {
return noSpace();
}
}
if (elementType == TYPE_ARGUMENTS || elementType == TYPE_PARAMETERS) {
if (type1 == LT || type2 == GT || type2 == LT || type1 == GT) {
return noSpace(); // Might want a user setting to control space within type
}
}
if (elementType == IS_EXPRESSION) {
if (type1 == NOT) {
return addSingleSpaceIf(true);
}
if (type2 == NOT) {
return noSpace();
}
}
if (type1 == TYPE_ARGUMENTS && (type2 == LBRACKET || type2 == LBRACE)) {
return noSpace(); // Might want a user setting to control space before/after type
}
if (type2 == RBRACE && type1 == MAP_LITERAL_ENTRY) {
return noSpace();
}
if (type2 == RBRACKET && type1 == LIST_LITERAL_EXPRESSION) {
return noSpace();
}
if (type2 == RBRACKET && type1 == EXPRESSION_LIST) {
return noSpace();
}
// Spacing in async functions.
if (elementType == FUNCTION_BODY || elementType == FUNCTION_EXPRESSION_BODY) {
if (type1 == ASYNC || type1 == SYNC) {
if (type2 == MUL) return noSpace();
return addSingleSpaceIf(true);
}
if (type1 == MUL) return addSingleSpaceIf(true);
}
if (elementType == REFERENCE_EXPRESSION && (type2 == DOT || type2 == QUEST_DOT)) {
return createSpacingForCallChain(collectSurroundingMessageSends(), node2);
}
if (type1 == DOT || type1 == QUEST_DOT || type1 == HASH) {
return noSpace(); // Seems odd that no plugin has a setting for spaces around DOT -- need a Lisp mode!
}
if (type2 == HASH) {
return addSingleSpaceIf(parentType == SYMBOL_LITERAL_EXPRESSION); // No space before closurization.
}
if (type1 == RETURN && type2 != SEMICOLON) {
return addSingleSpaceIf(true);
}
return Spacing.createSpacing(0, 1, 0, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_CODE);
}
private static int getMinLineBreaksBetweenTopLevelNodes(@NotNull final IElementType type1, @NotNull final IElementType type2) {
/*
libraryStatement
| partOfStatement
| importStatement
| exportStatement
| partStatement
| classDefinition
| enumDefinition
| functionTypeAlias
| getterOrSetterDeclaration
| functionDeclarationWithBodyOrNative
| varDeclarationListWithSemicolon
*/
if (type1 == LIBRARY_STATEMENT) return 2;
if (type1 == PART_OF_STATEMENT) return 2;
if (type1 == IMPORT_STATEMENT || type1 == EXPORT_STATEMENT) {
if (type2 != IMPORT_STATEMENT && type2 != EXPORT_STATEMENT) return 2;
}
if (type1 == PART) return 2;
if (type1 == CLASS_DEFINITION) return 2;
if (type1 == ENUM_DEFINITION) return 2;
if (type1 == GETTER_DECLARATION) return 2;
if (type1 == SETTER_DECLARATION) return 2;
if (type1 == FUNCTION_DECLARATION_WITH_BODY_OR_NATIVE) return 2;
return 1;
}
private Spacing addLineBreak() {
return Spacing.createSpacing(0, 0, 1, false, mySettings.KEEP_BLANK_LINES_IN_CODE);
}
private Spacing addSingleSpaceIf(boolean condition) {
return addSingleSpaceIf(condition, false);
}
private Spacing addSingleSpaceIf(boolean condition, boolean linesFeed) {
final int spaces = condition ? 1 : 0;
final int lines = linesFeed ? 1 : 0;
return Spacing.createSpacing(spaces, spaces, lines, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_CODE);
}
private Spacing noSpace() {
return Spacing.createSpacing(0, 0, 0, mySettings.KEEP_LINE_BREAKS, 0);
}
private Spacing setBraceSpace(boolean needSpaceSetting,
@CommonCodeStyleSettings.BraceStyleConstant int braceStyleSetting,
TextRange textRange) {
final int spaces = needSpaceSetting ? 1 : 0;
if (braceStyleSetting == CommonCodeStyleSettings.NEXT_LINE_IF_WRAPPED && textRange != null) {
return Spacing.createDependentLFSpacing(spaces, spaces, textRange, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_CODE);
}
else {
final int lineBreaks =
braceStyleSetting == CommonCodeStyleSettings.END_OF_LINE || braceStyleSetting == CommonCodeStyleSettings.NEXT_LINE_IF_WRAPPED
? 0
: 1;
return Spacing.createSpacing(spaces, spaces, lineBreaks, false, 0);
}
}
private static boolean doesMessageHaveArguments(ASTNode node) {
// node is a DOT
ASTNode parent = node.getTreeParent().getTreeParent();
if (parent == null) return false;
if (parent.getElementType() != CALL_EXPRESSION) return false;
ASTNode args = parent.getLastChildNode();
if (args == null) return false;
return args.getElementType() == ARGUMENTS;
}
private static Comparator<ASTNode> textRangeSorter() {
return Comparator.comparingInt(o -> o.getTextRange().getStartOffset());
}
private CallChain collectSurroundingMessageSends() {
CallChain calls = new CallChain();
collectPredecessorMessageSends(calls);
collectSuccessorMessageSends(calls);
return calls;
}
private void collectPredecessorMessageSends(CallChain calls) {
ASTNode node = myNode;
while (node != null) {
IElementType type = node.getElementType();
if (type == REFERENCE_EXPRESSION) {
collectDotIfMessageSend(calls, node);
node = node.getTreeParent();
}
else if (type == CALL_EXPRESSION) {
if (hasMultilineFunctionArgument(node)) {
calls.isFollowedByHardNewline = true;
break;
}
node = node.getTreeParent();
}
else {
break;
}
}
}
private void collectSuccessorMessageSends(CallChain calls) {
ASTNode node = myNode;
while (node != null) {
IElementType type = node.getElementType();
if (type == CALL_EXPRESSION) {
if (hasMultilineFunctionArgument(node)) {
calls.isPrecededByHardNewline = true;
break;
}
node = node.getFirstChildNode();
}
else if (type == REFERENCE_EXPRESSION) {
collectDotIfMessageSend(calls, node);
node = node.getFirstChildNode();
}
else {
break;
}
}
}
private static void collectDotIfMessageSend(CallChain calls, ASTNode node) {
ASTNode child = node.getFirstChildNode();
child = FormatterUtil.getNextNonWhitespaceSibling(child);
if (child != null) {
IElementType childType = child.getElementType();
if (childType == DOT || childType == QUEST_DOT || childType == HASH) {
calls.add(child);
}
}
}
private static boolean hasMultilineFunctionArgument(ASTNode node) {
ASTNode args = node.getLastChildNode();
ASTNode first = args == null ? null : args.getFirstChildNode();
args = first == null ? null : first.getTreeNext();
if (args != null && args.getElementType() == ARGUMENT_LIST) {
ASTNode arg = args.getFirstChildNode();
int n = 1;
while (arg != null) {
// TODO Max 9 args is totally arbitrary, possibly not even desirable.
if (n++ == 10 || arg.getElementType() == FUNCTION_EXPRESSION) {
if (arg.getText().indexOf('\n') >= 0) {
return true;
}
}
arg = arg.getTreeNext();
}
}
return false;
}
private Spacing createSpacingForCallChain(CallChain calls, ASTNode node2) {
// The rules involving call chains, like m.a.b().c.d(), are complex.
if (calls.list.size() < 2) {
return noSpace();
}
//if (calls.isPrecededByHardNewline) {
// // Rule: allow an inline chain before a hard newline but not after.
// return Spacing.createSpacing(0, 0, 1, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_CODE);
//}
boolean isAllProperties = true;
boolean mustSplit = false;
boolean mustStopAtNextMethod = false;
List<TextRange> ranges = new ArrayList<>();
for (ASTNode node : calls.list) {
if (doesMessageHaveArguments(node)) {
if (mustStopAtNextMethod) {
return Spacing.createDependentLFSpacing(0, 0, ranges, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_CODE);
}
isAllProperties = false;
}
else {
if (!isAllProperties) {
// Rule: split properties in a method chain.
mustSplit = true;
}
}
TextRange range = node.getTextRange();
ranges.add(new TextRange(range.getStartOffset() - 1, range.getEndOffset()));
if (node2 == node && isAllProperties) {
// Rule: do not split leading properties (unless too long to fit).
mustStopAtNextMethod = true;
}
}
// Not sure how to implement rule: split before all properties if they don't fit on two lines. TWO lines !?
if (isAllProperties && ranges.size() > 7) {
mustSplit = true;
}
if (mustSplit) {
return Spacing.createSpacing(0, 0, 1, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_CODE);
}
else {
return Spacing.createDependentLFSpacing(0, 0, ranges, mySettings.KEEP_LINE_BREAKS, mySettings.KEEP_BLANK_LINES_IN_CODE);
}
}
private TextRange textRangeFollowingMetadata() {
TextRange range = myNode.getTextRange();
ASTNode child = myNode.getFirstChildNode();
if (child == null || child.getElementType() != METADATA) return range;
while (child != null && (child.getElementType() == METADATA || child.getElementType() == WHITE_SPACE)) {
child = child.getTreeNext();
}
if (child == null) return range; // Avoid nullable warning.
return new TextRange(child.getTextRange().getStartOffset(), range.getEndOffset());
}
private static boolean allCascadesAreSameMethod(ASTNode[] children) {
for (int i = 1; i < children.length; i++) {
if (!cascadesAreSameMethod(children[i - 1], children[i])) {
return false;
}
}
return true;
}
private static boolean cascadesAreSameMethod(ASTNode child1, ASTNode child2) {
ASTNode call1 = child1.getLastChildNode();
if (call1.getElementType() == CALL_EXPRESSION) {
ASTNode call2 = child2.getLastChildNode();
if (call2.getElementType() == CALL_EXPRESSION) {
String name1 = getImmediateCallName(call1);
if (name1 != null) {
String name2 = getImmediateCallName(call2);
if (name1.equals(name2)) {
return true;
}
}
}
}
return false;
}
private static String getImmediateCallName(ASTNode callNode) {
ASTNode[] childs = callNode.getChildren(REFERENCE_EXPRESSION_SET);
if (childs.length != 1) return null;
ASTNode child = childs[0];
childs = child.getChildren(ID_SET);
if (childs.length != 1) return null;
child = childs[0];
return child.getText();
}
private static boolean isSpaceNeededBetweenPrefixOps(ASTNode node1, ASTNode node2) {
String op1 = node1.getText();
String op2 = node2.getText();
return op1.endsWith(op2.substring(op2.length() - 1));
}
private static boolean isSimpleLiteral(IElementType nodeType) {
// Literals that can be cascade receivers, excluding map and list.
return SIMPLE_LITERAL_SET.contains(nodeType);
}
private static boolean needsBlankLineBeforeFunction(IElementType elementType) {
return elementType == DART_FILE ||
elementType == CLASS_MEMBERS ||
elementType instanceof DartEmbeddedContentElementType;
}
private static boolean isEmbeddedComment(IElementType type, Block block) {
return COMMENTS.contains(type) && (!isDirectlyPrecededByNewline(block) || isDirectlyPrecededByBlockComment(block));
}
private static boolean isDirectlyPrecededByNewline(Block child) {
// The child is a line comment whose parent is the DART_FILE.
// Return true if it is (or will be) at the beginning of the line, or
// following a block comment that is at the beginning of the line.
ASTNode node = ((DartBlock)child).getNode();
return isDirectlyPrecededByNewline(node);
}
private static boolean isDirectlyPrecededByNewline(ASTNode node) {
while ((node = node.getTreePrev()) != null) {
if (node.getElementType() == WHITE_SPACE) {
if (node.getText().contains("\n")) return true;
continue;
}
if (node.getElementType() == MULTI_LINE_COMMENT) {
if (node.getTreePrev() == null) {
return true;
}
continue;
}
break;
}
return false;
}
@Nullable
private static ASTNode getPrevSiblingOnTheSameLineSkipCommentsAndWhitespace(@NotNull ASTNode node) {
while ((node = node.getTreePrev()) != null) {
if (node.getElementType() == WHITE_SPACE || COMMENTS.contains(node.getElementType())) {
if (node.getText().contains("\n")) {
return null;
}
else {
continue;
}
}
return node;
}
return null;
}
private static boolean isDirectlyPrecededByBlockComment(Block child) {
ASTNode node = ((DartBlock)child).getNode();
return isDirectlyPrecededByBlockComment(node);
}
private static boolean isDirectlyPrecededByBlockComment(ASTNode node) {
while ((node = node.getTreePrev()) != null) {
if (node.getElementType() == WHITE_SPACE) {
if (node.getText().contains("\n")) return false;
continue;
}
if (node.getElementType() == MULTI_LINE_COMMENT) {
return true;
}
break;
}
return false;
}
private static boolean isScriptTag(Block child) {
// The VM accepts any kind of whtespace prior to #! even though unix shells do not.
ASTNode node = ((DartBlock)child).getNode();
if (!node.getText().trim().startsWith("#!")) return false;
while ((node = node.getTreePrev()) != null) {
if (node.getElementType() != WHITE_SPACE) return false;
}
return true;
}
public static boolean hasMultipleInitializers(ASTNode node) {
return FormatterUtil.isPrecededBy(node.getLastChildNode(), SUPER_CALL_OR_FIELD_INITIALIZER, SKIP_COMMA);
}
// dart_style recognizes three forms of "empty block":
// e() {}
// f() => expr;
// M() : super();
private static boolean hasEmptyBlock(ASTNode node) {
if (node.getElementType() == CLASS_DEFINITION) return false;
ASTNode child = node;
while (true) {
child = child.getLastChildNode();
if (child == null) return false;
if (child.getElementType() == WHITE_SPACE) child = FormatterUtil.getPreviousNonWhitespaceSibling(child);
if (child == null) return false;
if (child.getElementType() == FUNCTION_BODY) {
ASTNode next = child.getLastChildNode();
if (next.getElementType() == WHITE_SPACE) next = FormatterUtil.getPreviousNonWhitespaceSibling(next);
if (next != null && next.getElementType() == SEMICOLON) {
next = FormatterUtil.getPreviousNonWhitespaceSibling(next);
if (next != null && DartIndentProcessor.EXPRESSIONS.contains(next.getElementType())) {
ASTNode arrow = FormatterUtil.getPreviousNonWhitespaceSibling(next);
if (arrow != null && arrow.getElementType() == EXPRESSION_BODY_DEF) {
return true;
}
}
return false;
}
// Look inside the function body.
continue;
}
if (child.getElementType() == SEMICOLON) {
ASTNode prev = FormatterUtil.getPreviousNonWhitespaceSibling(child);
return (prev != null && prev.getElementType() == INITIALIZERS);
}
if (!BLOCKS.contains(child.getElementType())) continue;
child = child.getLastChildNode();
if (child == null) return false;
if (child.getElementType() == WHITE_SPACE) child = FormatterUtil.getPreviousNonWhitespaceSibling(child);
if (child == null) return false;
if (child.getElementType() != RBRACE) return false;
child = FormatterUtil.getPreviousNonWhitespaceSibling(child);
return child != null && child.getElementType() == LBRACE;
}
}
private static boolean hasNewlineInText(ASTNode node) {
String comment = node.getText();
return comment.indexOf('\n') > 0;
}
private static boolean isBlankLineAfterComment(ASTNode node) {
// Assumes whitespace has been normalized.
ASTNode next = node.getTreeNext();
if (next == null || next.getElementType() != WHITE_SPACE) return false;
String comment = next.getText();
int n = comment.indexOf('\n');
return comment.indexOf('\n', n + 1) > 0;
}
private static class CallChain {
SortedList<ASTNode> list = new SortedList<>(textRangeSorter());
boolean isPrecededByHardNewline = false;
boolean isFollowedByHardNewline = false;
void add(ASTNode node) {
if (!list.contains(node)) {
list.add(node);
}
}
}
}