COMPARATOR =
+ Comparator.comparingInt(AnnotationOrModifier::position);
+
+ @Override
+ public int compareTo(AnnotationOrModifier o) {
+ return COMPARATOR.compare(this, o);
+ }
+ }
+
+ /**
+ * The modifiers annotations for a declaration, grouped in to a prefix that contains all of the
+ * declaration annotations and modifiers, and a suffix of type annotations.
+ *
+ * For examples like {@code @Deprecated public @Nullable Foo foo();}, this allows us to format
+ * {@code @Deprecated public} as declaration modifiers, and {@code @Nullable} as a type annotation
+ * on the return type.
+ */
+ @AutoValue
+ abstract static class DeclarationModifiersAndTypeAnnotations {
+ abstract ImmutableList declarationModifiers();
+
+ abstract ImmutableList typeAnnotations();
+
+ static DeclarationModifiersAndTypeAnnotations create(
+ ImmutableList declarationModifiers,
+ ImmutableList typeAnnotations) {
+ return new AutoValue_JavaInputAstVisitor_DeclarationModifiersAndTypeAnnotations(
+ declarationModifiers, typeAnnotations);
+ }
+
+ static DeclarationModifiersAndTypeAnnotations empty() {
+ return create(ImmutableList.of(), ImmutableList.of());
+ }
+
+ boolean hasDeclarationAnnotation() {
+ return declarationModifiers().stream().anyMatch(AnnotationOrModifier::isAnnotation);
+ }
+ }
+
+ /**
+ * Examines the token stream to convert the modifiers for a declaration into a {@link
+ * DeclarationModifiersAndTypeAnnotations}.
+ */
+ DeclarationModifiersAndTypeAnnotations splitModifiers(
+ ModifiersTree modifiersTree, List extends AnnotationTree> annotations) {
+ if (annotations.isEmpty() && !isModifier(builder.peekToken().get())) {
+ return DeclarationModifiersAndTypeAnnotations.empty();
+ }
+ RangeSet annotationRanges = TreeRangeSet.create();
+ for (AnnotationTree annotationTree : annotations) {
+ annotationRanges.add(
+ Range.closedOpen(
+ getStartPosition(annotationTree), getEndPosition(annotationTree, getCurrentPath())));
+ }
+ ImmutableList toks =
+ builder.peekTokens(
+ getStartPosition(modifiersTree),
+ (Input.Tok tok) ->
+ // ModifiersTree end position information isn't reliable, so scan tokens as long as
+ // we're seeing annotations or modifiers
+ annotationRanges.contains(tok.getPosition()) || isModifier(tok.getText()));
+ ImmutableList modifiers =
+ ImmutableList.copyOf(
+ Streams.concat(
+ toks.stream()
+ // reject tokens from inside AnnotationTrees, we only want modifiers
+ .filter(t -> !annotationRanges.contains(t.getPosition()))
+ .map(AnnotationOrModifier::ofModifier),
+ annotations.stream().map(AnnotationOrModifier::ofAnnotation))
+ .sorted()
+ .collect(toList()));
+ // Take a suffix of annotations that are well-known type annotations, and which appear after any
+ // declaration annotations or modifiers
+ ImmutableList.Builder typeAnnotations = ImmutableList.builder();
+ int idx = modifiers.size() - 1;
+ while (idx >= 0) {
+ AnnotationOrModifier modifier = modifiers.get(idx);
+ if (!modifier.isAnnotation() || !isTypeAnnotation(modifier.annotation())) {
+ break;
+ }
+ typeAnnotations.add(modifier.annotation());
+ idx--;
+ }
+ return DeclarationModifiersAndTypeAnnotations.create(
+ modifiers.subList(0, idx + 1), typeAnnotations.build().reverse());
+ }
+
+ private void formatAnnotationOrModifier(AnnotationOrModifier modifier) {
+ switch (modifier.getKind()) {
+ case MODIFIER:
+ token(modifier.modifier().getText());
+ break;
+ case ANNOTATION:
+ scan(modifier.annotation(), null);
+ break;
+ }
+ }
+
+ boolean isTypeAnnotation(AnnotationTree annotationTree) {
+ Tree annotationType = annotationTree.getAnnotationType();
+ if (!(annotationType instanceof IdentifierTree)) {
+ return false;
+ }
+ return typeAnnotationSimpleNames.contains(((IdentifierTree) annotationType).getName());
}
boolean nextIsModifier() {
- switch (builder.peekToken().get()) {
+ return isModifier(builder.peekToken().get());
+ }
+
+ private static boolean isModifier(String token) {
+ switch (token) {
case "public":
case "protected":
case "private":
@@ -2877,7 +3099,7 @@ private void visitDotWithPrefix(
}
/** Returns the simple names of expressions in a "." chain. */
- private List simpleNames(Deque stack) {
+ private static ImmutableList simpleNames(Deque stack) {
ImmutableList.Builder simpleNames = ImmutableList.builder();
OUTER:
for (ExpressionTree expression : stack) {
@@ -2934,14 +3156,14 @@ private void dotExpressionUpToArgs(ExpressionTree expression, Optional
* Returns the base expression of an erray access, e.g. given {@code foo[0][0]} returns {@code
* foo}.
*/
- private ExpressionTree getArrayBase(ExpressionTree node) {
+ private static ExpressionTree getArrayBase(ExpressionTree node) {
while (node instanceof ArrayAccessTree) {
node = ((ArrayAccessTree) node).getExpression();
}
return node;
}
- private ExpressionTree getMethodReceiver(MethodInvocationTree methodInvocation) {
+ private static ExpressionTree getMethodReceiver(MethodInvocationTree methodInvocation) {
ExpressionTree select = methodInvocation.getMethodSelect();
return select instanceof MemberSelectTree ? ((MemberSelectTree) select).getExpression() : null;
}
@@ -2982,7 +3204,7 @@ private void formatArrayIndices(Deque indices) {
* Returns all array indices for the given expression, e.g. given {@code foo[0][0]} returns the
* expressions for {@code [0][0]}.
*/
- private Deque getArrayIndices(ExpressionTree expression) {
+ private static Deque getArrayIndices(ExpressionTree expression) {
Deque indices = new ArrayDeque<>();
while (expression instanceof ArrayAccessTree) {
ArrayAccessTree array = (ArrayAccessTree) expression;
@@ -3282,16 +3504,22 @@ int declareOne(
new ArrayDeque<>(typeWithDims.isPresent() ? typeWithDims.get().dims : ImmutableList.of());
int baseDims = 0;
+ // preprocess to separate declaration annotations + modifiers, type annotations
+
+ DeclarationModifiersAndTypeAnnotations declarationAndTypeModifiers =
+ modifiers
+ .map(m -> splitModifiers(m, m.getAnnotations()))
+ .orElse(DeclarationModifiersAndTypeAnnotations.empty());
builder.open(
- kind == DeclarationKind.PARAMETER
- && (modifiers.isPresent() && !modifiers.get().getAnnotations().isEmpty())
+ kind == DeclarationKind.PARAMETER && declarationAndTypeModifiers.hasDeclarationAnnotation()
? plusFour
: ZERO);
{
- if (modifiers.isPresent()) {
- visitAndBreakModifiers(
- modifiers.get(), annotationsDirection, Optional.of(verticalAnnotationBreak));
- }
+ List annotations =
+ visitModifiers(
+ declarationAndTypeModifiers,
+ annotationsDirection,
+ Optional.of(verticalAnnotationBreak));
boolean isVar =
builder.peekToken().get().equals("var")
&& (!name.contentEquals("var") || builder.peekToken(1).get().equals("var"));
@@ -3302,6 +3530,7 @@ int declareOne(
{
builder.open(ZERO);
{
+ visitAnnotations(annotations, BreakOrNot.NO, BreakOrNot.YES);
if (typeWithDims.isPresent() && typeWithDims.get().node != null) {
scan(typeWithDims.get().node, null);
int totalDims = dims.size();
@@ -3573,7 +3802,8 @@ private void classDeclarationTypeList(String token, List extends Tree> types)
*
* e.g. {@code int x, y;} is parsed as {@code int x; int y;}.
*/
- private List variableFragments(PeekingIterator extends Tree> it, Tree first) {
+ private static List variableFragments(
+ PeekingIterator extends Tree> it, Tree first) {
List fragments = new ArrayList<>();
if (first.getKind() == VARIABLE) {
int start = getStartPosition(first);
@@ -3623,7 +3853,7 @@ private boolean hasTrailingToken(Input input, List extends Tree> nodes, String
* @param modifiers the list of {@link ModifiersTree}s
* @return whether the local can be declared with horizontal annotations
*/
- private Direction canLocalHaveHorizontalAnnotations(ModifiersTree modifiers) {
+ private static Direction canLocalHaveHorizontalAnnotations(ModifiersTree modifiers) {
int parameterlessAnnotations = 0;
for (AnnotationTree annotation : modifiers.getAnnotations()) {
if (annotation.getArguments().isEmpty()) {
@@ -3640,7 +3870,7 @@ private Direction canLocalHaveHorizontalAnnotations(ModifiersTree modifiers) {
* Should a field with a set of modifiers be declared with horizontal annotations? This is
* currently true if all annotations are parameterless annotations.
*/
- private Direction fieldAnnotationDirection(ModifiersTree modifiers) {
+ private static Direction fieldAnnotationDirection(ModifiersTree modifiers) {
for (AnnotationTree annotation : modifiers.getAnnotations()) {
if (!annotation.getArguments().isEmpty()) {
return Direction.VERTICAL;
diff --git a/core/src/main/java/com/google/googlejavaformat/java/java14/Java14InputAstVisitor.java b/core/src/main/java/com/google/googlejavaformat/java/java14/Java14InputAstVisitor.java
index e5227da4b..b0d2a7675 100644
--- a/core/src/main/java/com/google/googlejavaformat/java/java14/Java14InputAstVisitor.java
+++ b/core/src/main/java/com/google/googlejavaformat/java/java14/Java14InputAstVisitor.java
@@ -18,10 +18,10 @@
import com.google.common.base.Verify;
import com.google.common.collect.ImmutableList;
-import com.google.googlejavaformat.Op;
import com.google.googlejavaformat.OpsBuilder;
import com.google.googlejavaformat.OpsBuilder.BlankLineWanted;
import com.google.googlejavaformat.java.JavaInputAstVisitor;
+import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.BindingPatternTree;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.CaseTree;
@@ -112,7 +112,9 @@ public Void visitBindingPattern(BindingPatternTree node, Void unused) {
private void visitBindingPattern(ModifiersTree modifiers, Tree type, Name name) {
if (modifiers != null) {
- builder.addAll(visitModifiers(modifiers, Direction.HORIZONTAL, Optional.empty()));
+ List annotations =
+ visitModifiers(modifiers, Direction.HORIZONTAL, Optional.empty());
+ visitAnnotations(annotations, BreakOrNot.NO, BreakOrNot.YES);
}
scan(type, null);
builder.breakOp(" ");
@@ -160,14 +162,9 @@ public Void visitClass(ClassTree tree, Void unused) {
public void visitRecordDeclaration(ClassTree node) {
sync(node);
- List breaks =
- visitModifiers(
- node.getModifiers(),
- Direction.VERTICAL,
- /* declarationAnnotationBreak= */ Optional.empty());
+ typeDeclarationModifiers(node.getModifiers());
Verify.verify(node.getExtendsClause() == null);
boolean hasSuperInterfaceTypes = !node.getImplementsClause().isEmpty();
- builder.addAll(breaks);
token("record");
builder.space();
visit(node.getSimpleName());
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/TypeAnnotations.input b/core/src/test/resources/com/google/googlejavaformat/java/testdata/TypeAnnotations.input
new file mode 100644
index 000000000..ddaa8f1ad
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/TypeAnnotations.input
@@ -0,0 +1,33 @@
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+class TypeAnnotations {
+
+ @Deprecated
+ public @Nullable Object foo() {}
+
+ public @Deprecated Object foo() {}
+
+ @Nullable Foo handle() {
+ @Nullable Bar bar = bar();
+ try (@Nullable Baz baz = baz()) {}
+ }
+
+ Foo(
+ @Nullable Bar //
+ param1, //
+ Baz //
+ param2) {}
+
+ void g(
+ @Deprecated @Nullable ImmutableList veryVeryLooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,
+ @Deprecated @Nullable ImmutableList veryVeryLoooooooooooooooooooooooooooooooooooooooooooooooooooooooong) {}
+
+ @Deprecated @Nullable TypeAnnotations() {}
+
+ enum Foo {
+ @Nullable
+ BAR;
+ }
+
+ @Nullable @Nullable Object doubleTrouble() {}
+}
diff --git a/core/src/test/resources/com/google/googlejavaformat/java/testdata/TypeAnnotations.output b/core/src/test/resources/com/google/googlejavaformat/java/testdata/TypeAnnotations.output
new file mode 100644
index 000000000..8dd5d4efc
--- /dev/null
+++ b/core/src/test/resources/com/google/googlejavaformat/java/testdata/TypeAnnotations.output
@@ -0,0 +1,39 @@
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+class TypeAnnotations {
+
+ @Deprecated
+ public @Nullable Object foo() {}
+
+ public @Deprecated Object foo() {}
+
+ @Nullable Foo handle() {
+ @Nullable Bar bar = bar();
+ try (@Nullable Baz baz = baz()) {}
+ }
+
+ Foo(
+ @Nullable Bar //
+ param1, //
+ Baz //
+ param2) {}
+
+ void g(
+ @Deprecated
+ @Nullable ImmutableList
+ veryVeryLooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,
+ @Deprecated
+ @Nullable ImmutableList
+ veryVeryLoooooooooooooooooooooooooooooooooooooooooooooooooooooooong) {}
+
+ @Deprecated
+ @Nullable
+ TypeAnnotations() {}
+
+ enum Foo {
+ @Nullable
+ BAR;
+ }
+
+ @Nullable @Nullable Object doubleTrouble() {}
+}
diff --git a/pom.xml b/pom.xml
index aa85ac7c2..c66ed2f23 100644
--- a/pom.xml
+++ b/pom.xml
@@ -94,6 +94,7 @@
1.0
3.6.1
2.7.1
+ 1.8.2
3.1.0
3.2.1
@@ -118,6 +119,11 @@
error_prone_annotations
${errorprone.version}