• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2015 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 
15 package com.google.googlejavaformat;
16 
17 import static com.google.common.collect.Iterables.getLast;
18 
19 import com.google.common.base.MoreObjects;
20 import com.google.common.collect.DiscreteDomain;
21 import com.google.common.collect.Iterators;
22 import com.google.common.collect.Range;
23 import com.google.googlejavaformat.Output.BreakTag;
24 import java.util.ArrayList;
25 import java.util.List;
26 import java.util.Optional;
27 
28 /**
29  * {@link com.google.googlejavaformat.java.JavaInputAstVisitor JavaInputAstVisitor} outputs a
30  * sequence of {@link Op}s using {@link OpsBuilder}. This linear sequence is then transformed by
31  * {@link DocBuilder} into a tree-structured {@code Doc}. The top-level {@code Doc} is a {@link
32  * Level}, which contains a sequence of {@code Doc}s, including other {@link Level}s. Leaf {@code
33  * Doc}s are {@link Token}s, representing language-level tokens; {@link Tok}s, which may also
34  * represent non-token {@link Input.Tok}s, including comments and other white-space; {@link Space}s,
35  * representing single spaces; and {@link Break}s, which represent optional line-breaks.
36  */
37 public abstract class Doc {
38   /**
39    * Each {@link Break} in a {@link Level} is either {@link FillMode#UNIFIED} or {@link
40    * FillMode#INDEPENDENT}.
41    */
42   public enum FillMode {
43     /**
44      * If a {@link Level} will not fit on one line, all of its {@code UNIFIED} {@link Break}s will
45      * be broken.
46      */
47     UNIFIED,
48 
49     /**
50      * If a {@link Level} will not fit on one line, its {@code INDEPENDENT} {@link Break}s will be
51      * broken independently of each other, to fill in the {@link Level}.
52      */
53     INDEPENDENT,
54 
55     /**
56      * A {@code FORCED} {@link Break} will always be broken, and a {@link Level} it appears in will
57      * not fit on one line.
58      */
59     FORCED
60   }
61 
62   /** State for writing. */
63   public static final class State {
64     final int lastIndent;
65     final int indent;
66     final int column;
67     final boolean mustBreak;
68 
State(int lastIndent, int indent, int column, boolean mustBreak)69     State(int lastIndent, int indent, int column, boolean mustBreak) {
70       this.lastIndent = lastIndent;
71       this.indent = indent;
72       this.column = column;
73       this.mustBreak = mustBreak;
74     }
75 
State(int indent0, int column0)76     public State(int indent0, int column0) {
77       this(indent0, indent0, column0, false);
78     }
79 
withColumn(int column)80     State withColumn(int column) {
81       return new State(lastIndent, indent, column, mustBreak);
82     }
83 
withMustBreak(boolean mustBreak)84     State withMustBreak(boolean mustBreak) {
85       return new State(lastIndent, indent, column, mustBreak);
86     }
87 
88     @Override
toString()89     public String toString() {
90       return MoreObjects.toStringHelper(this)
91           .add("lastIndent", lastIndent)
92           .add("indent", indent)
93           .add("column", column)
94           .add("mustBreak", mustBreak)
95           .toString();
96     }
97   }
98 
99   private static final Range<Integer> EMPTY_RANGE = Range.closedOpen(-1, -1);
100   private static final DiscreteDomain<Integer> INTEGERS = DiscreteDomain.integers();
101 
102   // Memoized width; Float.POSITIVE_INFINITY if contains forced breaks.
103   private boolean widthComputed = false;
104   private float width = 0.0F;
105 
106   // Memoized flat; not defined (and never computed) if contains forced breaks.
107   private boolean flatComputed = false;
108   private String flat = "";
109 
110   // Memoized Range.
111   private boolean rangeComputed = false;
112   private Range<Integer> range = EMPTY_RANGE;
113 
114   /**
115    * Return the width of a {@code Doc}, or {@code Float.POSITIVE_INFINITY} if it must be broken.
116    *
117    * @return the width
118    */
getWidth()119   final float getWidth() {
120     if (!widthComputed) {
121       width = computeWidth();
122       widthComputed = true;
123     }
124     return width;
125   }
126 
127   /**
128    * Return a {@code Doc}'s flat-string value; not defined (and never called) if the (@code Doc}
129    * contains forced breaks.
130    *
131    * @return the flat-string value
132    */
getFlat()133   final String getFlat() {
134     if (!flatComputed) {
135       flat = computeFlat();
136       flatComputed = true;
137     }
138     return flat;
139   }
140 
141   /**
142    * Return the {@link Range} of a {@code Doc}.
143    *
144    * @return the {@code Doc}'s {@link Range}
145    */
range()146   final Range<Integer> range() {
147     if (!rangeComputed) {
148       range = computeRange();
149       rangeComputed = true;
150     }
151     return range;
152   }
153 
154   /**
155    * Compute the {@code Doc}'s width.
156    *
157    * @return the width, or {@code Float.POSITIVE_INFINITY} if it must be broken
158    */
computeWidth()159   abstract float computeWidth();
160 
161   /**
162    * Compute the {@code Doc}'s flat value. Not defined (and never called) if contains forced breaks.
163    *
164    * @return the flat value
165    */
computeFlat()166   abstract String computeFlat();
167 
168   /**
169    * Compute the {@code Doc}'s {@link Range} of {@link Input.Token}s.
170    *
171    * @return the {@link Range}
172    */
computeRange()173   abstract Range<Integer> computeRange();
174 
175   /**
176    * Make breaking decisions for a {@code Doc}.
177    *
178    * @param maxWidth the maximum line width
179    * @param state the current output state
180    * @return the new output state
181    */
computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state)182   public abstract State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state);
183 
184   /** Write a {@code Doc} to an {@link Output}, after breaking decisions have been made. */
write(Output output)185   public abstract void write(Output output);
186 
187   /** A {@code Level} inside a {@link Doc}. */
188   static final class Level extends Doc {
189     private final Indent plusIndent; // The extra indent following breaks.
190     private final List<Doc> docs = new ArrayList<>(); // The elements of the level.
191 
Level(Indent plusIndent)192     private Level(Indent plusIndent) {
193       this.plusIndent = plusIndent;
194     }
195 
196     /**
197      * Factory method for {@code Level}s.
198      *
199      * @param plusIndent the extra indent inside the {@code Level}
200      * @return the new {@code Level}
201      */
make(Indent plusIndent)202     static Level make(Indent plusIndent) {
203       return new Level(plusIndent);
204     }
205 
206     /**
207      * Add a {@link Doc} to the {@code Level}.
208      *
209      * @param doc the {@link Doc} to add
210      */
add(Doc doc)211     void add(Doc doc) {
212       docs.add(doc);
213     }
214 
215     @Override
computeWidth()216     float computeWidth() {
217       float thisWidth = 0.0F;
218       for (Doc doc : docs) {
219         thisWidth += doc.getWidth();
220       }
221       return thisWidth;
222     }
223 
224     @Override
computeFlat()225     String computeFlat() {
226       StringBuilder builder = new StringBuilder();
227       for (Doc doc : docs) {
228         builder.append(doc.getFlat());
229       }
230       return builder.toString();
231     }
232 
233     @Override
computeRange()234     Range<Integer> computeRange() {
235       Range<Integer> docRange = EMPTY_RANGE;
236       for (Doc doc : docs) {
237         docRange = union(docRange, doc.range());
238       }
239       return docRange;
240     }
241 
242     // State that needs to be preserved between calculating breaks and
243     // writing output.
244     // TODO(cushon): represent phases as separate immutable data.
245 
246     /** True if the entire {@link Level} fits on one line. */
247     boolean oneLine = false;
248 
249     /**
250      * Groups of {@link Doc}s that are children of the current {@link Level}, separated by {@link
251      * Break}s.
252      */
253     List<List<Doc>> splits = new ArrayList<>();
254 
255     /** {@link Break}s between {@link Doc}s in the current {@link Level}. */
256     List<Break> breaks = new ArrayList<>();
257 
258     @Override
computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state)259     public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) {
260       float thisWidth = getWidth();
261       if (state.column + thisWidth <= maxWidth) {
262         oneLine = true;
263         return state.withColumn(state.column + (int) thisWidth);
264       }
265       State broken =
266           computeBroken(
267               commentsHelper, maxWidth, new State(state.indent + plusIndent.eval(), state.column));
268       return state.withColumn(broken.column);
269     }
270 
splitByBreaks(List<Doc> docs, List<List<Doc>> splits, List<Break> breaks)271     private static void splitByBreaks(List<Doc> docs, List<List<Doc>> splits, List<Break> breaks) {
272       splits.clear();
273       breaks.clear();
274       splits.add(new ArrayList<>());
275       for (Doc doc : docs) {
276         if (doc instanceof Break) {
277           breaks.add((Break) doc);
278           splits.add(new ArrayList<>());
279         } else {
280           getLast(splits).add(doc);
281         }
282       }
283     }
284 
285     /** Compute breaks for a {@link Level} that spans multiple lines. */
computeBroken(CommentsHelper commentsHelper, int maxWidth, State state)286     private State computeBroken(CommentsHelper commentsHelper, int maxWidth, State state) {
287       splitByBreaks(docs, splits, breaks);
288 
289       state =
290           computeBreakAndSplit(
291               commentsHelper, maxWidth, state, /* optBreakDoc= */ Optional.empty(), splits.get(0));
292 
293       // Handle following breaks and split.
294       for (int i = 0; i < breaks.size(); i++) {
295         state =
296             computeBreakAndSplit(
297                 commentsHelper, maxWidth, state, Optional.of(breaks.get(i)), splits.get(i + 1));
298       }
299       return state;
300     }
301 
302     /** Lay out a Break-separated group of Docs in the current Level. */
computeBreakAndSplit( CommentsHelper commentsHelper, int maxWidth, State state, Optional<Break> optBreakDoc, List<Doc> split)303     private static State computeBreakAndSplit(
304         CommentsHelper commentsHelper,
305         int maxWidth,
306         State state,
307         Optional<Break> optBreakDoc,
308         List<Doc> split) {
309       float breakWidth = optBreakDoc.isPresent() ? optBreakDoc.get().getWidth() : 0.0F;
310       float splitWidth = getWidth(split);
311       boolean shouldBreak =
312           (optBreakDoc.isPresent() && optBreakDoc.get().fillMode == FillMode.UNIFIED)
313               || state.mustBreak
314               || state.column + breakWidth + splitWidth > maxWidth;
315 
316       if (optBreakDoc.isPresent()) {
317         state = optBreakDoc.get().computeBreaks(state, state.lastIndent, shouldBreak);
318       }
319       boolean enoughRoom = state.column + splitWidth <= maxWidth;
320       state = computeSplit(commentsHelper, maxWidth, split, state.withMustBreak(false));
321       if (!enoughRoom) {
322         state = state.withMustBreak(true); // Break after, too.
323       }
324       return state;
325     }
326 
computeSplit( CommentsHelper commentsHelper, int maxWidth, List<Doc> docs, State state)327     private static State computeSplit(
328         CommentsHelper commentsHelper, int maxWidth, List<Doc> docs, State state) {
329       for (Doc doc : docs) {
330         state = doc.computeBreaks(commentsHelper, maxWidth, state);
331       }
332       return state;
333     }
334 
335     @Override
write(Output output)336     public void write(Output output) {
337       if (oneLine) {
338         output.append(getFlat(), range()); // This is defined because width is finite.
339       } else {
340         writeFilled(output);
341       }
342     }
343 
writeFilled(Output output)344     private void writeFilled(Output output) {
345       // Handle first split.
346       for (Doc doc : splits.get(0)) {
347         doc.write(output);
348       }
349       // Handle following breaks and split.
350       for (int i = 0; i < breaks.size(); i++) {
351         breaks.get(i).write(output);
352         for (Doc doc : splits.get(i + 1)) {
353           doc.write(output);
354         }
355       }
356     }
357 
358     /**
359      * Get the width of a sequence of {@link Doc}s.
360      *
361      * @param docs the {@link Doc}s
362      * @return the width, or {@code Float.POSITIVE_INFINITY} if any {@link Doc} must be broken
363      */
getWidth(List<Doc> docs)364     static float getWidth(List<Doc> docs) {
365       float width = 0.0F;
366       for (Doc doc : docs) {
367         width += doc.getWidth();
368       }
369       return width;
370     }
371 
union(Range<Integer> x, Range<Integer> y)372     private static Range<Integer> union(Range<Integer> x, Range<Integer> y) {
373       return x.isEmpty() ? y : y.isEmpty() ? x : x.span(y).canonical(INTEGERS);
374     }
375 
376     @Override
toString()377     public String toString() {
378       return MoreObjects.toStringHelper(this)
379           .add("plusIndent", plusIndent)
380           .add("docs", docs)
381           .toString();
382     }
383   }
384 
385   /** A leaf {@link Doc} for a token. */
386   public static final class Token extends Doc implements Op {
387     /** Is a Token a real token, or imaginary (e.g., a token generated incorrectly, or an EOF)? */
388     public enum RealOrImaginary {
389       REAL,
390       IMAGINARY;
391 
isReal()392       boolean isReal() {
393         return this == REAL;
394       }
395     }
396 
397     private final Input.Token token;
398     private final RealOrImaginary realOrImaginary;
399     private final Indent plusIndentCommentsBefore;
400     private final Optional<Indent> breakAndIndentTrailingComment;
401 
Token( Input.Token token, RealOrImaginary realOrImaginary, Indent plusIndentCommentsBefore, Optional<Indent> breakAndIndentTrailingComment)402     private Token(
403         Input.Token token,
404         RealOrImaginary realOrImaginary,
405         Indent plusIndentCommentsBefore,
406         Optional<Indent> breakAndIndentTrailingComment) {
407       this.token = token;
408       this.realOrImaginary = realOrImaginary;
409       this.plusIndentCommentsBefore = plusIndentCommentsBefore;
410       this.breakAndIndentTrailingComment = breakAndIndentTrailingComment;
411     }
412 
413     /**
414      * How much extra to indent comments before the {@code Token}.
415      *
416      * @return the extra indent
417      */
getPlusIndentCommentsBefore()418     Indent getPlusIndentCommentsBefore() {
419       return plusIndentCommentsBefore;
420     }
421 
422     /** Force a line break and indent trailing javadoc or block comments. */
breakAndIndentTrailingComment()423     Optional<Indent> breakAndIndentTrailingComment() {
424       return breakAndIndentTrailingComment;
425     }
426 
427     /**
428      * Make a {@code Token}.
429      *
430      * @param token the {@link Input.Token} to wrap
431      * @param realOrImaginary did this {@link Input.Token} appear in the input, or was it generated
432      *     incorrectly?
433      * @param plusIndentCommentsBefore extra {@code plusIndent} for comments just before this token
434      * @return the new {@code Token}
435      */
make( Input.Token token, Doc.Token.RealOrImaginary realOrImaginary, Indent plusIndentCommentsBefore, Optional<Indent> breakAndIndentTrailingComment)436     static Op make(
437         Input.Token token,
438         Doc.Token.RealOrImaginary realOrImaginary,
439         Indent plusIndentCommentsBefore,
440         Optional<Indent> breakAndIndentTrailingComment) {
441       return new Token(
442           token, realOrImaginary, plusIndentCommentsBefore, breakAndIndentTrailingComment);
443     }
444 
445     /**
446      * Return the wrapped {@link Input.Token}.
447      *
448      * @return the {@link Input.Token}
449      */
getToken()450     Input.Token getToken() {
451       return token;
452     }
453 
454     /**
455      * Is the token good? That is, does it match an {@link Input.Token}?
456      *
457      * @return whether the @code Token} is good
458      */
realOrImaginary()459     RealOrImaginary realOrImaginary() {
460       return realOrImaginary;
461     }
462 
463     @Override
add(DocBuilder builder)464     public void add(DocBuilder builder) {
465       builder.add(this);
466     }
467 
468     @Override
computeWidth()469     float computeWidth() {
470       return token.getTok().length();
471     }
472 
473     @Override
computeFlat()474     String computeFlat() {
475       return token.getTok().getOriginalText();
476     }
477 
478     @Override
computeRange()479     Range<Integer> computeRange() {
480       return Range.singleton(token.getTok().getIndex()).canonical(INTEGERS);
481     }
482 
483     @Override
computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state)484     public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) {
485       String text = token.getTok().getOriginalText();
486       return state.withColumn(state.column + text.length());
487     }
488 
489     @Override
write(Output output)490     public void write(Output output) {
491       String text = token.getTok().getOriginalText();
492       output.append(text, range());
493     }
494 
495     @Override
toString()496     public String toString() {
497       return MoreObjects.toStringHelper(this)
498           .add("token", token)
499           .add("realOrImaginary", realOrImaginary)
500           .add("plusIndentCommentsBefore", plusIndentCommentsBefore)
501           .toString();
502     }
503   }
504 
505   /** A Leaf node in a {@link Doc} for a non-breaking space. */
506   static final class Space extends Doc implements Op {
507     private static final Space SPACE = new Space();
508 
Space()509     private Space() {}
510 
511     /**
512      * Factor method for {@code Space}.
513      *
514      * @return the new {@code Space}
515      */
make()516     static Space make() {
517       return SPACE;
518     }
519 
520     @Override
add(DocBuilder builder)521     public void add(DocBuilder builder) {
522       builder.add(this);
523     }
524 
525     @Override
computeWidth()526     float computeWidth() {
527       return 1.0F;
528     }
529 
530     @Override
computeFlat()531     String computeFlat() {
532       return " ";
533     }
534 
535     @Override
computeRange()536     Range<Integer> computeRange() {
537       return EMPTY_RANGE;
538     }
539 
540     @Override
computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state)541     public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) {
542       return state.withColumn(state.column + 1);
543     }
544 
545     @Override
write(Output output)546     public void write(Output output) {
547       output.append(" ", range());
548     }
549 
550     @Override
toString()551     public String toString() {
552       return MoreObjects.toStringHelper(this).toString();
553     }
554   }
555 
556   /** A leaf node in a {@link Doc} for an optional break. */
557   public static final class Break extends Doc implements Op {
558     private final FillMode fillMode;
559     private final String flat;
560     private final Indent plusIndent;
561     private final Optional<BreakTag> optTag;
562 
Break(FillMode fillMode, String flat, Indent plusIndent, Optional<BreakTag> optTag)563     private Break(FillMode fillMode, String flat, Indent plusIndent, Optional<BreakTag> optTag) {
564       this.fillMode = fillMode;
565       this.flat = flat;
566       this.plusIndent = plusIndent;
567       this.optTag = optTag;
568     }
569 
570     /**
571      * Make a {@code Break}.
572      *
573      * @param fillMode the {@link FillMode}
574      * @param flat the text when not broken
575      * @param plusIndent extra indent if taken
576      * @return the new {@code Break}
577      */
make(FillMode fillMode, String flat, Indent plusIndent)578     public static Break make(FillMode fillMode, String flat, Indent plusIndent) {
579       return new Break(fillMode, flat, plusIndent, /* optTag= */ Optional.empty());
580     }
581 
582     /**
583      * Make a {@code Break}.
584      *
585      * @param fillMode the {@link FillMode}
586      * @param flat the text when not broken
587      * @param plusIndent extra indent if taken
588      * @param optTag an optional tag for remembering whether the break was taken
589      * @return the new {@code Break}
590      */
make( FillMode fillMode, String flat, Indent plusIndent, Optional<BreakTag> optTag)591     public static Break make(
592         FillMode fillMode, String flat, Indent plusIndent, Optional<BreakTag> optTag) {
593       return new Break(fillMode, flat, plusIndent, optTag);
594     }
595 
596     /**
597      * Make a forced {@code Break}.
598      *
599      * @return the new forced {@code Break}
600      */
makeForced()601     public static Break makeForced() {
602       return make(FillMode.FORCED, "", Indent.Const.ZERO);
603     }
604 
605     /**
606      * Return the {@code Break}'s extra indent.
607      *
608      * @return the extra indent
609      */
getPlusIndent()610     int getPlusIndent() {
611       return plusIndent.eval();
612     }
613 
614     /**
615      * Is the {@code Break} forced?
616      *
617      * @return whether the {@code Break} is forced
618      */
isForced()619     boolean isForced() {
620       return fillMode == FillMode.FORCED;
621     }
622 
623     @Override
add(DocBuilder builder)624     public void add(DocBuilder builder) {
625       builder.breakDoc(this);
626     }
627 
628     @Override
computeWidth()629     float computeWidth() {
630       return isForced() ? Float.POSITIVE_INFINITY : (float) flat.length();
631     }
632 
633     @Override
computeFlat()634     String computeFlat() {
635       return flat;
636     }
637 
638     @Override
computeRange()639     Range<Integer> computeRange() {
640       return EMPTY_RANGE;
641     }
642 
643     /** Was this break taken? */
644     boolean broken;
645 
646     /** New indent after this break. */
647     int newIndent;
648 
computeBreaks(State state, int lastIndent, boolean broken)649     public State computeBreaks(State state, int lastIndent, boolean broken) {
650       if (optTag.isPresent()) {
651         optTag.get().recordBroken(broken);
652       }
653 
654       if (broken) {
655         this.broken = true;
656         this.newIndent = Math.max(lastIndent + plusIndent.eval(), 0);
657         return state.withColumn(newIndent);
658       } else {
659         this.broken = false;
660         this.newIndent = -1;
661         return state.withColumn(state.column + flat.length());
662       }
663     }
664 
665     @Override
computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state)666     public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) {
667       // Updating the state for {@link Break}s requires deciding if the break
668       // should be taken.
669       // TODO(cushon): this hierarchy is wrong, create a separate interface
670       // for unbreakable Docs?
671       throw new UnsupportedOperationException("Did you mean computeBreaks(State, int, boolean)?");
672     }
673 
674     @Override
write(Output output)675     public void write(Output output) {
676       if (broken) {
677         output.append("\n", EMPTY_RANGE);
678         output.indent(newIndent);
679       } else {
680         output.append(flat, range());
681       }
682     }
683 
684     @Override
toString()685     public String toString() {
686       return MoreObjects.toStringHelper(this)
687           .add("fillMode", fillMode)
688           .add("flat", flat)
689           .add("plusIndent", plusIndent)
690           .add("optTag", optTag)
691           .toString();
692     }
693   }
694 
695   /** A leaf node in a {@link Doc} for a non-token. */
696   static final class Tok extends Doc implements Op {
697     private final Input.Tok tok;
698 
Tok(Input.Tok tok)699     private Tok(Input.Tok tok) {
700       this.tok = tok;
701     }
702 
703     /**
704      * Factory method for a {@code Tok}.
705      *
706      * @param tok the {@link Input.Tok} to wrap
707      * @return the new {@code Tok}
708      */
make(Input.Tok tok)709     static Tok make(Input.Tok tok) {
710       return new Tok(tok);
711     }
712 
713     @Override
add(DocBuilder builder)714     public void add(DocBuilder builder) {
715       builder.add(this);
716     }
717 
718     @Override
computeWidth()719     float computeWidth() {
720       int idx = Newlines.firstBreak(tok.getOriginalText());
721       // only count the first line of multi-line block comments
722       if (tok.isComment()) {
723         if (idx > 0) {
724           return idx;
725         } else if (tok.isSlashSlashComment() && !tok.getOriginalText().startsWith("// ")) {
726           // Account for line comments with missing spaces, see computeFlat.
727           return tok.length() + 1;
728         } else {
729           return tok.length();
730         }
731       }
732       return idx != -1 ? Float.POSITIVE_INFINITY : (float) tok.length();
733     }
734 
735     @Override
computeFlat()736     String computeFlat() {
737       // TODO(cushon): commentsHelper.rewrite doesn't get called for spans that fit in a single
738       // line. That's fine for multi-line comment reflowing, but problematic for adding missing
739       // spaces in line comments.
740       if (tok.isSlashSlashComment() && !tok.getOriginalText().startsWith("// ")) {
741         return "// " + tok.getOriginalText().substring("//".length());
742       }
743       return tok.getOriginalText();
744     }
745 
746     @Override
computeRange()747     Range<Integer> computeRange() {
748       return Range.singleton(tok.getIndex()).canonical(INTEGERS);
749     }
750 
751     String text;
752 
753     @Override
computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state)754     public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) {
755       text = commentsHelper.rewrite(tok, maxWidth, state.column);
756       int firstLineLength = text.length() - Iterators.getLast(Newlines.lineOffsetIterator(text));
757       return state.withColumn(state.column + firstLineLength);
758     }
759 
760     @Override
write(Output output)761     public void write(Output output) {
762       output.append(text, range());
763     }
764 
765     @Override
toString()766     public String toString() {
767       return MoreObjects.toStringHelper(this).add("tok", tok).toString();
768     }
769   }
770 }
771