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