blob: 5addc6770b0e818993a3f07a0d33cfd3a31e861f [file] [log] [blame]
/*
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.googlejavaformat.java.javadoc;
import static com.google.googlejavaformat.java.javadoc.JavadocLexer.lex;
import static com.google.googlejavaformat.java.javadoc.Token.Type.BR_TAG;
import static com.google.googlejavaformat.java.javadoc.Token.Type.PARAGRAPH_OPEN_TAG;
import static java.util.regex.Pattern.CASE_INSENSITIVE;
import static java.util.regex.Pattern.compile;
import com.google.common.collect.ImmutableList;
import com.google.googlejavaformat.java.javadoc.JavadocLexer.LexException;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Entry point for formatting Javadoc.
*
* <p>This stateless class reads tokens from the stateful lexer and translates them to "requests"
* and "writes" to the stateful writer. It also munges tokens into "standardized" forms. Finally, it
* performs postprocessing to convert the written Javadoc to a one-liner if possible or to leave a
* single blank line if it's empty.
*/
public final class JavadocFormatter {
static final int MAX_LINE_LENGTH = 100;
/**
* Formats the given Javadoc comment, which must start with ∕✱✱ and end with ✱∕. The output will
* start and end with the same characters.
*/
public static String formatJavadoc(String input, int blockIndent) {
ImmutableList<Token> tokens;
try {
tokens = lex(input);
} catch (LexException e) {
return input;
}
String result = render(tokens, blockIndent);
return makeSingleLineIfPossible(blockIndent, result);
}
private static String render(List<Token> input, int blockIndent) {
JavadocWriter output = new JavadocWriter(blockIndent);
for (Token token : input) {
switch (token.getType()) {
case BEGIN_JAVADOC:
output.writeBeginJavadoc();
break;
case END_JAVADOC:
output.writeEndJavadoc();
return output.toString();
case FOOTER_JAVADOC_TAG_START:
output.writeFooterJavadocTagStart(token);
break;
case LIST_OPEN_TAG:
output.writeListOpen(token);
break;
case LIST_CLOSE_TAG:
output.writeListClose(token);
break;
case LIST_ITEM_OPEN_TAG:
output.writeListItemOpen(token);
break;
case HEADER_OPEN_TAG:
output.writeHeaderOpen(token);
break;
case HEADER_CLOSE_TAG:
output.writeHeaderClose(token);
break;
case PARAGRAPH_OPEN_TAG:
output.writeParagraphOpen(standardizePToken(token));
break;
case BLOCKQUOTE_OPEN_TAG:
case BLOCKQUOTE_CLOSE_TAG:
output.writeBlockquoteOpenOrClose(token);
break;
case PRE_OPEN_TAG:
output.writePreOpen(token);
break;
case PRE_CLOSE_TAG:
output.writePreClose(token);
break;
case CODE_OPEN_TAG:
output.writeCodeOpen(token);
break;
case CODE_CLOSE_TAG:
output.writeCodeClose(token);
break;
case TABLE_OPEN_TAG:
output.writeTableOpen(token);
break;
case TABLE_CLOSE_TAG:
output.writeTableClose(token);
break;
case MOE_BEGIN_STRIP_COMMENT:
output.requestMoeBeginStripComment(token);
break;
case MOE_END_STRIP_COMMENT:
output.writeMoeEndStripComment(token);
break;
case HTML_COMMENT:
output.writeHtmlComment(token);
break;
case BR_TAG:
output.writeBr(standardizeBrToken(token));
break;
case WHITESPACE:
output.requestWhitespace();
break;
case FORCED_NEWLINE:
output.writeLineBreakNoAutoIndent();
break;
case LITERAL:
output.writeLiteral(token);
break;
case PARAGRAPH_CLOSE_TAG:
case LIST_ITEM_CLOSE_TAG:
case OPTIONAL_LINE_BREAK:
break;
default:
throw new AssertionError(token.getType());
}
}
throw new AssertionError();
}
/*
* TODO(cpovirk): Is this really the right location for the standardize* methods? Maybe the lexer
* should include them as part of its own postprocessing? Or even the writer could make sense.
*/
private static Token standardizeBrToken(Token token) {
return standardize(token, STANDARD_BR_TOKEN);
}
private static Token standardizePToken(Token token) {
return standardize(token, STANDARD_P_TOKEN);
}
private static Token standardize(Token token, Token standardToken) {
return SIMPLE_TAG_PATTERN.matcher(token.getValue()).matches() ? standardToken : token;
}
private static final Token STANDARD_BR_TOKEN = new Token(BR_TAG, "<br>");
private static final Token STANDARD_P_TOKEN = new Token(PARAGRAPH_OPEN_TAG, "<p>");
private static final Pattern SIMPLE_TAG_PATTERN = compile("^<\\w+\\s*/?\\s*>", CASE_INSENSITIVE);
private static final Pattern ONE_CONTENT_LINE_PATTERN = compile(" */[*][*]\n *[*] (.*)\n *[*]/");
/**
* Returns the given string or a one-line version of it (e.g., "∕✱✱ Tests for foos. ✱∕") if it
* fits on one line.
*/
private static String makeSingleLineIfPossible(int blockIndent, String input) {
int oneLinerContentLength = MAX_LINE_LENGTH - "/** */".length() - blockIndent;
Matcher matcher = ONE_CONTENT_LINE_PATTERN.matcher(input);
if (matcher.matches() && matcher.group(1).isEmpty()) {
return "/** */";
} else if (matcher.matches() && matcher.group(1).length() <= oneLinerContentLength) {
return "/** " + matcher.group(1) + " */";
}
return input;
}
private JavadocFormatter() {}
}