• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2020 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 
15 package com.google.googlejavaformat.java.java17;
16 
17 import static com.google.common.collect.ImmutableList.toImmutableList;
18 import static com.google.common.collect.Iterables.getOnlyElement;
19 
20 import com.google.common.base.Verify;
21 import com.google.common.collect.ImmutableList;
22 import com.google.googlejavaformat.OpsBuilder;
23 import com.google.googlejavaformat.OpsBuilder.BlankLineWanted;
24 import com.google.googlejavaformat.java.JavaInputAstVisitor;
25 import com.sun.source.tree.AnnotationTree;
26 import com.sun.source.tree.BindingPatternTree;
27 import com.sun.source.tree.BlockTree;
28 import com.sun.source.tree.CaseLabelTree;
29 import com.sun.source.tree.CaseTree;
30 import com.sun.source.tree.ClassTree;
31 import com.sun.source.tree.CompilationUnitTree;
32 import com.sun.source.tree.ExpressionTree;
33 import com.sun.source.tree.InstanceOfTree;
34 import com.sun.source.tree.ModifiersTree;
35 import com.sun.source.tree.ModuleTree;
36 import com.sun.source.tree.SwitchExpressionTree;
37 import com.sun.source.tree.Tree;
38 import com.sun.source.tree.VariableTree;
39 import com.sun.source.tree.YieldTree;
40 import com.sun.tools.javac.code.Flags;
41 import com.sun.tools.javac.tree.JCTree;
42 import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
43 import com.sun.tools.javac.tree.TreeInfo;
44 import java.util.List;
45 import java.util.Optional;
46 import javax.lang.model.element.Name;
47 
48 /**
49  * Extends {@link JavaInputAstVisitor} with support for AST nodes that were added or modified in
50  * Java 17.
51  */
52 public class Java17InputAstVisitor extends JavaInputAstVisitor {
53 
Java17InputAstVisitor(OpsBuilder builder, int indentMultiplier)54   public Java17InputAstVisitor(OpsBuilder builder, int indentMultiplier) {
55     super(builder, indentMultiplier);
56   }
57 
58   @Override
handleModule(boolean first, CompilationUnitTree node)59   protected void handleModule(boolean first, CompilationUnitTree node) {
60     ModuleTree module = node.getModule();
61     if (module != null) {
62       if (!first) {
63         builder.blankLineWanted(BlankLineWanted.YES);
64       }
65       markForPartialFormat();
66       visitModule(module, null);
67       builder.forcedBreak();
68     }
69   }
70 
71   @Override
getPermitsClause(ClassTree node)72   protected List<? extends Tree> getPermitsClause(ClassTree node) {
73     return node.getPermitsClause();
74   }
75 
76   @Override
visitBindingPattern(BindingPatternTree node, Void unused)77   public Void visitBindingPattern(BindingPatternTree node, Void unused) {
78     sync(node);
79     VariableTree variableTree = node.getVariable();
80     visitBindingPattern(
81         variableTree.getModifiers(), variableTree.getType(), variableTree.getName());
82     return null;
83   }
84 
visitBindingPattern(ModifiersTree modifiers, Tree type, Name name)85   private void visitBindingPattern(ModifiersTree modifiers, Tree type, Name name) {
86     builder.open(plusFour);
87     if (modifiers != null) {
88       List<AnnotationTree> annotations =
89           visitModifiers(modifiers, Direction.HORIZONTAL, Optional.empty());
90       visitAnnotations(annotations, BreakOrNot.NO, BreakOrNot.YES);
91     }
92     scan(type, null);
93     builder.breakOp(" ");
94     if (name.isEmpty()) {
95       token("_");
96     } else {
97       visit(name);
98     }
99     builder.close();
100   }
101 
102   @Override
visitYield(YieldTree node, Void aVoid)103   public Void visitYield(YieldTree node, Void aVoid) {
104     sync(node);
105     token("yield");
106     builder.space();
107     scan(node.getValue(), null);
108     token(";");
109     return null;
110   }
111 
112   @Override
visitSwitchExpression(SwitchExpressionTree node, Void aVoid)113   public Void visitSwitchExpression(SwitchExpressionTree node, Void aVoid) {
114     sync(node);
115     visitSwitch(node.getExpression(), node.getCases());
116     return null;
117   }
118 
119   @Override
visitClass(ClassTree tree, Void unused)120   public Void visitClass(ClassTree tree, Void unused) {
121     switch (tree.getKind()) {
122       case ANNOTATION_TYPE:
123         visitAnnotationType(tree);
124         break;
125       case CLASS:
126       case INTERFACE:
127         visitClassDeclaration(tree);
128         break;
129       case ENUM:
130         visitEnumDeclaration(tree);
131         break;
132       case RECORD:
133         visitRecordDeclaration(tree);
134         break;
135       default:
136         throw new AssertionError(tree.getKind());
137     }
138     return null;
139   }
140 
visitRecordDeclaration(ClassTree node)141   public void visitRecordDeclaration(ClassTree node) {
142     sync(node);
143     typeDeclarationModifiers(node.getModifiers());
144     Verify.verify(node.getExtendsClause() == null);
145     boolean hasSuperInterfaceTypes = !node.getImplementsClause().isEmpty();
146     token("record");
147     builder.space();
148     visit(node.getSimpleName());
149     if (!node.getTypeParameters().isEmpty()) {
150       token("<");
151     }
152     builder.open(plusFour);
153     {
154       if (!node.getTypeParameters().isEmpty()) {
155         typeParametersRest(node.getTypeParameters(), hasSuperInterfaceTypes ? plusFour : ZERO);
156       }
157       ImmutableList<JCVariableDecl> parameters = recordVariables(node);
158       token("(");
159       if (!parameters.isEmpty()) {
160         // Break before args.
161         builder.breakToFill("");
162       }
163       // record headers can't declare receiver parameters
164       visitFormals(/* receiver= */ Optional.empty(), parameters);
165       token(")");
166       if (hasSuperInterfaceTypes) {
167         builder.breakToFill(" ");
168         builder.open(node.getImplementsClause().size() > 1 ? plusFour : ZERO);
169         token("implements");
170         builder.space();
171         boolean first = true;
172         for (Tree superInterfaceType : node.getImplementsClause()) {
173           if (!first) {
174             token(",");
175             builder.breakOp(" ");
176           }
177           scan(superInterfaceType, null);
178           first = false;
179         }
180         builder.close();
181       }
182     }
183     builder.close();
184     if (node.getMembers() == null) {
185       token(";");
186     } else {
187       List<Tree> members =
188           node.getMembers().stream()
189               .filter(t -> (TreeInfo.flags((JCTree) t) & Flags.GENERATED_MEMBER) == 0)
190               .collect(toImmutableList());
191       addBodyDeclarations(members, BracesOrNot.YES, FirstDeclarationsOrNot.YES);
192     }
193     dropEmptyDeclarations();
194   }
195 
recordVariables(ClassTree node)196   private static ImmutableList<JCVariableDecl> recordVariables(ClassTree node) {
197     return node.getMembers().stream()
198         .filter(JCVariableDecl.class::isInstance)
199         .map(JCVariableDecl.class::cast)
200         .filter(m -> (m.mods.flags & RECORD) == RECORD)
201         .collect(toImmutableList());
202   }
203 
204   @Override
visitInstanceOf(InstanceOfTree node, Void unused)205   public Void visitInstanceOf(InstanceOfTree node, Void unused) {
206     sync(node);
207     builder.open(plusFour);
208     scan(node.getExpression(), null);
209     builder.breakOp(" ");
210     builder.open(ZERO);
211     token("instanceof");
212     builder.breakOp(" ");
213     if (node.getPattern() != null) {
214       scan(node.getPattern(), null);
215     } else {
216       scan(node.getType(), null);
217     }
218     builder.close();
219     builder.close();
220     return null;
221   }
222 
223   @Override
visitCase(CaseTree node, Void unused)224   public Void visitCase(CaseTree node, Void unused) {
225     sync(node);
226     markForPartialFormat();
227     builder.forcedBreak();
228     List<? extends CaseLabelTree> labels = node.getLabels();
229     boolean isDefault =
230         labels.size() == 1 && getOnlyElement(labels).getKind().name().equals("DEFAULT_CASE_LABEL");
231     builder.open(
232         node.getCaseKind().equals(CaseTree.CaseKind.RULE)
233                 && !node.getBody().getKind().equals(Tree.Kind.BLOCK)
234             ? plusFour
235             : ZERO);
236     if (isDefault) {
237       token("default", plusTwo);
238     } else {
239       token("case", plusTwo);
240       builder.open(labels.size() > 1 ? plusFour : ZERO);
241       builder.space();
242       boolean first = true;
243       for (Tree expression : labels) {
244         if (!first) {
245           token(",");
246           builder.breakOp(" ");
247         }
248         scan(expression, null);
249         first = false;
250       }
251       builder.close();
252     }
253 
254     final ExpressionTree guard = getGuard(node);
255     if (guard != null) {
256       builder.space();
257       token("when");
258       builder.space();
259       scan(guard, null);
260     }
261 
262     switch (node.getCaseKind()) {
263       case STATEMENT:
264         token(":");
265         builder.open(plusTwo);
266         visitStatements(node.getStatements());
267         builder.close();
268         break;
269       case RULE:
270         builder.space();
271         token("-");
272         token(">");
273         if (node.getBody().getKind() == Tree.Kind.BLOCK) {
274           builder.space();
275           // Explicit call with {@link CollapseEmptyOrNot.YES} to handle empty case blocks.
276           visitBlock(
277               (BlockTree) node.getBody(),
278               CollapseEmptyOrNot.YES,
279               AllowLeadingBlankLine.NO,
280               AllowTrailingBlankLine.NO);
281         } else {
282           builder.breakOp(" ");
283           scan(node.getBody(), null);
284         }
285         builder.guessToken(";");
286         break;
287       default:
288         throw new AssertionError(node.getCaseKind());
289     }
290     builder.close();
291     return null;
292   }
293 
getGuard(final CaseTree node)294   protected ExpressionTree getGuard(final CaseTree node) {
295     return null;
296   }
297 }
298