Skip to content

Commit ce4f06d

Browse files
authored
Format text blocks + Intellij reflows strings (#1419)
Format text blocks + Intellij reflows strings
1 parent fa49a7c commit ce4f06d

File tree

20 files changed

+832
-10649
lines changed

20 files changed

+832
-10649
lines changed

debugger/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
/node_modules
55
/.pnp
66
.pnp.js
7+
yarn.lock
8+
package-lock.json
79

810
# testing
911
/coverage

debugger/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
React App that helps Debugging a javaInput's AST.
2+
3+
1. Run only one test from [FormatterIntegrationTest](https://github.com/palantir/palantir-java-format/blob/0ad916e4f27b5aab3870d633a16c50192b6e54cc/palantir-java-format/src/test/java/com/palantir/javaformat/java/FormatterIntegrationTest.java#L66) with ["debugOutput" = true](https://github.com/palantir/palantir-java-format/blob/0ad916e4f27b5aab3870d633a16c50192b6e54cc/palantir-java-format/src/test/java/com/palantir/javaformat/java/FormatterIntegrationTest.java#L66)
4+
2. Run `cd debugger && NODE_OPTIONS=--openssl-legacy-provider yarn start` that will open the app in the browser (<code>localhost:3000</code>)
5+
6+
7+
-----
8+
19
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
210

311
## Available Scripts

debugger/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@
33
"version": "0.1.0",
44
"private": true,
55
"dependencies": {
6+
"@blueprintjs/core": "^3.20.0",
67
"@types/jest": "24.0.23",
78
"@types/node": "12.12.7",
89
"@types/react": "16.9.11",
910
"@types/react-dom": "16.9.4",
1011
"react": "^16.11.0",
1112
"react-dom": "^16.11.0",
12-
"react-scripts": "3.2.0",
13-
"typescript": "3.7.2",
14-
"@blueprintjs/core": "^3.20.0",
15-
"react-treebeard": "^3.2.4"
13+
"react-scripts": "^3.2.0",
14+
"react-treebeard": "^3.2.4",
15+
"typescript": "3.7.2"
1616
},
1717
"scripts": {
1818
"start": "react-scripts start",

debugger/yarn.lock

Lines changed: 0 additions & 10546 deletions
This file was deleted.

palantir-java-format-jdk-bootstrap/src/main/java/com/palantir/javaformat/bootstrap/NativeImageFormatterService.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public NativeImageFormatterService(Path nativeImagePath) {
4545

4646
@Override
4747
public ImmutableList<Replacement> getFormatReplacements(String input, Collection<Range<Integer>> ranges) {
48+
Optional<String> output = Optional.empty();
4849
try {
4950
FormatterNativeImageArgs command = FormatterNativeImageArgs.builder()
5051
.nativeImagePath(nativeImagePath)
@@ -53,14 +54,15 @@ public ImmutableList<Replacement> getFormatReplacements(String input, Collection
5354
ranges.stream().map(RangeUtils::toStringRange).collect(Collectors.toList()))
5455
.build();
5556

56-
Optional<String> output = FormatterCommandRunner.runWithStdin(
57+
output = FormatterCommandRunner.runWithStdin(
5758
command.toArgs(), input, Optional.ofNullable(nativeImagePath.getParent()));
5859
if (output.isEmpty() || output.get().isEmpty()) {
5960
return ImmutableList.of();
6061
}
6162
return MAPPER.readValue(output.get(), new TypeReference<>() {});
6263
} catch (IOException e) {
63-
throw new UncheckedIOException("Error running the native image command", e);
64+
throw new UncheckedIOException(
65+
String.format("Error running the native image command; received output: %s", output), e);
6466
}
6567
}
6668

palantir-java-format-native/src/main/resources/META-INF/native-image/reachability-metadata.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@
5050
"type": "com.google.common.collect.RegularImmutableList",
5151
"allDeclaredFields": true
5252
},
53+
{
54+
"type": "java.util.ImmutableCollections$AbstractImmutableList",
55+
"allDeclaredFields": true
56+
},
57+
{
58+
"type": "java.util.ImmutableCollections$AbstractImmutableCollection",
59+
"allDeclaredFields": true
60+
},
61+
{
62+
"type": "java.util.ImmutableCollections$List12",
63+
"allDeclaredFields": true
64+
},
5365
{
5466
"type": "com.google.common.collect.SingletonImmutableList",
5567
"allDeclaredFields": true

palantir-java-format/src/main/java/com/palantir/javaformat/doc/Token.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.palantir.javaformat.Input;
2525
import com.palantir.javaformat.Op;
2626
import com.palantir.javaformat.Output;
27+
import com.palantir.javaformat.java.StringWrapper;
2728
import java.util.Optional;
2829

2930
/** A leaf {@link Doc} for a token. */
@@ -106,6 +107,11 @@ public void add(DocBuilder builder) {
106107

107108
@Override
108109
protected float computeWidth() {
110+
if (token.getTok().getOriginalText().startsWith(StringWrapper.TEXT_BLOCK_DELIMITER)) {
111+
// Palantir-specific: The size of a text block should not impact line length. This ensures that methods
112+
// applied to text blocks remain on the same line and are not split into multiple lines.
113+
return StringWrapper.TEXT_BLOCK_DELIMITER.length();
114+
}
109115
return token.getTok().length();
110116
}
111117

@@ -123,6 +129,11 @@ protected Range<Integer> computeRange() {
123129
public State computeBreaks(
124130
CommentsHelper commentsHelper, int maxWidth, State state, Obs.ExplorationNode observationNode) {
125131
String text = token.getTok().getOriginalText();
132+
if (text.startsWith(StringWrapper.TEXT_BLOCK_DELIMITER)) {
133+
// Palantir-specific: The size of a text block should not impact line length. This ensures that methods
134+
// applied to text blocks remain on the same line and are not split into multiple lines.
135+
return state.withColumn(state.column() + StringWrapper.TEXT_BLOCK_DELIMITER.length());
136+
}
126137
return state.withColumn(state.column() + text.length());
127138
}
128139

palantir-java-format/src/main/java/com/palantir/javaformat/java/FormatFileCallable.java

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,18 @@
1818
import com.fasterxml.jackson.databind.ObjectMapper;
1919
import com.fasterxml.jackson.databind.json.JsonMapper;
2020
import com.fasterxml.jackson.datatype.guava.GuavaModule;
21-
import com.google.common.collect.ImmutableList;
2221
import com.google.common.collect.Range;
2322
import com.google.common.collect.RangeSet;
2423
import com.google.common.collect.TreeRangeSet;
2524
import com.palantir.javaformat.Utils;
2625
import java.io.UncheckedIOException;
26+
import java.util.List;
27+
import java.util.Set;
2728
import java.util.concurrent.Callable;
2829

2930
/** Encapsulates information about a file to be formatted, including which parts of the file to format. */
3031
class FormatFileCallable implements Callable<String> {
31-
private final ObjectMapper MAPPER =
32+
private static final ObjectMapper MAPPER =
3233
JsonMapper.builder().addModule(new GuavaModule()).build();
3334

3435
private final String input;
@@ -49,14 +50,29 @@ public String call() throws FormatterException {
4950

5051
Formatter formatter = Formatter.createFormatter(options);
5152
if (parameters.outputReplacements()) {
52-
return formatReplacements(formatter);
53+
RangeSet<Integer> characterRanges = characterRanges(input);
54+
Set<Range<Integer>> rangesToChange = characterRanges.asRanges();
55+
List<Replacement> replacements = formatter.getFormatReplacements(input, rangesToChange);
56+
if (parameters.reflowLongStrings()) {
57+
String formattedText = Utils.applyReplacements(input, replacements);
58+
// avoid trying to wrap the input unless the lines needing wrapping are part of the "characterRanges"
59+
if (!StringWrapper.linesNeedWrapping(options.maxLineLength(), formattedText, characterRanges)) {
60+
return writeFormatReplacements(replacements);
61+
}
62+
String wrappedFormattedText = StringWrapper.wrap(options.maxLineLength(), formattedText, formatter);
63+
// if no wrapping change happened, return the initial replacements
64+
if (wrappedFormattedText.equals(formattedText)) {
65+
return writeFormatReplacements(replacements);
66+
}
67+
return writeFormatReplacements(List.of(Replacement.create(0, input.length(), wrappedFormattedText)));
68+
} else {
69+
return writeFormatReplacements(replacements);
70+
}
5371
}
5472
return formatFile(formatter);
5573
}
5674

57-
private String formatReplacements(Formatter formatter) throws FormatterException {
58-
ImmutableList<Replacement> replacements =
59-
formatter.getFormatReplacements(input, characterRanges(input).asRanges());
75+
String writeFormatReplacements(List<Replacement> replacements) throws FormatterException {
6076
try {
6177
return MAPPER.writeValueAsString(replacements);
6278
} catch (JsonProcessingException e) {

palantir-java-format/src/main/java/com/palantir/javaformat/java/JavaInputAstVisitor.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2725,17 +2725,32 @@ void visitDot(ExpressionTree node0) {
27252725
scan(getArrayBase(node), null);
27262726
token(".");
27272727
} else {
2728+
boolean isTextBlock = false;
2729+
// Special case for text blocks: if the node is a string literal that ends with """,
2730+
// don't add a break after it
2731+
if (node instanceof LiteralTree && node.getKind() == Tree.Kind.STRING_LITERAL) {
2732+
String sourceForNode = getSourceForNode(node, getCurrentPath());
2733+
isTextBlock = sourceForNode.trim().endsWith(StringWrapper.TEXT_BLOCK_DELIMITER);
2734+
}
2735+
27282736
builder.open(OpenOp.builder()
27292737
.debugName("visitDot")
27302738
.plusIndent(plusFour)
2731-
.breakBehaviour(BreakBehaviours.preferBreakingLastInnerLevel(true))
2739+
.breakBehaviour(
2740+
isTextBlock
2741+
? BreakBehaviours.breakOnlyIfInnerLevelsThenFitOnOneLine(false)
2742+
: BreakBehaviours.preferBreakingLastInnerLevel(true))
27322743
.breakabilityIfLastLevel(
2733-
LastLevelBreakability.ACCEPT_INLINE_CHAIN_IF_SIMPLE_OTHERWISE_CHECK_INNER)
2744+
isTextBlock
2745+
? LastLevelBreakability.ACCEPT_INLINE_CHAIN
2746+
: LastLevelBreakability.ACCEPT_INLINE_CHAIN_IF_SIMPLE_OTHERWISE_CHECK_INNER)
27342747
.columnLimitBeforeLastBreak(METHOD_CHAIN_COLUMN_LIMIT)
27352748
.isSimple(false)
27362749
.build());
27372750
scan(getArrayBase(node), null);
2738-
builder.breakOp();
2751+
if (!isTextBlock) {
2752+
builder.breakOp();
2753+
}
27392754
needDot = true;
27402755
}
27412756
formatArrayIndices(getArrayIndices(node));

0 commit comments

Comments
 (0)