• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2016 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.javadoc;
16 
17 import static com.google.googlejavaformat.java.javadoc.JavadocLexer.lex;
18 import static com.google.googlejavaformat.java.javadoc.Token.Type.BR_TAG;
19 import static com.google.googlejavaformat.java.javadoc.Token.Type.PARAGRAPH_OPEN_TAG;
20 import static java.util.regex.Pattern.CASE_INSENSITIVE;
21 import static java.util.regex.Pattern.compile;
22 
23 import com.google.common.collect.ImmutableList;
24 import com.google.googlejavaformat.java.javadoc.JavadocLexer.LexException;
25 import java.util.List;
26 import java.util.regex.Matcher;
27 import java.util.regex.Pattern;
28 
29 /**
30  * Entry point for formatting Javadoc.
31  *
32  * <p>This stateless class reads tokens from the stateful lexer and translates them to "requests"
33  * and "writes" to the stateful writer. It also munges tokens into "standardized" forms. Finally, it
34  * performs postprocessing to convert the written Javadoc to a one-liner if possible or to leave a
35  * single blank line if it's empty.
36  */
37 public final class JavadocFormatter {
38 
39   static final int MAX_LINE_LENGTH = 100;
40 
41   /**
42    * Formats the given Javadoc comment, which must start with ∕✱✱ and end with ✱∕. The output will
43    * start and end with the same characters.
44    */
formatJavadoc(String input, int blockIndent)45   public static String formatJavadoc(String input, int blockIndent) {
46     ImmutableList<Token> tokens;
47     try {
48       tokens = lex(input);
49     } catch (LexException e) {
50       return input;
51     }
52     String result = render(tokens, blockIndent);
53     return makeSingleLineIfPossible(blockIndent, result);
54   }
55 
render(List<Token> input, int blockIndent)56   private static String render(List<Token> input, int blockIndent) {
57     JavadocWriter output = new JavadocWriter(blockIndent);
58     for (Token token : input) {
59       switch (token.getType()) {
60         case BEGIN_JAVADOC:
61           output.writeBeginJavadoc();
62           break;
63         case END_JAVADOC:
64           output.writeEndJavadoc();
65           return output.toString();
66         case FOOTER_JAVADOC_TAG_START:
67           output.writeFooterJavadocTagStart(token);
68           break;
69         case LIST_OPEN_TAG:
70           output.writeListOpen(token);
71           break;
72         case LIST_CLOSE_TAG:
73           output.writeListClose(token);
74           break;
75         case LIST_ITEM_OPEN_TAG:
76           output.writeListItemOpen(token);
77           break;
78         case HEADER_OPEN_TAG:
79           output.writeHeaderOpen(token);
80           break;
81         case HEADER_CLOSE_TAG:
82           output.writeHeaderClose(token);
83           break;
84         case PARAGRAPH_OPEN_TAG:
85           output.writeParagraphOpen(standardizePToken(token));
86           break;
87         case BLOCKQUOTE_OPEN_TAG:
88         case BLOCKQUOTE_CLOSE_TAG:
89           output.writeBlockquoteOpenOrClose(token);
90           break;
91         case PRE_OPEN_TAG:
92           output.writePreOpen(token);
93           break;
94         case PRE_CLOSE_TAG:
95           output.writePreClose(token);
96           break;
97         case CODE_OPEN_TAG:
98           output.writeCodeOpen(token);
99           break;
100         case CODE_CLOSE_TAG:
101           output.writeCodeClose(token);
102           break;
103         case TABLE_OPEN_TAG:
104           output.writeTableOpen(token);
105           break;
106         case TABLE_CLOSE_TAG:
107           output.writeTableClose(token);
108           break;
109         case MOE_BEGIN_STRIP_COMMENT:
110           output.requestMoeBeginStripComment(token);
111           break;
112         case MOE_END_STRIP_COMMENT:
113           output.writeMoeEndStripComment(token);
114           break;
115         case HTML_COMMENT:
116           output.writeHtmlComment(token);
117           break;
118         case BR_TAG:
119           output.writeBr(standardizeBrToken(token));
120           break;
121         case WHITESPACE:
122           output.requestWhitespace();
123           break;
124         case FORCED_NEWLINE:
125           output.writeLineBreakNoAutoIndent();
126           break;
127         case LITERAL:
128           output.writeLiteral(token);
129           break;
130         case PARAGRAPH_CLOSE_TAG:
131         case LIST_ITEM_CLOSE_TAG:
132         case OPTIONAL_LINE_BREAK:
133           break;
134         default:
135           throw new AssertionError(token.getType());
136       }
137     }
138     throw new AssertionError();
139   }
140 
141   /*
142    * TODO(cpovirk): Is this really the right location for the standardize* methods? Maybe the lexer
143    * should include them as part of its own postprocessing? Or even the writer could make sense.
144    */
145 
standardizeBrToken(Token token)146   private static Token standardizeBrToken(Token token) {
147     return standardize(token, STANDARD_BR_TOKEN);
148   }
149 
standardizePToken(Token token)150   private static Token standardizePToken(Token token) {
151     return standardize(token, STANDARD_P_TOKEN);
152   }
153 
standardize(Token token, Token standardToken)154   private static Token standardize(Token token, Token standardToken) {
155     return SIMPLE_TAG_PATTERN.matcher(token.getValue()).matches() ? standardToken : token;
156   }
157 
158   private static final Token STANDARD_BR_TOKEN = new Token(BR_TAG, "<br>");
159   private static final Token STANDARD_P_TOKEN = new Token(PARAGRAPH_OPEN_TAG, "<p>");
160   private static final Pattern SIMPLE_TAG_PATTERN = compile("^<\\w+\\s*/?\\s*>", CASE_INSENSITIVE);
161 
162   private static final Pattern ONE_CONTENT_LINE_PATTERN = compile(" */[*][*]\n *[*] (.*)\n *[*]/");
163 
164   /**
165    * Returns the given string or a one-line version of it (e.g., "∕✱✱ Tests for foos. ✱∕") if it
166    * fits on one line.
167    */
makeSingleLineIfPossible(int blockIndent, String input)168   private static String makeSingleLineIfPossible(int blockIndent, String input) {
169     Matcher matcher = ONE_CONTENT_LINE_PATTERN.matcher(input);
170     if (matcher.matches()) {
171       String line = matcher.group(1);
172       if (line.isEmpty()) {
173         return "/** */";
174       } else if (oneLineJavadoc(line, blockIndent)) {
175         return "/** " + line + " */";
176       }
177     }
178     return input;
179   }
180 
oneLineJavadoc(String line, int blockIndent)181   private static boolean oneLineJavadoc(String line, int blockIndent) {
182     int oneLinerContentLength = MAX_LINE_LENGTH - "/**  */".length() - blockIndent;
183     if (line.length() > oneLinerContentLength) {
184       return false;
185     }
186     // If the javadoc contains only a tag, use multiple lines to encourage writing a summary
187     // fragment, unless it's /* @hide */.
188     if (line.startsWith("@") && !line.equals("@hide")) {
189       return false;
190     }
191     return true;
192   }
193 
JavadocFormatter()194   private JavadocFormatter() {}
195 }
196