Skip to content

Commit 6282d12

Browse files
cushonabashev
authored andcommitted
Reorder non-sealed as one modifier
"non-sealed private interface B" came out as text that no longer parsed, so the whole file failed with "<identifier> expected". javac lexes non-sealed as three tokens, "non", "-" and "sealed", and the modifier sorting looked at one token at a time: it never saw "non-sealed", took the trailing "sealed" as the modifier to move in front of "private", and left "non-" behind. This ports google/google-java-format#1107 by Liam Miller-Cushon: the sorter now reads the three tokens as one modifier and writes it back whole. The golden Sealed and the sealedClass test come from upstream, with the expected output in this project's style; a second unit test pins the shape that failed. The 15,747 files of the JDK 21 sources format exactly as before; none of them writes non-sealed out of order.
1 parent 746fb60 commit 6282d12

5 files changed

Lines changed: 153 additions & 26 deletions

File tree

‎open-java-format/src/main/java/com/palantir/javaformat/java/ModifierOrderer.java‎

Lines changed: 124 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
package com.palantir.javaformat.java;
1818

19+
import static com.google.common.base.Preconditions.checkState;
20+
import static com.google.common.collect.Iterables.getLast;
21+
1922
import com.google.common.collect.ImmutableList;
2023
import com.google.common.collect.Ordering;
2124
import com.google.common.collect.Range;
@@ -26,10 +29,10 @@
2629
import com.sun.tools.javac.parser.Tokens.TokenKind;
2730
import java.util.ArrayList;
2831
import java.util.Collection;
29-
import java.util.Collections;
3032
import java.util.Iterator;
3133
import java.util.List;
3234
import java.util.Map;
35+
import javax.annotation.Nullable;
3336
import javax.lang.model.element.Modifier;
3437

3538
/** Fixes sequences of modifiers to be in JLS order. */
@@ -40,6 +43,74 @@ static JavaInput reorderModifiers(String text) throws FormatterException {
4043
return reorderModifiers(new JavaInput(text), ImmutableList.of(Range.closedOpen(0, text.length())));
4144
}
4245

