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