Skip to content

Commit

Permalink
Improve formatting of "builder-style" chained invocations
Browse files Browse the repository at this point in the history
-------------
Created by MOE: http://code.google.com/p/moe-java
MOE_MIGRATED_REVID=92949638
  • Loading branch information
cushon authored and cgdecker committed May 11, 2015
1 parent 6b08f13 commit 0a0ff48
Show file tree
Hide file tree
Showing 11 changed files with 635 additions and 77 deletions.
Expand Up @@ -16,11 +16,13 @@


import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects;
import com.google.common.base.Optional; import com.google.common.base.Optional;
import com.google.common.base.Verify;
import com.google.common.collect.HashMultimap; import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.google.googlejavaformat.CloseOp; import com.google.googlejavaformat.CloseOp;
import com.google.googlejavaformat.Doc; import com.google.googlejavaformat.Doc;
import com.google.googlejavaformat.Doc.FillMode;
import com.google.googlejavaformat.Indent; import com.google.googlejavaformat.Indent;
import com.google.googlejavaformat.Input; import com.google.googlejavaformat.Input;
import com.google.googlejavaformat.Op; import com.google.googlejavaformat.Op;
Expand Down Expand Up @@ -220,6 +222,7 @@ boolean isYes() {


private static final Indent.Const ZERO = Indent.Const.ZERO; private static final Indent.Const ZERO = Indent.Const.ZERO;
private final Indent.Const minusTwo; private final Indent.Const minusTwo;
private final Indent.Const minusFour;
private final Indent.Const plusTwo; private final Indent.Const plusTwo;
private final Indent.Const plusFour; private final Indent.Const plusFour;
private final Indent.Const plusEight; private final Indent.Const plusEight;
Expand All @@ -235,6 +238,8 @@ boolean isYes() {
private static final int MAX_LINES_FOR_ARGUMENTS = 1; private static final int MAX_LINES_FOR_ARGUMENTS = 1;
private static final int MAX_LINES_FOR_ARRAY_INITIALIZERS = 3; private static final int MAX_LINES_FOR_ARRAY_INITIALIZERS = 3;
private static final int MAX_LINES_FOR_ANNOTATION_ELEMENT_VALUE_PAIRS = 1; private static final int MAX_LINES_FOR_ANNOTATION_ELEMENT_VALUE_PAIRS = 1;
private static final int MAX_LINES_FOR_CHAINED_ACCESSES = 1;
private static final int MAX_DEREFERENCES_BEFORE_BUILDER_STYLE = 3;


static { static {
PRECEDENCE.put("*", 10); PRECEDENCE.put("*", 10);
Expand Down Expand Up @@ -268,6 +273,7 @@ boolean isYes() {
public JavaInputAstVisitor(OpsBuilder builder, int indentMultiplier) { public JavaInputAstVisitor(OpsBuilder builder, int indentMultiplier) {
this.builder = builder; this.builder = builder;
minusTwo = Indent.Const.make(-2, indentMultiplier); minusTwo = Indent.Const.make(-2, indentMultiplier);
minusFour = Indent.Const.make(-4, indentMultiplier);
plusTwo = Indent.Const.make(+2, indentMultiplier); plusTwo = Indent.Const.make(+2, indentMultiplier);
plusFour = Indent.Const.make(+4, indentMultiplier); plusFour = Indent.Const.make(+4, indentMultiplier);
plusEight = Indent.Const.make(+8, indentMultiplier); plusEight = Indent.Const.make(+8, indentMultiplier);
Expand Down Expand Up @@ -2250,66 +2256,142 @@ void visitDot(Expression node0) {
ArrayDeque<Expression> stack = new ArrayDeque<>(); ArrayDeque<Expression> stack = new ArrayDeque<>();
LOOP: LOOP:
do { do {
stack.addLast(node); stack.addFirst(node);
switch (node.getNodeType()) { switch (node.getNodeType()) {
case ASTNode.FIELD_ACCESS: case ASTNode.FIELD_ACCESS:
node = ((FieldAccess) node).getExpression(); node = ((FieldAccess) node).getExpression();
break; break;
case ASTNode.METHOD_INVOCATION: case ASTNode.METHOD_INVOCATION:
node = ((MethodInvocation) node).getExpression(); node = ((MethodInvocation) node).getExpression();
break; break;
case ASTNode.QUALIFIED_NAME:
node = ((QualifiedName) node).getQualifier();
break;
default: default:
stack.removeLast();
break LOOP; break LOOP;
} }
} while (node != null); } while (node != null);
if (stack.isEmpty()) { Verify.verify(!stack.isEmpty());
node.accept(this);
return; int prefixIndex = TypeNameClassifier.typePrefixLength(simpleNames(stack));
} // record the number of 'dots' for determining whether to use builder-style
boolean fillNextBreak = isBaseName(Optional.fromNullable(node)); int dereferences = stack.size() - 1;
boolean needDot = node != null; if (prefixIndex >= 0) {
builder.open(plusFour); // adjust the builder-style threshold to ignore dereferences in the prefix, if there is one
if (node != null) { dereferences -= prefixIndex;
node.accept(this);
} }

builder.open(plusFour, MAX_LINES_FOR_CHAINED_ACCESSES);
Expression expression = null; Expression expression = null;
// TODO(jdd): Stack must be non-empty!
Optional<BreakTag> optionalTag = Optional.absent(); Optional<BreakTag> optionalTag = Optional.absent();
boolean needDot = false;
boolean endedOnMethod = false;

// Output the prefix of the dereference chain.
// e.g. the "ImmutableList" before ".builder().add()..."
if (prefixIndex >= 0) {
builder.open(ZERO);
boolean closed = false;
while (prefixIndex >= 0) {
expression = stack.removeFirst();
endedOnMethod |= expression.getNodeType() == ASTNode.METHOD_INVOCATION;
if (needDot) {
builder.breakToFill();
builder.guessToken(".");
}
dotExpressionUpToArgs(expression);
if (endedOnMethod) {
builder.close();
closed = true;
}
if (!stack.isEmpty()) {
dotExpressionArgsAndParen(expression);
} else {
optionalTag = Optional.of(genSym());
}
needDot = true;
prefixIndex--;
}

// Try to output the first dereference after the prefix on the same line,
// unless the prefix included a method.
if (!stack.isEmpty() && !endedOnMethod) {
expression = stack.removeFirst();
if (needDot) {
builder.breakToFill();
builder.guessToken(".");
}
dotExpressionUpToArgs(expression);
builder.close();
closed = true;

if (!stack.isEmpty()) {
dotExpressionArgsAndParen(expression);
} else {
optionalTag = Optional.of(genSym());
}
needDot = true;
}
if (!closed) {
builder.close();
}
}

// Output the rest of the dereferences, possibly in builder-style.
while (!stack.isEmpty()) { while (!stack.isEmpty()) {
expression = stack.removeLast(); expression = stack.removeFirst();
optionalTag = Optional.fromNullable(stack.isEmpty() ? genSym() : null); if (stack.isEmpty()) {
optionalTag = Optional.of(genSym());
}
if (needDot) { if (needDot) {
FillMode fillMode =
(dereferences >= MAX_DEREFERENCES_BEFORE_BUILDER_STYLE)
? FillMode.FORCED
: FillMode.UNIFIED;
builder.breakOp( builder.breakOp(
fillNextBreak ? Doc.FillMode.INDEPENDENT : Doc.FillMode.UNIFIED, "", ZERO, fillMode, "", ZERO, Optional.of(Doc.ProgressiveIndent.make(ZERO, ZERO)), optionalTag);
Optional.of(Doc.ProgressiveIndent.make(ZERO, ZERO)), optionalTag);
token("."); token(".");
} }
dotExpressionUpToArgs(expression); dotExpressionUpToArgs(expression);
if (stack.isEmpty()) { if (stack.isEmpty()) {
break; break;
} }
dotExpressionArgsAndParen(expression); dotExpressionArgsAndParen(expression);
fillNextBreak = false;
needDot = true; needDot = true;
} }
builder.close(); if (optionalTag.isPresent()) {
// TODO(jdd): Is this possible? builder.open(Indent.If.make(optionalTag.get(), ZERO, minusFour));
if (expression != null) { }
if (optionalTag.isPresent()) { dotExpressionArgsAndParen(expression);
builder.open(Indent.If.make(optionalTag.get(), plusFour, ZERO)); if (optionalTag.isPresent()) {
} else {
builder.open(ZERO);
}
dotExpressionArgsAndParen(expression);
builder.close(); builder.close();
} }
builder.close();
} }


private static boolean isBaseName(Optional<Expression> optionalNode) { /** Returns the simple names of expressions in a "." chain. */
return optionalNode.isPresent() private List<String> simpleNames(ArrayDeque<Expression> stack) {
&& (optionalNode.get().getNodeType() == ASTNode.SIMPLE_NAME ImmutableList.Builder<String> simpleNames = ImmutableList.builder();
|| optionalNode.get().getNodeType() == ASTNode.QUALIFIED_NAME); OUTER:
for (Expression expression : stack) {
switch (expression.getNodeType()) {
case ASTNode.FIELD_ACCESS:
simpleNames.add(((FieldAccess) expression).getName().getIdentifier());
break;
case ASTNode.QUALIFIED_NAME:
simpleNames.add(((QualifiedName) expression).getName().getIdentifier());
break;
case ASTNode.SIMPLE_NAME:
simpleNames.add(((SimpleName) expression).getIdentifier());
break;
case ASTNode.METHOD_INVOCATION:
simpleNames.add(((MethodInvocation) expression).getName().getIdentifier());
break OUTER;
default:
break OUTER;
}
}
return simpleNames.build();
} }


private void dotExpressionUpToArgs(Expression expression) { private void dotExpressionUpToArgs(Expression expression) {
Expand All @@ -2330,8 +2412,14 @@ private void dotExpressionUpToArgs(Expression expression) {
visit(methodInvocation.getName()); visit(methodInvocation.getName());
token("("); token("(");
break; break;
case ASTNode.QUALIFIED_NAME:
visit(((QualifiedName) expression).getName());
break;
case ASTNode.SIMPLE_NAME:
visit(((SimpleName) expression));
break;
default: default:
throw new IllegalArgumentException("bad expression type"); expression.accept(this);
} }
} }


Expand All @@ -2344,8 +2432,11 @@ private void dotExpressionArgsAndParen(Expression expression) {
addArguments(methodInvocation.arguments(), plusFour); addArguments(methodInvocation.arguments(), plusFour);
token(")"); token(")");
break; break;
case ASTNode.SIMPLE_NAME:
case ASTNode.QUALIFIED_NAME:
break;
default: default:
throw new IllegalArgumentException("bad expression type"); break;
} }
} }


Expand Down Expand Up @@ -2469,14 +2560,18 @@ private static boolean expressionsAreParallel(List<Expression> expressions, int
private void visitQualifiedName(QualifiedName node0, BreakOrNot breaks) { private void visitQualifiedName(QualifiedName node0, BreakOrNot breaks) {
QualifiedName node = node0; QualifiedName node = node0;
sync(node); sync(node);

// defer to visitDot for builder-style wrapping if breaks are enabled
if (breaks.isYes()) { if (breaks.isYes()) {
builder.open(plusFour); visitDot(node0);
return;
} }

// Collapse chains of "." operators. // Collapse chains of "." operators.
ArrayDeque<SimpleName> stack = new ArrayDeque<>(); ArrayDeque<SimpleName> stack = new ArrayDeque<>();
Name qualifier; Name qualifier;
while (true) { while (true) {
stack.addLast(node.getName()); stack.addFirst(node.getName());
qualifier = node.getQualifier(); qualifier = node.getQualifier();
if (qualifier == null || qualifier.getNodeType() != ASTNode.QUALIFIED_NAME) { if (qualifier == null || qualifier.getNodeType() != ASTNode.QUALIFIED_NAME) {
break; break;
Expand All @@ -2485,25 +2580,16 @@ private void visitQualifiedName(QualifiedName node0, BreakOrNot breaks) {
} }
if (qualifier != null) { if (qualifier != null) {
visitName(qualifier, breaks); visitName(qualifier, breaks);
if (breaks.isYes()) {
builder.breakToFill();
}
token("."); token(".");
} }
boolean needDot = false; boolean needDot = false;
while (!stack.isEmpty()) { for (SimpleName name : stack) {
if (needDot) { if (needDot) {
if (breaks.isYes()) {
builder.breakToFill();
}
token("."); token(".");
} }
visit(stack.removeLast()); visit(name);
needDot = true; needDot = true;
} }
if (breaks.isYes()) {
builder.close();
}
} }


// General helper functions. // General helper functions.
Expand Down

0 comments on commit 0a0ff48

Please sign in to comment.