46+
/**
47+
* The tokens that make up one modifier. Usually a single token (e.g. for {@code public}), but a modifier that
48+
* contains a {@code -} (e.g. {@code non-sealed}) is lexed as three tokens.
49+
*/
50+
static final class ModifierTokens implements Comparable<ModifierTokens> {
51+
private final ImmutableList<Token> tokens;
52+
53+
@Nullable
54+
private final Modifier modifier;
55+
56+
static ModifierTokens create(ImmutableList<Token> tokens) {
57+
return new ModifierTokens(tokens, asModifier(tokens));
58+
}
59+
60+
static ModifierTokens empty() {
61+
return new ModifierTokens(ImmutableList.of(), null);
62+
}
63+
64+
private ModifierTokens(ImmutableList<Token> tokens, @Nullable Modifier modifier) {
65+
this.tokens = tokens;
66+
this.modifier = modifier;
67+
}
68+
69+
boolean isEmpty() {
70+
return tokens.isEmpty() || modifier == null;
71+
}
72+
73+
@SuppressWarnings("for-rollout:NullAway")
74+
Modifier modifier() {
75+
return modifier;
76+
}
77+
78+
ImmutableList<Token> tokens() {
79+
return tokens;
80+
}
81+
82+
private Token first() {
83+
return tokens.get(0);
84+
}
85+
86+
private Token last() {
87+
return getLast(tokens);
88+
}
89+
90+
int startPosition() {
91+
return first().getTok().getPosition();
92+
}
93+
94+
int endPosition() {
95+
return last().getTok().getPosition() + last().getTok().length();
96+
}
97+
98+
ImmutableList<? extends Tok> getToksBefore() {
99+
return first().getToksBefore();
100+
}
101+
102+
ImmutableList<? extends Tok> getToksAfter() {
103+
return last().getToksAfter();
104+
}
105+
106+
@Override
107+
@SuppressWarnings("for-rollout:NullAway")
108+
public int compareTo(ModifierTokens o) {
109+
checkState(!isEmpty()); // empty ModifierTokens are filtered out prior to sorting
110+
return modifier.compareTo(o.modifier);
111+
}
112+
}
113+
43114
/** Reorders all modifiers in the given text and within the given character ranges to be in JLS order. */
44115
static JavaInput reorderModifiers(JavaInput javaInput, Collection<Range<Integer>> characterRanges)
45116
throws FormatterException {
@@ -52,43 +123,37 @@ static JavaInput reorderModifiers(JavaInput javaInput, Collection<Range<Integer>
52123
Iterator<? extends Token> it = javaInput.getTokens().iterator();
53124
TreeRangeMap<Integer, String> replacements = TreeRangeMap.create();
54125
while (it.hasNext()) {
55-
Token token = it.next();
56-
if (!tokenRanges.contains(token.getTok().getIndex())) {
57-
continue;
58-
}
59-
Modifier mod = asModifier(token);
60-
if (mod == null) {
126+
ModifierTokens tokens = getModifierTokens(it);
127+
if (tokens.isEmpty()
128+
|| !tokens.tokens().stream()
129+
.allMatch(token -> tokenRanges.contains(token.getTok().getIndex()))) {
61130
continue;
62131
}
63132

64-
List<Token> modifierTokens = new ArrayList<>();
65-
List<Modifier> mods = new ArrayList<>();
133+
List<ModifierTokens> modifierTokens = new ArrayList<>();
66134

67-
int begin = token.getTok().getPosition();
68-
mods.add(mod);
69-
modifierTokens.add(token);
135+
int begin = tokens.startPosition();
136+
modifierTokens.add(tokens);
70137

71138
int end = -1;
72139
while (it.hasNext()) {
73-
token = it.next();
74-
mod = asModifier(token);
75-
if (mod == null) {
140+
tokens = getModifierTokens(it);
141+
if (tokens.isEmpty()) {
76142
break;
77143
}
78-
mods.add(mod);
79-
modifierTokens.add(token);
80-
end = token.getTok().getPosition() + token.getTok().length();
144+
modifierTokens.add(tokens);
145+
end = tokens.endPosition();
81146
}
82147

83-
if (!Ordering.natural().isOrdered(mods)) {
84-
Collections.sort(mods);
148+
if (!Ordering.natural().isOrdered(modifierTokens)) {
149+
List<ModifierTokens> sorted = Ordering.natural().sortedCopy(modifierTokens);
85150
StringBuilder replacement = new StringBuilder();
86-
for (int i = 0; i < mods.size(); i++) {
151+
for (int i = 0; i < sorted.size(); i++) {
87152
if (i > 0) {
88153
addTrivia(replacement, modifierTokens.get(i).getToksBefore());
89154
}
90-
replacement.append(mods.get(i).toString());
91-
if (i < (modifierTokens.size() - 1)) {
155+
replacement.append(sorted.get(i).modifier());
156+
if (i < (sorted.size() - 1)) {
92157
addTrivia(replacement, modifierTokens.get(i).getToksAfter());
93158
}
94159
}
@@ -104,9 +169,45 @@ private static void addTrivia(StringBuilder replacement, ImmutableList<? extends
104169
}
105170
}
106171

172+
/**
173+
* Consumes the tokens of one modifier from the iterator: the next token, plus the two after it when it is the
174+
* {@code non} of a hyphenated modifier such as {@code non-sealed}. The result is empty if they are not a modifier.
175+
*/
176+
private static ModifierTokens getModifierTokens(Iterator<? extends Token> it) {
177+
Token token = it.next();
178+
ImmutableList.Builder<Token> result = ImmutableList.builder();
179+
result.add(token);
180+
if (!token.getTok().getText().equals("non")) {
181+
return ModifierTokens.create(result.build());
182+
}
183+
if (!it.hasNext()) {
184+
return ModifierTokens.empty();
185+
}
186+
Token dash = it.next();
187+
result.add(dash);
188+
if (!dash.getTok().getText().equals("-") || !it.hasNext()) {
189+
return ModifierTokens.empty();
190+
}
191+
result.add(it.next());
192+
return ModifierTokens.create(result.build());
193+
}
194+
195+
@Nullable
196+
private static Modifier asModifier(ImmutableList<Token> tokens) {
197+
if (tokens.size() == 1) {
198+
return asModifier(tokens.get(0));
199+
}
200+
Modifier modifier = asModifier(getLast(tokens));
201+
if (modifier == null) {
202+
return null;
203+
}
204+
return Modifier.valueOf("NON_" + modifier.name());
205+
}
206+
107207
/**
108208
* Returns the given token as a {@link javax.lang.model.element.Modifier}, or {@code null} if it is not a modifier.
109209
*/
210+
@Nullable
110211
@SuppressWarnings("for-rollout:NullAway")
111212
private static Modifier asModifier(Token token) {
112213
TokenKind kind = ((JavaInput.Tok) token.getTok()).kind();
@@ -140,8 +241,6 @@ private static Modifier asModifier(Token token) {
140241
}
141242
}
142243
switch (token.getTok().getText()) {
143-
case "non-sealed":
144-
return Modifier.valueOf("NON_SEALED");
145244
case "sealed":
146245
return Modifier.valueOf("SEALED");
147246
default:

‎open-java-format/src/test/java/com/palantir/javaformat/java/FileBasedTests.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public final class FileBasedTests {
4949
ImmutableMultimap.<Integer, String>builder()
5050
.putAll(14, "Records", "RSL", "Var", "ExpressionSwitch", "I574", "I594")
5151
.putAll(15, "I603")
52-
.putAll(16, "I588")
52+
.putAll(16, "I588", "Sealed")
5353
.putAll(17, "I683", "I684", "I696")
5454
.putAll(
5555
21,

‎open-java-format/src/test/java/com/palantir/javaformat/java/ModifierOrdererTest.java‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,17 @@ public void whitespace() throws FormatterException {
9999
.getText();
100100
assertThat(output).contains("public\n static int a;");
101101
}
102+
103+
@Test
104+
public void sealedClass() throws FormatterException {
105+
assertThat(ModifierOrderer.reorderModifiers("non-sealed sealed public").getText())
106+
.isEqualTo("public sealed non-sealed");
107+
}
108+
109+
@Test
110+
public void nonSealedBeforeAccessModifier() throws FormatterException {
111+
assertThat(ModifierOrderer.reorderModifiers("non-sealed private interface B extends I {}")
112+
.getText())
113+
.isEqualTo("private non-sealed interface B extends I {}");
114+
}
102115
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
class T {
2+
sealed interface I extends A permits C, B {}
3+
final class C implements I {}
4+
sealed private interface A permits I {}
5+
non-sealed private interface B extends I {}
6+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class T {
2+
sealed interface I extends A permits C, B {}
3+
4+
final class C implements I {}
5+
6+
private sealed interface A permits I {}
7+
8+
private non-sealed interface B extends I {}
9+
}

0 commit comments

Comments
 (0)