Home | History | Annotate | Download | only in Format
      1 //===--- WhitespaceManager.cpp - Format C++ code --------------------------===//
      2 //
      3 //                     The LLVM Compiler Infrastructure
      4 //
      5 // This file is distributed under the University of Illinois Open Source
      6 // License. See LICENSE.TXT for details.
      7 //
      8 //===----------------------------------------------------------------------===//
      9 ///
     10 /// \file
     11 /// \brief This file implements WhitespaceManager class.
     12 ///
     13 //===----------------------------------------------------------------------===//
     14 
     15 #include "WhitespaceManager.h"
     16 #include "llvm/ADT/STLExtras.h"
     17 
     18 namespace clang {
     19 namespace format {
     20 
     21 bool
     22 WhitespaceManager::Change::IsBeforeInFile::operator()(const Change &C1,
     23                                                       const Change &C2) const {
     24   return SourceMgr.isBeforeInTranslationUnit(
     25       C1.OriginalWhitespaceRange.getBegin(),
     26       C2.OriginalWhitespaceRange.getBegin());
     27 }
     28 
     29 WhitespaceManager::Change::Change(
     30     bool CreateReplacement, const SourceRange &OriginalWhitespaceRange,
     31     unsigned Spaces, unsigned StartOfTokenColumn, unsigned NewlinesBefore,
     32     StringRef PreviousLinePostfix, StringRef CurrentLinePrefix,
     33     tok::TokenKind Kind, bool ContinuesPPDirective)
     34     : CreateReplacement(CreateReplacement),
     35       OriginalWhitespaceRange(OriginalWhitespaceRange),
     36       StartOfTokenColumn(StartOfTokenColumn), NewlinesBefore(NewlinesBefore),
     37       PreviousLinePostfix(PreviousLinePostfix),
     38       CurrentLinePrefix(CurrentLinePrefix), Kind(Kind),
     39       ContinuesPPDirective(ContinuesPPDirective), Spaces(Spaces) {}
     40 
     41 void WhitespaceManager::replaceWhitespace(const FormatToken &Tok,
     42                                           unsigned Newlines, unsigned Spaces,
     43                                           unsigned StartOfTokenColumn,
     44                                           bool InPPDirective) {
     45   Changes.push_back(Change(true, Tok.WhitespaceRange, Spaces,
     46                            StartOfTokenColumn, Newlines, "", "",
     47                            Tok.Tok.getKind(), InPPDirective && !Tok.IsFirst));
     48 }
     49 
     50 void WhitespaceManager::addUntouchableToken(const FormatToken &Tok,
     51                                             bool InPPDirective) {
     52   Changes.push_back(
     53       Change(false, Tok.WhitespaceRange, /*Spaces=*/0,
     54              SourceMgr.getSpellingColumnNumber(Tok.Tok.getLocation()) - 1,
     55              Tok.NewlinesBefore, "", "", Tok.Tok.getKind(),
     56              InPPDirective && !Tok.IsFirst));
     57 }
     58 
     59 void WhitespaceManager::replaceWhitespaceInToken(
     60     const FormatToken &Tok, unsigned Offset, unsigned ReplaceChars,
     61     StringRef PreviousPostfix, StringRef CurrentPrefix, bool InPPDirective,
     62     unsigned Newlines, unsigned Spaces) {
     63   Changes.push_back(Change(
     64       true, SourceRange(Tok.getStartOfNonWhitespace().getLocWithOffset(Offset),
     65                         Tok.getStartOfNonWhitespace().getLocWithOffset(
     66                             Offset + ReplaceChars)),
     67       Spaces, Spaces, Newlines, PreviousPostfix, CurrentPrefix,
     68       // If we don't add a newline this change doesn't start a comment. Thus,
     69       // when we align line comments, we don't need to treat this change as one.
     70       // FIXME: We still need to take this change in account to properly
     71       // calculate the new length of the comment and to calculate the changes
     72       // for which to do the alignment when aligning comments.
     73       Tok.Type == TT_LineComment && Newlines > 0 ? tok::comment : tok::unknown,
     74       InPPDirective && !Tok.IsFirst));
     75 }
     76 
     77 const tooling::Replacements &WhitespaceManager::generateReplacements() {
     78   if (Changes.empty())
     79     return Replaces;
     80 
     81   std::sort(Changes.begin(), Changes.end(), Change::IsBeforeInFile(SourceMgr));
     82   calculateLineBreakInformation();
     83   alignTrailingComments();
     84   alignEscapedNewlines();
     85   generateChanges();
     86 
     87   return Replaces;
     88 }
     89 
     90 void WhitespaceManager::calculateLineBreakInformation() {
     91   Changes[0].PreviousEndOfTokenColumn = 0;
     92   for (unsigned i = 1, e = Changes.size(); i != e; ++i) {
     93     unsigned OriginalWhitespaceStart =
     94         SourceMgr.getFileOffset(Changes[i].OriginalWhitespaceRange.getBegin());
     95     unsigned PreviousOriginalWhitespaceEnd = SourceMgr.getFileOffset(
     96         Changes[i - 1].OriginalWhitespaceRange.getEnd());
     97     Changes[i - 1].TokenLength = OriginalWhitespaceStart -
     98                                  PreviousOriginalWhitespaceEnd +
     99                                  Changes[i].PreviousLinePostfix.size() +
    100                                  Changes[i - 1].CurrentLinePrefix.size();
    101 
    102     Changes[i].PreviousEndOfTokenColumn =
    103         Changes[i - 1].StartOfTokenColumn + Changes[i - 1].TokenLength;
    104 
    105     Changes[i - 1].IsTrailingComment =
    106         (Changes[i].NewlinesBefore > 0 || Changes[i].Kind == tok::eof) &&
    107         Changes[i - 1].Kind == tok::comment;
    108   }
    109   // FIXME: The last token is currently not always an eof token; in those
    110   // cases, setting TokenLength of the last token to 0 is wrong.
    111   Changes.back().TokenLength = 0;
    112   Changes.back().IsTrailingComment = Changes.back().Kind == tok::comment;
    113 }
    114 
    115 void WhitespaceManager::alignTrailingComments() {
    116   unsigned MinColumn = 0;
    117   unsigned MaxColumn = UINT_MAX;
    118   unsigned StartOfSequence = 0;
    119   bool BreakBeforeNext = false;
    120   unsigned Newlines = 0;
    121   for (unsigned i = 0, e = Changes.size(); i != e; ++i) {
    122     unsigned ChangeMinColumn = Changes[i].StartOfTokenColumn;
    123     // FIXME: Correctly handle ChangeMaxColumn in PP directives.
    124     unsigned ChangeMaxColumn = Style.ColumnLimit - Changes[i].TokenLength;
    125     Newlines += Changes[i].NewlinesBefore;
    126     if (Changes[i].IsTrailingComment) {
    127       // If this comment follows an } in column 0, it probably documents the
    128       // closing of a namespace and we don't want to align it.
    129       bool FollowsRBraceInColumn0 = i > 0 && Changes[i].NewlinesBefore == 0 &&
    130                                     Changes[i - 1].Kind == tok::r_brace &&
    131                                     Changes[i - 1].StartOfTokenColumn == 0;
    132       bool WasAlignedWithStartOfNextLine =
    133           // A comment on its own line.
    134           Changes[i].NewlinesBefore == 1 &&
    135           // Not the last line.
    136           i + 1 != e &&
    137           // The start of the next token was previously aligned with
    138           // the start of this comment.
    139           (SourceMgr.getSpellingColumnNumber(
    140                Changes[i].OriginalWhitespaceRange.getEnd()) ==
    141            SourceMgr.getSpellingColumnNumber(
    142                Changes[i + 1].OriginalWhitespaceRange.getEnd())) &&
    143           // Which is not a comment itself.
    144           Changes[i + 1].Kind != tok::comment;
    145       if (!Style.AlignTrailingComments || FollowsRBraceInColumn0) {
    146         alignTrailingComments(StartOfSequence, i, MinColumn);
    147         MinColumn = ChangeMinColumn;
    148         MaxColumn = ChangeMinColumn;
    149         StartOfSequence = i;
    150       } else if (BreakBeforeNext || Newlines > 1 ||
    151                  (ChangeMinColumn > MaxColumn || ChangeMaxColumn < MinColumn) ||
    152                  // Break the comment sequence if the previous line did not end
    153                  // in a trailing comment.
    154                  (Changes[i].NewlinesBefore == 1 && i > 0 &&
    155                   !Changes[i - 1].IsTrailingComment) ||
    156                  WasAlignedWithStartOfNextLine) {
    157         alignTrailingComments(StartOfSequence, i, MinColumn);
    158         MinColumn = ChangeMinColumn;
    159         MaxColumn = ChangeMaxColumn;
    160         StartOfSequence = i;
    161       } else {
    162         MinColumn = std::max(MinColumn, ChangeMinColumn);
    163         MaxColumn = std::min(MaxColumn, ChangeMaxColumn);
    164       }
    165       BreakBeforeNext =
    166           (i == 0) || (Changes[i].NewlinesBefore > 1) ||
    167           // Never start a sequence with a comment at the beginning of
    168           // the line.
    169           (Changes[i].NewlinesBefore == 1 && StartOfSequence == i);
    170       Newlines = 0;
    171     }
    172   }
    173   alignTrailingComments(StartOfSequence, Changes.size(), MinColumn);
    174 }
    175 
    176 void WhitespaceManager::alignTrailingComments(unsigned Start, unsigned End,
    177                                               unsigned Column) {
    178   for (unsigned i = Start; i != End; ++i) {
    179     if (Changes[i].IsTrailingComment) {
    180       assert(Column >= Changes[i].StartOfTokenColumn);
    181       Changes[i].Spaces += Column - Changes[i].StartOfTokenColumn;
    182       Changes[i].StartOfTokenColumn = Column;
    183     }
    184   }
    185 }
    186 
    187 void WhitespaceManager::alignEscapedNewlines() {
    188   unsigned MaxEndOfLine = 0;
    189   unsigned StartOfMacro = 0;
    190   for (unsigned i = 1, e = Changes.size(); i < e; ++i) {
    191     Change &C = Changes[i];
    192     if (C.NewlinesBefore > 0) {
    193       if (C.ContinuesPPDirective) {
    194         if (Style.AlignEscapedNewlinesLeft)
    195           MaxEndOfLine = std::max(C.PreviousEndOfTokenColumn + 2, MaxEndOfLine);
    196         else
    197           MaxEndOfLine = Style.ColumnLimit;
    198       } else {
    199         alignEscapedNewlines(StartOfMacro + 1, i, MaxEndOfLine);
    200         MaxEndOfLine = 0;
    201         StartOfMacro = i;
    202       }
    203     }
    204   }
    205   alignEscapedNewlines(StartOfMacro + 1, Changes.size(), MaxEndOfLine);
    206 }
    207 
    208 void WhitespaceManager::alignEscapedNewlines(unsigned Start, unsigned End,
    209                                              unsigned Column) {
    210   for (unsigned i = Start; i < End; ++i) {
    211     Change &C = Changes[i];
    212     if (C.NewlinesBefore > 0) {
    213       assert(C.ContinuesPPDirective);
    214       if (C.PreviousEndOfTokenColumn + 1 > Column)
    215         C.EscapedNewlineColumn = 0;
    216       else
    217         C.EscapedNewlineColumn = Column;
    218     }
    219   }
    220 }
    221 
    222 void WhitespaceManager::generateChanges() {
    223   for (unsigned i = 0, e = Changes.size(); i != e; ++i) {
    224     const Change &C = Changes[i];
    225     if (C.CreateReplacement) {
    226       std::string ReplacementText =
    227           C.PreviousLinePostfix +
    228           (C.ContinuesPPDirective
    229                ? getNewlineText(C.NewlinesBefore, C.Spaces,
    230                                 C.PreviousEndOfTokenColumn,
    231                                 C.EscapedNewlineColumn)
    232                : getNewlineText(C.NewlinesBefore, C.Spaces)) +
    233           C.CurrentLinePrefix;
    234       storeReplacement(C.OriginalWhitespaceRange, ReplacementText);
    235     }
    236   }
    237 }
    238 
    239 void WhitespaceManager::storeReplacement(const SourceRange &Range,
    240                                          StringRef Text) {
    241   unsigned WhitespaceLength = SourceMgr.getFileOffset(Range.getEnd()) -
    242                               SourceMgr.getFileOffset(Range.getBegin());
    243   // Don't create a replacement, if it does not change anything.
    244   if (StringRef(SourceMgr.getCharacterData(Range.getBegin()),
    245                 WhitespaceLength) == Text)
    246     return;
    247   Replaces.insert(tooling::Replacement(
    248       SourceMgr, CharSourceRange::getCharRange(Range), Text));
    249 }
    250 
    251 std::string WhitespaceManager::getNewlineText(unsigned Newlines,
    252                                               unsigned Spaces) {
    253   return std::string(Newlines, '\n') + getIndentText(Spaces);
    254 }
    255 
    256 std::string WhitespaceManager::getNewlineText(unsigned Newlines,
    257                                               unsigned Spaces,
    258                                               unsigned PreviousEndOfTokenColumn,
    259                                               unsigned EscapedNewlineColumn) {
    260   std::string NewlineText;
    261   if (Newlines > 0) {
    262     unsigned Offset =
    263         std::min<int>(EscapedNewlineColumn - 1, PreviousEndOfTokenColumn);
    264     for (unsigned i = 0; i < Newlines; ++i) {
    265       NewlineText += std::string(EscapedNewlineColumn - Offset - 1, ' ');
    266       NewlineText += "\\\n";
    267       Offset = 0;
    268     }
    269   }
    270   return NewlineText + getIndentText(Spaces);
    271 }
    272 
    273 std::string WhitespaceManager::getIndentText(unsigned Spaces) {
    274   if (!Style.UseTab)
    275     return std::string(Spaces, ' ');
    276 
    277   return std::string(Spaces / Style.IndentWidth, '\t') +
    278          std::string(Spaces % Style.IndentWidth, ' ');
    279 }
    280 
    281 } // namespace format
    282 } // namespace clang
    283