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