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 WhitespaceManager::Change::IsBeforeInFile::
     22 operator()(const Change &C1, const Change &C2) const {
     23   return SourceMgr.isBeforeInTranslationUnit(
     24       C1.OriginalWhitespaceRange.getBegin(),
     25       C2.OriginalWhitespaceRange.getBegin());
     26 }
     27 
     28 WhitespaceManager::Change::Change(
     29     bool CreateReplacement, const SourceRange &OriginalWhitespaceRange,
     30     unsigned IndentLevel, int Spaces, unsigned StartOfTokenColumn,
     31     unsigned NewlinesBefore, StringRef PreviousLinePostfix,
     32     StringRef CurrentLinePrefix, tok::TokenKind Kind, bool ContinuesPPDirective)
     33     : CreateReplacement(CreateReplacement),
     34       OriginalWhitespaceRange(OriginalWhitespaceRange),
     35       StartOfTokenColumn(StartOfTokenColumn), NewlinesBefore(NewlinesBefore),
     36       PreviousLinePostfix(PreviousLinePostfix),
     37       CurrentLinePrefix(CurrentLinePrefix), Kind(Kind),
     38       ContinuesPPDirective(ContinuesPPDirective), IndentLevel(IndentLevel),
     39       Spaces(Spaces) {}
     40 
     41 void WhitespaceManager::reset() {
     42   Changes.clear();
     43   Replaces.clear();
     44 }
     45 
     46 void WhitespaceManager::replaceWhitespace(FormatToken &Tok, unsigned Newlines,
     47                                           unsigned IndentLevel, unsigned Spaces,
     48                                           unsigned StartOfTokenColumn,
     49                                           bool InPPDirective) {
     50   if (Tok.Finalized)
     51     return;
     52   Tok.Decision = (Newlines > 0) ? FD_Break : FD_Continue;
     53   Changes.push_back(Change(true, Tok.WhitespaceRange, IndentLevel, Spaces,
     54                            StartOfTokenColumn, Newlines, "", "",
     55                            Tok.Tok.getKind(), InPPDirective && !Tok.IsFirst));
     56 }
     57 
     58 void WhitespaceManager::addUntouchableToken(const FormatToken &Tok,
     59                                             bool InPPDirective) {
     60   if (Tok.Finalized)
     61     return;
     62   Changes.push_back(Change(false, Tok.WhitespaceRange, /*IndentLevel=*/0,
     63                            /*Spaces=*/0, Tok.OriginalColumn, Tok.NewlinesBefore,
     64                            "", "", Tok.Tok.getKind(),
     65                            InPPDirective && !Tok.IsFirst));
     66 }
     67 
     68 void WhitespaceManager::replaceWhitespaceInToken(
     69     const FormatToken &Tok, unsigned Offset, unsigned ReplaceChars,
     70     StringRef PreviousPostfix, StringRef CurrentPrefix, bool InPPDirective,
     71     unsigned Newlines, unsigned IndentLevel, int Spaces) {
     72   if (Tok.Finalized)
     73     return;
     74   SourceLocation Start = Tok.getStartOfNonWhitespace().getLocWithOffset(Offset);
     75   Changes.push_back(Change(
     76       true, SourceRange(Start, Start.getLocWithOffset(ReplaceChars)),
     77       IndentLevel, Spaces, std::max(0, Spaces), Newlines, PreviousPostfix,
     78       CurrentPrefix,
     79       // If we don't add a newline this change doesn't start a comment. Thus,
     80       // when we align line comments, we don't need to treat this change as one.
     81       // FIXME: We still need to take this change in account to properly
     82       // calculate the new length of the comment and to calculate the changes
     83       // for which to do the alignment when aligning comments.
     84       Tok.Type == TT_LineComment && Newlines > 0 ? tok::comment : tok::unknown,
     85       InPPDirective && !Tok.IsFirst));
     86 }
     87 
     88 const tooling::Replacements &WhitespaceManager::generateReplacements() {
     89   if (Changes.empty())
     90     return Replaces;
     91 
     92   std::sort(Changes.begin(), Changes.end(), Change::IsBeforeInFile(SourceMgr));
     93   calculateLineBreakInformation();
     94   alignTrailingComments();
     95   alignEscapedNewlines();
     96   generateChanges();
     97 
     98   return Replaces;
     99 }
    100 
    101 void WhitespaceManager::calculateLineBreakInformation() {
    102   Changes[0].PreviousEndOfTokenColumn = 0;
    103   for (unsigned i = 1, e = Changes.size(); i != e; ++i) {
    104     unsigned OriginalWhitespaceStart =
    105         SourceMgr.getFileOffset(Changes[i].OriginalWhitespaceRange.getBegin());
    106     unsigned PreviousOriginalWhitespaceEnd = SourceMgr.getFileOffset(
    107         Changes[i - 1].OriginalWhitespaceRange.getEnd());
    108     Changes[i - 1].TokenLength = OriginalWhitespaceStart -
    109                                  PreviousOriginalWhitespaceEnd +
    110                                  Changes[i].PreviousLinePostfix.size() +
    111                                  Changes[i - 1].CurrentLinePrefix.size();
    112 
    113     Changes[i].PreviousEndOfTokenColumn =
    114         Changes[i - 1].StartOfTokenColumn + Changes[i - 1].TokenLength;
    115 
    116     Changes[i - 1].IsTrailingComment =
    117         (Changes[i].NewlinesBefore > 0 || Changes[i].Kind == tok::eof) &&
    118         Changes[i - 1].Kind == tok::comment;
    119   }
    120   // FIXME: The last token is currently not always an eof token; in those
    121   // cases, setting TokenLength of the last token to 0 is wrong.
    122   Changes.back().TokenLength = 0;
    123   Changes.back().IsTrailingComment = Changes.back().Kind == tok::comment;
    124 
    125   const WhitespaceManager::Change *LastBlockComment = nullptr;
    126   for (auto &Change : Changes) {
    127     Change.StartOfBlockComment = nullptr;
    128     Change.IndentationOffset = 0;
    129     if (Change.Kind == tok::comment) {
    130       LastBlockComment = &Change;
    131     } else if (Change.Kind == tok::unknown) {
    132       if ((Change.StartOfBlockComment = LastBlockComment))
    133         Change.IndentationOffset =
    134             Change.StartOfTokenColumn -
    135             Change.StartOfBlockComment->StartOfTokenColumn;
    136     } else {
    137       LastBlockComment = nullptr;
    138     }
    139   }
    140 }
    141 
    142 void WhitespaceManager::alignTrailingComments() {
    143   unsigned MinColumn = 0;
    144   unsigned MaxColumn = UINT_MAX;
    145   unsigned StartOfSequence = 0;
    146   bool BreakBeforeNext = false;
    147   unsigned Newlines = 0;
    148   for (unsigned i = 0, e = Changes.size(); i != e; ++i) {
    149     if (Changes[i].StartOfBlockComment)
    150       continue;
    151     Newlines += Changes[i].NewlinesBefore;
    152     if (!Changes[i].IsTrailingComment)
    153       continue;
    154 
    155     unsigned ChangeMinColumn = Changes[i].StartOfTokenColumn;
    156     unsigned ChangeMaxColumn = Style.ColumnLimit - Changes[i].TokenLength;
    157     if (i + 1 != e && Changes[i + 1].ContinuesPPDirective)
    158       ChangeMaxColumn -= 2;
    159     // If this comment follows an } in column 0, it probably documents the
    160     // closing of a namespace and we don't want to align it.
    161     bool FollowsRBraceInColumn0 = i > 0 && Changes[i].NewlinesBefore == 0 &&
    162                                   Changes[i - 1].Kind == tok::r_brace &&
    163                                   Changes[i - 1].StartOfTokenColumn == 0;
    164     bool WasAlignedWithStartOfNextLine = false;
    165     if (Changes[i].NewlinesBefore == 1) { // A comment on its own line.
    166       for (unsigned j = i + 1; j != e; ++j) {
    167         if (Changes[j].Kind != tok::comment) { // Skip over comments.
    168           // The start of the next token was previously aligned with the
    169           // start of this comment.
    170           WasAlignedWithStartOfNextLine =
    171               (SourceMgr.getSpellingColumnNumber(
    172                    Changes[i].OriginalWhitespaceRange.getEnd()) ==
    173                SourceMgr.getSpellingColumnNumber(
    174                    Changes[j].OriginalWhitespaceRange.getEnd()));
    175           break;
    176         }
    177       }
    178     }
    179     if (!Style.AlignTrailingComments || FollowsRBraceInColumn0) {
    180       alignTrailingComments(StartOfSequence, i, MinColumn);
    181       MinColumn = ChangeMinColumn;
    182       MaxColumn = ChangeMinColumn;
    183       StartOfSequence = i;
    184     } else if (BreakBeforeNext || Newlines > 1 ||
    185                (ChangeMinColumn > MaxColumn || ChangeMaxColumn < MinColumn) ||
    186                // Break the comment sequence if the previous line did not end
    187                // in a trailing comment.
    188                (Changes[i].NewlinesBefore == 1 && i > 0 &&
    189                 !Changes[i - 1].IsTrailingComment) ||
    190                WasAlignedWithStartOfNextLine) {
    191       alignTrailingComments(StartOfSequence, i, MinColumn);
    192       MinColumn = ChangeMinColumn;
    193       MaxColumn = ChangeMaxColumn;
    194       StartOfSequence = i;
    195     } else {
    196       MinColumn = std::max(MinColumn, ChangeMinColumn);
    197       MaxColumn = std::min(MaxColumn, ChangeMaxColumn);
    198     }
    199     BreakBeforeNext =
    200         (i == 0) || (Changes[i].NewlinesBefore > 1) ||
    201         // Never start a sequence with a comment at the beginning of
    202         // the line.
    203         (Changes[i].NewlinesBefore == 1 && StartOfSequence == i);
    204     Newlines = 0;
    205   }
    206   alignTrailingComments(StartOfSequence, Changes.size(), MinColumn);
    207 }
    208 
    209 void WhitespaceManager::alignTrailingComments(unsigned Start, unsigned End,
    210                                               unsigned Column) {
    211   for (unsigned i = Start; i != End; ++i) {
    212     int Shift = 0;
    213     if (Changes[i].IsTrailingComment) {
    214       Shift = Column - Changes[i].StartOfTokenColumn;
    215     }
    216     if (Changes[i].StartOfBlockComment) {
    217       Shift = Changes[i].IndentationOffset +
    218               Changes[i].StartOfBlockComment->StartOfTokenColumn -
    219               Changes[i].StartOfTokenColumn;
    220     }
    221     assert(Shift >= 0);
    222     Changes[i].Spaces += Shift;
    223     if (i + 1 != End)
    224       Changes[i + 1].PreviousEndOfTokenColumn += Shift;
    225     Changes[i].StartOfTokenColumn += Shift;
    226   }
    227 }
    228 
    229 void WhitespaceManager::alignEscapedNewlines() {
    230   unsigned MaxEndOfLine =
    231       Style.AlignEscapedNewlinesLeft ? 0 : Style.ColumnLimit;
    232   unsigned StartOfMacro = 0;
    233   for (unsigned i = 1, e = Changes.size(); i < e; ++i) {
    234     Change &C = Changes[i];
    235     if (C.NewlinesBefore > 0) {
    236       if (C.ContinuesPPDirective) {
    237         MaxEndOfLine = std::max(C.PreviousEndOfTokenColumn + 2, MaxEndOfLine);
    238       } else {
    239         alignEscapedNewlines(StartOfMacro + 1, i, MaxEndOfLine);
    240         MaxEndOfLine = Style.AlignEscapedNewlinesLeft ? 0 : Style.ColumnLimit;
    241         StartOfMacro = i;
    242       }
    243     }
    244   }
    245   alignEscapedNewlines(StartOfMacro + 1, Changes.size(), MaxEndOfLine);
    246 }
    247 
    248 void WhitespaceManager::alignEscapedNewlines(unsigned Start, unsigned End,
    249                                              unsigned Column) {
    250   for (unsigned i = Start; i < End; ++i) {
    251     Change &C = Changes[i];
    252     if (C.NewlinesBefore > 0) {
    253       assert(C.ContinuesPPDirective);
    254       if (C.PreviousEndOfTokenColumn + 1 > Column)
    255         C.EscapedNewlineColumn = 0;
    256       else
    257         C.EscapedNewlineColumn = Column;
    258     }
    259   }
    260 }
    261 
    262 void WhitespaceManager::generateChanges() {
    263   for (unsigned i = 0, e = Changes.size(); i != e; ++i) {
    264     const Change &C = Changes[i];
    265     if (C.CreateReplacement) {
    266       std::string ReplacementText = C.PreviousLinePostfix;
    267       if (C.ContinuesPPDirective)
    268         appendNewlineText(ReplacementText, C.NewlinesBefore,
    269                           C.PreviousEndOfTokenColumn, C.EscapedNewlineColumn);
    270       else
    271         appendNewlineText(ReplacementText, C.NewlinesBefore);
    272       appendIndentText(ReplacementText, C.IndentLevel, std::max(0, C.Spaces),
    273                        C.StartOfTokenColumn - std::max(0, C.Spaces));
    274       ReplacementText.append(C.CurrentLinePrefix);
    275       storeReplacement(C.OriginalWhitespaceRange, ReplacementText);
    276     }
    277   }
    278 }
    279 
    280 void WhitespaceManager::storeReplacement(const SourceRange &Range,
    281                                          StringRef Text) {
    282   unsigned WhitespaceLength = SourceMgr.getFileOffset(Range.getEnd()) -
    283                               SourceMgr.getFileOffset(Range.getBegin());
    284   // Don't create a replacement, if it does not change anything.
    285   if (StringRef(SourceMgr.getCharacterData(Range.getBegin()),
    286                 WhitespaceLength) == Text)
    287     return;
    288   Replaces.insert(tooling::Replacement(
    289       SourceMgr, CharSourceRange::getCharRange(Range), Text));
    290 }
    291 
    292 void WhitespaceManager::appendNewlineText(std::string &Text,
    293                                           unsigned Newlines) {
    294   for (unsigned i = 0; i < Newlines; ++i)
    295     Text.append(UseCRLF ? "\r\n" : "\n");
    296 }
    297 
    298 void WhitespaceManager::appendNewlineText(std::string &Text, unsigned Newlines,
    299                                           unsigned PreviousEndOfTokenColumn,
    300                                           unsigned EscapedNewlineColumn) {
    301   if (Newlines > 0) {
    302     unsigned Offset =
    303         std::min<int>(EscapedNewlineColumn - 1, PreviousEndOfTokenColumn);
    304     for (unsigned i = 0; i < Newlines; ++i) {
    305       Text.append(std::string(EscapedNewlineColumn - Offset - 1, ' '));
    306       Text.append(UseCRLF ? "\\\r\n" : "\\\n");
    307       Offset = 0;
    308     }
    309   }
    310 }
    311 
    312 void WhitespaceManager::appendIndentText(std::string &Text,
    313                                          unsigned IndentLevel, unsigned Spaces,
    314                                          unsigned WhitespaceStartColumn) {
    315   switch (Style.UseTab) {
    316   case FormatStyle::UT_Never:
    317     Text.append(std::string(Spaces, ' '));
    318     break;
    319   case FormatStyle::UT_Always: {
    320     unsigned FirstTabWidth =
    321         Style.TabWidth - WhitespaceStartColumn % Style.TabWidth;
    322     // Indent with tabs only when there's at least one full tab.
    323     if (FirstTabWidth + Style.TabWidth <= Spaces) {
    324       Spaces -= FirstTabWidth;
    325       Text.append("\t");
    326     }
    327     Text.append(std::string(Spaces / Style.TabWidth, '\t'));
    328     Text.append(std::string(Spaces % Style.TabWidth, ' '));
    329     break;
    330   }
    331   case FormatStyle::UT_ForIndentation:
    332     if (WhitespaceStartColumn == 0) {
    333       unsigned Indentation = IndentLevel * Style.IndentWidth;
    334       // This happens, e.g. when a line in a block comment is indented less than
    335       // the first one.
    336       if (Indentation > Spaces)
    337         Indentation = Spaces;
    338       unsigned Tabs = Indentation / Style.TabWidth;
    339       Text.append(std::string(Tabs, '\t'));
    340       Spaces -= Tabs * Style.TabWidth;
    341     }
    342     Text.append(std::string(Spaces, ' '));
    343     break;
    344   }
    345 }
    346 
    347 } // namespace format
    348 } // namespace clang
    349