Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nested method chains also adhere to column limit #123

Merged
merged 6 commits into from
Jan 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions changelog/@unreleased/pr-123.v2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type: fix
fix:
description: Nested method chains now also adhere to column limit.
links:
- https://github.com/palantir/palantir-java-format/pull/123
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,11 @@ public void breakOp(FillMode fillMode, String flat, Indent plusIndent, Optional<
add(Break.make(fillMode, flat, plusIndent, optionalTag));
}

/** Emit a self-built {@link Break}. */
public void breakOp(Break breakOp) {
add(breakOp);
}

private int lastPartialFormatBoundary = -1;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ public abstract class Break extends Doc implements Op {

public abstract Optional<BreakTag> optTag();

/** If this break is restricted by its parent {@link Level}'s {@link Level#getColumnLimitBeforeLastBreak()}. */
public abstract boolean hasColumnLimit();

/**
* Make a {@code Break}.
*
Expand All @@ -47,7 +50,12 @@ public abstract class Break extends Doc implements Op {
* @return the new {@code Break}
*/
public static Break make(FillMode fillMode, String flat, Indent plusIndent) {
return builder().fillMode(fillMode).flat(flat).plusIndent(plusIndent).build();
return builder()
.fillMode(fillMode)
.flat(flat)
.plusIndent(plusIndent)
.hasColumnLimit(false)
.build();
}

/**
Expand All @@ -65,6 +73,7 @@ public static Break make(FillMode fillMode, String flat, Indent plusIndent, Opti
.flat(flat)
.plusIndent(plusIndent)
.optTag(optTag)
.hasColumnLimit(false)
.build();
}

Expand All @@ -78,6 +87,7 @@ public static Break makeForced() {
.fillMode(FillMode.FORCED)
.flat("")
.plusIndent(Indent.Const.ZERO)
.hasColumnLimit(false)
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,25 +123,33 @@ public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State st
* maxWidth}
*/
private Optional<Integer> tryToFitOnOneLine(int maxWidth, State state, Iterable<Doc> docs) {
if (getColumnLimitBeforeLastBreak().isPresent()) {
float width = 0.0f;
float widthBeforeLastBreak = 0.0f;
for (Doc doc : docs) {
if (doc instanceof Break) {
widthBeforeLastBreak = width;
int column = state.column();
int columnBeforeLastBreak = 0; // Not activated by default
for (Doc doc : docs) {
if (doc instanceof Break && ((Break) doc).hasColumnLimit()) {
columnBeforeLastBreak = column;
} else if (doc instanceof Level) {
// Levels might have nested levels that have a 'columnLimitBeforeLastBreak' set, so recurse.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This recursion seems like it could end up being pretty expensive

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the integration tests take about 15s with this block commented out, and 20s with it as it is, so I think this is fine. (it's pretty much just the extremely degenerate test case that suffers)

State newState = state.withColumn(column);
Level innerLevel = (Level) doc;
Optional<Integer> newWidth = innerLevel.tryToFitOnOneLine(maxWidth, newState, innerLevel.getDocs());
if (!newWidth.isPresent()) {
return Optional.empty();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we return early here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It indicates that the innerLevel was not inlineable.
Currently, the only reason for that to happen is that it had a non-prefix method call (i.e. not something like foo.bar().stream()) that occurs after the column limit.
Once this happens it's no point trying to one line the parent level anymore.

}
width += doc.getWidth();
}
// Make an additional check that widthBeforeLastBreak fits in the column limit
if (state.column() + widthBeforeLastBreak > getColumnLimitBeforeLastBreak().getAsInt()) {
return Optional.empty();
column = newWidth.get();
continue;
}
column += doc.getWidth();
}
// Make an additional check that widthBeforeLastBreak fits in the column limit
if (getColumnLimitBeforeLastBreak().isPresent()
&& columnBeforeLastBreak > getColumnLimitBeforeLastBreak().getAsInt()) {
return Optional.empty();
}

// Check that the entirety of this level fits on the current line.
float thisWidth = getWidth(docs);
if (state.column() + thisWidth <= maxWidth) {
return Optional.of(state.column() + (int) thisWidth);
if (column <= maxWidth) {
return Optional.of(column);
}
return Optional.empty();
}
Expand Down Expand Up @@ -538,6 +546,10 @@ public OpenOp getOpenOp() {
return openOp;
}

/**
* An optional, more restrictive column limit for inner breaks that are marked as {@link Break#hasColumnLimit()}. If
* the level is to be considered one-lineable, the last such break must not start at a column higher than this.
*/
public OptionalInt getColumnLimitBeforeLastBreak() {
return openOp.columnLimitBeforeLastBreak();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2727,7 +2727,12 @@ private void visitRegularDot(List<ExpressionTree> items, boolean needDot) {
for (ExpressionTree e : items) {
if (needDot) {
if (length > minLength) {
builder.breakOp(FillMode.UNIFIED, "", ZERO);
builder.breakOp(Break.builder()
.fillMode(FillMode.UNIFIED)
.flat("")
.plusIndent(ZERO)
.hasColumnLimit(shouldHaveColumnLimit(e))
.build());
}
token(".");
length++;
Expand Down Expand Up @@ -2821,7 +2826,13 @@ private void visitDotWithPrefix(
fillMode = FillMode.UNIFIED;
}

builder.breakOp(fillMode, "", ZERO, Optional.of(nameTag));
builder.breakOp(Break.builder()
.fillMode(fillMode)
.flat("")
.plusIndent(ZERO)
.optTag(Optional.of(nameTag))
.hasColumnLimit(shouldHaveColumnLimit(e))
.build());
token(".");
}
BreakTag tyargTag = new BreakTag();
Expand All @@ -2841,6 +2852,22 @@ private void visitDotWithPrefix(
builder.close();
}

/**
* We only want to limit the max column of expressions that are method invocations. All of these expressions below
* would have a column limit.
*
* <ul>
* <li>{@code foo().bar()}
* <li>{@code foo().bar()[0]}
* <li>{@code foo().bar()[0][0]}
* </ul>
*
* Whereas an expression like a name {@code com.palantir.foo.bar.Baz} would not.
*/
private boolean shouldHaveColumnLimit(ExpressionTree expr) {
return getArrayBase(expr).getKind() == METHOD_INVOCATION;
}

/** Returns the simple names of expressions in a "." chain. */
private List<String> simpleNames(Deque<ExpressionTree> stack) {
ImmutableList.Builder<String> simpleNames = ImmutableList.builder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ class B20128760 {
.xxxxx();

xxxxxxXxxx(xxx.xxxXxXxxxxxxx(new XxxxxxxxxXxxxxxxx.Xxxxxxx()
.xxxxXxxxXxx(Xxxxx.xxxXxxxxXxxx(new XxxxXx(
xxxxxxxxx1.xxxXxxxXx().xxxXxxxx() + xxxxxxxxx2.xxxXxxxXx().xxxXxxxx())))
.xxxxXxxxXxx(Xxxxx.xxxXxxxxXxxx(
new XxxxXx(xxxxxxxxx1.xxxXxxxXx().xxxXxxxx()
+ xxxxxxxxx2.xxxXxxxXx().xxxXxxxx())))
.xxxxx())
.xxxXxxxxxx())
.xxxxxxxxXxxxxxx();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,37 @@ class B20701054 {
ImmutableList<String> x = ImmutableList.INSTANCE.add(1).build();
ImmutableList<String> x = ImmutableList.INSTANCE.add(1).add(2).build();
ImmutableList<String> x = ImmutableList.INSTANCE.add(1).add(2).add(3).build();
ImmutableList<String> x = ImmutableList.INSTANCE.add(1).add(2).add(3).add(4).build();
ImmutableList<String> x = ImmutableList.INSTANCE
.add(1)
.add(2)
.add(3)
.add(4)
.build();

ImmutableList<String> x = ImmutableList.builder().add(1).build();
ImmutableList<String> x = ImmutableList.builder().add(1).add(2).build();
ImmutableList<String> x = ImmutableList.builder().add(1).add(2).add(3).build();
ImmutableList<String> x = ImmutableList.builder().add(1).add(2).add(3).add(4).build();
ImmutableList<String> x = ImmutableList.builder().add(1).add(2).add(3).add(4).add(5).build();
ImmutableList<String> x = ImmutableList.builder().add(1).add(2).add(3).add(4).add(5).add(6).build();
ImmutableList<String> x = ImmutableList.builder()
.add(1)
.add(2)
.add(3)
.add(4)
.build();
ImmutableList<String> x = ImmutableList.builder()
.add(1)
.add(2)
.add(3)
.add(4)
.add(5)
.build();
ImmutableList<String> x = ImmutableList.builder()
.add(1)
.add(2)
.add(3)
.add(4)
.add(5)
.add(6)
.build();

ImmutableList<String> x = new ImmutableList.Builder<>()
.add(xxxxx)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ class B21465217 {
BufferedOutputStream bout = new BufferedOutputStream(out2);
OutputStreamWriter writer = new OutputStreamWriter(bout, UTF_8___________________________)) {}

try (Writer sourceWriter = env.getFiler().createSourceFile(qualifiedNamezzzzzzzz).openWriter()) {
try (Writer sourceWriter = env.getFiler()
.createSourceFile(qualifiedNamezzzzzzzz)
.openWriter()) {
sourceWriter.append(fileContents);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
class B22610221 {
{
for (Map.Entry<Key<Object>, Object> entry : FOO.bar().bazs().<Object>thing(someThing).entrySet()) {
for (Map.Entry<Key<Object>, Object> entry : FOO.bar()
.bazs()
.<Object>thing(someThing)
.entrySet()) {
output.put(entry.getKey(), entry.getValue());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,9 @@ class Xxx {
.xxxXxxx(XxxxXxxx.XXX)
.xxxxx())
.xxxxx())
.xxxXxxxxxxxxxXxxXxxxXxxxxxxxx(
XxxxxxxxxxXxxXxxxXxxxxxxxx.xxxXxxxxxx().xxxXxxxxxxxxxXxxxXx(42).xxxxx())
.xxxXxxxxxxxxxXxxXxxxXxxxxxxxx(XxxxxxxxxxXxxXxxxXxxxxxxxx.xxxXxxxxxx()
.xxxXxxxxxxxxxXxxxXx(42)
.xxxxx())
.xxxxx())
.xxxxx())
.xxxXxxxxxx(XxxxxxxXxxx.xxxXxxxxxx()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
class B32114928 {
{
Class<T> tClass = (Class<T>) verifyNotNull((ParameterizedType) getClass().getGenericSuperclass())
.getActualTypeArguments()[0];
Class<T> tClass = (Class<T>)
verifyNotNull((ParameterizedType) getClass().getGenericSuperclass())
.getActualTypeArguments()[0];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unclear if we want to be so strict with all methods, or if we should exempt non-side-effect-sounding methods like those that start with get[A-Z0-9]. @iamdanfox thoughts?

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ class PalantirDeeplyNestedCalls {
.foooos(ImmutableList.of(testFieldTwoV1, testFieldThreeV1))
.baaars(ImmutableList.of(MagicConfigV1.builder()
.baaars(ImmutableList.of(MagicConfigV1.builder()
.baaars(ImmutableList.of(
MagicConfigV1.builder().foooos(ImmutableList.of(testFieldFourV1)).build()))
.baaars(ImmutableList.of(MagicConfigV1.builder()
.foooos(ImmutableList.of(testFieldFourV1))
.build()))
.build()))
.build()))
.build()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public class Foo {
}

static String baz(Optional<String> optional) {
return optional.orElseGet(
(String) () -> bar(Optional.of("some thing that is very very very looooooong")).get());
return optional.orElseGet((String) () ->
bar(Optional.of("some thing that is very very very looooooong")).get());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changed beause it doesn't allow the dot in the bar(...).get() expression to exceed 80 chars anymore. It now ends up at exactly 80 chars, and to prove that, if I add another character to the string, then the .get() gets pushed onto the next line, as in:

return optional.orElseGet((String) () -> bar(Optional.of("some thing that is very very very loooooooong"))
        .get());

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class PalantirLongMethodChainArg {
void foo() {
return cache.get(ImmutableRequest.builder().group(group).name(artifact).addRepositories("release-jar").build());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class PalantirLongMethodChainArg {
void foo() {
return cache.get(ImmutableRequest.builder()
.group(group)
.name(artifact)
.addRepositories("release-jar")
.build());
}
}