mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
fix(google-java-format): update to v1.18.1
This commit is contained in:
+19
@@ -14,6 +14,10 @@
|
||||
|
||||
package com.google.googlejavaformat;
|
||||
|
||||
import com.google.googlejavaformat.Input.Tok;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Rewrite comments. This interface is implemented by {@link
|
||||
* com.google.googlejavaformat.java.JavaCommentsHelper JavaCommentsHelper}.
|
||||
@@ -28,4 +32,19 @@ public interface CommentsHelper {
|
||||
* @return the rewritten comment
|
||||
*/
|
||||
String rewrite(Input.Tok tok, int maxWidth, int column0);
|
||||
|
||||
static Optional<String> reformatParameterComment(Tok tok) {
|
||||
if (!tok.isSlashStarComment()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
var match = PARAMETER_COMMENT.matcher(tok.getOriginalText());
|
||||
if (!match.matches()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(String.format("/* %s= */", match.group(1)));
|
||||
}
|
||||
|
||||
Pattern PARAMETER_COMMENT =
|
||||
Pattern.compile(
|
||||
"/\\*\\s*(\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*(\\Q...\\E)?)\\s*=\\s*\\*/");
|
||||
}
|
||||
|
||||
+407
-413
@@ -15,14 +15,16 @@
|
||||
package com.google.googlejavaformat;
|
||||
|
||||
import static com.google.common.collect.Iterables.getLast;
|
||||
import static com.google.googlejavaformat.CommentsHelper.reformatParameterComment;
|
||||
import static java.lang.Math.max;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.base.Suppliers;
|
||||
import com.google.common.collect.DiscreteDomain;
|
||||
import com.google.common.collect.Iterators;
|
||||
import com.google.common.collect.Range;
|
||||
import com.google.googlejavaformat.Output.BreakTag;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -37,91 +39,6 @@ import java.util.Optional;
|
||||
* representing single spaces; and {@link Break}s, which represent optional line-breaks.
|
||||
*/
|
||||
public abstract class Doc {
|
||||
private static final Range<Integer> EMPTY_RANGE = Range.closedOpen(-1, -1);
|
||||
private static final DiscreteDomain<Integer> INTEGERS = DiscreteDomain.integers();
|
||||
// Memoized width; Float.POSITIVE_INFINITY if contains forced breaks.
|
||||
private boolean widthComputed = false;
|
||||
private float width = 0.0F;
|
||||
// Memoized flat; not defined (and never computed) if contains forced breaks.
|
||||
private boolean flatComputed = false;
|
||||
private String flat = "";
|
||||
// Memoized Range.
|
||||
private boolean rangeComputed = false;
|
||||
private Range<Integer> range = EMPTY_RANGE;
|
||||
|
||||
/**
|
||||
* Make breaking decisions for a {@code Doc}.
|
||||
*
|
||||
* @param maxWidth the maximum line width
|
||||
* @param state the current output state
|
||||
* @return the new output state
|
||||
*/
|
||||
public abstract State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state);
|
||||
|
||||
/** Write a {@code Doc} to an {@link Output}, after breaking decisions have been made. */
|
||||
public abstract void write(Output output);
|
||||
|
||||
/**
|
||||
* Return the width of a {@code Doc}, or {@code Float.POSITIVE_INFINITY} if it must be broken.
|
||||
*
|
||||
* @return the width
|
||||
*/
|
||||
final float getWidth() {
|
||||
if (!widthComputed) {
|
||||
width = computeWidth();
|
||||
widthComputed = true;
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the {@code Doc}'s width.
|
||||
*
|
||||
* @return the width, or {@code Float.POSITIVE_INFINITY} if it must be broken
|
||||
*/
|
||||
abstract float computeWidth();
|
||||
|
||||
/**
|
||||
* Return a {@code Doc}'s flat-string value; not defined (and never called) if the (@code Doc}
|
||||
* contains forced breaks.
|
||||
*
|
||||
* @return the flat-string value
|
||||
*/
|
||||
final String getFlat() {
|
||||
if (!flatComputed) {
|
||||
flat = computeFlat();
|
||||
flatComputed = true;
|
||||
}
|
||||
return flat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the {@code Doc}'s flat value. Not defined (and never called) if contains forced breaks.
|
||||
*
|
||||
* @return the flat value
|
||||
*/
|
||||
abstract String computeFlat();
|
||||
|
||||
/**
|
||||
* Return the {@link Range} of a {@code Doc}.
|
||||
*
|
||||
* @return the {@code Doc}'s {@link Range}
|
||||
*/
|
||||
final Range<Integer> range() {
|
||||
if (!rangeComputed) {
|
||||
range = computeRange();
|
||||
rangeComputed = true;
|
||||
}
|
||||
return range;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the {@code Doc}'s {@link Range} of {@link Input.Token}s.
|
||||
*
|
||||
* @return the {@link Range}
|
||||
*/
|
||||
abstract Range<Integer> computeRange();
|
||||
|
||||
/**
|
||||
* Each {@link Break} in a {@link Level} is either {@link FillMode#UNIFIED} or {@link
|
||||
* FillMode#INDEPENDENT}.
|
||||
@@ -153,10 +70,6 @@ public abstract class Doc {
|
||||
final int column;
|
||||
final boolean mustBreak;
|
||||
|
||||
public State(int indent0, int column0) {
|
||||
this(indent0, indent0, column0, false);
|
||||
}
|
||||
|
||||
State(int lastIndent, int indent, int column, boolean mustBreak) {
|
||||
this.lastIndent = lastIndent;
|
||||
this.indent = indent;
|
||||
@@ -164,6 +77,18 @@ public abstract class Doc {
|
||||
this.mustBreak = mustBreak;
|
||||
}
|
||||
|
||||
public State(int indent0, int column0) {
|
||||
this(indent0, indent0, column0, false);
|
||||
}
|
||||
|
||||
State withColumn(int column) {
|
||||
return new State(lastIndent, indent, column, mustBreak);
|
||||
}
|
||||
|
||||
State withMustBreak(boolean mustBreak) {
|
||||
return new State(lastIndent, indent, column, mustBreak);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
@@ -173,286 +98,85 @@ public abstract class Doc {
|
||||
.add("mustBreak", mustBreak)
|
||||
.toString();
|
||||
}
|
||||
|
||||
State withColumn(int column) {
|
||||
return new State(lastIndent, indent, column, mustBreak);
|
||||
}
|
||||
|
||||
State withMustBreak(boolean mustBreak) {
|
||||
return new State(lastIndent, indent, column, mustBreak);
|
||||
}
|
||||
}
|
||||
|
||||
/** A leaf {@link Doc} for a token. */
|
||||
public static final class Token extends Doc implements Op {
|
||||
private final Input.Token token;
|
||||
private final RealOrImaginary realOrImaginary;
|
||||
private final Indent plusIndentCommentsBefore;
|
||||
private final Optional<Indent> breakAndIndentTrailingComment;
|
||||
private static final Range<Integer> EMPTY_RANGE = Range.closedOpen(-1, -1);
|
||||
private static final DiscreteDomain<Integer> INTEGERS = DiscreteDomain.integers();
|
||||
|
||||
private Token(
|
||||
Input.Token token,
|
||||
RealOrImaginary realOrImaginary,
|
||||
Indent plusIndentCommentsBefore,
|
||||
Optional<Indent> breakAndIndentTrailingComment) {
|
||||
this.token = token;
|
||||
this.realOrImaginary = realOrImaginary;
|
||||
this.plusIndentCommentsBefore = plusIndentCommentsBefore;
|
||||
this.breakAndIndentTrailingComment = breakAndIndentTrailingComment;
|
||||
}
|
||||
// Memoized width; Float.POSITIVE_INFINITY if contains forced breaks.
|
||||
private final Supplier<Float> width = Suppliers.memoize(this::computeWidth);
|
||||
|
||||
/**
|
||||
* Make a {@code Token}.
|
||||
*
|
||||
* @param token the {@link Input.Token} to wrap
|
||||
* @param realOrImaginary did this {@link Input.Token} appear in the input, or was it generated
|
||||
* incorrectly?
|
||||
* @param plusIndentCommentsBefore extra {@code plusIndent} for comments just before this token
|
||||
* @return the new {@code Token}
|
||||
*/
|
||||
static Op make(
|
||||
Input.Token token,
|
||||
Doc.Token.RealOrImaginary realOrImaginary,
|
||||
Indent plusIndentCommentsBefore,
|
||||
Optional<Indent> breakAndIndentTrailingComment) {
|
||||
return new Token(
|
||||
token, realOrImaginary, plusIndentCommentsBefore, breakAndIndentTrailingComment);
|
||||
}
|
||||
// Memoized flat; not defined (and never computed) if contains forced breaks.
|
||||
private final Supplier<String> flat = Suppliers.memoize(this::computeFlat);
|
||||
|
||||
@Override
|
||||
public void add(DocBuilder builder) {
|
||||
builder.add(this);
|
||||
}
|
||||
// Memoized Range.
|
||||
private final Supplier<Range<Integer>> range = Suppliers.memoize(this::computeRange);
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("token", token)
|
||||
.add("realOrImaginary", realOrImaginary)
|
||||
.add("plusIndentCommentsBefore", plusIndentCommentsBefore)
|
||||
.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* How much extra to indent comments before the {@code Token}.
|
||||
*
|
||||
* @return the extra indent
|
||||
*/
|
||||
Indent getPlusIndentCommentsBefore() {
|
||||
return plusIndentCommentsBefore;
|
||||
}
|
||||
|
||||
/** Force a line break and indent trailing javadoc or block comments. */
|
||||
Optional<Indent> breakAndIndentTrailingComment() {
|
||||
return breakAndIndentTrailingComment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the wrapped {@link Input.Token}.
|
||||
*
|
||||
* @return the {@link Input.Token}
|
||||
*/
|
||||
Input.Token getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the token good? That is, does it match an {@link Input.Token}?
|
||||
*
|
||||
* @return whether the @code Token} is good
|
||||
*/
|
||||
RealOrImaginary realOrImaginary() {
|
||||
return realOrImaginary;
|
||||
}
|
||||
|
||||
@Override
|
||||
float computeWidth() {
|
||||
return token.getTok().length();
|
||||
}
|
||||
|
||||
@Override
|
||||
String computeFlat() {
|
||||
return token.getTok().getOriginalText();
|
||||
}
|
||||
|
||||
@Override
|
||||
Range<Integer> computeRange() {
|
||||
return Range.singleton(token.getTok().getIndex()).canonical(INTEGERS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) {
|
||||
String text = token.getTok().getOriginalText();
|
||||
return state.withColumn(state.column + text.length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Output output) {
|
||||
String text = token.getTok().getOriginalText();
|
||||
output.append(text, range());
|
||||
}
|
||||
|
||||
/** Is a Token a real token, or imaginary (e.g., a token generated incorrectly, or an EOF)? */
|
||||
public enum RealOrImaginary {
|
||||
REAL,
|
||||
IMAGINARY;
|
||||
|
||||
boolean isReal() {
|
||||
return this == REAL;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Return the width of a {@code Doc}, or {@code Float.POSITIVE_INFINITY} if it must be broken.
|
||||
*
|
||||
* @return the width
|
||||
*/
|
||||
final float getWidth() {
|
||||
return width.get();
|
||||
}
|
||||
|
||||
/** A leaf node in a {@link Doc} for an optional break. */
|
||||
public static final class Break extends Doc implements Op {
|
||||
private final FillMode fillMode;
|
||||
private final String flat;
|
||||
private final Indent plusIndent;
|
||||
private final Optional<BreakTag> optTag;
|
||||
/** Was this break taken? */
|
||||
boolean broken;
|
||||
/** New indent after this break. */
|
||||
int newIndent;
|
||||
|
||||
private Break(FillMode fillMode, String flat, Indent plusIndent, Optional<BreakTag> optTag) {
|
||||
this.fillMode = fillMode;
|
||||
this.flat = flat;
|
||||
this.plusIndent = plusIndent;
|
||||
this.optTag = optTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a {@code Break}.
|
||||
*
|
||||
* @param fillMode the {@link FillMode}
|
||||
* @param flat the text when not broken
|
||||
* @param plusIndent extra indent if taken
|
||||
* @param optTag an optional tag for remembering whether the break was taken
|
||||
* @return the new {@code Break}
|
||||
*/
|
||||
public static Break make(
|
||||
FillMode fillMode, String flat, Indent plusIndent, Optional<BreakTag> optTag) {
|
||||
return new Break(fillMode, flat, plusIndent, optTag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a forced {@code Break}.
|
||||
*
|
||||
* @return the new forced {@code Break}
|
||||
*/
|
||||
public static Break makeForced() {
|
||||
return make(FillMode.FORCED, "", Indent.Const.ZERO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a {@code Break}.
|
||||
*
|
||||
* @param fillMode the {@link FillMode}
|
||||
* @param flat the text when not broken
|
||||
* @param plusIndent extra indent if taken
|
||||
* @return the new {@code Break}
|
||||
*/
|
||||
public static Break make(FillMode fillMode, String flat, Indent plusIndent) {
|
||||
return new Break(fillMode, flat, plusIndent, /* optTag= */ Optional.empty());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(DocBuilder builder) {
|
||||
builder.breakDoc(this);
|
||||
}
|
||||
|
||||
public State computeBreaks(State state, int lastIndent, boolean broken) {
|
||||
if (optTag.isPresent()) {
|
||||
optTag.get().recordBroken(broken);
|
||||
}
|
||||
|
||||
if (broken) {
|
||||
this.broken = true;
|
||||
this.newIndent = max(lastIndent + plusIndent.eval(), 0);
|
||||
return state.withColumn(newIndent);
|
||||
} else {
|
||||
this.broken = false;
|
||||
this.newIndent = -1;
|
||||
return state.withColumn(state.column + flat.length());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("fillMode", fillMode)
|
||||
.add("flat", flat)
|
||||
.add("plusIndent", plusIndent)
|
||||
.add("optTag", optTag)
|
||||
.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@code Break}'s extra indent.
|
||||
*
|
||||
* @return the extra indent
|
||||
*/
|
||||
int getPlusIndent() {
|
||||
return plusIndent.eval();
|
||||
}
|
||||
|
||||
@Override
|
||||
float computeWidth() {
|
||||
return isForced() ? Float.POSITIVE_INFINITY : (float) flat.length();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the {@code Break} forced?
|
||||
*
|
||||
* @return whether the {@code Break} is forced
|
||||
*/
|
||||
boolean isForced() {
|
||||
return fillMode == FillMode.FORCED;
|
||||
}
|
||||
|
||||
@Override
|
||||
String computeFlat() {
|
||||
return flat;
|
||||
}
|
||||
|
||||
@Override
|
||||
Range<Integer> computeRange() {
|
||||
return EMPTY_RANGE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) {
|
||||
// Updating the state for {@link Break}s requires deciding if the break
|
||||
// should be taken.
|
||||
// TODO(cushon): this hierarchy is wrong, create a separate interface
|
||||
// for unbreakable Docs?
|
||||
throw new UnsupportedOperationException("Did you mean computeBreaks(State, int, boolean)?");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Output output) {
|
||||
if (broken) {
|
||||
output.append("\n", EMPTY_RANGE);
|
||||
output.indent(newIndent);
|
||||
} else {
|
||||
output.append(flat, range());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Return a {@code Doc}'s flat-string value; not defined (and never called) if the (@code Doc}
|
||||
* contains forced breaks.
|
||||
*
|
||||
* @return the flat-string value
|
||||
*/
|
||||
final String getFlat() {
|
||||
return flat.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link Range} of a {@code Doc}.
|
||||
*
|
||||
* @return the {@code Doc}'s {@link Range}
|
||||
*/
|
||||
final Range<Integer> range() {
|
||||
return range.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the {@code Doc}'s width.
|
||||
*
|
||||
* @return the width, or {@code Float.POSITIVE_INFINITY} if it must be broken
|
||||
*/
|
||||
abstract float computeWidth();
|
||||
|
||||
/**
|
||||
* Compute the {@code Doc}'s flat value. Not defined (and never called) if contains forced breaks.
|
||||
*
|
||||
* @return the flat value
|
||||
*/
|
||||
abstract String computeFlat();
|
||||
|
||||
/**
|
||||
* Compute the {@code Doc}'s {@link Range} of {@link Input.Token}s.
|
||||
*
|
||||
* @return the {@link Range}
|
||||
*/
|
||||
abstract Range<Integer> computeRange();
|
||||
|
||||
/**
|
||||
* Make breaking decisions for a {@code Doc}.
|
||||
*
|
||||
* @param maxWidth the maximum line width
|
||||
* @param state the current output state
|
||||
* @return the new output state
|
||||
*/
|
||||
public abstract State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state);
|
||||
|
||||
/** Write a {@code Doc} to an {@link Output}, after breaking decisions have been made. */
|
||||
public abstract void write(Output output);
|
||||
|
||||
/** A {@code Level} inside a {@link Doc}. */
|
||||
static final class Level extends Doc {
|
||||
private final Indent plusIndent; // The extra indent following breaks.
|
||||
private final List<Doc> docs = new ArrayList<>(); // The elements of the level.
|
||||
/** True if the entire {@link Level} fits on one line. */
|
||||
boolean oneLine = false;
|
||||
/**
|
||||
* Groups of {@link Doc}s that are children of the current {@link Level}, separated by {@link
|
||||
* Break}s.
|
||||
*/
|
||||
List<List<Doc>> splits = new ArrayList<>();
|
||||
/** {@link Break}s between {@link Doc}s in the current {@link Level}. */
|
||||
List<Break> breaks = new ArrayList<>();
|
||||
|
||||
private Level(Indent plusIndent) {
|
||||
this.plusIndent = plusIndent;
|
||||
@@ -468,18 +192,6 @@ public abstract class Doc {
|
||||
return new Level(plusIndent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("plusIndent", plusIndent)
|
||||
.add("docs", docs)
|
||||
.toString();
|
||||
}
|
||||
|
||||
// State that needs to be preserved between calculating breaks and
|
||||
// writing output.
|
||||
// TODO(cushon): represent phases as separate immutable data.
|
||||
|
||||
/**
|
||||
* Add a {@link Doc} to the {@code Level}.
|
||||
*
|
||||
@@ -516,6 +228,22 @@ public abstract class Doc {
|
||||
return docRange;
|
||||
}
|
||||
|
||||
// State that needs to be preserved between calculating breaks and
|
||||
// writing output.
|
||||
// TODO(cushon): represent phases as separate immutable data.
|
||||
|
||||
/** True if the entire {@link Level} fits on one line. */
|
||||
boolean oneLine = false;
|
||||
|
||||
/**
|
||||
* Groups of {@link Doc}s that are children of the current {@link Level}, separated by {@link
|
||||
* Break}s.
|
||||
*/
|
||||
List<List<Doc>> splits = new ArrayList<>();
|
||||
|
||||
/** {@link Break}s between {@link Doc}s in the current {@link Level}. */
|
||||
List<Break> breaks = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) {
|
||||
float thisWidth = getWidth();
|
||||
@@ -529,6 +257,20 @@ public abstract class Doc {
|
||||
return state.withColumn(broken.column);
|
||||
}
|
||||
|
||||
private static void splitByBreaks(List<Doc> docs, List<List<Doc>> splits, List<Break> breaks) {
|
||||
splits.clear();
|
||||
breaks.clear();
|
||||
splits.add(new ArrayList<>());
|
||||
for (Doc doc : docs) {
|
||||
if (doc instanceof Break) {
|
||||
breaks.add((Break) doc);
|
||||
splits.add(new ArrayList<>());
|
||||
} else {
|
||||
getLast(splits).add(doc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Compute breaks for a {@link Level} that spans multiple lines. */
|
||||
private State computeBroken(CommentsHelper commentsHelper, int maxWidth, State state) {
|
||||
splitByBreaks(docs, splits, breaks);
|
||||
@@ -546,20 +288,6 @@ public abstract class Doc {
|
||||
return state;
|
||||
}
|
||||
|
||||
private static void splitByBreaks(List<Doc> docs, List<List<Doc>> splits, List<Break> breaks) {
|
||||
splits.clear();
|
||||
breaks.clear();
|
||||
splits.add(new ArrayList<>());
|
||||
for (Doc doc : docs) {
|
||||
if (doc instanceof Break) {
|
||||
breaks.add((Break) doc);
|
||||
splits.add(new ArrayList<>());
|
||||
} else {
|
||||
getLast(splits).add(doc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Lay out a Break-separated group of Docs in the current Level. */
|
||||
private static State computeBreakAndSplit(
|
||||
CommentsHelper commentsHelper,
|
||||
@@ -593,20 +321,6 @@ public abstract class Doc {
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the width of a sequence of {@link Doc}s.
|
||||
*
|
||||
* @param docs the {@link Doc}s
|
||||
* @return the width, or {@code Float.POSITIVE_INFINITY} if any {@link Doc} must be broken
|
||||
*/
|
||||
static float getWidth(List<Doc> docs) {
|
||||
float width = 0.0F;
|
||||
for (Doc doc : docs) {
|
||||
width += doc.getWidth();
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Output output) {
|
||||
if (oneLine) {
|
||||
@@ -630,9 +344,151 @@ public abstract class Doc {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the width of a sequence of {@link Doc}s.
|
||||
*
|
||||
* @param docs the {@link Doc}s
|
||||
* @return the width, or {@code Float.POSITIVE_INFINITY} if any {@link Doc} must be broken
|
||||
*/
|
||||
static float getWidth(List<Doc> docs) {
|
||||
float width = 0.0F;
|
||||
for (Doc doc : docs) {
|
||||
width += doc.getWidth();
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
private static Range<Integer> union(Range<Integer> x, Range<Integer> y) {
|
||||
return x.isEmpty() ? y : y.isEmpty() ? x : x.span(y).canonical(INTEGERS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("plusIndent", plusIndent)
|
||||
.add("docs", docs)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/** A leaf {@link Doc} for a token. */
|
||||
public static final class Token extends Doc implements Op {
|
||||
/** Is a Token a real token, or imaginary (e.g., a token generated incorrectly, or an EOF)? */
|
||||
public enum RealOrImaginary {
|
||||
REAL,
|
||||
IMAGINARY;
|
||||
|
||||
boolean isReal() {
|
||||
return this == REAL;
|
||||
}
|
||||
}
|
||||
|
||||
private final Input.Token token;
|
||||
private final RealOrImaginary realOrImaginary;
|
||||
private final Indent plusIndentCommentsBefore;
|
||||
private final Optional<Indent> breakAndIndentTrailingComment;
|
||||
|
||||
private Token(
|
||||
Input.Token token,
|
||||
RealOrImaginary realOrImaginary,
|
||||
Indent plusIndentCommentsBefore,
|
||||
Optional<Indent> breakAndIndentTrailingComment) {
|
||||
this.token = token;
|
||||
this.realOrImaginary = realOrImaginary;
|
||||
this.plusIndentCommentsBefore = plusIndentCommentsBefore;
|
||||
this.breakAndIndentTrailingComment = breakAndIndentTrailingComment;
|
||||
}
|
||||
|
||||
/**
|
||||
* How much extra to indent comments before the {@code Token}.
|
||||
*
|
||||
* @return the extra indent
|
||||
*/
|
||||
Indent getPlusIndentCommentsBefore() {
|
||||
return plusIndentCommentsBefore;
|
||||
}
|
||||
|
||||
/** Force a line break and indent trailing javadoc or block comments. */
|
||||
Optional<Indent> breakAndIndentTrailingComment() {
|
||||
return breakAndIndentTrailingComment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a {@code Token}.
|
||||
*
|
||||
* @param token the {@link Input.Token} to wrap
|
||||
* @param realOrImaginary did this {@link Input.Token} appear in the input, or was it generated
|
||||
* incorrectly?
|
||||
* @param plusIndentCommentsBefore extra {@code plusIndent} for comments just before this token
|
||||
* @return the new {@code Token}
|
||||
*/
|
||||
static Op make(
|
||||
Input.Token token,
|
||||
Doc.Token.RealOrImaginary realOrImaginary,
|
||||
Indent plusIndentCommentsBefore,
|
||||
Optional<Indent> breakAndIndentTrailingComment) {
|
||||
return new Token(
|
||||
token, realOrImaginary, plusIndentCommentsBefore, breakAndIndentTrailingComment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the wrapped {@link Input.Token}.
|
||||
*
|
||||
* @return the {@link Input.Token}
|
||||
*/
|
||||
Input.Token getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the token good? That is, does it match an {@link Input.Token}?
|
||||
*
|
||||
* @return whether the @code Token} is good
|
||||
*/
|
||||
RealOrImaginary realOrImaginary() {
|
||||
return realOrImaginary;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(DocBuilder builder) {
|
||||
builder.add(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
float computeWidth() {
|
||||
return token.getTok().length();
|
||||
}
|
||||
|
||||
@Override
|
||||
String computeFlat() {
|
||||
return token.getTok().getOriginalText();
|
||||
}
|
||||
|
||||
@Override
|
||||
Range<Integer> computeRange() {
|
||||
return Range.singleton(token.getTok().getIndex()).canonical(INTEGERS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) {
|
||||
String text = token.getTok().getOriginalText();
|
||||
return state.withColumn(state.column + text.length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Output output) {
|
||||
String text = token.getTok().getOriginalText();
|
||||
output.append(text, range());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("token", token)
|
||||
.add("realOrImaginary", realOrImaginary)
|
||||
.add("plusIndentCommentsBefore", plusIndentCommentsBefore)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/** A Leaf node in a {@link Doc} for a non-breaking space. */
|
||||
@@ -655,11 +511,6 @@ public abstract class Doc {
|
||||
builder.add(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this).toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
float computeWidth() {
|
||||
return 1.0F;
|
||||
@@ -684,12 +535,155 @@ public abstract class Doc {
|
||||
public void write(Output output) {
|
||||
output.append(" ", range());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this).toString();
|
||||
}
|
||||
}
|
||||
|
||||
/** A leaf node in a {@link Doc} for an optional break. */
|
||||
public static final class Break extends Doc implements Op {
|
||||
private final FillMode fillMode;
|
||||
private final String flat;
|
||||
private final Indent plusIndent;
|
||||
private final Optional<BreakTag> optTag;
|
||||
|
||||
private Break(FillMode fillMode, String flat, Indent plusIndent, Optional<BreakTag> optTag) {
|
||||
this.fillMode = fillMode;
|
||||
this.flat = flat;
|
||||
this.plusIndent = plusIndent;
|
||||
this.optTag = optTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a {@code Break}.
|
||||
*
|
||||
* @param fillMode the {@link FillMode}
|
||||
* @param flat the text when not broken
|
||||
* @param plusIndent extra indent if taken
|
||||
* @return the new {@code Break}
|
||||
*/
|
||||
public static Break make(FillMode fillMode, String flat, Indent plusIndent) {
|
||||
return new Break(fillMode, flat, plusIndent, /* optTag= */ Optional.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a {@code Break}.
|
||||
*
|
||||
* @param fillMode the {@link FillMode}
|
||||
* @param flat the text when not broken
|
||||
* @param plusIndent extra indent if taken
|
||||
* @param optTag an optional tag for remembering whether the break was taken
|
||||
* @return the new {@code Break}
|
||||
*/
|
||||
public static Break make(
|
||||
FillMode fillMode, String flat, Indent plusIndent, Optional<BreakTag> optTag) {
|
||||
return new Break(fillMode, flat, plusIndent, optTag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a forced {@code Break}.
|
||||
*
|
||||
* @return the new forced {@code Break}
|
||||
*/
|
||||
public static Break makeForced() {
|
||||
return make(FillMode.FORCED, "", Indent.Const.ZERO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@code Break}'s extra indent.
|
||||
*
|
||||
* @return the extra indent
|
||||
*/
|
||||
int getPlusIndent() {
|
||||
return plusIndent.eval();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the {@code Break} forced?
|
||||
*
|
||||
* @return whether the {@code Break} is forced
|
||||
*/
|
||||
boolean isForced() {
|
||||
return fillMode == FillMode.FORCED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(DocBuilder builder) {
|
||||
builder.breakDoc(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
float computeWidth() {
|
||||
return isForced() ? Float.POSITIVE_INFINITY : (float) flat.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
String computeFlat() {
|
||||
return flat;
|
||||
}
|
||||
|
||||
@Override
|
||||
Range<Integer> computeRange() {
|
||||
return EMPTY_RANGE;
|
||||
}
|
||||
|
||||
/** Was this break taken? */
|
||||
boolean broken;
|
||||
|
||||
/** New indent after this break. */
|
||||
int newIndent;
|
||||
|
||||
public State computeBreaks(State state, int lastIndent, boolean broken) {
|
||||
if (optTag.isPresent()) {
|
||||
optTag.get().recordBroken(broken);
|
||||
}
|
||||
|
||||
if (broken) {
|
||||
this.broken = true;
|
||||
this.newIndent = max(lastIndent + plusIndent.eval(), 0);
|
||||
return state.withColumn(newIndent);
|
||||
} else {
|
||||
this.broken = false;
|
||||
this.newIndent = -1;
|
||||
return state.withColumn(state.column + flat.length());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) {
|
||||
// Updating the state for {@link Break}s requires deciding if the break
|
||||
// should be taken.
|
||||
// TODO(cushon): this hierarchy is wrong, create a separate interface
|
||||
// for unbreakable Docs?
|
||||
throw new UnsupportedOperationException("Did you mean computeBreaks(State, int, boolean)?");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Output output) {
|
||||
if (broken) {
|
||||
output.append("\n", EMPTY_RANGE);
|
||||
output.indent(newIndent);
|
||||
} else {
|
||||
output.append(flat, range());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("fillMode", fillMode)
|
||||
.add("flat", flat)
|
||||
.add("plusIndent", plusIndent)
|
||||
.add("optTag", optTag)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/** A leaf node in a {@link Doc} for a non-token. */
|
||||
static final class Tok extends Doc implements Op {
|
||||
private final Input.Tok tok;
|
||||
String text;
|
||||
|
||||
private Tok(Input.Tok tok) {
|
||||
this.tok = tok;
|
||||
@@ -710,11 +704,6 @@ public abstract class Doc {
|
||||
builder.add(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this).add("tok", tok).toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
float computeWidth() {
|
||||
int idx = Newlines.firstBreak(tok.getOriginalText());
|
||||
@@ -726,7 +715,7 @@ public abstract class Doc {
|
||||
// Account for line comments with missing spaces, see computeFlat.
|
||||
return tok.length() + 1;
|
||||
} else {
|
||||
return tok.length();
|
||||
return reformatParameterComment(tok).map(String::length).orElse(tok.length());
|
||||
}
|
||||
}
|
||||
return idx != -1 ? Float.POSITIVE_INFINITY : (float) tok.length();
|
||||
@@ -734,15 +723,13 @@ public abstract class Doc {
|
||||
|
||||
@Override
|
||||
String computeFlat() {
|
||||
// TODO(cushon): commentsHelper.rewrite doesn't get called for spans that fit in a
|
||||
// single
|
||||
// line. That's fine for multi-line comment reflowing, but problematic for adding
|
||||
// missing
|
||||
// TODO(cushon): commentsHelper.rewrite doesn't get called for spans that fit in a single
|
||||
// line. That's fine for multi-line comment reflowing, but problematic for adding missing
|
||||
// spaces in line comments.
|
||||
if (tok.isSlashSlashComment() && !tok.getOriginalText().startsWith("// ")) {
|
||||
return "// " + tok.getOriginalText().substring("//".length());
|
||||
}
|
||||
return tok.getOriginalText();
|
||||
return reformatParameterComment(tok).orElse(tok.getOriginalText());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -750,6 +737,8 @@ public abstract class Doc {
|
||||
return Range.singleton(tok.getIndex()).canonical(INTEGERS);
|
||||
}
|
||||
|
||||
String text;
|
||||
|
||||
@Override
|
||||
public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) {
|
||||
text = commentsHelper.rewrite(tok, maxWidth, state.column);
|
||||
@@ -761,5 +750,10 @@ public abstract class Doc {
|
||||
public void write(Output output) {
|
||||
output.append(text, range());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this).add("tok", tok).toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+18
-19
@@ -15,7 +15,6 @@
|
||||
package com.google.googlejavaformat;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.List;
|
||||
|
||||
@@ -60,24 +59,6 @@ public final class DocBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link Doc}.
|
||||
*
|
||||
* @return the {@link Doc}
|
||||
*/
|
||||
public Doc build() {
|
||||
return base;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("base", base)
|
||||
.add("stack", stack)
|
||||
.add("appendLevel", appendLevel)
|
||||
.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a new {@link Doc.Level}.
|
||||
*
|
||||
@@ -112,4 +93,22 @@ public final class DocBuilder {
|
||||
appendLevel = stack.peekLast();
|
||||
appendLevel.add(breakDoc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link Doc}.
|
||||
*
|
||||
* @return the {@link Doc}
|
||||
*/
|
||||
public Doc build() {
|
||||
return base;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("base", base)
|
||||
.add("stack", stack)
|
||||
.add("appendLevel", appendLevel)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
+7
-8
@@ -23,12 +23,6 @@ public class FormatterDiagnostic {
|
||||
private final String message;
|
||||
private final int column;
|
||||
|
||||
private FormatterDiagnostic(int lineNumber, int column, String message) {
|
||||
this.lineNumber = lineNumber;
|
||||
this.column = column;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public static FormatterDiagnostic create(String message) {
|
||||
return new FormatterDiagnostic(-1, -1, message);
|
||||
}
|
||||
@@ -40,6 +34,12 @@ public class FormatterDiagnostic {
|
||||
return new FormatterDiagnostic(lineNumber, column, message);
|
||||
}
|
||||
|
||||
private FormatterDiagnostic(int lineNumber, int column, String message) {
|
||||
this.lineNumber = lineNumber;
|
||||
this.column = column;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the line number on which the error occurred, or {@code -1} if the error does not have a
|
||||
* line number.
|
||||
@@ -67,8 +67,7 @@ public class FormatterDiagnostic {
|
||||
sb.append(lineNumber).append(':');
|
||||
}
|
||||
if (column >= 0) {
|
||||
// internal column numbers are 0-based, but diagnostics use 1-based indexing by
|
||||
// convention
|
||||
// internal column numbers are 0-based, but diagnostics use 1-based indexing by convention
|
||||
sb.append(column + 1).append(':');
|
||||
}
|
||||
if (lineNumber >= 0 || column >= 0) {
|
||||
|
||||
+11
-10
@@ -28,9 +28,10 @@ public abstract class Indent {
|
||||
|
||||
/** A constant function, returning a constant indent. */
|
||||
public static final class Const extends Indent {
|
||||
public static final Const ZERO = new Const(+0);
|
||||
private final int n;
|
||||
|
||||
public static final Const ZERO = new Const(+0);
|
||||
|
||||
private Const(int n) {
|
||||
this.n = n;
|
||||
}
|
||||
@@ -40,13 +41,13 @@ public abstract class Indent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this).add("n", n).toString();
|
||||
int eval() {
|
||||
return n;
|
||||
}
|
||||
|
||||
@Override
|
||||
int eval() {
|
||||
return n;
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this).add("n", n).toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +67,11 @@ public abstract class Indent {
|
||||
return new If(condition, thenIndent, elseIndent);
|
||||
}
|
||||
|
||||
@Override
|
||||
int eval() {
|
||||
return (condition.wasBreakTaken() ? thenIndent : elseIndent).eval();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
@@ -74,10 +80,5 @@ public abstract class Indent {
|
||||
.add("elseIndent", elseIndent)
|
||||
.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
int eval() {
|
||||
return (condition.wasBreakTaken() ? thenIndent : elseIndent).eval();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+48
-48
@@ -21,54 +21,6 @@ import com.google.common.collect.ImmutableRangeMap;
|
||||
|
||||
/** An input to the formatter. */
|
||||
public abstract class Input extends InputOutput {
|
||||
/**
|
||||
* Get the input tokens.
|
||||
*
|
||||
* @return the input tokens
|
||||
*/
|
||||
public abstract ImmutableList<? extends Token> getTokens();
|
||||
|
||||
/** A map from [start, end] position ranges to {@link Token}s. */
|
||||
public abstract ImmutableRangeMap<Integer, ? extends Token> getPositionTokenMap();
|
||||
|
||||
public abstract ImmutableMap<Integer, Integer> getPositionToColumnMap();
|
||||
|
||||
public abstract String getText();
|
||||
|
||||
/**
|
||||
* Get the number of toks.
|
||||
*
|
||||
* @return the number of toks, excluding the EOF tok
|
||||
*/
|
||||
public abstract int getkN();
|
||||
|
||||
/**
|
||||
* Get the Token by index.
|
||||
*
|
||||
* @param k the Tok index
|
||||
*/
|
||||
public abstract Token getToken(int k);
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this).add("super", super.toString()).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a diagnostic. Populates the input filename, and converts character offsets to
|
||||
* numbers.
|
||||
*/
|
||||
public FormatterDiagnostic createDiagnostic(int inputPosition, String message) {
|
||||
return FormatterDiagnostic.create(
|
||||
getLineNumber(inputPosition), getColumnNumber(inputPosition), message);
|
||||
}
|
||||
|
||||
/** Converts a character offset in the input to a line number. */
|
||||
public abstract int getLineNumber(int inputPosition);
|
||||
|
||||
/** Converts a character offset in the input to a 0-based column number. */
|
||||
public abstract int getColumnNumber(int inputPosition);
|
||||
|
||||
/**
|
||||
* A {@code Tok} ("tock") is a token, or a comment, or a newline, or a maximal string of blanks. A
|
||||
* token {@code Tok} underlies a {@link Token}, and each other {@code Tok} is attached to a single
|
||||
@@ -144,4 +96,52 @@ public abstract class Input extends InputOutput {
|
||||
*/
|
||||
ImmutableList<? extends Tok> getToksAfter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the input tokens.
|
||||
*
|
||||
* @return the input tokens
|
||||
*/
|
||||
public abstract ImmutableList<? extends Token> getTokens();
|
||||
|
||||
/** A map from [start, end] position ranges to {@link Token}s. */
|
||||
public abstract ImmutableRangeMap<Integer, ? extends Token> getPositionTokenMap();
|
||||
|
||||
public abstract ImmutableMap<Integer, Integer> getPositionToColumnMap();
|
||||
|
||||
public abstract String getText();
|
||||
|
||||
/**
|
||||
* Get the number of toks.
|
||||
*
|
||||
* @return the number of toks, excluding the EOF tok
|
||||
*/
|
||||
public abstract int getkN();
|
||||
|
||||
/**
|
||||
* Get the Token by index.
|
||||
*
|
||||
* @param k the Tok index
|
||||
*/
|
||||
public abstract Token getToken(int k);
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this).add("super", super.toString()).toString();
|
||||
}
|
||||
|
||||
/** Converts a character offset in the input to a line number. */
|
||||
public abstract int getLineNumber(int inputPosition);
|
||||
|
||||
/** Converts a character offset in the input to a 0-based column number. */
|
||||
public abstract int getColumnNumber(int inputPosition);
|
||||
|
||||
/**
|
||||
* Construct a diagnostic. Populates the input filename, and converts character offsets to
|
||||
* numbers.
|
||||
*/
|
||||
public FormatterDiagnostic createDiagnostic(int inputPosition, String message) {
|
||||
return FormatterDiagnostic.create(
|
||||
getLineNumber(inputPosition), getColumnNumber(inputPosition), message);
|
||||
}
|
||||
}
|
||||
|
||||
+49
-49
@@ -17,7 +17,6 @@ package com.google.googlejavaformat;
|
||||
import com.google.common.collect.DiscreteDomain;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -25,12 +24,60 @@ import java.util.Map;
|
||||
|
||||
/** This interface defines methods common to an {@link Input} or an {@link Output}. */
|
||||
public abstract class InputOutput {
|
||||
private ImmutableList<String> lines = ImmutableList.of();
|
||||
|
||||
protected static final Range<Integer> EMPTY_RANGE = Range.closedOpen(-1, -1);
|
||||
private static final DiscreteDomain<Integer> INTEGERS = DiscreteDomain.integers();
|
||||
|
||||
/** Set the lines. */
|
||||
protected final void setLines(ImmutableList<String> lines) {
|
||||
this.lines = lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the line count.
|
||||
*
|
||||
* @return the line count
|
||||
*/
|
||||
public final int getLineCount() {
|
||||
return lines.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a line.
|
||||
*
|
||||
* @param lineI the line number
|
||||
* @return the line
|
||||
*/
|
||||
public final String getLine(int lineI) {
|
||||
return lines.get(lineI);
|
||||
}
|
||||
|
||||
/** The {@link Range}s of the tokens or comments lying on each line, in any part. */
|
||||
protected final List<Range<Integer>> ranges = new ArrayList<>();
|
||||
|
||||
private ImmutableList<String> lines = ImmutableList.of();
|
||||
private static void addToRanges(List<Range<Integer>> ranges, int i, int k) {
|
||||
while (ranges.size() <= i) {
|
||||
ranges.add(EMPTY_RANGE);
|
||||
}
|
||||
Range<Integer> oldValue = ranges.get(i);
|
||||
ranges.set(i, Range.closedOpen(oldValue.isEmpty() ? k : oldValue.lowerEndpoint(), k + 1));
|
||||
}
|
||||
|
||||
protected final void computeRanges(List<? extends Input.Tok> toks) {
|
||||
int lineI = 0;
|
||||
for (Input.Tok tok : toks) {
|
||||
String txt = tok.getOriginalText();
|
||||
int lineI0 = lineI;
|
||||
lineI += Newlines.count(txt);
|
||||
int k = tok.getIndex();
|
||||
if (k >= 0) {
|
||||
for (int i = lineI0; i <= lineI; i++) {
|
||||
addToRanges(ranges, i, k);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an {@code InputOutput}, compute the map from tok indices to line ranges.
|
||||
@@ -55,15 +102,6 @@ public abstract class InputOutput {
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the line count.
|
||||
*
|
||||
* @return the line count
|
||||
*/
|
||||
public final int getLineCount() {
|
||||
return lines.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link Range} of {@link Input.Tok}s lying in any part on a line.
|
||||
*
|
||||
@@ -74,46 +112,8 @@ public abstract class InputOutput {
|
||||
return 0 <= lineI && lineI < ranges.size() ? ranges.get(lineI) : EMPTY_RANGE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a line.
|
||||
*
|
||||
* @param lineI the line number
|
||||
* @return the line
|
||||
*/
|
||||
public final String getLine(int lineI) {
|
||||
return lines.get(lineI);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "InputOutput{" + "lines=" + lines + ", ranges=" + ranges + '}';
|
||||
}
|
||||
|
||||
/** Set the lines. */
|
||||
protected final void setLines(ImmutableList<String> lines) {
|
||||
this.lines = lines;
|
||||
}
|
||||
|
||||
protected final void computeRanges(List<? extends Input.Tok> toks) {
|
||||
int lineI = 0;
|
||||
for (Input.Tok tok : toks) {
|
||||
String txt = tok.getOriginalText();
|
||||
int lineI0 = lineI;
|
||||
lineI += Newlines.count(txt);
|
||||
int k = tok.getIndex();
|
||||
if (k >= 0) {
|
||||
for (int i = lineI0; i <= lineI; i++) {
|
||||
addToRanges(ranges, i, k);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void addToRanges(List<Range<Integer>> ranges, int i, int k) {
|
||||
while (ranges.size() <= i) {
|
||||
ranges.add(EMPTY_RANGE);
|
||||
}
|
||||
Range<Integer> oldValue = ranges.get(i);
|
||||
ranges.set(i, Range.closedOpen(oldValue.isEmpty() ? k : oldValue.lowerEndpoint(), k + 1));
|
||||
}
|
||||
}
|
||||
|
||||
+11
-11
@@ -17,25 +17,17 @@ package com.google.googlejavaformat;
|
||||
import com.google.common.base.CharMatcher;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterators;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/** Platform-independent newline handling. */
|
||||
public class Newlines {
|
||||
|
||||
private static final ImmutableSet<String> BREAKS = ImmutableSet.of("\r\n", "\n", "\r");
|
||||
|
||||
/** Returns the number of line breaks in the input. */
|
||||
public static int count(String input) {
|
||||
return Iterators.size(lineOffsetIterator(input)) - 1;
|
||||
}
|
||||
|
||||
/** Returns an iterator over the start offsets of lines in the input. */
|
||||
public static Iterator<Integer> lineOffsetIterator(String input) {
|
||||
return new LineOffsetIterator(input);
|
||||
}
|
||||
|
||||
/** Returns the index of the first break in the input, or {@code -1}. */
|
||||
public static int firstBreak(String input) {
|
||||
Iterator<Integer> it = lineOffsetIterator(input);
|
||||
@@ -43,6 +35,8 @@ public class Newlines {
|
||||
return it.hasNext() ? it.next() : -1;
|
||||
}
|
||||
|
||||
private static final ImmutableSet<String> BREAKS = ImmutableSet.of("\r\n", "\n", "\r");
|
||||
|
||||
/** Returns true if the entire input string is a recognized line break. */
|
||||
public static boolean isNewline(String input) {
|
||||
return BREAKS.contains(input);
|
||||
@@ -98,6 +92,11 @@ public class Newlines {
|
||||
return CharMatcher.anyOf("\n\r").matchesAnyOf(text);
|
||||
}
|
||||
|
||||
/** Returns an iterator over the start offsets of lines in the input. */
|
||||
public static Iterator<Integer> lineOffsetIterator(String input) {
|
||||
return new LineOffsetIterator(input);
|
||||
}
|
||||
|
||||
/** Returns an iterator over lines in the input, including trailing whitespace. */
|
||||
public static Iterator<String> lineIterator(String input) {
|
||||
return new LineIterator(input);
|
||||
@@ -105,9 +104,9 @@ public class Newlines {
|
||||
|
||||
private static class LineOffsetIterator implements Iterator<Integer> {
|
||||
|
||||
private final String input;
|
||||
private int curr = 0;
|
||||
private int idx = 0;
|
||||
private final String input;
|
||||
|
||||
private LineOffsetIterator(String input) {
|
||||
this.input = input;
|
||||
@@ -156,11 +155,12 @@ public class Newlines {
|
||||
|
||||
private static class LineIterator implements Iterator<String> {
|
||||
|
||||
private final String input;
|
||||
private final Iterator<Integer> indices;
|
||||
int idx;
|
||||
String curr;
|
||||
|
||||
private final String input;
|
||||
private final Iterator<Integer> indices;
|
||||
|
||||
private LineIterator(String input) {
|
||||
this.input = input;
|
||||
this.indices = lineOffsetIterator(input);
|
||||
|
||||
+180
-182
@@ -28,7 +28,6 @@ import com.google.googlejavaformat.Indent.Const;
|
||||
import com.google.googlejavaformat.Input.Tok;
|
||||
import com.google.googlejavaformat.Input.Token;
|
||||
import com.google.googlejavaformat.Output.BreakTag;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -39,31 +38,7 @@ import java.util.Optional;
|
||||
*/
|
||||
public final class OpsBuilder {
|
||||
|
||||
private static final Indent.Const ZERO = Indent.Const.ZERO;
|
||||
private static final Doc.Space SPACE = Doc.Space.make();
|
||||
private final Input input;
|
||||
private final List<Op> ops = new ArrayList<>();
|
||||
private final Output output;
|
||||
private int tokenI = 0;
|
||||
private int inputPosition = Integer.MIN_VALUE;
|
||||
private int lastPartialFormatBoundary = -1;
|
||||
/** The number of unclosed open ops in the input stream. */
|
||||
int depth = 0;
|
||||
|
||||
/**
|
||||
* The {@code OpsBuilder} constructor.
|
||||
*
|
||||
* @param input the {@link Input}, used for retrieve information from the AST
|
||||
* @param output the {@link Output}, used here only to record blank-line information
|
||||
*/
|
||||
public OpsBuilder(Input input, Output output) {
|
||||
this.input = input;
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the actual size of the AST node at position, including comments.
|
||||
*/
|
||||
/** @return the actual size of the AST node at position, including comments. */
|
||||
public int actualSize(int position, int length) {
|
||||
Token startToken = input.getPositionTokenMap().get(position);
|
||||
int start = startToken.getTok().getPosition();
|
||||
@@ -82,9 +57,7 @@ public final class OpsBuilder {
|
||||
return end - start;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the start column of the token at {@code position}, including leading comments.
|
||||
*/
|
||||
/** @return the start column of the token at {@code position}, including leading comments. */
|
||||
public Integer actualStartColumn(int position) {
|
||||
Token startToken = input.getPositionTokenMap().get(position);
|
||||
int start = startToken.getTok().getPosition();
|
||||
@@ -100,15 +73,93 @@ public final class OpsBuilder {
|
||||
return start;
|
||||
}
|
||||
|
||||
/** Add a list of {@link Op}s. */
|
||||
public final void addAll(List<Op> ops) {
|
||||
for (Op op : ops) {
|
||||
add(op);
|
||||
/** A request to add or remove a blank line in the output. */
|
||||
public abstract static class BlankLineWanted {
|
||||
|
||||
/** Always emit a blank line. */
|
||||
public static final BlankLineWanted YES = new SimpleBlankLine(Optional.of(true));
|
||||
|
||||
/** Never emit a blank line. */
|
||||
public static final BlankLineWanted NO = new SimpleBlankLine(Optional.of(false));
|
||||
|
||||
/**
|
||||
* Explicitly preserve blank lines from the input (e.g. before the first member in a class
|
||||
* declaration). Overrides conditional blank lines.
|
||||
*/
|
||||
public static final BlankLineWanted PRESERVE =
|
||||
new SimpleBlankLine(/* wanted= */ Optional.empty());
|
||||
|
||||
/** Is the blank line wanted? */
|
||||
public abstract Optional<Boolean> wanted();
|
||||
|
||||
/** Merge this blank line request with another. */
|
||||
public abstract BlankLineWanted merge(BlankLineWanted wanted);
|
||||
|
||||
/** Emit a blank line if the given break is taken. */
|
||||
public static BlankLineWanted conditional(BreakTag breakTag) {
|
||||
return new ConditionalBlankLine(ImmutableList.of(breakTag));
|
||||
}
|
||||
|
||||
private static final class SimpleBlankLine extends BlankLineWanted {
|
||||
private final Optional<Boolean> wanted;
|
||||
|
||||
SimpleBlankLine(Optional<Boolean> wanted) {
|
||||
this.wanted = wanted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Boolean> wanted() {
|
||||
return wanted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlankLineWanted merge(BlankLineWanted other) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ConditionalBlankLine extends BlankLineWanted {
|
||||
|
||||
private final ImmutableList<BreakTag> tags;
|
||||
|
||||
ConditionalBlankLine(Iterable<BreakTag> tags) {
|
||||
this.tags = ImmutableList.copyOf(tags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Boolean> wanted() {
|
||||
for (BreakTag tag : tags) {
|
||||
if (tag.wasBreakTaken()) {
|
||||
return Optional.of(true);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlankLineWanted merge(BlankLineWanted other) {
|
||||
if (!(other instanceof ConditionalBlankLine)) {
|
||||
return other;
|
||||
}
|
||||
return new ConditionalBlankLine(
|
||||
Iterables.concat(this.tags, ((ConditionalBlankLine) other).tags));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final Input input;
|
||||
private final List<Op> ops = new ArrayList<>();
|
||||
private final Output output;
|
||||
private static final Indent.Const ZERO = Indent.Const.ZERO;
|
||||
|
||||
private int tokenI = 0;
|
||||
private int inputPosition = Integer.MIN_VALUE;
|
||||
|
||||
/** The number of unclosed open ops in the input stream. */
|
||||
int depth = 0;
|
||||
|
||||
/** Add an {@link Op}, and record open/close ops for later validation of unclosed levels. */
|
||||
private void add(Op op) {
|
||||
public final void add(Op op) {
|
||||
if (op instanceof OpenOp) {
|
||||
depth++;
|
||||
} else if (op instanceof CloseOp) {
|
||||
@@ -120,6 +171,24 @@ public final class OpsBuilder {
|
||||
ops.add(op);
|
||||
}
|
||||
|
||||
/** Add a list of {@link Op}s. */
|
||||
public final void addAll(List<Op> ops) {
|
||||
for (Op op : ops) {
|
||||
add(op);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@code OpsBuilder} constructor.
|
||||
*
|
||||
* @param input the {@link Input}, used for retrieve information from the AST
|
||||
* @param output the {@link Output}, used here only to record blank-line information
|
||||
*/
|
||||
public OpsBuilder(Input input, Output output) {
|
||||
this.input = input;
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
/** Get the {@code OpsBuilder}'s {@link Input}. */
|
||||
public final Input getInput() {
|
||||
return input;
|
||||
@@ -130,6 +199,22 @@ public final class OpsBuilder {
|
||||
return depth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that all open ops in the op stream have matching close ops.
|
||||
*
|
||||
* @throws FormattingError if any ops were unclosed
|
||||
*/
|
||||
public void checkClosed(int previous) {
|
||||
if (depth != previous) {
|
||||
throw new FormattingError(diagnostic(String.format("saw %d unclosed ops", depth)));
|
||||
}
|
||||
}
|
||||
|
||||
/** Create a {@link FormatterDiagnostic} at the current position. */
|
||||
public FormatterDiagnostic diagnostic(String message) {
|
||||
return input.createDiagnostic(inputPosition, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync to position in the input. If we've skipped outputting any tokens that were present in the
|
||||
* input tokens, output them here and optionally complain.
|
||||
@@ -150,11 +235,6 @@ public final class OpsBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
/** Create a {@link FormatterDiagnostic} at the current position. */
|
||||
public FormatterDiagnostic diagnostic(String message) {
|
||||
return input.createDiagnostic(inputPosition, message);
|
||||
}
|
||||
|
||||
/** Output any remaining tokens from the input stream (e.g. terminal whitespace). */
|
||||
public final void drain() {
|
||||
int inputPosition = input.getText().length() + 1;
|
||||
@@ -175,17 +255,6 @@ public final class OpsBuilder {
|
||||
checkClosed(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that all open ops in the op stream have matching close ops.
|
||||
*
|
||||
* @throws FormattingError if any ops were unclosed
|
||||
*/
|
||||
public void checkClosed(int previous) {
|
||||
if (depth != previous) {
|
||||
throw new FormattingError(diagnostic(String.format("saw %d unclosed ops", depth)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a new level by emitting an {@link OpenOp}.
|
||||
*
|
||||
@@ -200,6 +269,20 @@ public final class OpsBuilder {
|
||||
add(CloseOp.make());
|
||||
}
|
||||
|
||||
/** Return the text of the next {@link Input.Token}, or absent if there is none. */
|
||||
public final Optional<String> peekToken() {
|
||||
return peekToken(0);
|
||||
}
|
||||
|
||||
/** Return the text of an upcoming {@link Input.Token}, or absent if there is none. */
|
||||
public final Optional<String> peekToken(int skip) {
|
||||
ImmutableList<? extends Input.Token> tokens = input.getTokens();
|
||||
int idx = tokenI + skip;
|
||||
return idx < tokens.size()
|
||||
? Optional.of(tokens.get(idx).getTok().getOriginalText())
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Input.Tok}s starting at the current source position, which are satisfied by
|
||||
* the given predicate.
|
||||
@@ -264,20 +347,6 @@ public final class OpsBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
/** Return the text of the next {@link Input.Token}, or absent if there is none. */
|
||||
public final Optional<String> peekToken() {
|
||||
return peekToken(0);
|
||||
}
|
||||
|
||||
/** Return the text of an upcoming {@link Input.Token}, or absent if there is none. */
|
||||
public final Optional<String> peekToken(int skip) {
|
||||
ImmutableList<? extends Input.Token> tokens = input.getTokens();
|
||||
int idx = tokenI + skip;
|
||||
return idx < tokens.size()
|
||||
? Optional.of(tokens.get(idx).getTok().getOriginalText())
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a single- or multi-character op by breaking it into single-character {@link Doc.Token}s.
|
||||
*
|
||||
@@ -304,30 +373,6 @@ public final class OpsBuilder {
|
||||
breakOp(Doc.FillMode.UNIFIED, "", ZERO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a generic {@link Doc.Break}.
|
||||
*
|
||||
* @param fillMode the {@link Doc.FillMode}
|
||||
* @param flat the {@link Doc.Break} when not broken
|
||||
* @param plusIndent extra indent if taken
|
||||
*/
|
||||
public final void breakOp(Doc.FillMode fillMode, String flat, Indent plusIndent) {
|
||||
breakOp(fillMode, flat, plusIndent, /* optionalTag= */ Optional.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a generic {@link Doc.Break}.
|
||||
*
|
||||
* @param fillMode the {@link Doc.FillMode}
|
||||
* @param flat the {@link Doc.Break} when not broken
|
||||
* @param plusIndent extra indent if taken
|
||||
* @param optionalTag an optional tag for remembering whether the break was taken
|
||||
*/
|
||||
public final void breakOp(
|
||||
Doc.FillMode fillMode, String flat, Indent plusIndent, Optional<BreakTag> optionalTag) {
|
||||
add(Doc.Break.make(fillMode, flat, plusIndent, optionalTag));
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a {@link Doc.Break}.
|
||||
*
|
||||
@@ -374,6 +419,50 @@ public final class OpsBuilder {
|
||||
breakOp(Doc.FillMode.INDEPENDENT, flat, ZERO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a generic {@link Doc.Break}.
|
||||
*
|
||||
* @param fillMode the {@link Doc.FillMode}
|
||||
* @param flat the {@link Doc.Break} when not broken
|
||||
* @param plusIndent extra indent if taken
|
||||
*/
|
||||
public final void breakOp(Doc.FillMode fillMode, String flat, Indent plusIndent) {
|
||||
breakOp(fillMode, flat, plusIndent, /* optionalTag= */ Optional.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a generic {@link Doc.Break}.
|
||||
*
|
||||
* @param fillMode the {@link Doc.FillMode}
|
||||
* @param flat the {@link Doc.Break} when not broken
|
||||
* @param plusIndent extra indent if taken
|
||||
* @param optionalTag an optional tag for remembering whether the break was taken
|
||||
*/
|
||||
public final void breakOp(
|
||||
Doc.FillMode fillMode, String flat, Indent plusIndent, Optional<BreakTag> optionalTag) {
|
||||
add(Doc.Break.make(fillMode, flat, plusIndent, optionalTag));
|
||||
}
|
||||
|
||||
private int lastPartialFormatBoundary = -1;
|
||||
|
||||
/**
|
||||
* Make the boundary of a region that can be partially formatted. The boundary will be included in
|
||||
* the following region, e.g.: [[boundary0, boundary1), [boundary1, boundary2), ...].
|
||||
*/
|
||||
public void markForPartialFormat() {
|
||||
if (lastPartialFormatBoundary == -1) {
|
||||
lastPartialFormatBoundary = tokenI;
|
||||
return;
|
||||
}
|
||||
if (tokenI == lastPartialFormatBoundary) {
|
||||
return;
|
||||
}
|
||||
Token start = input.getTokens().get(lastPartialFormatBoundary);
|
||||
Token end = input.getTokens().get(tokenI - 1);
|
||||
output.markForPartialFormat(start, end);
|
||||
lastPartialFormatBoundary = tokenI;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force or suppress a blank line here in the output.
|
||||
*
|
||||
@@ -392,6 +481,8 @@ public final class OpsBuilder {
|
||||
return token.getTok().getIndex();
|
||||
}
|
||||
|
||||
private static final Doc.Space SPACE = Doc.Space.make();
|
||||
|
||||
/**
|
||||
* Build a list of {@link Op}s from the {@code OpsBuilder}.
|
||||
*
|
||||
@@ -452,8 +543,7 @@ public final class OpsBuilder {
|
||||
}
|
||||
}
|
||||
if (allowBlankAfterLastComment && newlines > 1) {
|
||||
// Force a line break after two newlines in a row following a line or block
|
||||
// comment
|
||||
// Force a line break after two newlines in a row following a line or block comment
|
||||
output.blankLine(token.getTok().getIndex(), BlankLineWanted.YES);
|
||||
}
|
||||
if (lastWasComment && newlines > 0) {
|
||||
@@ -545,24 +635,6 @@ public final class OpsBuilder {
|
||||
return newOps.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the boundary of a region that can be partially formatted. The boundary will be included in
|
||||
* the following region, e.g.: [[boundary0, boundary1), [boundary1, boundary2), ...].
|
||||
*/
|
||||
public void markForPartialFormat() {
|
||||
if (lastPartialFormatBoundary == -1) {
|
||||
lastPartialFormatBoundary = tokenI;
|
||||
return;
|
||||
}
|
||||
if (tokenI == lastPartialFormatBoundary) {
|
||||
return;
|
||||
}
|
||||
Token start = input.getTokens().get(lastPartialFormatBoundary);
|
||||
Token end = input.getTokens().get(tokenI - 1);
|
||||
output.markForPartialFormat(start, end);
|
||||
lastPartialFormatBoundary = tokenI;
|
||||
}
|
||||
|
||||
private static boolean isForcedBreak(Op op) {
|
||||
return op instanceof Doc.Break && ((Doc.Break) op).isForced();
|
||||
}
|
||||
@@ -583,78 +655,4 @@ public final class OpsBuilder {
|
||||
.add("inputPosition", inputPosition)
|
||||
.toString();
|
||||
}
|
||||
|
||||
/** A request to add or remove a blank line in the output. */
|
||||
public abstract static class BlankLineWanted {
|
||||
|
||||
/** Always emit a blank line. */
|
||||
public static final BlankLineWanted YES = new SimpleBlankLine(Optional.of(true));
|
||||
|
||||
/** Never emit a blank line. */
|
||||
public static final BlankLineWanted NO = new SimpleBlankLine(Optional.of(false));
|
||||
|
||||
/**
|
||||
* Explicitly preserve blank lines from the input (e.g. before the first member in a class
|
||||
* declaration). Overrides conditional blank lines.
|
||||
*/
|
||||
public static final BlankLineWanted PRESERVE =
|
||||
new SimpleBlankLine(/* wanted= */ Optional.empty());
|
||||
|
||||
/** Emit a blank line if the given break is taken. */
|
||||
public static BlankLineWanted conditional(BreakTag breakTag) {
|
||||
return new ConditionalBlankLine(ImmutableList.of(breakTag));
|
||||
}
|
||||
|
||||
/** Is the blank line wanted? */
|
||||
public abstract Optional<Boolean> wanted();
|
||||
|
||||
/** Merge this blank line request with another. */
|
||||
public abstract BlankLineWanted merge(BlankLineWanted wanted);
|
||||
|
||||
private static final class SimpleBlankLine extends BlankLineWanted {
|
||||
private final Optional<Boolean> wanted;
|
||||
|
||||
SimpleBlankLine(Optional<Boolean> wanted) {
|
||||
this.wanted = wanted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Boolean> wanted() {
|
||||
return wanted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlankLineWanted merge(BlankLineWanted other) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ConditionalBlankLine extends BlankLineWanted {
|
||||
|
||||
private final ImmutableList<BreakTag> tags;
|
||||
|
||||
ConditionalBlankLine(Iterable<BreakTag> tags) {
|
||||
this.tags = ImmutableList.copyOf(tags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Boolean> wanted() {
|
||||
for (BreakTag tag : tags) {
|
||||
if (tag.wasBreakTaken()) {
|
||||
return Optional.of(true);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlankLineWanted merge(BlankLineWanted other) {
|
||||
if (!(other instanceof ConditionalBlankLine)) {
|
||||
return other;
|
||||
}
|
||||
return new ConditionalBlankLine(
|
||||
Iterables.concat(this.tags, ((ConditionalBlankLine) other).tags));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+19
-20
@@ -17,11 +17,29 @@ package com.google.googlejavaformat;
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.collect.Range;
|
||||
import com.google.googlejavaformat.OpsBuilder.BlankLineWanted;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/** An output from the formatter. */
|
||||
public abstract class Output extends InputOutput {
|
||||
/** Unique identifier for a break. */
|
||||
public static final class BreakTag {
|
||||
|
||||
Optional<Boolean> taken = Optional.empty();
|
||||
|
||||
public void recordBroken(boolean broken) {
|
||||
// TODO(cushon): enforce invariants.
|
||||
// Currently we rely on setting Breaks multiple times, e.g. when deciding
|
||||
// whether a Level should be flowed. Using separate data structures
|
||||
// instead of mutation or adding an explicit 'reset' step would allow
|
||||
// a useful invariant to be enforced here.
|
||||
taken = Optional.of(broken);
|
||||
}
|
||||
|
||||
public boolean wasBreakTaken() {
|
||||
return taken.orElse(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indent by outputting {@code indent} spaces.
|
||||
*
|
||||
@@ -59,23 +77,4 @@ public abstract class Output extends InputOutput {
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this).add("super", super.toString()).toString();
|
||||
}
|
||||
|
||||
/** Unique identifier for a break. */
|
||||
public static final class BreakTag {
|
||||
|
||||
Optional<Boolean> taken = Optional.empty();
|
||||
|
||||
public void recordBroken(boolean broken) {
|
||||
// TODO(cushon): enforce invariants.
|
||||
// Currently we rely on setting Breaks multiple times, e.g. when deciding
|
||||
// whether a Level should be flowed. Using separate data structures
|
||||
// instead of mutation or adding an explicit 'reset' step would allow
|
||||
// a useful invariant to be enforced here.
|
||||
taken = Optional.of(broken);
|
||||
}
|
||||
|
||||
public boolean wasBreakTaken() {
|
||||
return taken.orElse(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+19
-20
@@ -16,7 +16,6 @@ package com.google.googlejavaformat.java;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableRangeSet;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
@@ -82,10 +81,6 @@ final class CommandLineOptions {
|
||||
this.formatJavadoc = formatJavadoc;
|
||||
}
|
||||
|
||||
static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/** The files to format. */
|
||||
ImmutableList<String> files() {
|
||||
return files;
|
||||
@@ -96,6 +91,21 @@ final class CommandLineOptions {
|
||||
return inPlace;
|
||||
}
|
||||
|
||||
/** Line ranges to format. */
|
||||
ImmutableRangeSet<Integer> lines() {
|
||||
return lines;
|
||||
}
|
||||
|
||||
/** Character offsets for partial formatting, paired with {@code lengths}. */
|
||||
ImmutableList<Integer> offsets() {
|
||||
return offsets;
|
||||
}
|
||||
|
||||
/** Partial formatting region lengths, paired with {@code offsets}. */
|
||||
ImmutableList<Integer> lengths() {
|
||||
return lengths;
|
||||
}
|
||||
|
||||
/** Use AOSP style instead of Google Style (4-space indentation). */
|
||||
boolean aosp() {
|
||||
return aosp;
|
||||
@@ -157,25 +167,14 @@ final class CommandLineOptions {
|
||||
return !lines().isEmpty() || !offsets().isEmpty() || !lengths().isEmpty();
|
||||
}
|
||||
|
||||
/** Line ranges to format. */
|
||||
ImmutableRangeSet<Integer> lines() {
|
||||
return lines;
|
||||
}
|
||||
|
||||
/** Character offsets for partial formatting, paired with {@code lengths}. */
|
||||
ImmutableList<Integer> offsets() {
|
||||
return offsets;
|
||||
}
|
||||
|
||||
/** Partial formatting region lengths, paired with {@code offsets}. */
|
||||
ImmutableList<Integer> lengths() {
|
||||
return lengths;
|
||||
}
|
||||
|
||||
boolean formatJavadoc() {
|
||||
return formatJavadoc;
|
||||
}
|
||||
|
||||
static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
static class Builder {
|
||||
|
||||
private final ImmutableList.Builder<String> files = ImmutableList.builder();
|
||||
|
||||
+75
-76
@@ -20,7 +20,6 @@ import com.google.common.base.CharMatcher;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ImmutableRangeSet;
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
@@ -38,81 +37,6 @@ final class CommandLineOptionsParser {
|
||||
private static final Splitter ARG_SPLITTER =
|
||||
Splitter.on(CharMatcher.breakingWhitespace()).omitEmptyStrings().trimResults();
|
||||
|
||||
private static Integer parseInteger(Iterator<String> it, String flag, String value) {
|
||||
try {
|
||||
return Integer.valueOf(getValue(flag, it, value));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("invalid integer value for %s: %s", flag, value), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getValue(String flag, Iterator<String> it, String value) {
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
if (!it.hasNext()) {
|
||||
throw new IllegalArgumentException("required value was not provided for: " + flag);
|
||||
}
|
||||
return it.next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse multiple --lines flags, like {"1:12,14,20:36", "40:45,50"}. Multiple ranges can be given
|
||||
* with multiple --lines flags or separated by commas. A single line can be set by a single
|
||||
* number. Line numbers are {@code 1}-based, but are converted to the {@code 0}-based numbering
|
||||
* used internally by google-java-format.
|
||||
*/
|
||||
private static void parseRangeSet(ImmutableRangeSet.Builder<Integer> result, String ranges) {
|
||||
for (String range : COMMA_SPLITTER.split(ranges)) {
|
||||
result.add(parseRange(range));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a range, as in "1:12" or "42". Line numbers provided are {@code 1}-based, but are
|
||||
* converted here to {@code 0}-based.
|
||||
*/
|
||||
private static Range<Integer> parseRange(String arg) {
|
||||
List<String> args = COLON_SPLITTER.splitToList(arg);
|
||||
switch (args.size()) {
|
||||
case 1:
|
||||
int line = Integer.parseInt(args.get(0)) - 1;
|
||||
return Range.closedOpen(line, line + 1);
|
||||
case 2:
|
||||
int line0 = Integer.parseInt(args.get(0)) - 1;
|
||||
int line1 = Integer.parseInt(args.get(1)) - 1;
|
||||
return Range.closedOpen(line0, line1 + 1);
|
||||
default:
|
||||
throw new IllegalArgumentException(arg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-processes an argument list, expanding arguments of the form {@code @filename} by reading
|
||||
* the content of the file and appending whitespace-delimited options to {@code arguments}.
|
||||
*/
|
||||
private static void expandParamsFiles(Iterable<String> args, List<String> expanded) {
|
||||
for (String arg : args) {
|
||||
if (arg.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (!arg.startsWith("@")) {
|
||||
expanded.add(arg);
|
||||
} else if (arg.startsWith("@@")) {
|
||||
expanded.add(arg.substring(1));
|
||||
} else {
|
||||
Path path = Paths.get(arg.substring(1));
|
||||
try {
|
||||
String sequence = new String(Files.readAllBytes(path), UTF_8);
|
||||
expandParamsFiles(ARG_SPLITTER.split(sequence), expanded);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(path + ": could not read file: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Parses {@link CommandLineOptions}. */
|
||||
static CommandLineOptions parse(Iterable<String> options) {
|
||||
CommandLineOptions.Builder optionsBuilder = CommandLineOptions.builder();
|
||||
@@ -207,4 +131,79 @@ final class CommandLineOptionsParser {
|
||||
}
|
||||
return optionsBuilder.build();
|
||||
}
|
||||
|
||||
private static Integer parseInteger(Iterator<String> it, String flag, String value) {
|
||||
try {
|
||||
return Integer.valueOf(getValue(flag, it, value));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("invalid integer value for %s: %s", flag, value), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getValue(String flag, Iterator<String> it, String value) {
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
if (!it.hasNext()) {
|
||||
throw new IllegalArgumentException("required value was not provided for: " + flag);
|
||||
}
|
||||
return it.next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse multiple --lines flags, like {"1:12,14,20:36", "40:45,50"}. Multiple ranges can be given
|
||||
* with multiple --lines flags or separated by commas. A single line can be set by a single
|
||||
* number. Line numbers are {@code 1}-based, but are converted to the {@code 0}-based numbering
|
||||
* used internally by google-java-format.
|
||||
*/
|
||||
private static void parseRangeSet(ImmutableRangeSet.Builder<Integer> result, String ranges) {
|
||||
for (String range : COMMA_SPLITTER.split(ranges)) {
|
||||
result.add(parseRange(range));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a range, as in "1:12" or "42". Line numbers provided are {@code 1}-based, but are
|
||||
* converted here to {@code 0}-based.
|
||||
*/
|
||||
private static Range<Integer> parseRange(String arg) {
|
||||
List<String> args = COLON_SPLITTER.splitToList(arg);
|
||||
switch (args.size()) {
|
||||
case 1:
|
||||
int line = Integer.parseInt(args.get(0)) - 1;
|
||||
return Range.closedOpen(line, line + 1);
|
||||
case 2:
|
||||
int line0 = Integer.parseInt(args.get(0)) - 1;
|
||||
int line1 = Integer.parseInt(args.get(1)) - 1;
|
||||
return Range.closedOpen(line0, line1 + 1);
|
||||
default:
|
||||
throw new IllegalArgumentException(arg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-processes an argument list, expanding arguments of the form {@code @filename} by reading
|
||||
* the content of the file and appending whitespace-delimited options to {@code arguments}.
|
||||
*/
|
||||
private static void expandParamsFiles(Iterable<String> args, List<String> expanded) {
|
||||
for (String arg : args) {
|
||||
if (arg.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (!arg.startsWith("@")) {
|
||||
expanded.add(arg);
|
||||
} else if (arg.startsWith("@@")) {
|
||||
expanded.add(arg.substring(1));
|
||||
} else {
|
||||
Path path = Paths.get(arg.substring(1));
|
||||
try {
|
||||
String sequence = new String(Files.readAllBytes(path), UTF_8);
|
||||
expandParamsFiles(ARG_SPLITTER.split(sequence), expanded);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(path + ": could not read file: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+34
-35
@@ -15,12 +15,11 @@
|
||||
package com.google.googlejavaformat.java;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import openjdk.source.tree.AnnotatedTypeTree;
|
||||
import openjdk.source.tree.AnnotationTree;
|
||||
import openjdk.source.tree.ArrayTypeTree;
|
||||
import openjdk.source.tree.Tree;
|
||||
import openjdk.tools.javac.tree.JCTree;
|
||||
|
||||
import com.sun.source.tree.AnnotatedTypeTree;
|
||||
import com.sun.source.tree.AnnotationTree;
|
||||
import com.sun.source.tree.ArrayTypeTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.tools.javac.tree.JCTree;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -37,6 +36,35 @@ import java.util.List;
|
||||
*/
|
||||
class DimensionHelpers {
|
||||
|
||||
/** The array dimension specifiers (including any type annotations) associated with a type. */
|
||||
static class TypeWithDims {
|
||||
final Tree node;
|
||||
final ImmutableList<List<AnnotationTree>> dims;
|
||||
|
||||
public TypeWithDims(Tree node, ImmutableList<List<AnnotationTree>> dims) {
|
||||
this.node = node;
|
||||
this.dims = dims;
|
||||
}
|
||||
}
|
||||
|
||||
enum SortedDims {
|
||||
YES,
|
||||
NO
|
||||
}
|
||||
|
||||
/** Returns a (possibly re-ordered) {@link TypeWithDims} for the given type. */
|
||||
static TypeWithDims extractDims(Tree node, SortedDims sorted) {
|
||||
Deque<List<AnnotationTree>> builder = new ArrayDeque<>();
|
||||
node = extractDims(builder, node);
|
||||
Iterable<List<AnnotationTree>> dims;
|
||||
if (sorted == SortedDims.YES) {
|
||||
dims = reorderBySourcePosition(builder);
|
||||
} else {
|
||||
dims = builder;
|
||||
}
|
||||
return new TypeWithDims(node, ImmutableList.copyOf(dims));
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotate the list of dimension specifiers until all dimensions with type annotations appear in
|
||||
* source order.
|
||||
@@ -93,33 +121,4 @@ class DimensionHelpers {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns a (possibly re-ordered) {@link TypeWithDims} for the given type. */
|
||||
static TypeWithDims extractDims(Tree node, SortedDims sorted) {
|
||||
Deque<List<AnnotationTree>> builder = new ArrayDeque<>();
|
||||
node = extractDims(builder, node);
|
||||
Iterable<List<AnnotationTree>> dims;
|
||||
if (sorted == SortedDims.YES) {
|
||||
dims = reorderBySourcePosition(builder);
|
||||
} else {
|
||||
dims = builder;
|
||||
}
|
||||
return new TypeWithDims(node, ImmutableList.copyOf(dims));
|
||||
}
|
||||
|
||||
enum SortedDims {
|
||||
YES,
|
||||
NO
|
||||
}
|
||||
|
||||
/** The array dimension specifiers (including any type annotations) associated with a type. */
|
||||
static class TypeWithDims {
|
||||
final Tree node;
|
||||
final ImmutableList<List<AnnotationTree>> dims;
|
||||
|
||||
public TypeWithDims(Tree node, ImmutableList<List<AnnotationTree>> dims) {
|
||||
this.node = node;
|
||||
this.dims = dims;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+45
-13
@@ -14,41 +14,73 @@
|
||||
|
||||
package com.google.googlejavaformat.java;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.common.collect.Range;
|
||||
import com.google.common.collect.RangeSet;
|
||||
import com.google.common.collect.TreeRangeSet;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.Callable;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Encapsulates information about a file to be formatted, including which parts of the file to
|
||||
* format.
|
||||
*/
|
||||
class FormatFileCallable implements Callable<String> {
|
||||
class FormatFileCallable implements Callable<FormatFileCallable.Result> {
|
||||
|
||||
@AutoValue
|
||||
abstract static class Result {
|
||||
abstract @Nullable Path path();
|
||||
|
||||
abstract String input();
|
||||
|
||||
abstract @Nullable String output();
|
||||
|
||||
boolean changed() {
|
||||
return !input().equals(output());
|
||||
}
|
||||
|
||||
abstract @Nullable FormatterException exception();
|
||||
|
||||
static Result create(
|
||||
@Nullable Path path,
|
||||
String input,
|
||||
@Nullable String output,
|
||||
@Nullable FormatterException exception) {
|
||||
return new AutoValue_FormatFileCallable_Result(path, input, output, exception);
|
||||
}
|
||||
}
|
||||
|
||||
private final Path path;
|
||||
private final String input;
|
||||
private final CommandLineOptions parameters;
|
||||
private final JavaFormatterOptions options;
|
||||
|
||||
public FormatFileCallable(
|
||||
CommandLineOptions parameters, String input, JavaFormatterOptions options) {
|
||||
CommandLineOptions parameters, Path path, String input, JavaFormatterOptions options) {
|
||||
this.path = path;
|
||||
this.input = input;
|
||||
this.parameters = parameters;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String call() throws FormatterException {
|
||||
if (parameters.fixImportsOnly()) {
|
||||
return fixImports(input);
|
||||
}
|
||||
public Result call() {
|
||||
try {
|
||||
if (parameters.fixImportsOnly()) {
|
||||
return Result.create(path, input, fixImports(input), /* exception= */ null);
|
||||
}
|
||||
|
||||
Formatter formatter = new Formatter(options);
|
||||
String formatted = formatter.formatSource(input, characterRanges(input).asRanges());
|
||||
formatted = fixImports(formatted);
|
||||
if (parameters.reflowLongStrings()) {
|
||||
formatted = StringWrapper.wrap(Formatter.MAX_LINE_LENGTH, formatted, formatter);
|
||||
Formatter formatter = new Formatter(options);
|
||||
String formatted = formatter.formatSource(input, characterRanges(input).asRanges());
|
||||
formatted = fixImports(formatted);
|
||||
if (parameters.reflowLongStrings()) {
|
||||
formatted = StringWrapper.wrap(Formatter.MAX_LINE_LENGTH, formatted, formatter);
|
||||
}
|
||||
return Result.create(path, input, formatted, /* exception= */ null);
|
||||
} catch (FormatterException e) {
|
||||
return Result.create(path, input, /* output= */ null, e);
|
||||
}
|
||||
return formatted;
|
||||
}
|
||||
|
||||
private String fixImports(String input) throws FormatterException {
|
||||
|
||||
+122
-127
@@ -14,8 +14,6 @@
|
||||
|
||||
package com.google.googlejavaformat.java;
|
||||
|
||||
import static com.google.common.base.StandardSystemProperty.JAVA_CLASS_VERSION;
|
||||
import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -33,28 +31,25 @@ import com.google.googlejavaformat.FormattingError;
|
||||
import com.google.googlejavaformat.Newlines;
|
||||
import com.google.googlejavaformat.Op;
|
||||
import com.google.googlejavaformat.OpsBuilder;
|
||||
import openjdk.tools.javac.file.JavacFileManager;
|
||||
import openjdk.tools.javac.parser.JavacParser;
|
||||
import openjdk.tools.javac.parser.ParserFactory;
|
||||
import openjdk.tools.javac.tree.JCTree.JCCompilationUnit;
|
||||
import openjdk.tools.javac.util.Context;
|
||||
import openjdk.tools.javac.util.Log;
|
||||
import openjdk.tools.javac.util.Options;
|
||||
|
||||
import com.sun.tools.javac.file.JavacFileManager;
|
||||
import com.sun.tools.javac.parser.JavacParser;
|
||||
import com.sun.tools.javac.parser.ParserFactory;
|
||||
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
|
||||
import com.sun.tools.javac.util.Context;
|
||||
import com.sun.tools.javac.util.Log;
|
||||
import com.sun.tools.javac.util.Options;
|
||||
import java.io.IOError;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import jdkx.tools.Diagnostic;
|
||||
import jdkx.tools.DiagnosticCollector;
|
||||
import jdkx.tools.DiagnosticListener;
|
||||
import jdkx.tools.JavaFileObject;
|
||||
import jdkx.tools.SimpleJavaFileObject;
|
||||
import jdkx.tools.StandardLocation;
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.DiagnosticCollector;
|
||||
import javax.tools.DiagnosticListener;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.SimpleJavaFileObject;
|
||||
import javax.tools.StandardLocation;
|
||||
|
||||
/**
|
||||
* This is google-java-format, a new Java formatter that follows the Google Java Style Guide quite
|
||||
@@ -106,111 +101,6 @@ public final class Formatter {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts zero-indexed, [closed, open) line ranges in the given source file to character ranges.
|
||||
*/
|
||||
public static RangeSet<Integer> lineRangesToCharRanges(
|
||||
String input, RangeSet<Integer> lineRanges) {
|
||||
List<Integer> lines = new ArrayList<>();
|
||||
Iterators.addAll(lines, Newlines.lineOffsetIterator(input));
|
||||
lines.add(input.length() + 1);
|
||||
|
||||
final RangeSet<Integer> characterRanges = TreeRangeSet.create();
|
||||
for (Range<Integer> lineRange :
|
||||
lineRanges.subRangeSet(Range.closedOpen(0, lines.size() - 1)).asRanges()) {
|
||||
int lineStart = lines.get(lineRange.lowerEndpoint());
|
||||
// Exclude the trailing newline. This isn't strictly necessary, but handling blank lines
|
||||
// as empty ranges is convenient.
|
||||
int lineEnd = lines.get(lineRange.upperEndpoint()) - 1;
|
||||
Range<Integer> range = Range.closedOpen(lineStart, lineEnd);
|
||||
characterRanges.add(range);
|
||||
}
|
||||
return characterRanges;
|
||||
}
|
||||
|
||||
private static int getJavaMajorVersion() {
|
||||
try {
|
||||
Method versionMethod = Runtime.class.getMethod("version");
|
||||
Object version = versionMethod.invoke(null);
|
||||
return (int) version.getClass().getMethod("major").invoke(version);
|
||||
} catch (Exception e) {
|
||||
// continue below
|
||||
}
|
||||
int version = (int) Double.parseDouble(JAVA_CLASS_VERSION.value());
|
||||
if (49 <= version && version <= 52) {
|
||||
return version - (49 - 5);
|
||||
}
|
||||
throw new IllegalStateException("Unknown Java version: " + JAVA_SPECIFICATION_VERSION.value());
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the given input (a Java compilation unit) into the output stream.
|
||||
*
|
||||
* @throws FormatterException if the input cannot be parsed
|
||||
*/
|
||||
public void formatSource(CharSource input, CharSink output)
|
||||
throws FormatterException, IOException {
|
||||
// TODO(cushon): proper support for streaming input/output. Input may
|
||||
// not be feasible (parsing) but output should be easier.
|
||||
output.write(formatSource(input.read()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Format an input string (a Java compilation unit) into an output string.
|
||||
*
|
||||
* <p>Leaves import statements untouched.
|
||||
*
|
||||
* @param input the input string
|
||||
* @return the output string
|
||||
* @throws FormatterException if the input string cannot be parsed
|
||||
*/
|
||||
public String formatSource(String input) throws FormatterException {
|
||||
return formatSource(input, ImmutableList.of(Range.closedOpen(0, input.length())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Format an input string (a Java compilation unit), for only the specified character ranges.
|
||||
* These ranges are extended as necessary (e.g., to encompass whole lines).
|
||||
*
|
||||
* @param input the input string
|
||||
* @param characterRanges the character ranges to be reformatted
|
||||
* @return the output string
|
||||
* @throws FormatterException if the input string cannot be parsed
|
||||
*/
|
||||
public String formatSource(String input, Collection<Range<Integer>> characterRanges)
|
||||
throws FormatterException {
|
||||
return JavaOutput.applyReplacements(input, getFormatReplacements(input, characterRanges));
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a list of {@link Replacement}s to convert from input to output.
|
||||
*
|
||||
* @param input the input compilation unit
|
||||
* @param characterRanges the character ranges to reformat
|
||||
* @return a list of {@link Replacement}s, sorted from low index to high index, without overlaps
|
||||
* @throws FormatterException if the input string cannot be parsed
|
||||
*/
|
||||
public ImmutableList<Replacement> getFormatReplacements(
|
||||
String input, Collection<Range<Integer>> characterRanges) throws FormatterException {
|
||||
JavaInput javaInput = new JavaInput(input);
|
||||
|
||||
// TODO(cushon): this is only safe because the modifier ordering doesn't affect whitespace,
|
||||
// and doesn't change the replacements that are output. This is not true in general for
|
||||
// 'de-linting' changes (e.g. import ordering).
|
||||
javaInput = ModifierOrderer.reorderModifiers(javaInput, characterRanges);
|
||||
|
||||
String lineSeparator = Newlines.guessLineSeparator(input);
|
||||
JavaOutput javaOutput =
|
||||
new JavaOutput(lineSeparator, javaInput, new JavaCommentsHelper(lineSeparator, options));
|
||||
try {
|
||||
format(javaInput, javaOutput, options);
|
||||
} catch (FormattingError e) {
|
||||
throw new FormatterException(e.diagnostics());
|
||||
}
|
||||
RangeSet<Integer> tokenRangeSet = javaInput.characterRangesToTokenRanges(characterRanges);
|
||||
return javaOutput.getFormatReplacements(tokenRangeSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a {@code Formatter} given a Java compilation unit. Parses the code; builds a {@link
|
||||
* JavaInput} and the corresponding {@link JavaOutput}.
|
||||
@@ -246,9 +136,9 @@ public final class Formatter {
|
||||
JavacParser parser =
|
||||
parserFactory.newParser(
|
||||
javaInput.getText(),
|
||||
/*keepDocComments=*/ true,
|
||||
/*keepEndPos=*/ true,
|
||||
/*keepLineMap=*/ true);
|
||||
/* keepDocComments= */ true,
|
||||
/* keepEndPos= */ true,
|
||||
/* keepLineMap= */ true);
|
||||
unit = parser.parseCompilationUnit();
|
||||
unit.sourcefile = source;
|
||||
|
||||
@@ -260,7 +150,20 @@ public final class Formatter {
|
||||
}
|
||||
OpsBuilder builder = new OpsBuilder(javaInput, javaOutput);
|
||||
// Output the compilation unit.
|
||||
JavaInputAstVisitor visitor = new JavaInputAstVisitor(builder, options.indentationMultiplier());
|
||||
JavaInputAstVisitor visitor;
|
||||
if (Runtime.version().feature() >= 17) {
|
||||
try {
|
||||
visitor =
|
||||
Class.forName("com.google.googlejavaformat.java.java17.Java17InputAstVisitor")
|
||||
.asSubclass(JavaInputAstVisitor.class)
|
||||
.getConstructor(OpsBuilder.class, int.class)
|
||||
.newInstance(builder, options.indentationMultiplier());
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new LinkageError(e.getMessage(), e);
|
||||
}
|
||||
} else {
|
||||
visitor = new JavaInputAstVisitor(builder, options.indentationMultiplier());
|
||||
}
|
||||
visitor.scan(unit, null);
|
||||
builder.sync(javaInput.getText().length());
|
||||
builder.drain();
|
||||
@@ -285,6 +188,31 @@ public final class Formatter {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the given input (a Java compilation unit) into the output stream.
|
||||
*
|
||||
* @throws FormatterException if the input cannot be parsed
|
||||
*/
|
||||
public void formatSource(CharSource input, CharSink output)
|
||||
throws FormatterException, IOException {
|
||||
// TODO(cushon): proper support for streaming input/output. Input may
|
||||
// not be feasible (parsing) but output should be easier.
|
||||
output.write(formatSource(input.read()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Format an input string (a Java compilation unit) into an output string.
|
||||
*
|
||||
* <p>Leaves import statements untouched.
|
||||
*
|
||||
* @param input the input string
|
||||
* @return the output string
|
||||
* @throws FormatterException if the input string cannot be parsed
|
||||
*/
|
||||
public String formatSource(String input) throws FormatterException {
|
||||
return formatSource(input, ImmutableList.of(Range.closedOpen(0, input.length())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats an input string (a Java compilation unit) and fixes imports.
|
||||
*
|
||||
@@ -304,4 +232,71 @@ public final class Formatter {
|
||||
formatted = StringWrapper.wrap(formatted, this);
|
||||
return formatted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format an input string (a Java compilation unit), for only the specified character ranges.
|
||||
* These ranges are extended as necessary (e.g., to encompass whole lines).
|
||||
*
|
||||
* @param input the input string
|
||||
* @param characterRanges the character ranges to be reformatted
|
||||
* @return the output string
|
||||
* @throws FormatterException if the input string cannot be parsed
|
||||
*/
|
||||
public String formatSource(String input, Collection<Range<Integer>> characterRanges)
|
||||
throws FormatterException {
|
||||
return JavaOutput.applyReplacements(input, getFormatReplacements(input, characterRanges));
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a list of {@link Replacement}s to convert from input to output.
|
||||
*
|
||||
* @param input the input compilation unit
|
||||
* @param characterRanges the character ranges to reformat
|
||||
* @return a list of {@link Replacement}s, sorted from low index to high index, without overlaps
|
||||
* @throws FormatterException if the input string cannot be parsed
|
||||
*/
|
||||
public ImmutableList<Replacement> getFormatReplacements(
|
||||
String input, Collection<Range<Integer>> characterRanges) throws FormatterException {
|
||||
JavaInput javaInput = new JavaInput(input);
|
||||
|
||||
// TODO(cushon): this is only safe because the modifier ordering doesn't affect whitespace,
|
||||
// and doesn't change the replacements that are output. This is not true in general for
|
||||
// 'de-linting' changes (e.g. import ordering).
|
||||
if (options.reorderModifiers()) {
|
||||
javaInput = ModifierOrderer.reorderModifiers(javaInput, characterRanges);
|
||||
}
|
||||
|
||||
String lineSeparator = Newlines.guessLineSeparator(input);
|
||||
JavaOutput javaOutput =
|
||||
new JavaOutput(lineSeparator, javaInput, new JavaCommentsHelper(lineSeparator, options));
|
||||
try {
|
||||
format(javaInput, javaOutput, options);
|
||||
} catch (FormattingError e) {
|
||||
throw new FormatterException(e.diagnostics());
|
||||
}
|
||||
RangeSet<Integer> tokenRangeSet = javaInput.characterRangesToTokenRanges(characterRanges);
|
||||
return javaOutput.getFormatReplacements(tokenRangeSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts zero-indexed, [closed, open) line ranges in the given source file to character ranges.
|
||||
*/
|
||||
public static RangeSet<Integer> lineRangesToCharRanges(
|
||||
String input, RangeSet<Integer> lineRanges) {
|
||||
List<Integer> lines = new ArrayList<>();
|
||||
Iterators.addAll(lines, Newlines.lineOffsetIterator(input));
|
||||
lines.add(input.length() + 1);
|
||||
|
||||
final RangeSet<Integer> characterRanges = TreeRangeSet.create();
|
||||
for (Range<Integer> lineRange :
|
||||
lineRanges.subRangeSet(Range.closedOpen(0, lines.size() - 1)).asRanges()) {
|
||||
int lineStart = lines.get(lineRange.lowerEndpoint());
|
||||
// Exclude the trailing newline. This isn't strictly necessary, but handling blank lines
|
||||
// as empty ranges is convenient.
|
||||
int lineEnd = lines.get(lineRange.upperEndpoint()) - 1;
|
||||
Range<Integer> range = Range.closedOpen(lineStart, lineEnd);
|
||||
characterRanges.add(range);
|
||||
}
|
||||
return characterRanges;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-8
@@ -19,11 +19,9 @@ import static java.util.Locale.ENGLISH;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.googlejavaformat.FormatterDiagnostic;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jdkx.tools.Diagnostic;
|
||||
import jdkx.tools.JavaFileObject;
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.JavaFileObject;
|
||||
|
||||
/** Checked exception class for formatter errors. */
|
||||
public final class FormatterException extends Exception {
|
||||
@@ -43,6 +41,10 @@ public final class FormatterException extends Exception {
|
||||
this.diagnostics = ImmutableList.copyOf(diagnostics);
|
||||
}
|
||||
|
||||
public List<FormatterDiagnostic> diagnostics() {
|
||||
return diagnostics;
|
||||
}
|
||||
|
||||
public static FormatterException fromJavacDiagnostics(
|
||||
Iterable<Diagnostic<? extends JavaFileObject>> diagnostics) {
|
||||
return new FormatterException(
|
||||
@@ -53,8 +55,4 @@ public final class FormatterException extends Exception {
|
||||
return FormatterDiagnostic.create(
|
||||
(int) input.getLineNumber(), (int) input.getColumnNumber(), input.getMessage(ENGLISH));
|
||||
}
|
||||
|
||||
public List<FormatterDiagnostic> diagnostics() {
|
||||
return diagnostics;
|
||||
}
|
||||
}
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2021 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;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.spi.ToolProvider;
|
||||
|
||||
/** Provide a way to be invoked without necessarily starting a new VM. */
|
||||
@AutoService(ToolProvider.class)
|
||||
public class GoogleJavaFormatToolProvider implements ToolProvider {
|
||||
@Override
|
||||
public String name() {
|
||||
return "google-java-format";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int run(PrintWriter out, PrintWriter err, String... args) {
|
||||
try {
|
||||
return Main.main(out, err, args);
|
||||
} catch (RuntimeException e) {
|
||||
err.print(e.getMessage());
|
||||
return -1; // pass non-zero value back indicating an error has happened
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -16,7 +16,7 @@ package com.google.googlejavaformat.java;
|
||||
|
||||
class GoogleJavaFormatVersion {
|
||||
|
||||
static String version() {
|
||||
return "%VERSION%";
|
||||
}
|
||||
static String version() {
|
||||
return "%VERSION%";
|
||||
}
|
||||
}
|
||||
|
||||
+180
-179
@@ -25,8 +25,7 @@ import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.googlejavaformat.Newlines;
|
||||
import com.google.googlejavaformat.java.JavaFormatterOptions.Style;
|
||||
import com.google.googlejavaformat.java.JavaInput.Tok;
|
||||
import openjdk.tools.javac.parser.Tokens.TokenKind;
|
||||
|
||||
import com.sun.tools.javac.parser.Tokens.TokenKind;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
@@ -38,81 +37,6 @@ import java.util.stream.Stream;
|
||||
public class ImportOrderer {
|
||||
|
||||
private static final Splitter DOT_SPLITTER = Splitter.on('.');
|
||||
/**
|
||||
* {@link TokenKind}s that indicate the start of a type definition. We use this to avoid scanning
|
||||
* the whole file, since we know that imports must precede any type definition.
|
||||
*/
|
||||
private static final ImmutableSet<TokenKind> CLASS_START =
|
||||
ImmutableSet.of(TokenKind.CLASS, TokenKind.INTERFACE, TokenKind.ENUM);
|
||||
/**
|
||||
* We use this set to find the first import, and again to check that there are no imports after
|
||||
* the place we stopped gathering them. An annotation definition ({@code @interface}) is two
|
||||
* tokens, the second which is {@code interface}, so we don't need a separate entry for that.
|
||||
*/
|
||||
private static final ImmutableSet<String> IMPORT_OR_CLASS_START =
|
||||
ImmutableSet.of("import", "class", "interface", "enum");
|
||||
/**
|
||||
* A {@link Comparator} that orders {@link Import}s by Google Style, defined at
|
||||
* https://google.github.io/styleguide/javaguide.html#s3.3.3-import-ordering-and-spacing.
|
||||
*/
|
||||
private static final Comparator<Import> GOOGLE_IMPORT_COMPARATOR =
|
||||
Comparator.comparing(Import::isStatic, trueFirst()).thenComparing(Import::imported);
|
||||
/**
|
||||
* A {@link Comparator} that orders {@link Import}s by AOSP Style, defined at
|
||||
* https://source.android.com/setup/contribute/code-style#order-import-statements and implemented
|
||||
* in IntelliJ at
|
||||
* https://android.googlesource.com/platform/development/+/master/ide/intellij/codestyles/AndroidStyle.xml.
|
||||
*/
|
||||
private static final Comparator<Import> AOSP_IMPORT_COMPARATOR =
|
||||
Comparator.comparing(Import::isStatic, trueFirst())
|
||||
.thenComparing(Import::isAndroid, trueFirst())
|
||||
.thenComparing(Import::isThirdParty, trueFirst())
|
||||
.thenComparing(Import::isJava, trueFirst())
|
||||
.thenComparing(Import::imported);
|
||||
|
||||
private final String text;
|
||||
private final ImmutableList<Tok> toks;
|
||||
private final String lineSeparator;
|
||||
private final Comparator<Import> importComparator;
|
||||
private final BiFunction<Import, Import, Boolean> shouldInsertBlankLineFn;
|
||||
|
||||
private ImportOrderer(String text, ImmutableList<Tok> toks, Style style) {
|
||||
this.text = text;
|
||||
this.toks = toks;
|
||||
this.lineSeparator = Newlines.guessLineSeparator(text);
|
||||
if (style.equals(Style.GOOGLE)) {
|
||||
this.importComparator = GOOGLE_IMPORT_COMPARATOR;
|
||||
this.shouldInsertBlankLineFn = ImportOrderer::shouldInsertBlankLineGoogle;
|
||||
} else if (style.equals(Style.AOSP)) {
|
||||
this.importComparator = AOSP_IMPORT_COMPARATOR;
|
||||
this.shouldInsertBlankLineFn = ImportOrderer::shouldInsertBlankLineAosp;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported code style: " + style);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether to insert a blank line between the {@code prev} and {@code curr} {@link
|
||||
* Import}s based on Google style.
|
||||
*/
|
||||
private static boolean shouldInsertBlankLineGoogle(Import prev, Import curr) {
|
||||
return prev.isStatic() && !curr.isStatic();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether to insert a blank line between the {@code prev} and {@code curr} {@link
|
||||
* Import}s based on AOSP style.
|
||||
*/
|
||||
private static boolean shouldInsertBlankLineAosp(Import prev, Import curr) {
|
||||
if (prev.isStatic() && !curr.isStatic()) {
|
||||
return true;
|
||||
}
|
||||
// insert blank line between "com.android" from "com.anythingelse"
|
||||
if (prev.isAndroid() && !curr.isAndroid()) {
|
||||
return true;
|
||||
}
|
||||
return !prev.topLevel().equals(curr.topLevel());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorder the inputs in {@code text}, a complete Java program. On success, another complete Java
|
||||
@@ -180,6 +104,163 @@ public class ImportOrderer {
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link TokenKind}s that indicate the start of a type definition. We use this to avoid scanning
|
||||
* the whole file, since we know that imports must precede any type definition.
|
||||
*/
|
||||
private static final ImmutableSet<TokenKind> CLASS_START =
|
||||
ImmutableSet.of(TokenKind.CLASS, TokenKind.INTERFACE, TokenKind.ENUM);
|
||||
|
||||
/**
|
||||
* We use this set to find the first import, and again to check that there are no imports after
|
||||
* the place we stopped gathering them. An annotation definition ({@code @interface}) is two
|
||||
* tokens, the second which is {@code interface}, so we don't need a separate entry for that.
|
||||
*/
|
||||
private static final ImmutableSet<String> IMPORT_OR_CLASS_START =
|
||||
ImmutableSet.of("import", "class", "interface", "enum");
|
||||
|
||||
/**
|
||||
* A {@link Comparator} that orders {@link Import}s by Google Style, defined at
|
||||
* https://google.github.io/styleguide/javaguide.html#s3.3.3-import-ordering-and-spacing.
|
||||
*/
|
||||
private static final Comparator<Import> GOOGLE_IMPORT_COMPARATOR =
|
||||
Comparator.comparing(Import::isStatic, trueFirst()).thenComparing(Import::imported);
|
||||
|
||||
/**
|
||||
* A {@link Comparator} that orders {@link Import}s by AOSP Style, defined at
|
||||
* https://source.android.com/setup/contribute/code-style#order-import-statements and implemented
|
||||
* in IntelliJ at
|
||||
* https://android.googlesource.com/platform/development/+/master/ide/intellij/codestyles/AndroidStyle.xml.
|
||||
*/
|
||||
private static final Comparator<Import> AOSP_IMPORT_COMPARATOR =
|
||||
Comparator.comparing(Import::isStatic, trueFirst())
|
||||
.thenComparing(Import::isAndroid, trueFirst())
|
||||
.thenComparing(Import::isThirdParty, trueFirst())
|
||||
.thenComparing(Import::isJava, trueFirst())
|
||||
.thenComparing(Import::imported);
|
||||
|
||||
/**
|
||||
* Determines whether to insert a blank line between the {@code prev} and {@code curr} {@link
|
||||
* Import}s based on Google style.
|
||||
*/
|
||||
private static boolean shouldInsertBlankLineGoogle(Import prev, Import curr) {
|
||||
return prev.isStatic() && !curr.isStatic();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether to insert a blank line between the {@code prev} and {@code curr} {@link
|
||||
* Import}s based on AOSP style.
|
||||
*/
|
||||
private static boolean shouldInsertBlankLineAosp(Import prev, Import curr) {
|
||||
if (prev.isStatic() && !curr.isStatic()) {
|
||||
return true;
|
||||
}
|
||||
// insert blank line between "com.android" from "com.anythingelse"
|
||||
if (prev.isAndroid() && !curr.isAndroid()) {
|
||||
return true;
|
||||
}
|
||||
return !prev.topLevel().equals(curr.topLevel());
|
||||
}
|
||||
|
||||
private final String text;
|
||||
private final ImmutableList<Tok> toks;
|
||||
private final String lineSeparator;
|
||||
private final Comparator<Import> importComparator;
|
||||
private final BiFunction<Import, Import, Boolean> shouldInsertBlankLineFn;
|
||||
|
||||
private ImportOrderer(String text, ImmutableList<Tok> toks, Style style) {
|
||||
this.text = text;
|
||||
this.toks = toks;
|
||||
this.lineSeparator = Newlines.guessLineSeparator(text);
|
||||
if (style.equals(Style.GOOGLE)) {
|
||||
this.importComparator = GOOGLE_IMPORT_COMPARATOR;
|
||||
this.shouldInsertBlankLineFn = ImportOrderer::shouldInsertBlankLineGoogle;
|
||||
} else if (style.equals(Style.AOSP)) {
|
||||
this.importComparator = AOSP_IMPORT_COMPARATOR;
|
||||
this.shouldInsertBlankLineFn = ImportOrderer::shouldInsertBlankLineAosp;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported code style: " + style);
|
||||
}
|
||||
}
|
||||
|
||||
/** An import statement. */
|
||||
class Import {
|
||||
private final String imported;
|
||||
private final boolean isStatic;
|
||||
private final String trailing;
|
||||
|
||||
Import(String imported, String trailing, boolean isStatic) {
|
||||
this.imported = imported;
|
||||
this.trailing = trailing;
|
||||
this.isStatic = isStatic;
|
||||
}
|
||||
|
||||
/** The name being imported, for example {@code java.util.List}. */
|
||||
String imported() {
|
||||
return imported;
|
||||
}
|
||||
|
||||
/** True if this is {@code import static}. */
|
||||
boolean isStatic() {
|
||||
return isStatic;
|
||||
}
|
||||
|
||||
/** The top-level package of the import. */
|
||||
String topLevel() {
|
||||
return DOT_SPLITTER.split(imported()).iterator().next();
|
||||
}
|
||||
|
||||
/** True if this is an Android import per AOSP style. */
|
||||
boolean isAndroid() {
|
||||
return Stream.of("android.", "androidx.", "dalvik.", "libcore.", "com.android.")
|
||||
.anyMatch(imported::startsWith);
|
||||
}
|
||||
|
||||
/** True if this is a Java import per AOSP style. */
|
||||
boolean isJava() {
|
||||
switch (topLevel()) {
|
||||
case "java":
|
||||
case "javax":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@code //} comment lines after the final {@code ;}, up to and including the line
|
||||
* terminator of the last one. Note: In case two imports were separated by a space (which is
|
||||
* disallowed by the style guide), the trailing whitespace of the first import does not include
|
||||
* a line terminator.
|
||||
*/
|
||||
String trailing() {
|
||||
return trailing;
|
||||
}
|
||||
|
||||
/** True if this is a third-party import per AOSP style. */
|
||||
public boolean isThirdParty() {
|
||||
return !(isAndroid() || isJava());
|
||||
}
|
||||
|
||||
// One or multiple lines, the import itself and following comments, including the line
|
||||
// terminator.
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("import ");
|
||||
if (isStatic()) {
|
||||
sb.append("static ");
|
||||
}
|
||||
sb.append(imported()).append(';');
|
||||
if (trailing().trim().isEmpty()) {
|
||||
sb.append(lineSeparator);
|
||||
} else {
|
||||
sb.append(trailing());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private String tokString(int start, int end) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = start; i < end; i++) {
|
||||
@@ -188,6 +269,16 @@ public class ImportOrderer {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static class ImportsAndIndex {
|
||||
final ImmutableSortedSet<Import> imports;
|
||||
final int index;
|
||||
|
||||
ImportsAndIndex(ImmutableSortedSet<Import> imports, int index) {
|
||||
this.imports = imports;
|
||||
this.index = index;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans a sequence of import lines. The parsing uses this approximate grammar:
|
||||
*
|
||||
@@ -245,8 +336,7 @@ public class ImportOrderer {
|
||||
trailing.append(tokenAt(i));
|
||||
i++;
|
||||
}
|
||||
// Gather (if any) all single line comments and accompanied line terminators following
|
||||
// this
|
||||
// Gather (if any) all single line comments and accompanied line terminators following this
|
||||
// import
|
||||
while (isSlashSlashCommentToken(i)) {
|
||||
trailing.append(tokenAt(i));
|
||||
@@ -276,8 +366,7 @@ public class ImportOrderer {
|
||||
private String reorderedImportsString(ImmutableSortedSet<Import> imports) {
|
||||
Preconditions.checkArgument(!imports.isEmpty(), "imports");
|
||||
|
||||
// Pretend that the first import was preceded by another import of the same kind, so we
|
||||
// don't
|
||||
// Pretend that the first import was preceded by another import of the same kind, so we don't
|
||||
// insert a newline there.
|
||||
Import prevImport = imports.iterator().next();
|
||||
|
||||
@@ -293,6 +382,16 @@ public class ImportOrderer {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static class StringAndIndex {
|
||||
private final String string;
|
||||
private final int index;
|
||||
|
||||
StringAndIndex(String string, int index) {
|
||||
this.string = string;
|
||||
this.index = index;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans the imported thing, the dot-separated name that comes after import [static] and before
|
||||
* the semicolon. We don't allow spaces inside the dot-separated name. Wildcard imports are
|
||||
@@ -381,102 +480,4 @@ public class ImportOrderer {
|
||||
private boolean isNewlineToken(int i) {
|
||||
return toks.get(i).isNewline();
|
||||
}
|
||||
|
||||
private static class ImportsAndIndex {
|
||||
final ImmutableSortedSet<Import> imports;
|
||||
final int index;
|
||||
|
||||
ImportsAndIndex(ImmutableSortedSet<Import> imports, int index) {
|
||||
this.imports = imports;
|
||||
this.index = index;
|
||||
}
|
||||
}
|
||||
|
||||
private static class StringAndIndex {
|
||||
private final String string;
|
||||
private final int index;
|
||||
|
||||
StringAndIndex(String string, int index) {
|
||||
this.string = string;
|
||||
this.index = index;
|
||||
}
|
||||
}
|
||||
|
||||
/** An import statement. */
|
||||
class Import {
|
||||
private final String imported;
|
||||
private final boolean isStatic;
|
||||
private final String trailing;
|
||||
|
||||
Import(String imported, String trailing, boolean isStatic) {
|
||||
this.imported = imported;
|
||||
this.trailing = trailing;
|
||||
this.isStatic = isStatic;
|
||||
}
|
||||
|
||||
/** True if this is a third-party import per AOSP style. */
|
||||
public boolean isThirdParty() {
|
||||
return !(isAndroid() || isJava());
|
||||
}
|
||||
|
||||
/** True if this is an Android import per AOSP style. */
|
||||
boolean isAndroid() {
|
||||
return Stream.of("android.", "androidx.", "dalvik.", "libcore.", "com.android.")
|
||||
.anyMatch(imported::startsWith);
|
||||
}
|
||||
|
||||
/** True if this is a Java import per AOSP style. */
|
||||
boolean isJava() {
|
||||
switch (topLevel()) {
|
||||
case "java":
|
||||
case "javax":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** The top-level package of the import. */
|
||||
String topLevel() {
|
||||
return DOT_SPLITTER.split(imported()).iterator().next();
|
||||
}
|
||||
|
||||
/** The name being imported, for example {@code java.util.List}. */
|
||||
String imported() {
|
||||
return imported;
|
||||
}
|
||||
|
||||
// One or multiple lines, the import itself and following comments, including the line
|
||||
// terminator.
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("import ");
|
||||
if (isStatic()) {
|
||||
sb.append("static ");
|
||||
}
|
||||
sb.append(imported()).append(';');
|
||||
if (trailing().trim().isEmpty()) {
|
||||
sb.append(lineSeparator);
|
||||
} else {
|
||||
sb.append(trailing());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/** True if this is {@code import static}. */
|
||||
boolean isStatic() {
|
||||
return isStatic;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@code //} comment lines after the final {@code ;}, up to and including the line
|
||||
* terminator of the last one. Note: In case two imports were separated by a space (which is
|
||||
* disallowed by the style guide), the trailing whitespace of the first import does not include
|
||||
* a line terminator.
|
||||
*/
|
||||
String trailing() {
|
||||
return trailing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-9
@@ -20,7 +20,6 @@ import com.google.googlejavaformat.CommentsHelper;
|
||||
import com.google.googlejavaformat.Input.Tok;
|
||||
import com.google.googlejavaformat.Newlines;
|
||||
import com.google.googlejavaformat.java.javadoc.JavadocFormatter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -30,10 +29,6 @@ import java.util.regex.Pattern;
|
||||
/** {@code JavaCommentsHelper} extends {@link CommentsHelper} to rewrite Java comments. */
|
||||
public final class JavaCommentsHelper implements CommentsHelper {
|
||||
|
||||
// Preserve special `//noinspection` and `//$NON-NLS-x$` comments used by IDEs, which cannot
|
||||
// contain leading spaces.
|
||||
private static final Pattern LINE_COMMENT_MISSING_SPACE_PREFIX =
|
||||
Pattern.compile("^(//+)(?!noinspection|\\$NON-NLS-\\d+\\$)[^\\s/]");
|
||||
private final String lineSeparator;
|
||||
private final JavaFormatterOptions options;
|
||||
|
||||
@@ -58,11 +53,13 @@ public final class JavaCommentsHelper implements CommentsHelper {
|
||||
}
|
||||
if (tok.isSlashSlashComment()) {
|
||||
return indentLineComments(lines, column0);
|
||||
} else if (javadocShaped(lines)) {
|
||||
return indentJavadoc(lines, column0);
|
||||
} else {
|
||||
return preserveIndentation(lines, column0);
|
||||
}
|
||||
return CommentsHelper.reformatParameterComment(tok)
|
||||
.orElseGet(
|
||||
() ->
|
||||
javadocShaped(lines)
|
||||
? indentJavadoc(lines, column0)
|
||||
: preserveIndentation(lines, column0));
|
||||
}
|
||||
|
||||
// For non-javadoc-shaped block comments, shift the entire block to the correct
|
||||
@@ -107,6 +104,11 @@ public final class JavaCommentsHelper implements CommentsHelper {
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
// Preserve special `//noinspection` and `//$NON-NLS-x$` comments used by IDEs, which cannot
|
||||
// contain leading spaces.
|
||||
private static final Pattern LINE_COMMENT_MISSING_SPACE_PREFIX =
|
||||
Pattern.compile("^(//+)(?!noinspection|\\$NON-NLS-\\d+\\$)[^\\s/]");
|
||||
|
||||
private List<String> wrapLineComments(List<String> lines, int column0) {
|
||||
List<String> result = new ArrayList<>();
|
||||
for (String line : lines) {
|
||||
@@ -179,3 +181,4 @@ public final class JavaCommentsHelper implements CommentsHelper {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+34
-48
@@ -14,6 +14,7 @@
|
||||
|
||||
package com.google.googlejavaformat.java;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.errorprone.annotations.Immutable;
|
||||
|
||||
/**
|
||||
@@ -27,39 +28,8 @@ import com.google.errorprone.annotations.Immutable;
|
||||
* preferences, and in fact it would work directly against our primary goals.
|
||||
*/
|
||||
@Immutable
|
||||
public class JavaFormatterOptions {
|
||||
|
||||
private final Style style;
|
||||
private final boolean formatJavadoc;
|
||||
|
||||
private JavaFormatterOptions(Style style, boolean formatJavadoc) {
|
||||
this.style = style;
|
||||
this.formatJavadoc = formatJavadoc;
|
||||
}
|
||||
|
||||
/** Returns the default formatting options. */
|
||||
public static JavaFormatterOptions defaultOptions() {
|
||||
return builder().build();
|
||||
}
|
||||
|
||||
/** Returns a builder for {@link JavaFormatterOptions}. */
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/** Returns the multiplier for the unit of indent. */
|
||||
public int indentationMultiplier() {
|
||||
return style.indentationMultiplier();
|
||||
}
|
||||
|
||||
public boolean formatJavadoc() {
|
||||
return formatJavadoc;
|
||||
}
|
||||
|
||||
/** Returns the code style. */
|
||||
public Style style() {
|
||||
return style;
|
||||
}
|
||||
@AutoValue
|
||||
public abstract class JavaFormatterOptions {
|
||||
|
||||
public enum Style {
|
||||
/** The default Google Java Style configuration. */
|
||||
@@ -79,25 +49,41 @@ public class JavaFormatterOptions {
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the multiplier for the unit of indent. */
|
||||
public int indentationMultiplier() {
|
||||
return style().indentationMultiplier();
|
||||
}
|
||||
|
||||
public abstract boolean formatJavadoc();
|
||||
|
||||
public abstract boolean reorderModifiers();
|
||||
|
||||
/** Returns the code style. */
|
||||
public abstract Style style();
|
||||
|
||||
/** Returns the default formatting options. */
|
||||
public static JavaFormatterOptions defaultOptions() {
|
||||
return builder().build();
|
||||
}
|
||||
|
||||
/** Returns a builder for {@link JavaFormatterOptions}. */
|
||||
public static Builder builder() {
|
||||
return new AutoValue_JavaFormatterOptions.Builder()
|
||||
.style(Style.GOOGLE)
|
||||
.formatJavadoc(true)
|
||||
.reorderModifiers(true);
|
||||
}
|
||||
|
||||
/** A builder for {@link JavaFormatterOptions}. */
|
||||
public static class Builder {
|
||||
private Style style = Style.GOOGLE;
|
||||
private boolean formatJavadoc = true;
|
||||
@AutoValue.Builder
|
||||
public abstract static class Builder {
|
||||
|
||||
private Builder() {}
|
||||
public abstract Builder style(Style style);
|
||||
|
||||
public Builder style(Style style) {
|
||||
this.style = style;
|
||||
return this;
|
||||
}
|
||||
public abstract Builder formatJavadoc(boolean formatJavadoc);
|
||||
|
||||
public Builder formatJavadoc(boolean formatJavadoc) {
|
||||
this.formatJavadoc = formatJavadoc;
|
||||
return this;
|
||||
}
|
||||
public abstract Builder reorderModifiers(boolean reorderModifiers);
|
||||
|
||||
public JavaFormatterOptions build() {
|
||||
return new JavaFormatterOptions(style, formatJavadoc);
|
||||
}
|
||||
public abstract JavaFormatterOptions build();
|
||||
}
|
||||
}
|
||||
|
||||
+450
-453
@@ -33,452 +33,29 @@ import com.google.common.collect.TreeRangeSet;
|
||||
import com.google.googlejavaformat.Input;
|
||||
import com.google.googlejavaformat.Newlines;
|
||||
import com.google.googlejavaformat.java.JavacTokens.RawTok;
|
||||
import openjdk.tools.javac.file.JavacFileManager;
|
||||
import openjdk.tools.javac.parser.Tokens.TokenKind;
|
||||
import openjdk.tools.javac.tree.JCTree.JCCompilationUnit;
|
||||
import openjdk.tools.javac.util.Context;
|
||||
import openjdk.tools.javac.util.Log;
|
||||
import openjdk.tools.javac.util.Log.DeferredDiagnosticHandler;
|
||||
import openjdk.tools.javac.util.Options;
|
||||
|
||||
import com.sun.tools.javac.file.JavacFileManager;
|
||||
import com.sun.tools.javac.parser.Tokens.TokenKind;
|
||||
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
|
||||
import com.sun.tools.javac.util.Context;
|
||||
import com.sun.tools.javac.util.Log;
|
||||
import com.sun.tools.javac.util.Log.DeferredDiagnosticHandler;
|
||||
import com.sun.tools.javac.util.Options;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import jdkx.tools.Diagnostic;
|
||||
import jdkx.tools.DiagnosticCollector;
|
||||
import jdkx.tools.DiagnosticListener;
|
||||
import jdkx.tools.JavaFileObject;
|
||||
import jdkx.tools.JavaFileObject.Kind;
|
||||
import jdkx.tools.SimpleJavaFileObject;
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.DiagnosticCollector;
|
||||
import javax.tools.DiagnosticListener;
|
||||
import javax.tools.JavaFileManager;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.JavaFileObject.Kind;
|
||||
import javax.tools.SimpleJavaFileObject;
|
||||
|
||||
/** {@code JavaInput} extends {@link Input} to represent a Java input document. */
|
||||
public final class JavaInput extends Input {
|
||||
private final String text; // The input.
|
||||
private final ImmutableMap<Integer, Integer> positionToColumnMap; // Map Tok position to column.
|
||||
private final ImmutableList<Token> tokens; // The Tokens for this input.
|
||||
private final ImmutableRangeMap<Integer, Token> positionTokenMap; // Map position to Token.
|
||||
|
||||
/*
|
||||
* The following lists record the sequential indices of the {@code Tok}s on each input line. (Only
|
||||
* tokens and comments have sequential indices.) Tokens and {@code //} comments lie on just one
|
||||
* line; {@code /*} comments can lie on multiple lines. These data structures (along with
|
||||
* equivalent ones for the formatted output) let us compute correspondences between the input and
|
||||
* output.
|
||||
*/
|
||||
/** Map from Tok index to the associated Token. */
|
||||
private final Token[] kToToken;
|
||||
|
||||
private int kN; // The number of numbered toks (tokens or comments), excluding the EOF.
|
||||
private JCCompilationUnit unit;
|
||||
|
||||
/**
|
||||
* Input constructor.
|
||||
*
|
||||
* @param text the input text
|
||||
* @throws FormatterException if the input cannot be parsed
|
||||
*/
|
||||
public JavaInput(String text) throws FormatterException {
|
||||
this.text = checkNotNull(text);
|
||||
setLines(ImmutableList.copyOf(Newlines.lineIterator(text)));
|
||||
ImmutableList<Tok> toks = buildToks(text);
|
||||
positionToColumnMap = makePositionToColumnMap(toks);
|
||||
tokens = buildTokens(toks);
|
||||
ImmutableRangeMap.Builder<Integer, Token> tokenLocations = ImmutableRangeMap.builder();
|
||||
for (Token token : tokens) {
|
||||
Input.Tok end = JavaOutput.endTok(token);
|
||||
int upper = end.getPosition();
|
||||
if (!end.getText().isEmpty()) {
|
||||
upper += end.length() - 1;
|
||||
}
|
||||
tokenLocations.put(Range.closed(JavaOutput.startTok(token).getPosition(), upper), token);
|
||||
}
|
||||
positionTokenMap = tokenLocations.build();
|
||||
|
||||
// adjust kN for EOF
|
||||
kToToken = new Token[kN + 1];
|
||||
for (Token token : tokens) {
|
||||
for (Input.Tok tok : token.getToksBefore()) {
|
||||
if (tok.getIndex() < 0) {
|
||||
continue;
|
||||
}
|
||||
kToToken[tok.getIndex()] = token;
|
||||
}
|
||||
kToToken[token.getTok().getIndex()] = token;
|
||||
for (Input.Tok tok : token.getToksAfter()) {
|
||||
if (tok.getIndex() < 0) {
|
||||
continue;
|
||||
}
|
||||
kToToken[tok.getIndex()] = token;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static ImmutableMap<Integer, Integer> makePositionToColumnMap(List<Tok> toks) {
|
||||
ImmutableMap.Builder<Integer, Integer> builder = ImmutableMap.builder();
|
||||
for (Tok tok : toks) {
|
||||
builder.put(tok.getPosition(), tok.getColumn());
|
||||
}
|
||||
return builder.buildOrThrow();
|
||||
}
|
||||
|
||||
/** Lex the input and build the list of toks. */
|
||||
private ImmutableList<Tok> buildToks(String text) throws FormatterException {
|
||||
ImmutableList<Tok> toks = buildToks(text, ImmutableSet.of());
|
||||
kN = getLast(toks).getIndex();
|
||||
computeRanges(toks);
|
||||
return toks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lex the input and build the list of toks.
|
||||
*
|
||||
* @param text the text to be lexed.
|
||||
* @param stopTokens a set of tokens which should cause lexing to stop. If one of these is found,
|
||||
* the returned list will include tokens up to but not including that token.
|
||||
*/
|
||||
static ImmutableList<Tok> buildToks(String text, ImmutableSet<TokenKind> stopTokens)
|
||||
throws FormatterException {
|
||||
stopTokens = ImmutableSet.<TokenKind>builder().addAll(stopTokens).add(TokenKind.EOF).build();
|
||||
Context context = new Context();
|
||||
Options.instance(context).put("--enable-preview", "true");
|
||||
new JavacFileManager(context, true, UTF_8);
|
||||
DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
|
||||
context.put(DiagnosticListener.class, diagnosticCollector);
|
||||
Log log = Log.instance(context);
|
||||
log.useSource(
|
||||
new SimpleJavaFileObject(URI.create("Source.java"), Kind.SOURCE) {
|
||||
@Override
|
||||
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
|
||||
return text;
|
||||
}
|
||||
});
|
||||
DeferredDiagnosticHandler diagnostics = new DeferredDiagnosticHandler(log);
|
||||
ImmutableList<RawTok> rawToks = JavacTokens.getTokens(text, context, stopTokens);
|
||||
if (diagnostics.getDiagnostics().stream().anyMatch(d -> d.getKind() == Diagnostic.Kind.ERROR)) {
|
||||
return ImmutableList.of(new Tok(0, "", "", 0, 0, true, null)); // EOF
|
||||
}
|
||||
int kN = 0;
|
||||
List<Tok> toks = new ArrayList<>();
|
||||
int charI = 0;
|
||||
int columnI = 0;
|
||||
for (RawTok t : rawToks) {
|
||||
if (stopTokens.contains(t.kind())) {
|
||||
break;
|
||||
}
|
||||
int charI0 = t.pos();
|
||||
// Get string, possibly with Unicode escapes.
|
||||
String originalTokText = text.substring(charI0, t.endPos());
|
||||
String tokText =
|
||||
t.kind() == TokenKind.STRINGLITERAL
|
||||
? t.stringVal() // Unicode escapes removed.
|
||||
: originalTokText;
|
||||
char tokText0 = tokText.charAt(0); // The token's first character.
|
||||
final boolean isToken; // Is this tok a token?
|
||||
final boolean isNumbered; // Is this tok numbered? (tokens and comments)
|
||||
String extraNewline = null; // Extra newline at end?
|
||||
List<String> strings = new ArrayList<>();
|
||||
if (Character.isWhitespace(tokText0)) {
|
||||
isToken = false;
|
||||
isNumbered = false;
|
||||
Iterator<String> it = Newlines.lineIterator(originalTokText);
|
||||
while (it.hasNext()) {
|
||||
String line = it.next();
|
||||
String newline = Newlines.getLineEnding(line);
|
||||
if (newline != null) {
|
||||
String spaces = line.substring(0, line.length() - newline.length());
|
||||
if (!spaces.isEmpty()) {
|
||||
strings.add(spaces);
|
||||
}
|
||||
strings.add(newline);
|
||||
} else if (!line.isEmpty()) {
|
||||
strings.add(line);
|
||||
}
|
||||
}
|
||||
} else if (tokText.startsWith("'") || tokText.startsWith("\"")) {
|
||||
isToken = true;
|
||||
isNumbered = true;
|
||||
strings.add(originalTokText);
|
||||
} else if (tokText.startsWith("//") || tokText.startsWith("/*")) {
|
||||
// For compatibility with an earlier lexer, the newline after a // comment is its
|
||||
// own tok.
|
||||
if (tokText.startsWith("//")
|
||||
&& (originalTokText.endsWith("\n") || originalTokText.endsWith("\r"))) {
|
||||
extraNewline = Newlines.getLineEnding(originalTokText);
|
||||
tokText = tokText.substring(0, tokText.length() - extraNewline.length());
|
||||
originalTokText =
|
||||
originalTokText.substring(0, originalTokText.length() - extraNewline.length());
|
||||
}
|
||||
isToken = false;
|
||||
isNumbered = true;
|
||||
strings.add(originalTokText);
|
||||
} else if (Character.isJavaIdentifierStart(tokText0)
|
||||
|| Character.isDigit(tokText0)
|
||||
|| (tokText0 == '.' && tokText.length() > 1 && Character.isDigit(tokText.charAt(1)))) {
|
||||
// Identifier, keyword, or numeric literal (a dot may begin a number, as in .2D).
|
||||
isToken = true;
|
||||
isNumbered = true;
|
||||
strings.add(tokText);
|
||||
} else {
|
||||
// Other tokens ("+" or "++" or ">>" are broken into one-character toks, because
|
||||
// ">>"
|
||||
// cannot be lexed without syntactic knowledge. This implementation fails if the
|
||||
// token
|
||||
// contains Unicode escapes.
|
||||
isToken = true;
|
||||
isNumbered = true;
|
||||
for (char c : tokText.toCharArray()) {
|
||||
strings.add(String.valueOf(c));
|
||||
}
|
||||
}
|
||||
if (strings.size() == 1) {
|
||||
toks.add(
|
||||
new Tok(
|
||||
isNumbered ? kN++ : -1,
|
||||
originalTokText,
|
||||
tokText,
|
||||
charI,
|
||||
columnI,
|
||||
isToken,
|
||||
t.kind()));
|
||||
charI += originalTokText.length();
|
||||
columnI = updateColumn(columnI, originalTokText);
|
||||
|
||||
} else {
|
||||
if (strings.size() != 1 && !tokText.equals(originalTokText)) {
|
||||
throw new FormatterException(
|
||||
"Unicode escapes not allowed in whitespace or multi-character" + " operators");
|
||||
}
|
||||
for (String str : strings) {
|
||||
toks.add(new Tok(isNumbered ? kN++ : -1, str, str, charI, columnI, isToken, null));
|
||||
charI += str.length();
|
||||
columnI = updateColumn(columnI, originalTokText);
|
||||
}
|
||||
}
|
||||
if (extraNewline != null) {
|
||||
toks.add(new Tok(-1, extraNewline, extraNewline, charI, columnI, false, null));
|
||||
columnI = 0;
|
||||
charI += extraNewline.length();
|
||||
}
|
||||
}
|
||||
toks.add(new Tok(kN, "", "", charI, columnI, true, null)); // EOF tok.
|
||||
return ImmutableList.copyOf(toks);
|
||||
}
|
||||
|
||||
private static int updateColumn(int columnI, String originalTokText) {
|
||||
Integer last = Iterators.getLast(Newlines.lineOffsetIterator(originalTokText));
|
||||
if (last > 0) {
|
||||
columnI = originalTokText.length() - last;
|
||||
} else {
|
||||
columnI += originalTokText.length();
|
||||
}
|
||||
return columnI;
|
||||
}
|
||||
|
||||
private static ImmutableList<Token> buildTokens(List<Tok> toks) {
|
||||
ImmutableList.Builder<Token> tokens = ImmutableList.builder();
|
||||
int k = 0;
|
||||
int kN = toks.size();
|
||||
|
||||
// Remaining non-tokens before the token go here.
|
||||
ImmutableList.Builder<Tok> toksBefore = ImmutableList.builder();
|
||||
|
||||
OUTERMOST:
|
||||
while (k < kN) {
|
||||
while (!toks.get(k).isToken()) {
|
||||
Tok tok = toks.get(k++);
|
||||
toksBefore.add(tok);
|
||||
if (isParamComment(tok)) {
|
||||
while (toks.get(k).isNewline()) {
|
||||
// drop newlines after parameter comments
|
||||
k++;
|
||||
}
|
||||
}
|
||||
}
|
||||
Tok tok = toks.get(k++);
|
||||
|
||||
// Non-tokens starting on the same line go here too.
|
||||
ImmutableList.Builder<Tok> toksAfter = ImmutableList.builder();
|
||||
OUTER:
|
||||
while (k < kN && !toks.get(k).isToken()) {
|
||||
// Don't attach inline comments to certain leading tokens, e.g. for
|
||||
// `f(/*flag1=*/true).
|
||||
//
|
||||
// Attaching inline comments to the right token is hard, and this barely
|
||||
// scratches the surface. But it's enough to do a better job with parameter
|
||||
// name comments.
|
||||
//
|
||||
// TODO(cushon): find a better strategy.
|
||||
if (toks.get(k).isSlashStarComment()) {
|
||||
switch (tok.getText()) {
|
||||
case "(":
|
||||
case "<":
|
||||
case ".":
|
||||
break OUTER;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (toks.get(k).isJavadocComment()) {
|
||||
switch (tok.getText()) {
|
||||
case ";":
|
||||
break OUTER;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isParamComment(toks.get(k))) {
|
||||
tokens.add(new Token(toksBefore.build(), tok, toksAfter.build()));
|
||||
toksBefore = ImmutableList.<Tok>builder().add(toks.get(k++));
|
||||
// drop newlines after parameter comments
|
||||
while (toks.get(k).isNewline()) {
|
||||
k++;
|
||||
}
|
||||
continue OUTERMOST;
|
||||
}
|
||||
Tok nonTokenAfter = toks.get(k++);
|
||||
toksAfter.add(nonTokenAfter);
|
||||
if (Newlines.containsBreaks(nonTokenAfter.getText())) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
tokens.add(new Token(toksBefore.build(), tok, toksAfter.build()));
|
||||
toksBefore = ImmutableList.builder();
|
||||
}
|
||||
return tokens.build();
|
||||
}
|
||||
|
||||
private static boolean isParamComment(Tok tok) {
|
||||
return tok.isSlashStarComment()
|
||||
&& tok.getText().matches("\\/\\*[A-Za-z0-9\\s_\\-]+=\\s*\\*\\/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the input tokens.
|
||||
*
|
||||
* @return the input tokens
|
||||
*/
|
||||
@Override
|
||||
public ImmutableList<? extends Input.Token> getTokens() {
|
||||
return tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the navigable map from position to {@link Token}. Used to look for tokens following a given
|
||||
* one, and to implement the --offset and --length flags to reformat a character range in the
|
||||
* input file.
|
||||
*
|
||||
* @return the navigable map from position to {@link Token}
|
||||
*/
|
||||
@Override
|
||||
public ImmutableRangeMap<Integer, Token> getPositionTokenMap() {
|
||||
return positionTokenMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableMap<Integer, Integer> getPositionToColumnMap() {
|
||||
return positionToColumnMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the input text.
|
||||
*
|
||||
* @return the input text
|
||||
*/
|
||||
@Override
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of toks.
|
||||
*
|
||||
* @return the number of toks, excluding the EOF tok
|
||||
*/
|
||||
@Override
|
||||
public int getkN() {
|
||||
return kN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Token by index.
|
||||
*
|
||||
* @param k the Tok index
|
||||
*/
|
||||
@Override
|
||||
public Token getToken(int k) {
|
||||
return kToToken[k];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("tokens", tokens)
|
||||
.add("super", super.toString())
|
||||
.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLineNumber(int inputPosition) {
|
||||
Verify.verifyNotNull(unit, "Expected compilation unit to be set.");
|
||||
return unit.getLineMap().getLineNumber(inputPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnNumber(int inputPosition) {
|
||||
Verify.verifyNotNull(unit, "Expected compilation unit to be set.");
|
||||
return unit.getLineMap().getColumnNumber(inputPosition);
|
||||
}
|
||||
|
||||
// TODO(cushon): refactor JavaInput so the CompilationUnit can be passed into
|
||||
// the constructor.
|
||||
public void setCompilationUnit(JCCompilationUnit unit) {
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
public RangeSet<Integer> characterRangesToTokenRanges(Collection<Range<Integer>> characterRanges)
|
||||
throws FormatterException {
|
||||
RangeSet<Integer> tokenRangeSet = TreeRangeSet.create();
|
||||
for (Range<Integer> characterRange : characterRanges) {
|
||||
tokenRangeSet.add(
|
||||
characterRangeToTokenRange(characterRange.canonical(DiscreteDomain.integers())));
|
||||
}
|
||||
return tokenRangeSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from a character range to a token range.
|
||||
*
|
||||
* @param characterRange the {@code 0}-based {@link Range} of characters
|
||||
* @return the {@code 0}-based {@link Range} of tokens
|
||||
* @throws FormatterException if the upper endpoint of the range is outside the file
|
||||
*/
|
||||
Range<Integer> characterRangeToTokenRange(Range<Integer> characterRange)
|
||||
throws FormatterException {
|
||||
if (characterRange.upperEndpoint() > text.length()) {
|
||||
throw new FormatterException(
|
||||
String.format(
|
||||
"error: invalid length %d, offset + length (%d) is outside the file",
|
||||
characterRange.upperEndpoint() - characterRange.lowerEndpoint(),
|
||||
characterRange.upperEndpoint()));
|
||||
}
|
||||
// empty range stands for "format the line under the cursor"
|
||||
Range<Integer> nonEmptyRange =
|
||||
characterRange.isEmpty()
|
||||
? Range.closedOpen(characterRange.lowerEndpoint(), characterRange.lowerEndpoint() + 1)
|
||||
: characterRange;
|
||||
ImmutableCollection<Token> enclosed =
|
||||
getPositionTokenMap().subRangeMap(nonEmptyRange).asMapOfRanges().values();
|
||||
if (enclosed.isEmpty()) {
|
||||
return EMPTY_RANGE;
|
||||
}
|
||||
return Range.closedOpen(
|
||||
enclosed.iterator().next().getTok().getIndex(), getLast(enclosed).getTok().getIndex() + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code JavaInput} is a sequence of {@link Tok}s that cover the Java input. A {@link Tok} is
|
||||
* either a token (if {@code isToken()}), or a non-token, which is a comment (if {@code
|
||||
@@ -533,16 +110,6 @@ public final class JavaInput extends Input {
|
||||
return index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumn() {
|
||||
return columnI;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return text;
|
||||
@@ -558,6 +125,20 @@ public final class JavaInput extends Input {
|
||||
return originalText.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumn() {
|
||||
return columnI;
|
||||
}
|
||||
|
||||
boolean isToken() {
|
||||
return isToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNewline() {
|
||||
return Newlines.isNewline(text);
|
||||
@@ -575,8 +156,7 @@ public final class JavaInput extends Input {
|
||||
|
||||
@Override
|
||||
public boolean isJavadocComment() {
|
||||
// comments like `/***` are also javadoc, but their formatting probably won't be
|
||||
// improved
|
||||
// comments like `/***` are also javadoc, but their formatting probably won't be improved
|
||||
// by the javadoc formatter
|
||||
return text.startsWith("/**") && text.charAt("/**".length()) != '*' && text.length() > 4;
|
||||
}
|
||||
@@ -600,10 +180,6 @@ public final class JavaInput extends Input {
|
||||
public TokenKind kind() {
|
||||
return kind;
|
||||
}
|
||||
|
||||
boolean isToken() {
|
||||
return isToken;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -670,4 +246,425 @@ public final class JavaInput extends Input {
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private final String text; // The input.
|
||||
private int kN; // The number of numbered toks (tokens or comments), excluding the EOF.
|
||||
|
||||
/*
|
||||
* The following lists record the sequential indices of the {@code Tok}s on each input line. (Only
|
||||
* tokens and comments have sequential indices.) Tokens and {@code //} comments lie on just one
|
||||
* line; {@code /*} comments can lie on multiple lines. These data structures (along with
|
||||
* equivalent ones for the formatted output) let us compute correspondences between the input and
|
||||
* output.
|
||||
*/
|
||||
|
||||
private final ImmutableMap<Integer, Integer> positionToColumnMap; // Map Tok position to column.
|
||||
private final ImmutableList<Token> tokens; // The Tokens for this input.
|
||||
private final ImmutableRangeMap<Integer, Token> positionTokenMap; // Map position to Token.
|
||||
|
||||
/** Map from Tok index to the associated Token. */
|
||||
private final Token[] kToToken;
|
||||
|
||||
/**
|
||||
* Input constructor.
|
||||
*
|
||||
* @param text the input text
|
||||
* @throws FormatterException if the input cannot be parsed
|
||||
*/
|
||||
public JavaInput(String text) throws FormatterException {
|
||||
this.text = checkNotNull(text);
|
||||
setLines(ImmutableList.copyOf(Newlines.lineIterator(text)));
|
||||
ImmutableList<Tok> toks = buildToks(text);
|
||||
positionToColumnMap = makePositionToColumnMap(toks);
|
||||
tokens = buildTokens(toks);
|
||||
ImmutableRangeMap.Builder<Integer, Token> tokenLocations = ImmutableRangeMap.builder();
|
||||
for (Token token : tokens) {
|
||||
Input.Tok end = JavaOutput.endTok(token);
|
||||
int upper = end.getPosition();
|
||||
if (!end.getText().isEmpty()) {
|
||||
upper += end.length() - 1;
|
||||
}
|
||||
tokenLocations.put(Range.closed(JavaOutput.startTok(token).getPosition(), upper), token);
|
||||
}
|
||||
positionTokenMap = tokenLocations.build();
|
||||
|
||||
// adjust kN for EOF
|
||||
kToToken = new Token[kN + 1];
|
||||
for (Token token : tokens) {
|
||||
for (Input.Tok tok : token.getToksBefore()) {
|
||||
if (tok.getIndex() < 0) {
|
||||
continue;
|
||||
}
|
||||
kToToken[tok.getIndex()] = token;
|
||||
}
|
||||
kToToken[token.getTok().getIndex()] = token;
|
||||
for (Input.Tok tok : token.getToksAfter()) {
|
||||
if (tok.getIndex() < 0) {
|
||||
continue;
|
||||
}
|
||||
kToToken[tok.getIndex()] = token;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static ImmutableMap<Integer, Integer> makePositionToColumnMap(List<Tok> toks) {
|
||||
ImmutableMap.Builder<Integer, Integer> builder = ImmutableMap.builder();
|
||||
for (Tok tok : toks) {
|
||||
builder.put(tok.getPosition(), tok.getColumn());
|
||||
}
|
||||
return builder.buildOrThrow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the input text.
|
||||
*
|
||||
* @return the input text
|
||||
*/
|
||||
@Override
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableMap<Integer, Integer> getPositionToColumnMap() {
|
||||
return positionToColumnMap;
|
||||
}
|
||||
|
||||
/** Lex the input and build the list of toks. */
|
||||
private ImmutableList<Tok> buildToks(String text) throws FormatterException {
|
||||
ImmutableList<Tok> toks = buildToks(text, ImmutableSet.of());
|
||||
kN = getLast(toks).getIndex();
|
||||
computeRanges(toks);
|
||||
return toks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lex the input and build the list of toks.
|
||||
*
|
||||
* @param text the text to be lexed.
|
||||
* @param stopTokens a set of tokens which should cause lexing to stop. If one of these is found,
|
||||
* the returned list will include tokens up to but not including that token.
|
||||
*/
|
||||
static ImmutableList<Tok> buildToks(String text, ImmutableSet<TokenKind> stopTokens)
|
||||
throws FormatterException {
|
||||
stopTokens = ImmutableSet.<TokenKind>builder().addAll(stopTokens).add(TokenKind.EOF).build();
|
||||
Context context = new Context();
|
||||
Options.instance(context).put("--enable-preview", "true");
|
||||
JavaFileManager fileManager = new JavacFileManager(context, false, UTF_8);
|
||||
context.put(JavaFileManager.class, fileManager);
|
||||
DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
|
||||
context.put(DiagnosticListener.class, diagnosticCollector);
|
||||
Log log = Log.instance(context);
|
||||
log.useSource(
|
||||
new SimpleJavaFileObject(URI.create("Source.java"), Kind.SOURCE) {
|
||||
@Override
|
||||
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
|
||||
return text;
|
||||
}
|
||||
});
|
||||
DeferredDiagnosticHandler diagnostics = new DeferredDiagnosticHandler(log);
|
||||
ImmutableList<RawTok> rawToks = JavacTokens.getTokens(text, context, stopTokens);
|
||||
if (diagnostics.getDiagnostics().stream().anyMatch(d -> d.getKind() == Diagnostic.Kind.ERROR)) {
|
||||
return ImmutableList.of(new Tok(0, "", "", 0, 0, true, null)); // EOF
|
||||
}
|
||||
int kN = 0;
|
||||
List<Tok> toks = new ArrayList<>();
|
||||
int charI = 0;
|
||||
int columnI = 0;
|
||||
for (RawTok t : rawToks) {
|
||||
if (stopTokens.contains(t.kind())) {
|
||||
break;
|
||||
}
|
||||
int charI0 = t.pos();
|
||||
// Get string, possibly with Unicode escapes.
|
||||
String originalTokText = text.substring(charI0, t.endPos());
|
||||
String tokText =
|
||||
t.kind() == TokenKind.STRINGLITERAL
|
||||
? t.stringVal() // Unicode escapes removed.
|
||||
: originalTokText;
|
||||
char tokText0 = tokText.charAt(0); // The token's first character.
|
||||
final boolean isToken; // Is this tok a token?
|
||||
final boolean isNumbered; // Is this tok numbered? (tokens and comments)
|
||||
String extraNewline = null; // Extra newline at end?
|
||||
List<String> strings = new ArrayList<>();
|
||||
if (Character.isWhitespace(tokText0)) {
|
||||
isToken = false;
|
||||
isNumbered = false;
|
||||
Iterator<String> it = Newlines.lineIterator(originalTokText);
|
||||
while (it.hasNext()) {
|
||||
String line = it.next();
|
||||
String newline = Newlines.getLineEnding(line);
|
||||
if (newline != null) {
|
||||
String spaces = line.substring(0, line.length() - newline.length());
|
||||
if (!spaces.isEmpty()) {
|
||||
strings.add(spaces);
|
||||
}
|
||||
strings.add(newline);
|
||||
} else if (!line.isEmpty()) {
|
||||
strings.add(line);
|
||||
}
|
||||
}
|
||||
} else if (tokText.startsWith("'") || tokText.startsWith("\"")) {
|
||||
isToken = true;
|
||||
isNumbered = true;
|
||||
strings.add(originalTokText);
|
||||
} else if (tokText.startsWith("//") || tokText.startsWith("/*")) {
|
||||
// For compatibility with an earlier lexer, the newline after a // comment is its own tok.
|
||||
if (tokText.startsWith("//")
|
||||
&& (originalTokText.endsWith("\n") || originalTokText.endsWith("\r"))) {
|
||||
extraNewline = Newlines.getLineEnding(originalTokText);
|
||||
tokText = tokText.substring(0, tokText.length() - extraNewline.length());
|
||||
originalTokText =
|
||||
originalTokText.substring(0, originalTokText.length() - extraNewline.length());
|
||||
}
|
||||
isToken = false;
|
||||
isNumbered = true;
|
||||
strings.add(originalTokText);
|
||||
} else if (Character.isJavaIdentifierStart(tokText0)
|
||||
|| Character.isDigit(tokText0)
|
||||
|| (tokText0 == '.' && tokText.length() > 1 && Character.isDigit(tokText.charAt(1)))) {
|
||||
// Identifier, keyword, or numeric literal (a dot may begin a number, as in .2D).
|
||||
isToken = true;
|
||||
isNumbered = true;
|
||||
strings.add(tokText);
|
||||
} else {
|
||||
// Other tokens ("+" or "++" or ">>" are broken into one-character toks, because ">>"
|
||||
// cannot be lexed without syntactic knowledge. This implementation fails if the token
|
||||
// contains Unicode escapes.
|
||||
isToken = true;
|
||||
isNumbered = true;
|
||||
for (char c : tokText.toCharArray()) {
|
||||
strings.add(String.valueOf(c));
|
||||
}
|
||||
}
|
||||
if (strings.size() == 1) {
|
||||
toks.add(
|
||||
new Tok(
|
||||
isNumbered ? kN++ : -1,
|
||||
originalTokText,
|
||||
tokText,
|
||||
charI,
|
||||
columnI,
|
||||
isToken,
|
||||
t.kind()));
|
||||
charI += originalTokText.length();
|
||||
columnI = updateColumn(columnI, originalTokText);
|
||||
|
||||
} else {
|
||||
if (strings.size() != 1 && !tokText.equals(originalTokText)) {
|
||||
throw new FormatterException(
|
||||
"Unicode escapes not allowed in whitespace or multi-character operators");
|
||||
}
|
||||
for (String str : strings) {
|
||||
toks.add(new Tok(isNumbered ? kN++ : -1, str, str, charI, columnI, isToken, null));
|
||||
charI += str.length();
|
||||
columnI = updateColumn(columnI, originalTokText);
|
||||
}
|
||||
}
|
||||
if (extraNewline != null) {
|
||||
toks.add(new Tok(-1, extraNewline, extraNewline, charI, columnI, false, null));
|
||||
columnI = 0;
|
||||
charI += extraNewline.length();
|
||||
}
|
||||
}
|
||||
toks.add(new Tok(kN, "", "", charI, columnI, true, null)); // EOF tok.
|
||||
return ImmutableList.copyOf(toks);
|
||||
}
|
||||
|
||||
private static int updateColumn(int columnI, String originalTokText) {
|
||||
Integer last = Iterators.getLast(Newlines.lineOffsetIterator(originalTokText));
|
||||
if (last > 0) {
|
||||
columnI = originalTokText.length() - last;
|
||||
} else {
|
||||
columnI += originalTokText.length();
|
||||
}
|
||||
return columnI;
|
||||
}
|
||||
|
||||
private static ImmutableList<Token> buildTokens(List<Tok> toks) {
|
||||
ImmutableList.Builder<Token> tokens = ImmutableList.builder();
|
||||
int k = 0;
|
||||
int kN = toks.size();
|
||||
|
||||
// Remaining non-tokens before the token go here.
|
||||
ImmutableList.Builder<Tok> toksBefore = ImmutableList.builder();
|
||||
|
||||
OUTERMOST:
|
||||
while (k < kN) {
|
||||
while (!toks.get(k).isToken()) {
|
||||
Tok tok = toks.get(k++);
|
||||
toksBefore.add(tok);
|
||||
if (isParamComment(tok)) {
|
||||
while (toks.get(k).isNewline()) {
|
||||
// drop newlines after parameter comments
|
||||
k++;
|
||||
}
|
||||
}
|
||||
}
|
||||
Tok tok = toks.get(k++);
|
||||
|
||||
// Non-tokens starting on the same line go here too.
|
||||
ImmutableList.Builder<Tok> toksAfter = ImmutableList.builder();
|
||||
OUTER:
|
||||
while (k < kN && !toks.get(k).isToken()) {
|
||||
// Don't attach inline comments to certain leading tokens, e.g. for `f(/*flag1=*/true).
|
||||
//
|
||||
// Attaching inline comments to the right token is hard, and this barely
|
||||
// scratches the surface. But it's enough to do a better job with parameter
|
||||
// name comments.
|
||||
//
|
||||
// TODO(cushon): find a better strategy.
|
||||
if (toks.get(k).isSlashStarComment()) {
|
||||
switch (tok.getText()) {
|
||||
case "(":
|
||||
case "<":
|
||||
case ".":
|
||||
break OUTER;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (toks.get(k).isJavadocComment()) {
|
||||
switch (tok.getText()) {
|
||||
case ";":
|
||||
break OUTER;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isParamComment(toks.get(k))) {
|
||||
tokens.add(new Token(toksBefore.build(), tok, toksAfter.build()));
|
||||
toksBefore = ImmutableList.<Tok>builder().add(toks.get(k++));
|
||||
// drop newlines after parameter comments
|
||||
while (toks.get(k).isNewline()) {
|
||||
k++;
|
||||
}
|
||||
continue OUTERMOST;
|
||||
}
|
||||
Tok nonTokenAfter = toks.get(k++);
|
||||
toksAfter.add(nonTokenAfter);
|
||||
if (Newlines.containsBreaks(nonTokenAfter.getText())) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
tokens.add(new Token(toksBefore.build(), tok, toksAfter.build()));
|
||||
toksBefore = ImmutableList.builder();
|
||||
}
|
||||
return tokens.build();
|
||||
}
|
||||
|
||||
private static boolean isParamComment(Tok tok) {
|
||||
return tok.isSlashStarComment()
|
||||
&& tok.getText().matches("\\/\\*[A-Za-z0-9\\s_\\-]+=\\s*\\*\\/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from a character range to a token range.
|
||||
*
|
||||
* @param characterRange the {@code 0}-based {@link Range} of characters
|
||||
* @return the {@code 0}-based {@link Range} of tokens
|
||||
* @throws FormatterException if the upper endpoint of the range is outside the file
|
||||
*/
|
||||
Range<Integer> characterRangeToTokenRange(Range<Integer> characterRange)
|
||||
throws FormatterException {
|
||||
if (characterRange.upperEndpoint() > text.length()) {
|
||||
throw new FormatterException(
|
||||
String.format(
|
||||
"error: invalid length %d, offset + length (%d) is outside the file",
|
||||
characterRange.upperEndpoint() - characterRange.lowerEndpoint(),
|
||||
characterRange.upperEndpoint()));
|
||||
}
|
||||
// empty range stands for "format the line under the cursor"
|
||||
Range<Integer> nonEmptyRange =
|
||||
characterRange.isEmpty()
|
||||
? Range.closedOpen(characterRange.lowerEndpoint(), characterRange.lowerEndpoint() + 1)
|
||||
: characterRange;
|
||||
ImmutableCollection<Token> enclosed =
|
||||
getPositionTokenMap().subRangeMap(nonEmptyRange).asMapOfRanges().values();
|
||||
if (enclosed.isEmpty()) {
|
||||
return EMPTY_RANGE;
|
||||
}
|
||||
return Range.closedOpen(
|
||||
enclosed.iterator().next().getTok().getIndex(), getLast(enclosed).getTok().getIndex() + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of toks.
|
||||
*
|
||||
* @return the number of toks, excluding the EOF tok
|
||||
*/
|
||||
@Override
|
||||
public int getkN() {
|
||||
return kN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Token by index.
|
||||
*
|
||||
* @param k the Tok index
|
||||
*/
|
||||
@Override
|
||||
public Token getToken(int k) {
|
||||
return kToToken[k];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the input tokens.
|
||||
*
|
||||
* @return the input tokens
|
||||
*/
|
||||
@Override
|
||||
public ImmutableList<? extends Input.Token> getTokens() {
|
||||
return tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the navigable map from position to {@link Token}. Used to look for tokens following a given
|
||||
* one, and to implement the --offset and --length flags to reformat a character range in the
|
||||
* input file.
|
||||
*
|
||||
* @return the navigable map from position to {@link Token}
|
||||
*/
|
||||
@Override
|
||||
public ImmutableRangeMap<Integer, Token> getPositionTokenMap() {
|
||||
return positionTokenMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("tokens", tokens)
|
||||
.add("super", super.toString())
|
||||
.toString();
|
||||
}
|
||||
|
||||
private JCCompilationUnit unit;
|
||||
|
||||
@Override
|
||||
public int getLineNumber(int inputPosition) {
|
||||
Verify.verifyNotNull(unit, "Expected compilation unit to be set.");
|
||||
return unit.getLineMap().getLineNumber(inputPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnNumber(int inputPosition) {
|
||||
Verify.verifyNotNull(unit, "Expected compilation unit to be set.");
|
||||
return unit.getLineMap().getColumnNumber(inputPosition);
|
||||
}
|
||||
|
||||
// TODO(cushon): refactor JavaInput so the CompilationUnit can be passed into
|
||||
// the constructor.
|
||||
public void setCompilationUnit(JCCompilationUnit unit) {
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
public RangeSet<Integer> characterRangesToTokenRanges(Collection<Range<Integer>> characterRanges)
|
||||
throws FormatterException {
|
||||
RangeSet<Integer> tokenRangeSet = TreeRangeSet.create();
|
||||
for (Range<Integer> characterRange : characterRanges) {
|
||||
tokenRangeSet.add(
|
||||
characterRangeToTokenRange(characterRange.canonical(DiscreteDomain.integers())));
|
||||
}
|
||||
return tokenRangeSet;
|
||||
}
|
||||
}
|
||||
|
||||
+2657
-2651
File diff suppressed because it is too large
Load Diff
+86
-90
@@ -31,7 +31,6 @@ import com.google.googlejavaformat.Input.Token;
|
||||
import com.google.googlejavaformat.Newlines;
|
||||
import com.google.googlejavaformat.OpsBuilder.BlankLineWanted;
|
||||
import com.google.googlejavaformat.Output;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -75,31 +74,20 @@ public final class JavaOutput extends Output {
|
||||
kN = javaInput.getkN();
|
||||
}
|
||||
|
||||
public static String applyReplacements(String input, List<Replacement> replacements) {
|
||||
replacements = new ArrayList<>(replacements);
|
||||
replacements.sort(comparing((Replacement r) -> r.getReplaceRange().lowerEndpoint()).reversed());
|
||||
StringBuilder writer = new StringBuilder(input);
|
||||
for (Replacement replacement : replacements) {
|
||||
writer.replace(
|
||||
replacement.getReplaceRange().lowerEndpoint(),
|
||||
replacement.getReplaceRange().upperEndpoint(),
|
||||
replacement.getReplacementString());
|
||||
@Override
|
||||
public void blankLine(int k, BlankLineWanted wanted) {
|
||||
if (blankLines.containsKey(k)) {
|
||||
blankLines.put(k, blankLines.get(k).merge(wanted));
|
||||
} else {
|
||||
blankLines.put(k, wanted);
|
||||
}
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
/** The earliest position of any Tok in the Token, including leading whitespace. */
|
||||
public static int startPosition(Token token) {
|
||||
int min = token.getTok().getPosition();
|
||||
for (Input.Tok tok : token.getToksBefore()) {
|
||||
min = min(min, tok.getPosition());
|
||||
}
|
||||
return min;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void indent(int indent) {
|
||||
spacesPending.append(Strings.repeat(" ", indent));
|
||||
public void markForPartialFormat(Token start, Token end) {
|
||||
int lo = JavaOutput.startTok(start).getIndex();
|
||||
int hi = JavaOutput.endTok(end).getIndex();
|
||||
partialFormatRanges.add(Range.closed(lo, hi));
|
||||
}
|
||||
|
||||
// TODO(user): Add invariant.
|
||||
@@ -123,7 +111,7 @@ public final class JavaOutput extends Output {
|
||||
* there's a blank line here and it's a comment.
|
||||
*/
|
||||
BlankLineWanted wanted = blankLines.getOrDefault(lastK, BlankLineWanted.NO);
|
||||
if (isComment(text) ? sawNewlines : wanted.wanted().orElse(sawNewlines)) {
|
||||
if ((sawNewlines && isComment(text)) || wanted.wanted().orElse(sawNewlines)) {
|
||||
++newlinesPending;
|
||||
}
|
||||
}
|
||||
@@ -190,67 +178,8 @@ public final class JavaOutput extends Output {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void blankLine(int k, BlankLineWanted wanted) {
|
||||
if (blankLines.containsKey(k)) {
|
||||
blankLines.put(k, blankLines.get(k).merge(wanted));
|
||||
} else {
|
||||
blankLines.put(k, wanted);
|
||||
}
|
||||
}
|
||||
|
||||
// The following methods can be used after the Output has been built.
|
||||
|
||||
@Override
|
||||
public void markForPartialFormat(Token start, Token end) {
|
||||
int lo = JavaOutput.startTok(start).getIndex();
|
||||
int hi = JavaOutput.endTok(end).getIndex();
|
||||
partialFormatRanges.add(Range.closed(lo, hi));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommentsHelper getCommentsHelper() {
|
||||
return commentsHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("iLine", iLine)
|
||||
.add("lastK", lastK)
|
||||
.add("spacesPending", spacesPending.toString().replace("\t", "\\t"))
|
||||
.add("newlinesPending", newlinesPending)
|
||||
.add("blankLines", blankLines)
|
||||
.add("super", super.toString())
|
||||
.toString();
|
||||
}
|
||||
|
||||
/** The earliest non-whitespace Tok in the Token. */
|
||||
public static Input.Tok startTok(Token token) {
|
||||
for (Input.Tok tok : token.getToksBefore()) {
|
||||
if (tok.getIndex() >= 0) {
|
||||
return tok;
|
||||
}
|
||||
}
|
||||
return token.getTok();
|
||||
}
|
||||
|
||||
/** The last non-whitespace Tok in the Token. */
|
||||
public static Input.Tok endTok(Token token) {
|
||||
for (int i = token.getToksAfter().size() - 1; i >= 0; i--) {
|
||||
Input.Tok tok = token.getToksAfter().get(i);
|
||||
if (tok.getIndex() >= 0) {
|
||||
return tok;
|
||||
}
|
||||
}
|
||||
return token.getTok();
|
||||
}
|
||||
|
||||
private boolean isComment(String text) {
|
||||
return text.startsWith("//") || text.startsWith("/*");
|
||||
}
|
||||
|
||||
private static Range<Integer> union(Range<Integer> x, Range<Integer> y) {
|
||||
return x.isEmpty() ? y : y.isEmpty() ? x : x.span(y).canonical(DiscreteDomain.integers());
|
||||
public void indent(int indent) {
|
||||
spacesPending.append(Strings.repeat(" ", indent));
|
||||
}
|
||||
|
||||
/** Flush any incomplete last line, then add the EOF token into our data structures. */
|
||||
@@ -268,6 +197,13 @@ public final class JavaOutput extends Output {
|
||||
setLines(ImmutableList.copyOf(mutableLines));
|
||||
}
|
||||
|
||||
// The following methods can be used after the Output has been built.
|
||||
|
||||
@Override
|
||||
public CommentsHelper getCommentsHelper() {
|
||||
return commentsHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a list of {@link Replacement}s to convert from input to output.
|
||||
*
|
||||
@@ -333,10 +269,8 @@ public final class JavaOutput extends Output {
|
||||
replaceTo = javaInput.getText().length();
|
||||
}
|
||||
// Replace trailing whitespace in the input with the whitespace from the formatted file.
|
||||
// If the trailing whitespace in the input includes one or more line breaks, preserve
|
||||
// the
|
||||
// whitespace after the last newline to avoid re-indenting the line following the
|
||||
// formatted
|
||||
// If the trailing whitespace in the input includes one or more line breaks, preserve the
|
||||
// whitespace after the last newline to avoid re-indenting the line following the formatted
|
||||
// line.
|
||||
int newline = -1;
|
||||
while (replaceTo < javaInput.getText().length()) {
|
||||
@@ -347,8 +281,7 @@ public final class JavaOutput extends Output {
|
||||
int newlineLength = Newlines.hasNewlineAt(javaInput.getText(), replaceTo);
|
||||
if (newlineLength != -1) {
|
||||
newline = replaceTo;
|
||||
// Skip over the entire newline; don't count the second character of \r\n as a
|
||||
// newline.
|
||||
// Skip over the entire newline; don't count the second character of \r\n as a newline.
|
||||
replaceTo += newlineLength;
|
||||
} else {
|
||||
replaceTo++;
|
||||
@@ -401,4 +334,67 @@ public final class JavaOutput extends Output {
|
||||
hiTok = partialFormatRanges.rangeContaining(hiTok).upperEndpoint();
|
||||
return Range.closedOpen(loTok, hiTok + 1);
|
||||
}
|
||||
|
||||
public static String applyReplacements(String input, List<Replacement> replacements) {
|
||||
replacements = new ArrayList<>(replacements);
|
||||
replacements.sort(comparing((Replacement r) -> r.getReplaceRange().lowerEndpoint()).reversed());
|
||||
StringBuilder writer = new StringBuilder(input);
|
||||
for (Replacement replacement : replacements) {
|
||||
writer.replace(
|
||||
replacement.getReplaceRange().lowerEndpoint(),
|
||||
replacement.getReplaceRange().upperEndpoint(),
|
||||
replacement.getReplacementString());
|
||||
}
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
/** The earliest position of any Tok in the Token, including leading whitespace. */
|
||||
public static int startPosition(Token token) {
|
||||
int min = token.getTok().getPosition();
|
||||
for (Input.Tok tok : token.getToksBefore()) {
|
||||
min = min(min, tok.getPosition());
|
||||
}
|
||||
return min;
|
||||
}
|
||||
|
||||
/** The earliest non-whitespace Tok in the Token. */
|
||||
public static Input.Tok startTok(Token token) {
|
||||
for (Input.Tok tok : token.getToksBefore()) {
|
||||
if (tok.getIndex() >= 0) {
|
||||
return tok;
|
||||
}
|
||||
}
|
||||
return token.getTok();
|
||||
}
|
||||
|
||||
/** The last non-whitespace Tok in the Token. */
|
||||
public static Input.Tok endTok(Token token) {
|
||||
for (int i = token.getToksAfter().size() - 1; i >= 0; i--) {
|
||||
Input.Tok tok = token.getToksAfter().get(i);
|
||||
if (tok.getIndex() >= 0) {
|
||||
return tok;
|
||||
}
|
||||
}
|
||||
return token.getTok();
|
||||
}
|
||||
|
||||
private boolean isComment(String text) {
|
||||
return text.startsWith("//") || text.startsWith("/*");
|
||||
}
|
||||
|
||||
private static Range<Integer> union(Range<Integer> x, Range<Integer> y) {
|
||||
return x.isEmpty() ? y : y.isEmpty() ? x : x.span(y).canonical(DiscreteDomain.integers());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("iLine", iLine)
|
||||
.add("lastK", lastK)
|
||||
.add("spacesPending", spacesPending.toString().replace("\t", "\\t"))
|
||||
.add("newlinesPending", newlinesPending)
|
||||
.add("blankLines", blankLines)
|
||||
.add("super", super.toString())
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
+58
-59
@@ -18,16 +18,15 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
import openjdk.tools.javac.parser.JavaTokenizer;
|
||||
import openjdk.tools.javac.parser.Scanner;
|
||||
import openjdk.tools.javac.parser.ScannerFactory;
|
||||
import openjdk.tools.javac.parser.Tokens.Comment;
|
||||
import openjdk.tools.javac.parser.Tokens.Comment.CommentStyle;
|
||||
import openjdk.tools.javac.parser.Tokens.Token;
|
||||
import openjdk.tools.javac.parser.Tokens.TokenKind;
|
||||
import openjdk.tools.javac.parser.UnicodeReader;
|
||||
import openjdk.tools.javac.util.Context;
|
||||
|
||||
import com.sun.tools.javac.parser.JavaTokenizer;
|
||||
import com.sun.tools.javac.parser.Scanner;
|
||||
import com.sun.tools.javac.parser.ScannerFactory;
|
||||
import com.sun.tools.javac.parser.Tokens.Comment;
|
||||
import com.sun.tools.javac.parser.Tokens.Comment.CommentStyle;
|
||||
import com.sun.tools.javac.parser.Tokens.Token;
|
||||
import com.sun.tools.javac.parser.Tokens.TokenKind;
|
||||
import com.sun.tools.javac.parser.UnicodeReader;
|
||||
import com.sun.tools.javac.util.Context;
|
||||
import java.util.Set;
|
||||
|
||||
/** A wrapper around javac's lexer. */
|
||||
@@ -37,6 +36,41 @@ class JavacTokens {
|
||||
// TODO(b/33103797): fix javac and remove the work-around
|
||||
private static final CharSequence EOF_COMMENT = "\n//EOF";
|
||||
|
||||
/** An unprocessed input token, including whitespace and comments. */
|
||||
static class RawTok {
|
||||
private final String stringVal;
|
||||
private final TokenKind kind;
|
||||
private final int pos;
|
||||
private final int endPos;
|
||||
|
||||
RawTok(String stringVal, TokenKind kind, int pos, int endPos) {
|
||||
this.stringVal = stringVal;
|
||||
this.kind = kind;
|
||||
this.pos = pos;
|
||||
this.endPos = endPos;
|
||||
}
|
||||
|
||||
/** The token kind, or {@code null} for whitespace and comments. */
|
||||
public TokenKind kind() {
|
||||
return kind;
|
||||
}
|
||||
|
||||
/** The start position. */
|
||||
public int pos() {
|
||||
return pos;
|
||||
}
|
||||
|
||||
/** The end position. */
|
||||
public int endPos() {
|
||||
return endPos;
|
||||
}
|
||||
|
||||
/** The escaped string value of a literal, or {@code null} for other tokens. */
|
||||
public String stringVal() {
|
||||
return stringVal;
|
||||
}
|
||||
}
|
||||
|
||||
/** Lex the input and return a list of {@link RawTok}s. */
|
||||
public static ImmutableList<RawTok> getTokens(
|
||||
String source, Context context, Set<TokenKind> stopTokens) {
|
||||
@@ -86,41 +120,6 @@ class JavacTokens {
|
||||
return tokens.build();
|
||||
}
|
||||
|
||||
/** An unprocessed input token, including whitespace and comments. */
|
||||
static class RawTok {
|
||||
private final String stringVal;
|
||||
private final TokenKind kind;
|
||||
private final int pos;
|
||||
private final int endPos;
|
||||
|
||||
RawTok(String stringVal, TokenKind kind, int pos, int endPos) {
|
||||
this.stringVal = stringVal;
|
||||
this.kind = kind;
|
||||
this.pos = pos;
|
||||
this.endPos = endPos;
|
||||
}
|
||||
|
||||
/** The token kind, or {@code null} for whitespace and comments. */
|
||||
public TokenKind kind() {
|
||||
return kind;
|
||||
}
|
||||
|
||||
/** The start position. */
|
||||
public int pos() {
|
||||
return pos;
|
||||
}
|
||||
|
||||
/** The end position. */
|
||||
public int endPos() {
|
||||
return endPos;
|
||||
}
|
||||
|
||||
/** The escaped string value of a literal, or {@code null} for other tokens. */
|
||||
public String stringVal() {
|
||||
return stringVal;
|
||||
}
|
||||
}
|
||||
|
||||
/** A {@link JavaTokenizer} that saves comments. */
|
||||
static class CommentSavingTokenizer extends JavaTokenizer {
|
||||
CommentSavingTokenizer(ScannerFactory fac, char[] buffer, int length) {
|
||||
@@ -171,20 +170,6 @@ class JavacTokens {
|
||||
this.style = style;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Comment: '%s'", getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
String text = this.text;
|
||||
if (text == null) {
|
||||
this.text = text = new String(reader.getRawCharacters());
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the source position of the character at index {@code index} in the comment text.
|
||||
*
|
||||
@@ -206,6 +191,15 @@ class JavacTokens {
|
||||
return style;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
String text = this.text;
|
||||
if (text == null) {
|
||||
this.text = text = new String(reader.getRawCharacters());
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* We don't care about {@code @deprecated} javadoc tags (see the DepAnn check).
|
||||
*
|
||||
@@ -215,6 +209,11 @@ class JavacTokens {
|
||||
public boolean isDeprecated() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Comment: '%s'", getText());
|
||||
}
|
||||
}
|
||||
|
||||
// Scanner(ScannerFactory, JavaTokenizer) is package-private
|
||||
|
||||
+52
-37
@@ -16,11 +16,12 @@ package com.google.googlejavaformat.java;
|
||||
|
||||
import static java.lang.Math.min;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.util.Comparator.comparing;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import com.google.googlejavaformat.FormatterDiagnostic;
|
||||
import com.google.googlejavaformat.java.JavaFormatterOptions.Style;
|
||||
|
||||
import java.io.IOError;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -29,18 +30,25 @@ import java.io.PrintWriter;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorCompletionService;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
/** The main class for the Java formatter CLI. */
|
||||
public final class Main {
|
||||
private static final int MAX_THREADS = 20;
|
||||
private static final String STDIN_FILENAME = "<stdin>";
|
||||
|
||||
static String versionString() {
|
||||
return "google-java-format: Version " + GoogleJavaFormatVersion.version();
|
||||
}
|
||||
|
||||
private final PrintWriter outWriter;
|
||||
private final PrintWriter errWriter;
|
||||
private final InputStream inStream;
|
||||
@@ -75,7 +83,8 @@ public final class Main {
|
||||
return formatter.format(args);
|
||||
} catch (UsageException e) {
|
||||
err.print(e.getMessage());
|
||||
return 0;
|
||||
// We return exit code 2 to differentiate usage issues from code formatting issues.
|
||||
return 2;
|
||||
} finally {
|
||||
err.flush();
|
||||
out.flush();
|
||||
@@ -112,58 +121,59 @@ public final class Main {
|
||||
}
|
||||
}
|
||||
|
||||
static String versionString() {
|
||||
return "google-java-format: Version " + GoogleJavaFormatVersion.version();
|
||||
}
|
||||
|
||||
private int formatFiles(CommandLineOptions parameters, JavaFormatterOptions options) {
|
||||
int numThreads = min(MAX_THREADS, parameters.files().size());
|
||||
ExecutorService executorService = Executors.newFixedThreadPool(numThreads);
|
||||
|
||||
Map<Path, String> inputs = new LinkedHashMap<>();
|
||||
Map<Path, Future<String>> results = new LinkedHashMap<>();
|
||||
ExecutorCompletionService<FormatFileCallable.Result> cs =
|
||||
new ExecutorCompletionService<>(executorService);
|
||||
boolean allOk = true;
|
||||
|
||||
int files = 0;
|
||||
for (String fileName : parameters.files()) {
|
||||
if (!fileName.endsWith(".java")) {
|
||||
errWriter.println("Skipping non-Java file: " + fileName);
|
||||
continue;
|
||||
}
|
||||
Path path = Paths.get(fileName);
|
||||
String input;
|
||||
try {
|
||||
input = new String(Files.readAllBytes(path), UTF_8);
|
||||
inputs.put(path, input);
|
||||
results.put(
|
||||
path, executorService.submit(new FormatFileCallable(parameters, input, options)));
|
||||
String input = new String(Files.readAllBytes(path), UTF_8);
|
||||
cs.submit(new FormatFileCallable(parameters, path, input, options));
|
||||
files++;
|
||||
} catch (IOException e) {
|
||||
errWriter.println(fileName + ": could not read file: " + e.getMessage());
|
||||
allOk = false;
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<Path, Future<String>> result : results.entrySet()) {
|
||||
Path path = result.getKey();
|
||||
String formatted;
|
||||
List<FormatFileCallable.Result> results = new ArrayList<>();
|
||||
while (files > 0) {
|
||||
try {
|
||||
formatted = result.getValue().get();
|
||||
files--;
|
||||
results.add(cs.take().get());
|
||||
} catch (InterruptedException e) {
|
||||
errWriter.println(e.getMessage());
|
||||
allOk = false;
|
||||
continue;
|
||||
} catch (ExecutionException e) {
|
||||
if (e.getCause() instanceof FormatterException) {
|
||||
for (FormatterDiagnostic diagnostic : ((FormatterException) e.getCause()).diagnostics()) {
|
||||
errWriter.println(path + ":" + diagnostic);
|
||||
}
|
||||
} else {
|
||||
errWriter.println(path + ": error: " + e.getCause().getMessage());
|
||||
e.getCause().printStackTrace(errWriter);
|
||||
errWriter.println("error: " + e.getCause().getMessage());
|
||||
e.getCause().printStackTrace(errWriter);
|
||||
allOk = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Collections.sort(results, comparing(FormatFileCallable.Result::path));
|
||||
for (FormatFileCallable.Result result : results) {
|
||||
Path path = result.path();
|
||||
if (result.exception() != null) {
|
||||
for (FormatterDiagnostic diagnostic : result.exception().diagnostics()) {
|
||||
errWriter.println(path + ":" + diagnostic);
|
||||
}
|
||||
allOk = false;
|
||||
continue;
|
||||
}
|
||||
boolean changed = !formatted.equals(inputs.get(path));
|
||||
String formatted = result.output();
|
||||
boolean changed = result.changed();
|
||||
if (changed && parameters.setExitIfChanged()) {
|
||||
allOk = false;
|
||||
}
|
||||
@@ -186,6 +196,10 @@ public final class Main {
|
||||
outWriter.write(formatted);
|
||||
}
|
||||
}
|
||||
if (!MoreExecutors.shutdownAndAwaitTermination(executorService, Duration.ofSeconds(5))) {
|
||||
errWriter.println("Failed to shut down ExecutorService");
|
||||
allOk = false;
|
||||
}
|
||||
return allOk ? 0 : 1;
|
||||
}
|
||||
|
||||
@@ -198,9 +212,16 @@ public final class Main {
|
||||
}
|
||||
String stdinFilename = parameters.assumeFilename().orElse(STDIN_FILENAME);
|
||||
boolean ok = true;
|
||||
try {
|
||||
String output = new FormatFileCallable(parameters, input, options).call();
|
||||
boolean changed = !input.equals(output);
|
||||
FormatFileCallable.Result result =
|
||||
new FormatFileCallable(parameters, null, input, options).call();
|
||||
if (result.exception() != null) {
|
||||
for (FormatterDiagnostic diagnostic : result.exception().diagnostics()) {
|
||||
errWriter.println(stdinFilename + ":" + diagnostic);
|
||||
}
|
||||
ok = false;
|
||||
} else {
|
||||
String output = result.output();
|
||||
boolean changed = result.changed();
|
||||
if (changed && parameters.setExitIfChanged()) {
|
||||
ok = false;
|
||||
}
|
||||
@@ -211,12 +232,6 @@ public final class Main {
|
||||
} else {
|
||||
outWriter.write(output);
|
||||
}
|
||||
} catch (FormatterException e) {
|
||||
for (FormatterDiagnostic diagnostic : e.diagnostics()) {
|
||||
errWriter.println(stdinFilename + ":" + diagnostic);
|
||||
}
|
||||
ok = false;
|
||||
// TODO(cpovirk): Catch other types of exception (as we do in the formatFiles case).
|
||||
}
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
|
||||
+4
-7
@@ -23,8 +23,7 @@ import com.google.common.collect.RangeSet;
|
||||
import com.google.common.collect.TreeRangeMap;
|
||||
import com.google.googlejavaformat.Input.Tok;
|
||||
import com.google.googlejavaformat.Input.Token;
|
||||
import openjdk.tools.javac.parser.Tokens.TokenKind;
|
||||
|
||||
import com.sun.tools.javac.parser.Tokens.TokenKind;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -32,8 +31,7 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import jdkx.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
/** Fixes sequences of modifiers to be in JLS order. */
|
||||
final class ModifierOrderer {
|
||||
@@ -112,7 +110,7 @@ final class ModifierOrderer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given token as a {@link jdkx.lang.model.element.Modifier}, or {@code null} if it
|
||||
* Returns the given token as a {@link javax.lang.model.element.Modifier}, or {@code null} if it
|
||||
* is not a modifier.
|
||||
*/
|
||||
private static Modifier asModifier(Token token) {
|
||||
@@ -159,8 +157,7 @@ final class ModifierOrderer {
|
||||
/** Applies replacements to the given string. */
|
||||
private static JavaInput applyReplacements(
|
||||
JavaInput javaInput, TreeRangeMap<Integer, String> replacementMap) throws FormatterException {
|
||||
// process in descending order so the replacement ranges aren't perturbed if any
|
||||
// replacements
|
||||
// process in descending order so the replacement ranges aren't perturbed if any replacements
|
||||
// differ in size from the input
|
||||
Map<Range<Integer>, String> ranges = replacementMap.asDescendingMapOfRanges();
|
||||
if (ranges.isEmpty()) {
|
||||
|
||||
+185
-181
@@ -30,31 +30,29 @@ import com.google.common.collect.RangeSet;
|
||||
import com.google.common.collect.TreeRangeMap;
|
||||
import com.google.common.collect.TreeRangeSet;
|
||||
import com.google.googlejavaformat.Newlines;
|
||||
import openjdk.source.doctree.DocCommentTree;
|
||||
import openjdk.source.doctree.ReferenceTree;
|
||||
import openjdk.source.tree.CaseTree;
|
||||
import openjdk.source.tree.IdentifierTree;
|
||||
import openjdk.source.tree.ImportTree;
|
||||
import openjdk.source.tree.Tree;
|
||||
import openjdk.source.util.DocTreePath;
|
||||
import openjdk.source.util.DocTreePathScanner;
|
||||
import openjdk.source.util.TreePathScanner;
|
||||
import openjdk.source.util.TreeScanner;
|
||||
import openjdk.tools.javac.api.JavacTrees;
|
||||
import openjdk.tools.javac.file.JavacFileManager;
|
||||
import openjdk.tools.javac.parser.JavacParser;
|
||||
import openjdk.tools.javac.parser.ParserFactory;
|
||||
import openjdk.tools.javac.tree.DCTree;
|
||||
import openjdk.tools.javac.tree.DCTree.DCReference;
|
||||
import openjdk.tools.javac.tree.JCTree;
|
||||
import openjdk.tools.javac.tree.JCTree.JCCompilationUnit;
|
||||
import openjdk.tools.javac.tree.JCTree.JCFieldAccess;
|
||||
import openjdk.tools.javac.tree.JCTree.JCIdent;
|
||||
import openjdk.tools.javac.tree.JCTree.JCImport;
|
||||
import openjdk.tools.javac.util.Context;
|
||||
import openjdk.tools.javac.util.Log;
|
||||
import openjdk.tools.javac.util.Options;
|
||||
|
||||
import com.sun.source.doctree.DocCommentTree;
|
||||
import com.sun.source.doctree.ReferenceTree;
|
||||
import com.sun.source.tree.CaseTree;
|
||||
import com.sun.source.tree.IdentifierTree;
|
||||
import com.sun.source.tree.ImportTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.util.DocTreePath;
|
||||
import com.sun.source.util.DocTreePathScanner;
|
||||
import com.sun.source.util.TreePathScanner;
|
||||
import com.sun.source.util.TreeScanner;
|
||||
import com.sun.tools.javac.api.JavacTrees;
|
||||
import com.sun.tools.javac.file.JavacFileManager;
|
||||
import com.sun.tools.javac.parser.JavacParser;
|
||||
import com.sun.tools.javac.parser.ParserFactory;
|
||||
import com.sun.tools.javac.tree.DCTree;
|
||||
import com.sun.tools.javac.tree.DCTree.DCReference;
|
||||
import com.sun.tools.javac.tree.JCTree;
|
||||
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
|
||||
import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
|
||||
import com.sun.tools.javac.tree.JCTree.JCImport;
|
||||
import com.sun.tools.javac.util.Context;
|
||||
import com.sun.tools.javac.util.Log;
|
||||
import com.sun.tools.javac.util.Options;
|
||||
import java.io.IOError;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
@@ -63,13 +61,12 @@ import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import jdkx.tools.Diagnostic;
|
||||
import jdkx.tools.DiagnosticCollector;
|
||||
import jdkx.tools.DiagnosticListener;
|
||||
import jdkx.tools.JavaFileObject;
|
||||
import jdkx.tools.SimpleJavaFileObject;
|
||||
import jdkx.tools.StandardLocation;
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.DiagnosticCollector;
|
||||
import javax.tools.DiagnosticListener;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.SimpleJavaFileObject;
|
||||
import javax.tools.StandardLocation;
|
||||
|
||||
/**
|
||||
* Removes unused imports from a source file. Imports that are only used in javadoc are also
|
||||
@@ -77,6 +74,145 @@ import jdkx.tools.StandardLocation;
|
||||
*/
|
||||
public class RemoveUnusedImports {
|
||||
|
||||
// Visits an AST, recording all simple names that could refer to imported
|
||||
// types and also any javadoc references that could refer to imported
|
||||
// types (`@link`, `@see`, `@throws`, etc.)
|
||||
//
|
||||
// No attempt is made to determine whether simple names occur in contexts
|
||||
// where they are type names, so there will be false positives. For example,
|
||||
// `List` is not identified as unused import below:
|
||||
//
|
||||
// ```
|
||||
// import java.util.List;
|
||||
// class List {}
|
||||
// ```
|
||||
//
|
||||
// This is still reasonably effective in practice because type names differ
|
||||
// from other kinds of names in casing convention, and simple name
|
||||
// clashes between imported and declared types are rare.
|
||||
private static class UnusedImportScanner extends TreePathScanner<Void, Void> {
|
||||
|
||||
private final Set<String> usedNames = new LinkedHashSet<>();
|
||||
private final Multimap<String, Range<Integer>> usedInJavadoc = HashMultimap.create();
|
||||
final JavacTrees trees;
|
||||
final DocTreeScanner docTreeSymbolScanner;
|
||||
|
||||
private UnusedImportScanner(JavacTrees trees) {
|
||||
this.trees = trees;
|
||||
docTreeSymbolScanner = new DocTreeScanner();
|
||||
}
|
||||
|
||||
/** Skip the imports themselves when checking for usage. */
|
||||
@Override
|
||||
public Void visitImport(ImportTree importTree, Void usedSymbols) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitIdentifier(IdentifierTree tree, Void unused) {
|
||||
if (tree == null) {
|
||||
return null;
|
||||
}
|
||||
usedNames.add(tree.getName().toString());
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO(cushon): remove this override when pattern matching in switch is no longer a preview
|
||||
// feature, and TreePathScanner visits CaseTree#getLabels instead of CaseTree#getExpressions
|
||||
@SuppressWarnings("unchecked") // reflection
|
||||
@Override
|
||||
public Void visitCase(CaseTree tree, Void unused) {
|
||||
if (CASE_TREE_GET_LABELS != null) {
|
||||
try {
|
||||
scan((List<? extends Tree>) CASE_TREE_GET_LABELS.invoke(tree), null);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new LinkageError(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
return super.visitCase(tree, null);
|
||||
}
|
||||
|
||||
private static final Method CASE_TREE_GET_LABELS = caseTreeGetLabels();
|
||||
|
||||
private static Method caseTreeGetLabels() {
|
||||
try {
|
||||
return CaseTree.class.getMethod("getLabels");
|
||||
} catch (NoSuchMethodException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void scan(Tree tree, Void unused) {
|
||||
if (tree == null) {
|
||||
return null;
|
||||
}
|
||||
scanJavadoc();
|
||||
return super.scan(tree, unused);
|
||||
}
|
||||
|
||||
private void scanJavadoc() {
|
||||
if (getCurrentPath() == null) {
|
||||
return;
|
||||
}
|
||||
DocCommentTree commentTree = trees.getDocCommentTree(getCurrentPath());
|
||||
if (commentTree == null) {
|
||||
return;
|
||||
}
|
||||
docTreeSymbolScanner.scan(new DocTreePath(getCurrentPath(), commentTree), null);
|
||||
}
|
||||
|
||||
// scan javadoc comments, checking for references to imported types
|
||||
class DocTreeScanner extends DocTreePathScanner<Void, Void> {
|
||||
@Override
|
||||
public Void visitIdentifier(com.sun.source.doctree.IdentifierTree node, Void aVoid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitReference(ReferenceTree referenceTree, Void unused) {
|
||||
DCReference reference = (DCReference) referenceTree;
|
||||
long basePos =
|
||||
reference
|
||||
.pos((DCTree.DCDocComment) getCurrentPath().getDocComment())
|
||||
.getStartPosition();
|
||||
// the position of trees inside the reference node aren't stored, but the qualifier's
|
||||
// start position is the beginning of the reference node
|
||||
if (reference.qualifierExpression != null) {
|
||||
new ReferenceScanner(basePos).scan(reference.qualifierExpression, null);
|
||||
}
|
||||
// Record uses inside method parameters. The javadoc tool doesn't use these, but
|
||||
// IntelliJ does.
|
||||
if (reference.paramTypes != null) {
|
||||
for (JCTree param : reference.paramTypes) {
|
||||
// TODO(cushon): get start positions for the parameters
|
||||
new ReferenceScanner(-1).scan(param, null);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// scans the qualifier and parameters of a javadoc reference for possible type names
|
||||
private class ReferenceScanner extends TreeScanner<Void, Void> {
|
||||
private final long basePos;
|
||||
|
||||
public ReferenceScanner(long basePos) {
|
||||
this.basePos = basePos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitIdentifier(IdentifierTree node, Void aVoid) {
|
||||
usedInJavadoc.put(
|
||||
node.getName().toString(),
|
||||
basePos != -1
|
||||
? Range.closedOpen((int) basePos, (int) basePos + node.getName().length())
|
||||
: null);
|
||||
return super.visitIdentifier(node, aVoid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String removeUnusedImports(final String contents) throws FormatterException {
|
||||
Context context = new Context();
|
||||
JCCompilationUnit unit = parse(context, contents);
|
||||
@@ -115,7 +251,10 @@ public class RemoveUnusedImports {
|
||||
ParserFactory parserFactory = ParserFactory.instance(context);
|
||||
JavacParser parser =
|
||||
parserFactory.newParser(
|
||||
javaInput, /*keepDocComments=*/ true, /*keepEndPos=*/ true, /*keepLineMap=*/ true);
|
||||
javaInput,
|
||||
/* keepDocComments= */ true,
|
||||
/* keepEndPos= */ true,
|
||||
/* keepLineMap= */ true);
|
||||
unit = parser.parseCompilationUnit();
|
||||
unit.sourcefile = source;
|
||||
Iterable<Diagnostic<? extends JavaFileObject>> errorDiagnostics =
|
||||
@@ -153,9 +292,7 @@ public class RemoveUnusedImports {
|
||||
}
|
||||
|
||||
private static String getSimpleName(JCImport importTree) {
|
||||
return importTree.getQualifiedIdentifier() instanceof JCIdent
|
||||
? ((JCIdent) importTree.getQualifiedIdentifier()).getName().toString()
|
||||
: ((JCFieldAccess) importTree.getQualifiedIdentifier()).getIdentifier().toString();
|
||||
return getQualifiedIdentifier(importTree).getIdentifier().toString();
|
||||
}
|
||||
|
||||
private static boolean isUnused(
|
||||
@@ -164,18 +301,15 @@ public class RemoveUnusedImports {
|
||||
Multimap<String, Range<Integer>> usedInJavadoc,
|
||||
JCImport importTree,
|
||||
String simpleName) {
|
||||
String qualifier =
|
||||
((JCFieldAccess) importTree.getQualifiedIdentifier()).getExpression().toString();
|
||||
JCFieldAccess qualifiedIdentifier = getQualifiedIdentifier(importTree);
|
||||
String qualifier = qualifiedIdentifier.getExpression().toString();
|
||||
if (qualifier.equals("java.lang")) {
|
||||
return true;
|
||||
}
|
||||
if (unit.getPackageName() != null && unit.getPackageName().toString().equals(qualifier)) {
|
||||
return true;
|
||||
}
|
||||
if (importTree.getQualifiedIdentifier() instanceof JCFieldAccess
|
||||
&& ((JCFieldAccess) importTree.getQualifiedIdentifier())
|
||||
.getIdentifier()
|
||||
.contentEquals("*")) {
|
||||
if (qualifiedIdentifier.getIdentifier().contentEquals("*")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -188,6 +322,15 @@ public class RemoveUnusedImports {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static JCFieldAccess getQualifiedIdentifier(JCImport importTree) {
|
||||
// Use reflection because the return type is JCTree in some versions and JCFieldAccess in others
|
||||
try {
|
||||
return (JCFieldAccess) JCImport.class.getMethod("getQualifiedIdentifier").invoke(importTree);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new LinkageError(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Applies the replacements to the given source, and re-format any edited javadoc. */
|
||||
private static String applyReplacements(String source, RangeMap<Integer, String> replacements) {
|
||||
// save non-empty fixed ranges for reformatting after fixes are applied
|
||||
@@ -212,143 +355,4 @@ public class RemoveUnusedImports {
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
// Visits an AST, recording all simple names that could refer to imported
|
||||
// types and also any javadoc references that could refer to imported
|
||||
// types (`@link`, `@see`, `@throws`, etc.)
|
||||
//
|
||||
// No attempt is made to determine whether simple names occur in contexts
|
||||
// where they are type names, so there will be false positives. For example,
|
||||
// `List` is not identified as unused import below:
|
||||
//
|
||||
// ```
|
||||
// import java.util.List;
|
||||
// class List {}
|
||||
// ```
|
||||
//
|
||||
// This is still reasonably effective in practice because type names differ
|
||||
// from other kinds of names in casing convention, and simple name
|
||||
// clashes between imported and declared types are rare.
|
||||
private static class UnusedImportScanner extends TreePathScanner<Void, Void> {
|
||||
|
||||
private static final Method CASE_TREE_GET_LABELS = caseTreeGetLabels();
|
||||
private final Set<String> usedNames = new LinkedHashSet<>();
|
||||
private final Multimap<String, Range<Integer>> usedInJavadoc = HashMultimap.create();
|
||||
final JavacTrees trees;
|
||||
final DocTreeScanner docTreeSymbolScanner;
|
||||
|
||||
private UnusedImportScanner(JavacTrees trees) {
|
||||
this.trees = trees;
|
||||
docTreeSymbolScanner = new DocTreeScanner();
|
||||
}
|
||||
|
||||
private static Method caseTreeGetLabels() {
|
||||
try {
|
||||
return CaseTree.class.getMethod("getLabels");
|
||||
} catch (NoSuchMethodException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Skip the imports themselves when checking for usage. */
|
||||
@Override
|
||||
public Void visitImport(ImportTree importTree, Void usedSymbols) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO(cushon): remove this override when pattern matching in switch is no longer a preview
|
||||
// feature, and TreePathScanner visits CaseTree#getLabels instead of CaseTree#getExpressions
|
||||
@SuppressWarnings("unchecked") // reflection
|
||||
@Override
|
||||
public Void visitCase(CaseTree tree, Void unused) {
|
||||
if (CASE_TREE_GET_LABELS != null) {
|
||||
try {
|
||||
scan((List<? extends Tree>) CASE_TREE_GET_LABELS.invoke(tree), null);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new LinkageError(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
return super.visitCase(tree, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitIdentifier(IdentifierTree tree, Void unused) {
|
||||
if (tree == null) {
|
||||
return null;
|
||||
}
|
||||
usedNames.add(tree.getName().toString());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void scan(Tree tree, Void unused) {
|
||||
if (tree == null) {
|
||||
return null;
|
||||
}
|
||||
scanJavadoc();
|
||||
return super.scan(tree, unused);
|
||||
}
|
||||
|
||||
private void scanJavadoc() {
|
||||
if (getCurrentPath() == null) {
|
||||
return;
|
||||
}
|
||||
DocCommentTree commentTree = trees.getDocCommentTree(getCurrentPath());
|
||||
if (commentTree == null) {
|
||||
return;
|
||||
}
|
||||
docTreeSymbolScanner.scan(new DocTreePath(getCurrentPath(), commentTree), null);
|
||||
}
|
||||
|
||||
// scan javadoc comments, checking for references to imported types
|
||||
class DocTreeScanner extends DocTreePathScanner<Void, Void> {
|
||||
@Override
|
||||
public Void visitIdentifier(openjdk.source.doctree.IdentifierTree node, Void aVoid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitReference(ReferenceTree referenceTree, Void unused) {
|
||||
DCReference reference = (DCReference) referenceTree;
|
||||
long basePos =
|
||||
reference
|
||||
.pos((DCTree.DCDocComment) getCurrentPath().getDocComment())
|
||||
.getStartPosition();
|
||||
// the position of trees inside the reference node aren't stored, but the
|
||||
// qualifier's
|
||||
// start position is the beginning of the reference node
|
||||
if (reference.qualifierExpression != null) {
|
||||
new ReferenceScanner(basePos).scan(reference.qualifierExpression, null);
|
||||
}
|
||||
// Record uses inside method parameters. The javadoc tool doesn't use these, but
|
||||
// IntelliJ does.
|
||||
if (reference.paramTypes != null) {
|
||||
for (JCTree param : reference.paramTypes) {
|
||||
// TODO(cushon): get start positions for the parameters
|
||||
new ReferenceScanner(-1).scan(param, null);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// scans the qualifier and parameters of a javadoc reference for possible type names
|
||||
private class ReferenceScanner extends TreeScanner<Void, Void> {
|
||||
private final long basePos;
|
||||
|
||||
public ReferenceScanner(long basePos) {
|
||||
this.basePos = basePos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitIdentifier(IdentifierTree node, Void aVoid) {
|
||||
usedInJavadoc.put(
|
||||
node.getName().toString(),
|
||||
basePos != -1
|
||||
? Range.closedOpen((int) basePos, (int) basePos + node.getName().length())
|
||||
: null);
|
||||
return super.visitIdentifier(node, aVoid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+15
-16
@@ -18,7 +18,6 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
@@ -29,6 +28,12 @@ import java.util.Objects;
|
||||
*/
|
||||
public final class Replacement {
|
||||
|
||||
public static Replacement create(int startPosition, int endPosition, String replaceWith) {
|
||||
checkArgument(startPosition >= 0, "startPosition must be non-negative");
|
||||
checkArgument(startPosition <= endPosition, "startPosition cannot be after endPosition");
|
||||
return new Replacement(Range.closedOpen(startPosition, endPosition), replaceWith);
|
||||
}
|
||||
|
||||
private final Range<Integer> replaceRange;
|
||||
private final String replacementString;
|
||||
|
||||
@@ -37,15 +42,14 @@ public final class Replacement {
|
||||
this.replacementString = checkNotNull(replacementString, "Null replacementString");
|
||||
}
|
||||
|
||||
public static Replacement create(int startPosition, int endPosition, String replaceWith) {
|
||||
checkArgument(startPosition >= 0, "startPosition must be non-negative");
|
||||
checkArgument(startPosition <= endPosition, "startPosition cannot be after endPosition");
|
||||
return new Replacement(Range.closedOpen(startPosition, endPosition), replaceWith);
|
||||
/** The range of characters in the original source to replace. */
|
||||
public Range<Integer> getReplaceRange() {
|
||||
return replaceRange;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(replaceRange, replacementString);
|
||||
/** The string to replace the range of characters with. */
|
||||
public String getReplacementString() {
|
||||
return replacementString;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -61,13 +65,8 @@ public final class Replacement {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** The range of characters in the original source to replace. */
|
||||
public Range<Integer> getReplaceRange() {
|
||||
return replaceRange;
|
||||
}
|
||||
|
||||
/** The string to replace the range of characters with. */
|
||||
public String getReplacementString() {
|
||||
return replacementString;
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(replaceRange, replacementString);
|
||||
}
|
||||
}
|
||||
|
||||
+57
-58
@@ -23,16 +23,71 @@ import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Range;
|
||||
import com.google.common.collect.RangeSet;
|
||||
import com.google.common.collect.TreeRangeSet;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/** Formats a subset of a compilation unit. */
|
||||
public class SnippetFormatter {
|
||||
|
||||
/** The kind of snippet to format. */
|
||||
public enum SnippetKind {
|
||||
COMPILATION_UNIT,
|
||||
CLASS_BODY_DECLARATIONS,
|
||||
STATEMENTS,
|
||||
EXPRESSION
|
||||
}
|
||||
|
||||
private class SnippetWrapper {
|
||||
int offset;
|
||||
final StringBuilder contents = new StringBuilder();
|
||||
|
||||
public SnippetWrapper append(String str) {
|
||||
contents.append(str);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SnippetWrapper appendSource(String source) {
|
||||
this.offset = contents.length();
|
||||
contents.append(source);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void closeBraces(int initialIndent) {
|
||||
for (int i = initialIndent; --i >= 0; ) {
|
||||
contents.append("\n").append(createIndentationString(i)).append("}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final int INDENTATION_SIZE = 2;
|
||||
private static final CharMatcher NOT_WHITESPACE = CharMatcher.whitespace().negate();
|
||||
private final Formatter formatter = new Formatter();
|
||||
private static final CharMatcher NOT_WHITESPACE = CharMatcher.whitespace().negate();
|
||||
|
||||
public String createIndentationString(int indentationLevel) {
|
||||
Preconditions.checkArgument(
|
||||
indentationLevel >= 0,
|
||||
"Indentation level cannot be less than zero. Given: %s",
|
||||
indentationLevel);
|
||||
int spaces = indentationLevel * INDENTATION_SIZE;
|
||||
StringBuilder buf = new StringBuilder(spaces);
|
||||
for (int i = 0; i < spaces; i++) {
|
||||
buf.append(' ');
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static Range<Integer> offsetRange(Range<Integer> range, int offset) {
|
||||
range = range.canonical(DiscreteDomain.integers());
|
||||
return Range.closedOpen(range.lowerEndpoint() + offset, range.upperEndpoint() + offset);
|
||||
}
|
||||
|
||||
private static List<Range<Integer>> offsetRanges(List<Range<Integer>> ranges, int offset) {
|
||||
List<Range<Integer>> result = new ArrayList<>();
|
||||
for (Range<Integer> range : ranges) {
|
||||
result.add(offsetRange(range, offset));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Runs the Google Java formatter on the given source, with only the given ranges specified. */
|
||||
public ImmutableList<Replacement> format(
|
||||
@@ -67,19 +122,6 @@ public class SnippetFormatter {
|
||||
.collect(toImmutableList());
|
||||
}
|
||||
|
||||
private static List<Range<Integer>> offsetRanges(List<Range<Integer>> ranges, int offset) {
|
||||
List<Range<Integer>> result = new ArrayList<>();
|
||||
for (Range<Integer> range : ranges) {
|
||||
result.add(offsetRange(range, offset));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Range<Integer> offsetRange(Range<Integer> range, int offset) {
|
||||
range = range.canonical(DiscreteDomain.integers());
|
||||
return Range.closedOpen(range.lowerEndpoint() + offset, range.upperEndpoint() + offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates {@code Replacement}s rewriting {@code source} to {@code replacement}, under the
|
||||
* assumption that they differ in whitespace alone.
|
||||
@@ -170,47 +212,4 @@ public class SnippetFormatter {
|
||||
throw new IllegalArgumentException("Unknown snippet kind: " + kind);
|
||||
}
|
||||
}
|
||||
|
||||
public String createIndentationString(int indentationLevel) {
|
||||
Preconditions.checkArgument(
|
||||
indentationLevel >= 0,
|
||||
"Indentation level cannot be less than zero. Given: %s",
|
||||
indentationLevel);
|
||||
int spaces = indentationLevel * INDENTATION_SIZE;
|
||||
StringBuilder buf = new StringBuilder(spaces);
|
||||
for (int i = 0; i < spaces; i++) {
|
||||
buf.append(' ');
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/** The kind of snippet to format. */
|
||||
public enum SnippetKind {
|
||||
COMPILATION_UNIT,
|
||||
CLASS_BODY_DECLARATIONS,
|
||||
STATEMENTS,
|
||||
EXPRESSION
|
||||
}
|
||||
|
||||
private class SnippetWrapper {
|
||||
final StringBuilder contents = new StringBuilder();
|
||||
int offset;
|
||||
|
||||
public SnippetWrapper append(String str) {
|
||||
contents.append(str);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SnippetWrapper appendSource(String source) {
|
||||
this.offset = contents.length();
|
||||
contents.append(source);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void closeBraces(int initialIndent) {
|
||||
for (int i = initialIndent; --i >= 0; ) {
|
||||
contents.append("\n").append(createIndentationString(i)).append("}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+51
-56
@@ -26,22 +26,21 @@ import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Range;
|
||||
import com.google.common.collect.TreeRangeMap;
|
||||
import com.google.googlejavaformat.Newlines;
|
||||
import openjdk.source.tree.BinaryTree;
|
||||
import openjdk.source.tree.LiteralTree;
|
||||
import openjdk.source.tree.MemberSelectTree;
|
||||
import openjdk.source.tree.Tree;
|
||||
import openjdk.source.tree.Tree.Kind;
|
||||
import openjdk.source.util.TreePath;
|
||||
import openjdk.source.util.TreePathScanner;
|
||||
import openjdk.tools.javac.file.JavacFileManager;
|
||||
import openjdk.tools.javac.parser.JavacParser;
|
||||
import openjdk.tools.javac.parser.ParserFactory;
|
||||
import openjdk.tools.javac.tree.JCTree;
|
||||
import openjdk.tools.javac.util.Context;
|
||||
import openjdk.tools.javac.util.Log;
|
||||
import openjdk.tools.javac.util.Options;
|
||||
import openjdk.tools.javac.util.Position;
|
||||
|
||||
import com.sun.source.tree.BinaryTree;
|
||||
import com.sun.source.tree.LiteralTree;
|
||||
import com.sun.source.tree.MemberSelectTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.tree.Tree.Kind;
|
||||
import com.sun.source.util.TreePath;
|
||||
import com.sun.source.util.TreePathScanner;
|
||||
import com.sun.tools.javac.file.JavacFileManager;
|
||||
import com.sun.tools.javac.parser.JavacParser;
|
||||
import com.sun.tools.javac.parser.ParserFactory;
|
||||
import com.sun.tools.javac.tree.JCTree;
|
||||
import com.sun.tools.javac.util.Context;
|
||||
import com.sun.tools.javac.util.Log;
|
||||
import com.sun.tools.javac.util.Options;
|
||||
import com.sun.tools.javac.util.Position;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.net.URI;
|
||||
@@ -53,21 +52,15 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jdkx.tools.Diagnostic;
|
||||
import jdkx.tools.DiagnosticCollector;
|
||||
import jdkx.tools.DiagnosticListener;
|
||||
import jdkx.tools.JavaFileObject;
|
||||
import jdkx.tools.SimpleJavaFileObject;
|
||||
import jdkx.tools.StandardLocation;
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.DiagnosticCollector;
|
||||
import javax.tools.DiagnosticListener;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.SimpleJavaFileObject;
|
||||
import javax.tools.StandardLocation;
|
||||
|
||||
/** Wraps string literals that exceed the column limit. */
|
||||
public final class StringWrapper {
|
||||
public static final CharMatcher STRING_CONCAT_DELIMITER =
|
||||
CharMatcher.whitespace().or(CharMatcher.anyOf("\"+"));
|
||||
|
||||
private StringWrapper() {}
|
||||
|
||||
/** Reflows long string literals in the given Java source code. */
|
||||
public static String wrap(String input, Formatter formatter) throws FormatterException {
|
||||
return StringWrapper.wrap(Formatter.MAX_LINE_LENGTH, input, formatter);
|
||||
@@ -87,8 +80,7 @@ public final class StringWrapper {
|
||||
String firstPass = formatter.formatSource(input, replacements.asMapOfRanges().keySet());
|
||||
|
||||
if (!firstPass.equals(input)) {
|
||||
// If formatting the replacement ranges resulted in a change, recalculate the
|
||||
// replacements on
|
||||
// If formatting the replacement ranges resulted in a change, recalculate the replacements on
|
||||
// the updated input.
|
||||
input = firstPass;
|
||||
replacements = getReflowReplacements(columnLimit, input);
|
||||
@@ -98,14 +90,15 @@ public final class StringWrapper {
|
||||
|
||||
{
|
||||
// We really don't want bugs in this pass to change the behaviour of programs we're
|
||||
// formatting, so check that the pretty-printed AST is the same before and after
|
||||
// reformatting.
|
||||
// formatting, so check that the pretty-printed AST is the same before and after reformatting.
|
||||
String expected = parse(input, /* allowStringFolding= */ true).toString();
|
||||
String actual = parse(result, /* allowStringFolding= */ true).toString();
|
||||
if (!expected.equals(actual)) {
|
||||
throw new FormatterException(
|
||||
String.format(
|
||||
"Something has gone terribly wrong. Please file a bug: "
|
||||
"Something has gone terribly wrong. We planned to make the below formatting change,"
|
||||
+ " but have aborted because it would unexpectedly change the AST.\n"
|
||||
+ "Please file a bug: "
|
||||
+ "https://github.com/google/google-java-format/issues/new"
|
||||
+ "\n\n=== Actual: ===\n%s\n=== Expected: ===\n%s\n",
|
||||
actual, expected));
|
||||
@@ -158,8 +151,7 @@ public final class StringWrapper {
|
||||
// i.e. `ONE + TWO + THREE`
|
||||
// We need this information to handle continuation indents.
|
||||
AtomicBoolean first = new AtomicBoolean(false);
|
||||
// Finds the set of string literals in the concat expression that includes the one that
|
||||
// needs
|
||||
// Finds the set of string literals in the concat expression that includes the one that needs
|
||||
// to be wrapped.
|
||||
List<Tree> flat = flatten(input, unit, path, enclosing, first);
|
||||
// Zero-indexed start column
|
||||
@@ -250,7 +242,7 @@ public final class StringWrapper {
|
||||
* @param columnLimit the number of columns to wrap at
|
||||
* @param startColumn the column position of the beginning of the original text
|
||||
* @param trailing extra space to leave after the last line, to accommodate a ; or )
|
||||
* @param components the text to reflow. This is a list of "words" of a single literal. Its first
|
||||
* @param components the text to reflow. This is a list of “words” of a single literal. Its first
|
||||
* and last quotes have been stripped
|
||||
* @param first0 true if the text includes the beginning of its enclosing concat chain
|
||||
*/
|
||||
@@ -270,8 +262,7 @@ public final class StringWrapper {
|
||||
while (!input.isEmpty()) {
|
||||
int length = 0;
|
||||
List<String> line = new ArrayList<>();
|
||||
// If we know this is going to be the last line, then remove a bit of width to account
|
||||
// for the
|
||||
// If we know this is going to be the last line, then remove a bit of width to account for the
|
||||
// trailing characters.
|
||||
if (input.stream().mapToInt(String::length).sum() <= width) {
|
||||
// This isn’t quite optimal, but arguably good enough. See b/179561701
|
||||
@@ -357,12 +348,28 @@ public final class StringWrapper {
|
||||
input.subSequence(getEndPosition(unit, one), getStartPosition(two)));
|
||||
}
|
||||
|
||||
public static final CharMatcher STRING_CONCAT_DELIMITER =
|
||||
CharMatcher.whitespace().or(CharMatcher.anyOf("\"+"));
|
||||
|
||||
private static int getEndPosition(JCTree.JCCompilationUnit unit, Tree tree) {
|
||||
return ((JCTree) tree).getEndPosition(unit.endPositions);
|
||||
}
|
||||
|
||||
private static int getStartPosition(Tree tree) {
|
||||
return ((JCTree) tree).getStartPosition();
|
||||
}
|
||||
|
||||
private static int getEndPosition(JCTree.JCCompilationUnit unit, Tree tree) {
|
||||
return ((JCTree) tree).getEndPosition(unit.endPositions);
|
||||
/** Returns true if any lines in the given Java source exceed the column limit. */
|
||||
private static boolean longLines(int columnLimit, String input) {
|
||||
// TODO(cushon): consider adding Newlines.lineIterable?
|
||||
Iterator<String> it = Newlines.lineIterator(input);
|
||||
while (it.hasNext()) {
|
||||
String line = it.next();
|
||||
if (line.length() > columnLimit) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Parses the given Java source. */
|
||||
@@ -391,7 +398,7 @@ public final class StringWrapper {
|
||||
ParserFactory parserFactory = ParserFactory.instance(context);
|
||||
JavacParser parser =
|
||||
parserFactory.newParser(
|
||||
source, /*keepDocComments=*/ true, /*keepEndPos=*/ true, /*keepLineMap=*/ true);
|
||||
source, /* keepDocComments= */ true, /* keepEndPos= */ true, /* keepLineMap= */ true);
|
||||
unit = parser.parseCompilationUnit();
|
||||
unit.sourcefile = sjfo;
|
||||
Iterable<Diagnostic<? extends JavaFileObject>> errorDiagnostics =
|
||||
@@ -403,24 +410,10 @@ public final class StringWrapper {
|
||||
return unit;
|
||||
}
|
||||
|
||||
/** Returns true if any lines in the given Java source exceed the column limit. */
|
||||
private static boolean longLines(int columnLimit, String input) {
|
||||
// TODO(cushon): consider adding Newlines.lineIterable?
|
||||
Iterator<String> it = Newlines.lineIterator(input);
|
||||
while (it.hasNext()) {
|
||||
String line = it.next();
|
||||
if (line.length() > columnLimit) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Applies replacements to the given string. */
|
||||
private static String applyReplacements(
|
||||
String javaInput, TreeRangeMap<Integer, String> replacementMap) throws FormatterException {
|
||||
// process in descending order so the replacement ranges aren't perturbed if any
|
||||
// replacements
|
||||
// process in descending order so the replacement ranges aren't perturbed if any replacements
|
||||
// differ in size from the input
|
||||
Map<Range<Integer>, String> ranges = replacementMap.asDescendingMapOfRanges();
|
||||
if (ranges.isEmpty()) {
|
||||
@@ -433,4 +426,6 @@ public final class StringWrapper {
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private StringWrapper() {}
|
||||
}
|
||||
|
||||
+13
-15
@@ -14,23 +14,21 @@
|
||||
|
||||
package com.google.googlejavaformat.java;
|
||||
|
||||
import openjdk.source.tree.ClassTree;
|
||||
import openjdk.source.tree.CompoundAssignmentTree;
|
||||
import openjdk.source.tree.ExpressionTree;
|
||||
import openjdk.source.tree.IdentifierTree;
|
||||
import openjdk.source.tree.MemberSelectTree;
|
||||
import openjdk.source.tree.MethodInvocationTree;
|
||||
import openjdk.source.tree.ParenthesizedTree;
|
||||
import openjdk.source.tree.Tree;
|
||||
import openjdk.source.util.TreePath;
|
||||
import openjdk.tools.javac.tree.JCTree;
|
||||
import openjdk.tools.javac.tree.Pretty;
|
||||
import openjdk.tools.javac.tree.TreeInfo;
|
||||
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.CompoundAssignmentTree;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.IdentifierTree;
|
||||
import com.sun.source.tree.MemberSelectTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.source.tree.ParenthesizedTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.util.TreePath;
|
||||
import com.sun.tools.javac.tree.JCTree;
|
||||
import com.sun.tools.javac.tree.Pretty;
|
||||
import com.sun.tools.javac.tree.TreeInfo;
|
||||
import java.io.IOError;
|
||||
import java.io.IOException;
|
||||
|
||||
import jdkx.lang.model.element.Name;
|
||||
import javax.lang.model.element.Name;
|
||||
|
||||
/** Utilities for working with {@link Tree}s. */
|
||||
class Trees {
|
||||
|
||||
+27
-28
@@ -15,7 +15,6 @@
|
||||
package com.google.googlejavaformat.java;
|
||||
|
||||
import com.google.common.base.Verify;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -24,33 +23,6 @@ public final class TypeNameClassifier {
|
||||
|
||||
private TypeNameClassifier() {}
|
||||
|
||||
/**
|
||||
* Returns the end index (inclusive) of the longest prefix that matches the naming conventions of
|
||||
* a type or static field access, or -1 if no such prefix was found.
|
||||
*
|
||||
* <p>Examples:
|
||||
*
|
||||
* <ul>
|
||||
* <li>ClassName
|
||||
* <li>ClassName.staticMemberName
|
||||
* <li>com.google.ClassName.InnerClass.staticMemberName
|
||||
* </ul>
|
||||
*/
|
||||
static Optional<Integer> typePrefixLength(List<String> nameParts) {
|
||||
TyParseState state = TyParseState.START;
|
||||
Optional<Integer> typeLength = Optional.empty();
|
||||
for (int i = 0; i < nameParts.size(); i++) {
|
||||
state = state.next(JavaCaseFormat.from(nameParts.get(i)));
|
||||
if (state == TyParseState.REJECT) {
|
||||
break;
|
||||
}
|
||||
if (state.isSingleUnit()) {
|
||||
typeLength = Optional.of(i);
|
||||
}
|
||||
}
|
||||
return typeLength;
|
||||
}
|
||||
|
||||
/** A state machine for classifying qualified names. */
|
||||
private enum TyParseState {
|
||||
|
||||
@@ -138,6 +110,33 @@ public final class TypeNameClassifier {
|
||||
public abstract TyParseState next(JavaCaseFormat n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the end index (inclusive) of the longest prefix that matches the naming conventions of
|
||||
* a type or static field access, or -1 if no such prefix was found.
|
||||
*
|
||||
* <p>Examples:
|
||||
*
|
||||
* <ul>
|
||||
* <li>ClassName
|
||||
* <li>ClassName.staticMemberName
|
||||
* <li>com.google.ClassName.InnerClass.staticMemberName
|
||||
* </ul>
|
||||
*/
|
||||
static Optional<Integer> typePrefixLength(List<String> nameParts) {
|
||||
TyParseState state = TyParseState.START;
|
||||
Optional<Integer> typeLength = Optional.empty();
|
||||
for (int i = 0; i < nameParts.size(); i++) {
|
||||
state = state.next(JavaCaseFormat.from(nameParts.get(i)));
|
||||
if (state == TyParseState.REJECT) {
|
||||
break;
|
||||
}
|
||||
if (state.isSingleUnit()) {
|
||||
typeLength = Optional.of(i);
|
||||
}
|
||||
}
|
||||
return typeLength;
|
||||
}
|
||||
|
||||
/** Case formats used in Java identifiers. */
|
||||
public enum JavaCaseFormat {
|
||||
UPPERCASE,
|
||||
|
||||
+4
-4
@@ -81,6 +81,10 @@ final class UsageException extends Exception {
|
||||
super(buildMessage(null));
|
||||
}
|
||||
|
||||
UsageException(String message) {
|
||||
super(buildMessage(checkNotNull(message)));
|
||||
}
|
||||
|
||||
private static String buildMessage(String message) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (message != null) {
|
||||
@@ -101,8 +105,4 @@ final class UsageException extends Exception {
|
||||
private static void appendLines(StringBuilder builder, String[] lines) {
|
||||
NEWLINE_JOINER.appendTo(builder, lines).append(System.lineSeparator());
|
||||
}
|
||||
|
||||
UsageException(String message) {
|
||||
super(buildMessage(checkNotNull(message)));
|
||||
}
|
||||
}
|
||||
|
||||
+8
-13
@@ -17,17 +17,14 @@ package com.google.googlejavaformat.java.filer;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.google.googlejavaformat.java.Formatter;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import jdkx.annotation.processing.Filer;
|
||||
import jdkx.annotation.processing.Messager;
|
||||
import jdkx.lang.model.element.Element;
|
||||
import jdkx.tools.FileObject;
|
||||
import jdkx.tools.JavaFileManager;
|
||||
import jdkx.tools.JavaFileObject;
|
||||
import javax.annotation.processing.Filer;
|
||||
import javax.annotation.processing.Messager;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.tools.FileObject;
|
||||
import javax.tools.JavaFileManager;
|
||||
import javax.tools.JavaFileObject;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* A decorating {@link Filer} implementation which formats Java source files with a {@link
|
||||
@@ -40,9 +37,7 @@ public final class FormattingFiler implements Filer {
|
||||
private final Formatter formatter = new Formatter();
|
||||
private final Messager messager;
|
||||
|
||||
/**
|
||||
* @param delegate filer to decorate
|
||||
*/
|
||||
/** @param delegate filer to decorate */
|
||||
public FormattingFiler(Filer delegate) {
|
||||
this(delegate, null);
|
||||
}
|
||||
|
||||
+7
-12
@@ -20,16 +20,13 @@ import com.google.common.io.CharSink;
|
||||
import com.google.common.io.CharSource;
|
||||
import com.google.googlejavaformat.java.Formatter;
|
||||
import com.google.googlejavaformat.java.FormatterException;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
import jdkx.annotation.processing.Messager;
|
||||
import jdkx.tools.Diagnostic;
|
||||
import jdkx.tools.ForwardingJavaFileObject;
|
||||
import jdkx.tools.JavaFileObject;
|
||||
import javax.annotation.processing.Messager;
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.ForwardingJavaFileObject;
|
||||
import javax.tools.JavaFileObject;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/** A {@link JavaFileObject} decorator which {@linkplain Formatter formats} source code. */
|
||||
final class FormattingJavaFileObject extends ForwardingJavaFileObject<JavaFileObject> {
|
||||
@@ -81,10 +78,8 @@ final class FormattingJavaFileObject extends ForwardingJavaFileObject<JavaFileOb
|
||||
}
|
||||
});
|
||||
} catch (FormatterException e) {
|
||||
// An exception will happen when the code being formatted has an error. It's
|
||||
// better to
|
||||
// log the exception and emit unformatted code so the developer can view the
|
||||
// code which
|
||||
// An exception will happen when the code being formatted has an error. It's better to
|
||||
// log the exception and emit unformatted code so the developer can view the code which
|
||||
// caused a problem.
|
||||
try (Writer writer = fileObject.openWriter()) {
|
||||
writer.append(stringBuilder.toString());
|
||||
|
||||
+270
@@ -0,0 +1,270 @@
|
||||
/*
|
||||
* Copyright 2020 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.java17;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.common.collect.Iterables.getOnlyElement;
|
||||
|
||||
import com.google.common.base.Verify;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
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.CaseLabelTree;
|
||||
import com.sun.source.tree.CaseTree;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.tree.InstanceOfTree;
|
||||
import com.sun.source.tree.ModifiersTree;
|
||||
import com.sun.source.tree.ModuleTree;
|
||||
import com.sun.source.tree.SwitchExpressionTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.tree.VariableTree;
|
||||
import com.sun.source.tree.YieldTree;
|
||||
import com.sun.tools.javac.code.Flags;
|
||||
import com.sun.tools.javac.tree.JCTree;
|
||||
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
|
||||
import com.sun.tools.javac.tree.TreeInfo;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import javax.lang.model.element.Name;
|
||||
|
||||
/**
|
||||
* Extends {@link JavaInputAstVisitor} with support for AST nodes that were added or modified in
|
||||
* Java 17.
|
||||
*/
|
||||
public class Java17InputAstVisitor extends JavaInputAstVisitor {
|
||||
|
||||
public Java17InputAstVisitor(OpsBuilder builder, int indentMultiplier) {
|
||||
super(builder, indentMultiplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleModule(boolean first, CompilationUnitTree node) {
|
||||
ModuleTree module = node.getModule();
|
||||
if (module != null) {
|
||||
if (!first) {
|
||||
builder.blankLineWanted(BlankLineWanted.YES);
|
||||
}
|
||||
markForPartialFormat();
|
||||
visitModule(module, null);
|
||||
builder.forcedBreak();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<? extends Tree> getPermitsClause(ClassTree node) {
|
||||
return node.getPermitsClause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitBindingPattern(BindingPatternTree node, Void unused) {
|
||||
sync(node);
|
||||
VariableTree variableTree = node.getVariable();
|
||||
visitBindingPattern(
|
||||
variableTree.getModifiers(), variableTree.getType(), variableTree.getName());
|
||||
return null;
|
||||
}
|
||||
|
||||
private void visitBindingPattern(ModifiersTree modifiers, Tree type, Name name) {
|
||||
if (modifiers != null) {
|
||||
List<AnnotationTree> annotations =
|
||||
visitModifiers(modifiers, Direction.HORIZONTAL, Optional.empty());
|
||||
visitAnnotations(annotations, BreakOrNot.NO, BreakOrNot.YES);
|
||||
}
|
||||
scan(type, null);
|
||||
builder.breakOp(" ");
|
||||
visit(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitYield(YieldTree node, Void aVoid) {
|
||||
sync(node);
|
||||
token("yield");
|
||||
builder.space();
|
||||
scan(node.getValue(), null);
|
||||
token(";");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitSwitchExpression(SwitchExpressionTree node, Void aVoid) {
|
||||
sync(node);
|
||||
visitSwitch(node.getExpression(), node.getCases());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitClass(ClassTree tree, Void unused) {
|
||||
switch (tree.getKind()) {
|
||||
case ANNOTATION_TYPE:
|
||||
visitAnnotationType(tree);
|
||||
break;
|
||||
case CLASS:
|
||||
case INTERFACE:
|
||||
visitClassDeclaration(tree);
|
||||
break;
|
||||
case ENUM:
|
||||
visitEnumDeclaration(tree);
|
||||
break;
|
||||
case RECORD:
|
||||
visitRecordDeclaration(tree);
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError(tree.getKind());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void visitRecordDeclaration(ClassTree node) {
|
||||
sync(node);
|
||||
typeDeclarationModifiers(node.getModifiers());
|
||||
Verify.verify(node.getExtendsClause() == null);
|
||||
boolean hasSuperInterfaceTypes = !node.getImplementsClause().isEmpty();
|
||||
token("record");
|
||||
builder.space();
|
||||
visit(node.getSimpleName());
|
||||
if (!node.getTypeParameters().isEmpty()) {
|
||||
token("<");
|
||||
}
|
||||
builder.open(plusFour);
|
||||
{
|
||||
if (!node.getTypeParameters().isEmpty()) {
|
||||
typeParametersRest(node.getTypeParameters(), hasSuperInterfaceTypes ? plusFour : ZERO);
|
||||
}
|
||||
ImmutableList<JCVariableDecl> parameters = recordVariables(node);
|
||||
token("(");
|
||||
if (!parameters.isEmpty()) {
|
||||
// Break before args.
|
||||
builder.breakToFill("");
|
||||
}
|
||||
// record headers can't declare receiver parameters
|
||||
visitFormals(/* receiver= */ Optional.empty(), parameters);
|
||||
token(")");
|
||||
if (hasSuperInterfaceTypes) {
|
||||
builder.breakToFill(" ");
|
||||
builder.open(node.getImplementsClause().size() > 1 ? plusFour : ZERO);
|
||||
token("implements");
|
||||
builder.space();
|
||||
boolean first = true;
|
||||
for (Tree superInterfaceType : node.getImplementsClause()) {
|
||||
if (!first) {
|
||||
token(",");
|
||||
builder.breakOp(" ");
|
||||
}
|
||||
scan(superInterfaceType, null);
|
||||
first = false;
|
||||
}
|
||||
builder.close();
|
||||
}
|
||||
}
|
||||
builder.close();
|
||||
if (node.getMembers() == null) {
|
||||
token(";");
|
||||
} else {
|
||||
List<Tree> members =
|
||||
node.getMembers().stream()
|
||||
.filter(t -> (TreeInfo.flags((JCTree) t) & Flags.GENERATED_MEMBER) == 0)
|
||||
.collect(toImmutableList());
|
||||
addBodyDeclarations(members, BracesOrNot.YES, FirstDeclarationsOrNot.YES);
|
||||
}
|
||||
dropEmptyDeclarations();
|
||||
}
|
||||
|
||||
private static ImmutableList<JCVariableDecl> recordVariables(ClassTree node) {
|
||||
return node.getMembers().stream()
|
||||
.filter(JCVariableDecl.class::isInstance)
|
||||
.map(JCVariableDecl.class::cast)
|
||||
.filter(m -> (m.mods.flags & RECORD) == RECORD)
|
||||
.collect(toImmutableList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitInstanceOf(InstanceOfTree node, Void unused) {
|
||||
sync(node);
|
||||
builder.open(plusFour);
|
||||
scan(node.getExpression(), null);
|
||||
builder.breakOp(" ");
|
||||
builder.open(ZERO);
|
||||
token("instanceof");
|
||||
builder.breakOp(" ");
|
||||
if (node.getPattern() != null) {
|
||||
scan(node.getPattern(), null);
|
||||
} else {
|
||||
scan(node.getType(), null);
|
||||
}
|
||||
builder.close();
|
||||
builder.close();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitCase(CaseTree node, Void unused) {
|
||||
sync(node);
|
||||
markForPartialFormat();
|
||||
builder.forcedBreak();
|
||||
List<? extends CaseLabelTree> labels = node.getLabels();
|
||||
boolean isDefault =
|
||||
labels.size() == 1 && getOnlyElement(labels).getKind().name().equals("DEFAULT_CASE_LABEL");
|
||||
if (isDefault) {
|
||||
token("default", plusTwo);
|
||||
} else {
|
||||
token("case", plusTwo);
|
||||
builder.open(labels.size() > 1 ? plusFour : ZERO);
|
||||
builder.space();
|
||||
boolean first = true;
|
||||
for (Tree expression : labels) {
|
||||
if (!first) {
|
||||
token(",");
|
||||
builder.breakOp(" ");
|
||||
}
|
||||
scan(expression, null);
|
||||
first = false;
|
||||
}
|
||||
builder.close();
|
||||
}
|
||||
switch (node.getCaseKind()) {
|
||||
case STATEMENT:
|
||||
token(":");
|
||||
builder.open(plusTwo);
|
||||
visitStatements(node.getStatements());
|
||||
builder.close();
|
||||
break;
|
||||
case RULE:
|
||||
builder.space();
|
||||
token("-");
|
||||
token(">");
|
||||
builder.space();
|
||||
if (node.getBody().getKind() == Tree.Kind.BLOCK) {
|
||||
// Explicit call with {@link CollapseEmptyOrNot.YES} to handle empty case blocks.
|
||||
visitBlock(
|
||||
(BlockTree) node.getBody(),
|
||||
CollapseEmptyOrNot.YES,
|
||||
AllowLeadingBlankLine.NO,
|
||||
AllowTrailingBlankLine.NO);
|
||||
} else {
|
||||
scan(node.getBody(), null);
|
||||
}
|
||||
builder.guessToken(";");
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError(node.getCaseKind());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+13
-12
@@ -22,7 +22,6 @@ 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;
|
||||
@@ -37,19 +36,8 @@ import java.util.regex.Pattern;
|
||||
*/
|
||||
public final class JavadocFormatter {
|
||||
|
||||
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);
|
||||
|
||||
/*
|
||||
* 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 final Pattern ONE_CONTENT_LINE_PATTERN = compile(" */[*][*]\n *[*] (.*)\n *[*]/");
|
||||
static final int MAX_LINE_LENGTH = 100;
|
||||
|
||||
private JavadocFormatter() {}
|
||||
|
||||
/**
|
||||
* Formats the given Javadoc comment, which must start with ∕✱✱ and end with ✱∕. The output will
|
||||
* start and end with the same characters.
|
||||
@@ -150,6 +138,11 @@ public final class JavadocFormatter {
|
||||
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);
|
||||
}
|
||||
@@ -162,6 +155,12 @@ public final class JavadocFormatter {
|
||||
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.
|
||||
@@ -191,4 +190,6 @@ public final class JavadocFormatter {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private JavadocFormatter() {}
|
||||
}
|
||||
|
||||
+84
-85
@@ -54,7 +54,6 @@ import com.google.common.base.CharMatcher;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.PeekingIterator;
|
||||
import com.google.googlejavaformat.java.javadoc.Token.Type;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
@@ -63,78 +62,6 @@ import java.util.regex.Pattern;
|
||||
|
||||
/** Lexer for the Javadoc formatter. */
|
||||
final class JavadocLexer {
|
||||
private static final Pattern NON_UNIX_LINE_ENDING = Pattern.compile("\r\n?");
|
||||
private static final CharMatcher NEWLINE = CharMatcher.is('\n');
|
||||
/*
|
||||
* This also eats any trailing whitespace. We would be smart enough to ignore that, anyway --
|
||||
* except in the case of <pre>/<table>, inside which we otherwise leave whitespace intact.
|
||||
*
|
||||
* We'd remove the trailing whitespace later on (in JavaCommentsHelper.rewrite), but I feel safer
|
||||
* stripping it now: It otherwise might confuse our line-length count, which we use for wrapping.
|
||||
*/
|
||||
private static final Pattern NEWLINE_PATTERN = compile("^[ \t]*\n[ \t]*[*]?[ \t]?");
|
||||
// We ensure elsewhere that we match this only at the beginning of a line.
|
||||
// Only match tags that start with a lowercase letter, to avoid false matches on unescaped
|
||||
// annotations inside code blocks.
|
||||
// Match "@param <T>" specially in case the <T> is a <P> or other HTML tag we treat specially.
|
||||
private static final Pattern FOOTER_TAG_PATTERN = compile("^@(param\\s+<\\w+>|[a-z]\\w*)");
|
||||
private static final Pattern MOE_BEGIN_STRIP_COMMENT_PATTERN =
|
||||
compile("^<!--\\s*M" + "OE:begin_intracomment_strip\\s*-->");
|
||||
private static final Pattern MOE_END_STRIP_COMMENT_PATTERN =
|
||||
compile("^<!--\\s*M" + "OE:end_intracomment_strip\\s*-->");
|
||||
private static final Pattern HTML_COMMENT_PATTERN = fullCommentPattern();
|
||||
private static final Pattern PRE_OPEN_PATTERN = openTagPattern("pre");
|
||||
private static final Pattern PRE_CLOSE_PATTERN = closeTagPattern("pre");
|
||||
private static final Pattern CODE_OPEN_PATTERN = openTagPattern("code");
|
||||
private static final Pattern CODE_CLOSE_PATTERN = closeTagPattern("code");
|
||||
private static final Pattern TABLE_OPEN_PATTERN = openTagPattern("table");
|
||||
private static final Pattern TABLE_CLOSE_PATTERN = closeTagPattern("table");
|
||||
private static final Pattern LIST_OPEN_PATTERN = openTagPattern("ul|ol|dl");
|
||||
private static final Pattern LIST_CLOSE_PATTERN = closeTagPattern("ul|ol|dl");
|
||||
private static final Pattern LIST_ITEM_OPEN_PATTERN = openTagPattern("li|dt|dd");
|
||||
private static final Pattern LIST_ITEM_CLOSE_PATTERN = closeTagPattern("li|dt|dd");
|
||||
private static final Pattern HEADER_OPEN_PATTERN = openTagPattern("h[1-6]");
|
||||
private static final Pattern HEADER_CLOSE_PATTERN = closeTagPattern("h[1-6]");
|
||||
private static final Pattern PARAGRAPH_OPEN_PATTERN = openTagPattern("p");
|
||||
private static final Pattern PARAGRAPH_CLOSE_PATTERN = closeTagPattern("p");
|
||||
private static final Pattern BLOCKQUOTE_OPEN_PATTERN = openTagPattern("blockquote");
|
||||
private static final Pattern BLOCKQUOTE_CLOSE_PATTERN = closeTagPattern("blockquote");
|
||||
private static final Pattern BR_PATTERN = openTagPattern("br");
|
||||
private static final Pattern INLINE_TAG_OPEN_PATTERN = compile("^[{]@\\w*");
|
||||
/*
|
||||
* We exclude < so that we don't swallow following HTML tags. This lets us fix up "foo<p>" (~400
|
||||
* hits in Google-internal code). We will join unnecessarily split "words" (like "foo<b>bar</b>")
|
||||
* in a later step. There's a similar story for braces. I'm not sure I actually need to exclude @
|
||||
* or *. TODO(cpovirk): Try removing them.
|
||||
*
|
||||
* Thanks to the "rejoin" step in joinAdjacentLiteralsAndAdjacentWhitespace(), we could get away
|
||||
* with matching only one character here. That would eliminate the need for the regex entirely.
|
||||
* That might be faster or slower than what we do now.
|
||||
*/
|
||||
private static final Pattern LITERAL_PATTERN = compile("^.[^ \t\n@<{}*]*", DOTALL);
|
||||
private final CharStream input;
|
||||
private final NestingCounter braceDepth = new NestingCounter();
|
||||
private final NestingCounter preDepth = new NestingCounter();
|
||||
private final NestingCounter codeDepth = new NestingCounter();
|
||||
private final NestingCounter tableDepth = new NestingCounter();
|
||||
private boolean somethingSinceNewline;
|
||||
|
||||
private JavadocLexer(CharStream input) {
|
||||
this.input = checkNotNull(input);
|
||||
}
|
||||
|
||||
private static Pattern fullCommentPattern() {
|
||||
return compile("^<!--.*?-->", DOTALL);
|
||||
}
|
||||
|
||||
private static Pattern openTagPattern(String namePattern) {
|
||||
return compile(format("^<(?:%s)\\b[^>]*>", namePattern), CASE_INSENSITIVE);
|
||||
}
|
||||
|
||||
private static Pattern closeTagPattern(String namePattern) {
|
||||
return compile(format("^</(?:%s)\\b[^>]*>", namePattern), CASE_INSENSITIVE);
|
||||
}
|
||||
|
||||
/** Takes a Javadoc comment, including ∕✱✱ and ✱∕, and returns tokens, including ∕✱✱ and ✱∕. */
|
||||
static ImmutableList<Token> lex(String input) throws LexException {
|
||||
/*
|
||||
@@ -153,6 +80,8 @@ final class JavadocLexer {
|
||||
return NON_UNIX_LINE_ENDING.matcher(input).replaceAll("\n");
|
||||
}
|
||||
|
||||
private static final Pattern NON_UNIX_LINE_ENDING = Pattern.compile("\r\n?");
|
||||
|
||||
private static String stripJavadocBeginAndEnd(String input) {
|
||||
/*
|
||||
* We do this ahead of time so that the main part of the lexer need not say things like
|
||||
@@ -163,6 +92,17 @@ final class JavadocLexer {
|
||||
return input.substring("/**".length(), input.length() - "*/".length());
|
||||
}
|
||||
|
||||
private final CharStream input;
|
||||
private final NestingCounter braceDepth = new NestingCounter();
|
||||
private final NestingCounter preDepth = new NestingCounter();
|
||||
private final NestingCounter codeDepth = new NestingCounter();
|
||||
private final NestingCounter tableDepth = new NestingCounter();
|
||||
private boolean somethingSinceNewline;
|
||||
|
||||
private JavadocLexer(CharStream input) {
|
||||
this.input = checkNotNull(input);
|
||||
}
|
||||
|
||||
private ImmutableList<Token> generateTokens() throws LexException {
|
||||
ImmutableList.Builder<Token> tokens = ImmutableList.builder();
|
||||
|
||||
@@ -200,10 +140,8 @@ final class JavadocLexer {
|
||||
somethingSinceNewline = false;
|
||||
return preserveExistingFormatting ? FORCED_NEWLINE : WHITESPACE;
|
||||
} else if (input.tryConsume(" ") || input.tryConsume("\t")) {
|
||||
// TODO(cpovirk): How about weird whitespace chars? Ideally we'd distinguish breaking
|
||||
// vs. not.
|
||||
// Returning LITERAL here prevent us from breaking a <pre> line. For more info, see
|
||||
// LITERAL.
|
||||
// TODO(cpovirk): How about weird whitespace chars? Ideally we'd distinguish breaking vs. not.
|
||||
// Returning LITERAL here prevent us from breaking a <pre> line. For more info, see LITERAL.
|
||||
return preserveExistingFormatting ? LITERAL : WHITESPACE;
|
||||
}
|
||||
|
||||
@@ -377,8 +315,7 @@ final class JavadocLexer {
|
||||
output.add(new Token(WHITESPACE, seenWhitespace.toString()));
|
||||
}
|
||||
|
||||
// We have another token coming, possibly of type OTHER. Leave it for the next
|
||||
// iteration.
|
||||
// We have another token coming, possibly of type OTHER. Leave it for the next iteration.
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -411,8 +348,7 @@ final class JavadocLexer {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// TODO(cpovirk): Or just `continue` from the <p> case and move this out of the
|
||||
// `else`?
|
||||
// TODO(cpovirk): Or just `continue` from the <p> case and move this out of the `else`?
|
||||
output.add(tokens.next());
|
||||
}
|
||||
}
|
||||
@@ -426,10 +362,6 @@ final class JavadocLexer {
|
||||
*/
|
||||
}
|
||||
|
||||
private static boolean hasMultipleNewlines(String s) {
|
||||
return NEWLINE.countIn(s) > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces whitespace after a {@code href=...>} token with an "optional link break." This allows
|
||||
* us to output either {@code <a href=foo>foo</a>} or {@code <a href=foo>\nfoo</a>}, depending on
|
||||
@@ -554,5 +486,72 @@ final class JavadocLexer {
|
||||
}
|
||||
}
|
||||
|
||||
private static final CharMatcher NEWLINE = CharMatcher.is('\n');
|
||||
|
||||
private static boolean hasMultipleNewlines(String s) {
|
||||
return NEWLINE.countIn(s) > 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* This also eats any trailing whitespace. We would be smart enough to ignore that, anyway --
|
||||
* except in the case of <pre>/<table>, inside which we otherwise leave whitespace intact.
|
||||
*
|
||||
* We'd remove the trailing whitespace later on (in JavaCommentsHelper.rewrite), but I feel safer
|
||||
* stripping it now: It otherwise might confuse our line-length count, which we use for wrapping.
|
||||
*/
|
||||
private static final Pattern NEWLINE_PATTERN = compile("^[ \t]*\n[ \t]*[*]?[ \t]?");
|
||||
|
||||
// We ensure elsewhere that we match this only at the beginning of a line.
|
||||
// Only match tags that start with a lowercase letter, to avoid false matches on unescaped
|
||||
// annotations inside code blocks.
|
||||
// Match "@param <T>" specially in case the <T> is a <P> or other HTML tag we treat specially.
|
||||
private static final Pattern FOOTER_TAG_PATTERN = compile("^@(param\\s+<\\w+>|[a-z]\\w*)");
|
||||
private static final Pattern MOE_BEGIN_STRIP_COMMENT_PATTERN =
|
||||
compile("^<!--\\s*M" + "OE:begin_intracomment_strip\\s*-->");
|
||||
private static final Pattern MOE_END_STRIP_COMMENT_PATTERN =
|
||||
compile("^<!--\\s*M" + "OE:end_intracomment_strip\\s*-->");
|
||||
private static final Pattern HTML_COMMENT_PATTERN = fullCommentPattern();
|
||||
private static final Pattern PRE_OPEN_PATTERN = openTagPattern("pre");
|
||||
private static final Pattern PRE_CLOSE_PATTERN = closeTagPattern("pre");
|
||||
private static final Pattern CODE_OPEN_PATTERN = openTagPattern("code");
|
||||
private static final Pattern CODE_CLOSE_PATTERN = closeTagPattern("code");
|
||||
private static final Pattern TABLE_OPEN_PATTERN = openTagPattern("table");
|
||||
private static final Pattern TABLE_CLOSE_PATTERN = closeTagPattern("table");
|
||||
private static final Pattern LIST_OPEN_PATTERN = openTagPattern("ul|ol|dl");
|
||||
private static final Pattern LIST_CLOSE_PATTERN = closeTagPattern("ul|ol|dl");
|
||||
private static final Pattern LIST_ITEM_OPEN_PATTERN = openTagPattern("li|dt|dd");
|
||||
private static final Pattern LIST_ITEM_CLOSE_PATTERN = closeTagPattern("li|dt|dd");
|
||||
private static final Pattern HEADER_OPEN_PATTERN = openTagPattern("h[1-6]");
|
||||
private static final Pattern HEADER_CLOSE_PATTERN = closeTagPattern("h[1-6]");
|
||||
private static final Pattern PARAGRAPH_OPEN_PATTERN = openTagPattern("p");
|
||||
private static final Pattern PARAGRAPH_CLOSE_PATTERN = closeTagPattern("p");
|
||||
private static final Pattern BLOCKQUOTE_OPEN_PATTERN = openTagPattern("blockquote");
|
||||
private static final Pattern BLOCKQUOTE_CLOSE_PATTERN = closeTagPattern("blockquote");
|
||||
private static final Pattern BR_PATTERN = openTagPattern("br");
|
||||
private static final Pattern INLINE_TAG_OPEN_PATTERN = compile("^[{]@\\w*");
|
||||
/*
|
||||
* We exclude < so that we don't swallow following HTML tags. This lets us fix up "foo<p>" (~400
|
||||
* hits in Google-internal code). We will join unnecessarily split "words" (like "foo<b>bar</b>")
|
||||
* in a later step. There's a similar story for braces. I'm not sure I actually need to exclude @
|
||||
* or *. TODO(cpovirk): Try removing them.
|
||||
*
|
||||
* Thanks to the "rejoin" step in joinAdjacentLiteralsAndAdjacentWhitespace(), we could get away
|
||||
* with matching only one character here. That would eliminate the need for the regex entirely.
|
||||
* That might be faster or slower than what we do now.
|
||||
*/
|
||||
private static final Pattern LITERAL_PATTERN = compile("^.[^ \t\n@<{}*]*", DOTALL);
|
||||
|
||||
private static Pattern fullCommentPattern() {
|
||||
return compile("^<!--.*?-->", DOTALL);
|
||||
}
|
||||
|
||||
private static Pattern openTagPattern(String namePattern) {
|
||||
return compile(format("^<(?:%s)\\b[^>]*>", namePattern), CASE_INSENSITIVE);
|
||||
}
|
||||
|
||||
private static Pattern closeTagPattern(String namePattern) {
|
||||
return compile(format("^</(?:%s)\\b[^>]*>", namePattern), CASE_INSENSITIVE);
|
||||
}
|
||||
|
||||
static class LexException extends Exception {}
|
||||
}
|
||||
|
||||
+138
-141
@@ -27,7 +27,6 @@ import static com.google.googlejavaformat.java.javadoc.Token.Type.HEADER_OPEN_TA
|
||||
import static com.google.googlejavaformat.java.javadoc.Token.Type.LIST_ITEM_OPEN_TAG;
|
||||
import static com.google.googlejavaformat.java.javadoc.Token.Type.PARAGRAPH_OPEN_TAG;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.googlejavaformat.java.javadoc.Token.Type;
|
||||
|
||||
@@ -39,22 +38,8 @@ import com.google.googlejavaformat.java.javadoc.Token.Type;
|
||||
* are we inside?"
|
||||
*/
|
||||
final class JavadocWriter {
|
||||
/**
|
||||
* Tokens that are always pinned to the following token. For example, {@code <p>} in {@code <p>Foo
|
||||
* bar} (never {@code <p> Foo bar} or {@code <p>\nFoo bar}).
|
||||
*
|
||||
* <p>This is not the only kind of "pinning" that we do: See also the joining of LITERAL tokens
|
||||
* done by the lexer. The special pinning here is necessary because these tokens are not of type
|
||||
* LITERAL (because they require other special handling).
|
||||
*/
|
||||
private static final ImmutableSet<Type> START_OF_LINE_TOKENS =
|
||||
immutableEnumSet(LIST_ITEM_OPEN_TAG, PARAGRAPH_OPEN_TAG, HEADER_OPEN_TAG);
|
||||
|
||||
private final int blockIndent;
|
||||
private final StringBuilder output = new StringBuilder();
|
||||
private final NestingCounter continuingListItemCount = new NestingCounter();
|
||||
private final NestingCounter continuingListCount = new NestingCounter();
|
||||
private final NestingCounter postWriteModifiedContinuingListCount = new NestingCounter();
|
||||
/**
|
||||
* Whether we are inside an {@code <li>} element, excluding the case in which the {@code <li>}
|
||||
* contains a {@code <ul>} or {@code <ol>} that we are also inside -- unless of course we're
|
||||
@@ -63,6 +48,9 @@ final class JavadocWriter {
|
||||
private boolean continuingListItemOfInnermostList;
|
||||
|
||||
private boolean continuingFooterTag;
|
||||
private final NestingCounter continuingListItemCount = new NestingCounter();
|
||||
private final NestingCounter continuingListCount = new NestingCounter();
|
||||
private final NestingCounter postWriteModifiedContinuingListCount = new NestingCounter();
|
||||
private int remainingOnLine;
|
||||
private boolean atStartOfLine;
|
||||
private RequestedWhitespace requestedWhitespace = NONE;
|
||||
@@ -74,95 +62,6 @@ final class JavadocWriter {
|
||||
this.blockIndent = blockIndent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return output.toString();
|
||||
}
|
||||
|
||||
private void requestBlankLine() {
|
||||
requestWhitespace(BLANK_LINE);
|
||||
}
|
||||
|
||||
private void requestNewline() {
|
||||
requestWhitespace(NEWLINE);
|
||||
}
|
||||
|
||||
private void writeToken(Token token) {
|
||||
if (requestedMoeBeginStripComment != null) {
|
||||
requestNewline();
|
||||
}
|
||||
|
||||
if (requestedWhitespace == BLANK_LINE
|
||||
&& (postWriteModifiedContinuingListCount.isPositive() || continuingFooterTag)) {
|
||||
/*
|
||||
* We don't write blank lines inside lists or footer tags, even in cases where we otherwise
|
||||
* would (e.g., before a <p> tag). Justification: We don't write blank lines _between_ list
|
||||
* items or footer tags, so it would be strange to write blank lines _within_ one. Of course,
|
||||
* an alternative approach would be to go ahead and write blank lines between items/tags,
|
||||
* either always or only in the case that an item contains a blank line.
|
||||
*/
|
||||
requestedWhitespace = NEWLINE;
|
||||
}
|
||||
|
||||
if (requestedWhitespace == BLANK_LINE) {
|
||||
writeBlankLine();
|
||||
requestedWhitespace = NONE;
|
||||
} else if (requestedWhitespace == NEWLINE) {
|
||||
writeNewline();
|
||||
requestedWhitespace = NONE;
|
||||
}
|
||||
boolean needWhitespace = (requestedWhitespace == WHITESPACE);
|
||||
|
||||
/*
|
||||
* Write a newline if necessary to respect the line limit. (But if we're at the beginning of the
|
||||
* line, a newline won't help. Or it might help but only by separating "<p>veryverylongword,"
|
||||
* which goes against our style.)
|
||||
*/
|
||||
if (!atStartOfLine && token.length() + (needWhitespace ? 1 : 0) > remainingOnLine) {
|
||||
writeNewline();
|
||||
}
|
||||
if (!atStartOfLine && needWhitespace) {
|
||||
output.append(" ");
|
||||
remainingOnLine--;
|
||||
}
|
||||
|
||||
if (requestedMoeBeginStripComment != null) {
|
||||
output.append(requestedMoeBeginStripComment.getValue());
|
||||
requestedMoeBeginStripComment = null;
|
||||
indentForMoeEndStripComment = innerIndent();
|
||||
requestNewline();
|
||||
writeToken(token);
|
||||
return;
|
||||
}
|
||||
|
||||
output.append(token.getValue());
|
||||
|
||||
if (!START_OF_LINE_TOKENS.contains(token.getType())) {
|
||||
atStartOfLine = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO(cpovirk): We really want the number of "characters," not chars. Figure out what the
|
||||
* right way of measuring that is (grapheme count (with BreakIterator?)? sum of widths of all
|
||||
* graphemes? I don't think that our style guide is specific about this.). Moreover, I am
|
||||
* probably brushing other problems with surrogates, etc. under the table. Hopefully I mostly
|
||||
* get away with it by joining all non-space, non-tab characters together.
|
||||
*
|
||||
* Possibly the "width" question has no right answer:
|
||||
* http://denisbider.blogspot.com/2015/09/when-monospace-fonts-arent-unicode.html
|
||||
*/
|
||||
remainingOnLine -= token.length();
|
||||
requestedWhitespace = NONE;
|
||||
wroteAnythingSignificant = true;
|
||||
}
|
||||
|
||||
private void writeBlankLine() {
|
||||
output.append("\n");
|
||||
appendSpaces(blockIndent + 1);
|
||||
output.append("*");
|
||||
writeNewline();
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests whitespace between the previously written token and the next written token. The
|
||||
* request may be honored, or it may be overridden by a request for "more significant" whitespace,
|
||||
@@ -172,10 +71,6 @@ final class JavadocWriter {
|
||||
requestWhitespace(WHITESPACE);
|
||||
}
|
||||
|
||||
private void requestWhitespace(RequestedWhitespace requestedWhitespace) {
|
||||
this.requestedWhitespace = max(requestedWhitespace, this.requestedWhitespace);
|
||||
}
|
||||
|
||||
void requestMoeBeginStripComment(Token token) {
|
||||
// We queue this up so that we can put it after any requested whitespace.
|
||||
requestedMoeBeginStripComment = checkNotNull(token);
|
||||
@@ -190,37 +85,6 @@ final class JavadocWriter {
|
||||
writeNewline();
|
||||
}
|
||||
|
||||
private void writeNewline() {
|
||||
writeNewline(AUTO_INDENT);
|
||||
}
|
||||
|
||||
private void writeNewline(AutoIndent autoIndent) {
|
||||
output.append("\n");
|
||||
appendSpaces(blockIndent + 1);
|
||||
output.append("*");
|
||||
appendSpaces(1);
|
||||
remainingOnLine = JavadocFormatter.MAX_LINE_LENGTH - blockIndent - 3;
|
||||
if (autoIndent == AUTO_INDENT) {
|
||||
appendSpaces(innerIndent());
|
||||
remainingOnLine -= innerIndent();
|
||||
}
|
||||
atStartOfLine = true;
|
||||
}
|
||||
|
||||
private int innerIndent() {
|
||||
int innerIndent = continuingListItemCount.value() * 4 + continuingListCount.value() * 2;
|
||||
if (continuingFooterTag) {
|
||||
innerIndent += 4;
|
||||
}
|
||||
return innerIndent;
|
||||
}
|
||||
|
||||
// If this is a hotspot, keep a String of many spaces around, and call append(string, start,
|
||||
// end).
|
||||
private void appendSpaces(int count) {
|
||||
output.append(Strings.repeat(" ", count));
|
||||
}
|
||||
|
||||
void writeEndJavadoc() {
|
||||
output.append("\n");
|
||||
appendSpaces(blockIndent + 1);
|
||||
@@ -246,8 +110,7 @@ final class JavadocWriter {
|
||||
postWriteModifiedContinuingListCount.reset();
|
||||
|
||||
if (!wroteAnythingSignificant) {
|
||||
// Javadoc consists solely of tags. This is frowned upon in general but OK for
|
||||
// @Overrides.
|
||||
// Javadoc consists solely of tags. This is frowned upon in general but OK for @Overrides.
|
||||
} else if (!continuingFooterTag) {
|
||||
// First footer tag after a body tag.
|
||||
requestBlankLine();
|
||||
@@ -392,6 +255,23 @@ final class JavadocWriter {
|
||||
writeToken(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return output.toString();
|
||||
}
|
||||
|
||||
private void requestBlankLine() {
|
||||
requestWhitespace(BLANK_LINE);
|
||||
}
|
||||
|
||||
private void requestNewline() {
|
||||
requestWhitespace(NEWLINE);
|
||||
}
|
||||
|
||||
private void requestWhitespace(RequestedWhitespace requestedWhitespace) {
|
||||
this.requestedWhitespace = max(requestedWhitespace, this.requestedWhitespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* The kind of whitespace that has been requested between the previous and next tokens. The order
|
||||
* of the values is significant: It goes from lowest priority to highest. For example, if the
|
||||
@@ -406,8 +286,125 @@ final class JavadocWriter {
|
||||
;
|
||||
}
|
||||
|
||||
private void writeToken(Token token) {
|
||||
if (requestedMoeBeginStripComment != null) {
|
||||
requestNewline();
|
||||
}
|
||||
|
||||
if (requestedWhitespace == BLANK_LINE
|
||||
&& (postWriteModifiedContinuingListCount.isPositive() || continuingFooterTag)) {
|
||||
/*
|
||||
* We don't write blank lines inside lists or footer tags, even in cases where we otherwise
|
||||
* would (e.g., before a <p> tag). Justification: We don't write blank lines _between_ list
|
||||
* items or footer tags, so it would be strange to write blank lines _within_ one. Of course,
|
||||
* an alternative approach would be to go ahead and write blank lines between items/tags,
|
||||
* either always or only in the case that an item contains a blank line.
|
||||
*/
|
||||
requestedWhitespace = NEWLINE;
|
||||
}
|
||||
|
||||
if (requestedWhitespace == BLANK_LINE) {
|
||||
writeBlankLine();
|
||||
requestedWhitespace = NONE;
|
||||
} else if (requestedWhitespace == NEWLINE) {
|
||||
writeNewline();
|
||||
requestedWhitespace = NONE;
|
||||
}
|
||||
boolean needWhitespace = (requestedWhitespace == WHITESPACE);
|
||||
|
||||
/*
|
||||
* Write a newline if necessary to respect the line limit. (But if we're at the beginning of the
|
||||
* line, a newline won't help. Or it might help but only by separating "<p>veryverylongword,"
|
||||
* which goes against our style.)
|
||||
*/
|
||||
if (!atStartOfLine && token.length() + (needWhitespace ? 1 : 0) > remainingOnLine) {
|
||||
writeNewline();
|
||||
}
|
||||
if (!atStartOfLine && needWhitespace) {
|
||||
output.append(" ");
|
||||
remainingOnLine--;
|
||||
}
|
||||
|
||||
if (requestedMoeBeginStripComment != null) {
|
||||
output.append(requestedMoeBeginStripComment.getValue());
|
||||
requestedMoeBeginStripComment = null;
|
||||
indentForMoeEndStripComment = innerIndent();
|
||||
requestNewline();
|
||||
writeToken(token);
|
||||
return;
|
||||
}
|
||||
|
||||
output.append(token.getValue());
|
||||
|
||||
if (!START_OF_LINE_TOKENS.contains(token.getType())) {
|
||||
atStartOfLine = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO(cpovirk): We really want the number of "characters," not chars. Figure out what the
|
||||
* right way of measuring that is (grapheme count (with BreakIterator?)? sum of widths of all
|
||||
* graphemes? I don't think that our style guide is specific about this.). Moreover, I am
|
||||
* probably brushing other problems with surrogates, etc. under the table. Hopefully I mostly
|
||||
* get away with it by joining all non-space, non-tab characters together.
|
||||
*
|
||||
* Possibly the "width" question has no right answer:
|
||||
* http://denisbider.blogspot.com/2015/09/when-monospace-fonts-arent-unicode.html
|
||||
*/
|
||||
remainingOnLine -= token.length();
|
||||
requestedWhitespace = NONE;
|
||||
wroteAnythingSignificant = true;
|
||||
}
|
||||
|
||||
private void writeBlankLine() {
|
||||
output.append("\n");
|
||||
appendSpaces(blockIndent + 1);
|
||||
output.append("*");
|
||||
writeNewline();
|
||||
}
|
||||
|
||||
private void writeNewline() {
|
||||
writeNewline(AUTO_INDENT);
|
||||
}
|
||||
|
||||
private void writeNewline(AutoIndent autoIndent) {
|
||||
output.append("\n");
|
||||
appendSpaces(blockIndent + 1);
|
||||
output.append("*");
|
||||
appendSpaces(1);
|
||||
remainingOnLine = JavadocFormatter.MAX_LINE_LENGTH - blockIndent - 3;
|
||||
if (autoIndent == AUTO_INDENT) {
|
||||
appendSpaces(innerIndent());
|
||||
remainingOnLine -= innerIndent();
|
||||
}
|
||||
atStartOfLine = true;
|
||||
}
|
||||
|
||||
enum AutoIndent {
|
||||
AUTO_INDENT,
|
||||
NO_AUTO_INDENT
|
||||
}
|
||||
|
||||
private int innerIndent() {
|
||||
int innerIndent = continuingListItemCount.value() * 4 + continuingListCount.value() * 2;
|
||||
if (continuingFooterTag) {
|
||||
innerIndent += 4;
|
||||
}
|
||||
return innerIndent;
|
||||
}
|
||||
|
||||
// If this is a hotspot, keep a String of many spaces around, and call append(string, start, end).
|
||||
private void appendSpaces(int count) {
|
||||
output.append(" ".repeat(count));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tokens that are always pinned to the following token. For example, {@code <p>} in {@code <p>Foo
|
||||
* bar} (never {@code <p> Foo bar} or {@code <p>\nFoo bar}).
|
||||
*
|
||||
* <p>This is not the only kind of "pinning" that we do: See also the joining of LITERAL tokens
|
||||
* done by the lexer. The special pinning here is necessary because these tokens are not of type
|
||||
* LITERAL (because they require other special handling).
|
||||
*/
|
||||
private static final ImmutableSet<Type> START_OF_LINE_TOKENS =
|
||||
immutableEnumSet(LIST_ITEM_OPEN_TAG, PARAGRAPH_OPEN_TAG, HEADER_OPEN_TAG);
|
||||
}
|
||||
|
||||
+25
-25
@@ -20,31 +20,6 @@ package com.google.googlejavaformat.java.javadoc;
|
||||
* are in {@link JavadocLexer}.
|
||||
*/
|
||||
final class Token {
|
||||
private final Type type;
|
||||
private final String value;
|
||||
|
||||
Token(Type type, String value) {
|
||||
this.type = type;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "\n" + getType() + ": " + getValue();
|
||||
}
|
||||
|
||||
Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
int length() {
|
||||
return value.length();
|
||||
}
|
||||
|
||||
/**
|
||||
* Javadoc token type.
|
||||
*
|
||||
@@ -128,4 +103,29 @@ final class Token {
|
||||
LITERAL,
|
||||
;
|
||||
}
|
||||
|
||||
private final Type type;
|
||||
private final String value;
|
||||
|
||||
Token(Type type, String value) {
|
||||
this.type = type;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
int length() {
|
||||
return value.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "\n" + getType() + ": " + getValue();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user