clang API Documentation

WhitespaceManager.cpp
Go to the documentation of this file.
00001 //===--- WhitespaceManager.cpp - Format C++ code --------------------------===//
00002 //
00003 //                     The LLVM Compiler Infrastructure
00004 //
00005 // This file is distributed under the University of Illinois Open Source
00006 // License. See LICENSE.TXT for details.
00007 //
00008 //===----------------------------------------------------------------------===//
00009 ///
00010 /// \file
00011 /// \brief This file implements WhitespaceManager class.
00012 ///
00013 //===----------------------------------------------------------------------===//
00014 
00015 #include "WhitespaceManager.h"
00016 #include "llvm/ADT/STLExtras.h"
00017 
00018 namespace clang {
00019 namespace format {
00020 
00021 bool WhitespaceManager::Change::IsBeforeInFile::
00022 operator()(const Change &C1, const Change &C2) const {
00023   return SourceMgr.isBeforeInTranslationUnit(
00024       C1.OriginalWhitespaceRange.getBegin(),
00025       C2.OriginalWhitespaceRange.getBegin());
00026 }
00027 
00028 WhitespaceManager::Change::Change(
00029     bool CreateReplacement, const SourceRange &OriginalWhitespaceRange,
00030     unsigned IndentLevel, int Spaces, unsigned StartOfTokenColumn,
00031     unsigned NewlinesBefore, StringRef PreviousLinePostfix,
00032     StringRef CurrentLinePrefix, tok::TokenKind Kind, bool ContinuesPPDirective)
00033     : CreateReplacement(CreateReplacement),
00034       OriginalWhitespaceRange(OriginalWhitespaceRange),
00035       StartOfTokenColumn(StartOfTokenColumn), NewlinesBefore(NewlinesBefore),
00036       PreviousLinePostfix(PreviousLinePostfix),
00037       CurrentLinePrefix(CurrentLinePrefix), Kind(Kind),
00038       ContinuesPPDirective(ContinuesPPDirective), IndentLevel(IndentLevel),
00039       Spaces(Spaces) {}
00040 
00041 void WhitespaceManager::reset() {
00042   Changes.clear();
00043   Replaces.clear();
00044 }
00045 
00046 void WhitespaceManager::replaceWhitespace(FormatToken &Tok, unsigned Newlines,
00047                                           unsigned IndentLevel, unsigned Spaces,
00048                                           unsigned StartOfTokenColumn,
00049                                           bool InPPDirective) {
00050   if (Tok.Finalized)
00051     return;
00052   Tok.Decision = (Newlines > 0) ? FD_Break : FD_Continue;
00053   Changes.push_back(Change(true, Tok.WhitespaceRange, IndentLevel, Spaces,
00054                            StartOfTokenColumn, Newlines, "", "",
00055                            Tok.Tok.getKind(), InPPDirective && !Tok.IsFirst));
00056 }
00057 
00058 void WhitespaceManager::addUntouchableToken(const FormatToken &Tok,
00059                                             bool InPPDirective) {
00060   if (Tok.Finalized)
00061     return;
00062   Changes.push_back(Change(false, Tok.WhitespaceRange, /*IndentLevel=*/0,
00063                            /*Spaces=*/0, Tok.OriginalColumn, Tok.NewlinesBefore,
00064                            "", "", Tok.Tok.getKind(),
00065                            InPPDirective && !Tok.IsFirst));
00066 }
00067 
00068 void WhitespaceManager::replaceWhitespaceInToken(
00069     const FormatToken &Tok, unsigned Offset, unsigned ReplaceChars,
00070     StringRef PreviousPostfix, StringRef CurrentPrefix, bool InPPDirective,
00071     unsigned Newlines, unsigned IndentLevel, int Spaces) {
00072   if (Tok.Finalized)
00073     return;
00074   SourceLocation Start = Tok.getStartOfNonWhitespace().getLocWithOffset(Offset);
00075   Changes.push_back(Change(
00076       true, SourceRange(Start, Start.getLocWithOffset(ReplaceChars)),
00077       IndentLevel, Spaces, std::max(0, Spaces), Newlines, PreviousPostfix,
00078       CurrentPrefix,
00079       // If we don't add a newline this change doesn't start a comment. Thus,
00080       // when we align line comments, we don't need to treat this change as one.
00081       // FIXME: We still need to take this change in account to properly
00082       // calculate the new length of the comment and to calculate the changes
00083       // for which to do the alignment when aligning comments.
00084       Tok.Type == TT_LineComment && Newlines > 0 ? tok::comment : tok::unknown,
00085       InPPDirective && !Tok.IsFirst));
00086 }
00087 
00088 const tooling::Replacements &WhitespaceManager::generateReplacements() {
00089   if (Changes.empty())
00090     return Replaces;
00091 
00092   std::sort(Changes.begin(), Changes.end(), Change::IsBeforeInFile(SourceMgr));
00093   calculateLineBreakInformation();
00094   alignTrailingComments();
00095   alignEscapedNewlines();
00096   generateChanges();
00097 
00098   return Replaces;
00099 }
00100 
00101 void WhitespaceManager::calculateLineBreakInformation() {
00102   Changes[0].PreviousEndOfTokenColumn = 0;
00103   for (unsigned i = 1, e = Changes.size(); i != e; ++i) {
00104     unsigned OriginalWhitespaceStart =
00105         SourceMgr.getFileOffset(Changes[i].OriginalWhitespaceRange.getBegin());
00106     unsigned PreviousOriginalWhitespaceEnd = SourceMgr.getFileOffset(
00107         Changes[i - 1].OriginalWhitespaceRange.getEnd());
00108     Changes[i - 1].TokenLength = OriginalWhitespaceStart -
00109                                  PreviousOriginalWhitespaceEnd +
00110                                  Changes[i].PreviousLinePostfix.size() +
00111                                  Changes[i - 1].CurrentLinePrefix.size();
00112 
00113     Changes[i].PreviousEndOfTokenColumn =
00114         Changes[i - 1].StartOfTokenColumn + Changes[i - 1].TokenLength;
00115 
00116     Changes[i - 1].IsTrailingComment =
00117         (Changes[i].NewlinesBefore > 0 || Changes[i].Kind == tok::eof) &&
00118         Changes[i - 1].Kind == tok::comment;
00119   }
00120   // FIXME: The last token is currently not always an eof token; in those
00121   // cases, setting TokenLength of the last token to 0 is wrong.
00122   Changes.back().TokenLength = 0;
00123   Changes.back().IsTrailingComment = Changes.back().Kind == tok::comment;
00124 
00125   const WhitespaceManager::Change *LastBlockComment = nullptr;
00126   for (auto &Change : Changes) {
00127     Change.StartOfBlockComment = nullptr;
00128     Change.IndentationOffset = 0;
00129     if (Change.Kind == tok::comment) {
00130       LastBlockComment = &Change;
00131     } else if (Change.Kind == tok::unknown) {
00132       if ((Change.StartOfBlockComment = LastBlockComment))
00133         Change.IndentationOffset =
00134             Change.StartOfTokenColumn -
00135             Change.StartOfBlockComment->StartOfTokenColumn;
00136     } else {
00137       LastBlockComment = nullptr;
00138     }
00139   }
00140 }
00141 
00142 void WhitespaceManager::alignTrailingComments() {
00143   unsigned MinColumn = 0;
00144   unsigned MaxColumn = UINT_MAX;
00145   unsigned StartOfSequence = 0;
00146   bool BreakBeforeNext = false;
00147   unsigned Newlines = 0;
00148   for (unsigned i = 0, e = Changes.size(); i != e; ++i) {
00149     if (Changes[i].StartOfBlockComment)
00150       continue;
00151     Newlines += Changes[i].NewlinesBefore;
00152     if (!Changes[i].IsTrailingComment)
00153       continue;
00154 
00155     unsigned ChangeMinColumn = Changes[i].StartOfTokenColumn;
00156     unsigned ChangeMaxColumn = Style.ColumnLimit - Changes[i].TokenLength;
00157     if (i + 1 != e && Changes[i + 1].ContinuesPPDirective)
00158       ChangeMaxColumn -= 2;
00159     // If this comment follows an } in column 0, it probably documents the
00160     // closing of a namespace and we don't want to align it.
00161     bool FollowsRBraceInColumn0 = i > 0 && Changes[i].NewlinesBefore == 0 &&
00162                                   Changes[i - 1].Kind == tok::r_brace &&
00163                                   Changes[i - 1].StartOfTokenColumn == 0;
00164     bool WasAlignedWithStartOfNextLine = false;
00165     if (Changes[i].NewlinesBefore == 1) { // A comment on its own line.
00166       for (unsigned j = i + 1; j != e; ++j) {
00167         if (Changes[j].Kind != tok::comment) { // Skip over comments.
00168           // The start of the next token was previously aligned with the
00169           // start of this comment.
00170           WasAlignedWithStartOfNextLine =
00171               (SourceMgr.getSpellingColumnNumber(
00172                    Changes[i].OriginalWhitespaceRange.getEnd()) ==
00173                SourceMgr.getSpellingColumnNumber(
00174                    Changes[j].OriginalWhitespaceRange.getEnd()));
00175           break;
00176         }
00177       }
00178     }
00179     if (!Style.AlignTrailingComments || FollowsRBraceInColumn0) {
00180       alignTrailingComments(StartOfSequence, i, MinColumn);
00181       MinColumn = ChangeMinColumn;
00182       MaxColumn = ChangeMinColumn;
00183       StartOfSequence = i;
00184     } else if (BreakBeforeNext || Newlines > 1 ||
00185                (ChangeMinColumn > MaxColumn || ChangeMaxColumn < MinColumn) ||
00186                // Break the comment sequence if the previous line did not end
00187                // in a trailing comment.
00188                (Changes[i].NewlinesBefore == 1 && i > 0 &&
00189                 !Changes[i - 1].IsTrailingComment) ||
00190                WasAlignedWithStartOfNextLine) {
00191       alignTrailingComments(StartOfSequence, i, MinColumn);
00192       MinColumn = ChangeMinColumn;
00193       MaxColumn = ChangeMaxColumn;
00194       StartOfSequence = i;
00195     } else {
00196       MinColumn = std::max(MinColumn, ChangeMinColumn);
00197       MaxColumn = std::min(MaxColumn, ChangeMaxColumn);
00198     }
00199     BreakBeforeNext =
00200         (i == 0) || (Changes[i].NewlinesBefore > 1) ||
00201         // Never start a sequence with a comment at the beginning of
00202         // the line.
00203         (Changes[i].NewlinesBefore == 1 && StartOfSequence == i);
00204     Newlines = 0;
00205   }
00206   alignTrailingComments(StartOfSequence, Changes.size(), MinColumn);
00207 }
00208 
00209 void WhitespaceManager::alignTrailingComments(unsigned Start, unsigned End,
00210                                               unsigned Column) {
00211   for (unsigned i = Start; i != End; ++i) {
00212     int Shift = 0;
00213     if (Changes[i].IsTrailingComment) {
00214       Shift = Column - Changes[i].StartOfTokenColumn;
00215     }
00216     if (Changes[i].StartOfBlockComment) {
00217       Shift = Changes[i].IndentationOffset +
00218               Changes[i].StartOfBlockComment->StartOfTokenColumn -
00219               Changes[i].StartOfTokenColumn;
00220     }
00221     assert(Shift >= 0);
00222     Changes[i].Spaces += Shift;
00223     if (i + 1 != End)
00224       Changes[i + 1].PreviousEndOfTokenColumn += Shift;
00225     Changes[i].StartOfTokenColumn += Shift;
00226   }
00227 }
00228 
00229 void WhitespaceManager::alignEscapedNewlines() {
00230   unsigned MaxEndOfLine =
00231       Style.AlignEscapedNewlinesLeft ? 0 : Style.ColumnLimit;
00232   unsigned StartOfMacro = 0;
00233   for (unsigned i = 1, e = Changes.size(); i < e; ++i) {
00234     Change &C = Changes[i];
00235     if (C.NewlinesBefore > 0) {
00236       if (C.ContinuesPPDirective) {
00237         MaxEndOfLine = std::max(C.PreviousEndOfTokenColumn + 2, MaxEndOfLine);
00238       } else {
00239         alignEscapedNewlines(StartOfMacro + 1, i, MaxEndOfLine);
00240         MaxEndOfLine = Style.AlignEscapedNewlinesLeft ? 0 : Style.ColumnLimit;
00241         StartOfMacro = i;
00242       }
00243     }
00244   }
00245   alignEscapedNewlines(StartOfMacro + 1, Changes.size(), MaxEndOfLine);
00246 }
00247 
00248 void WhitespaceManager::alignEscapedNewlines(unsigned Start, unsigned End,
00249                                              unsigned Column) {
00250   for (unsigned i = Start; i < End; ++i) {
00251     Change &C = Changes[i];
00252     if (C.NewlinesBefore > 0) {
00253       assert(C.ContinuesPPDirective);
00254       if (C.PreviousEndOfTokenColumn + 1 > Column)
00255         C.EscapedNewlineColumn = 0;
00256       else
00257         C.EscapedNewlineColumn = Column;
00258     }
00259   }
00260 }
00261 
00262 void WhitespaceManager::generateChanges() {
00263   for (unsigned i = 0, e = Changes.size(); i != e; ++i) {
00264     const Change &C = Changes[i];
00265     if (C.CreateReplacement) {
00266       std::string ReplacementText = C.PreviousLinePostfix;
00267       if (C.ContinuesPPDirective)
00268         appendNewlineText(ReplacementText, C.NewlinesBefore,
00269                           C.PreviousEndOfTokenColumn, C.EscapedNewlineColumn);
00270       else
00271         appendNewlineText(ReplacementText, C.NewlinesBefore);
00272       appendIndentText(ReplacementText, C.IndentLevel, std::max(0, C.Spaces),
00273                        C.StartOfTokenColumn - std::max(0, C.Spaces));
00274       ReplacementText.append(C.CurrentLinePrefix);
00275       storeReplacement(C.OriginalWhitespaceRange, ReplacementText);
00276     }
00277   }
00278 }
00279 
00280 void WhitespaceManager::storeReplacement(const SourceRange &Range,
00281                                          StringRef Text) {
00282   unsigned WhitespaceLength = SourceMgr.getFileOffset(Range.getEnd()) -
00283                               SourceMgr.getFileOffset(Range.getBegin());
00284   // Don't create a replacement, if it does not change anything.
00285   if (StringRef(SourceMgr.getCharacterData(Range.getBegin()),
00286                 WhitespaceLength) == Text)
00287     return;
00288   Replaces.insert(tooling::Replacement(
00289       SourceMgr, CharSourceRange::getCharRange(Range), Text));
00290 }
00291 
00292 void WhitespaceManager::appendNewlineText(std::string &Text,
00293                                           unsigned Newlines) {
00294   for (unsigned i = 0; i < Newlines; ++i)
00295     Text.append(UseCRLF ? "\r\n" : "\n");
00296 }
00297 
00298 void WhitespaceManager::appendNewlineText(std::string &Text, unsigned Newlines,
00299                                           unsigned PreviousEndOfTokenColumn,
00300                                           unsigned EscapedNewlineColumn) {
00301   if (Newlines > 0) {
00302     unsigned Offset =
00303         std::min<int>(EscapedNewlineColumn - 1, PreviousEndOfTokenColumn);
00304     for (unsigned i = 0; i < Newlines; ++i) {
00305       Text.append(std::string(EscapedNewlineColumn - Offset - 1, ' '));
00306       Text.append(UseCRLF ? "\\\r\n" : "\\\n");
00307       Offset = 0;
00308     }
00309   }
00310 }
00311 
00312 void WhitespaceManager::appendIndentText(std::string &Text,
00313                                          unsigned IndentLevel, unsigned Spaces,
00314                                          unsigned WhitespaceStartColumn) {
00315   switch (Style.UseTab) {
00316   case FormatStyle::UT_Never:
00317     Text.append(std::string(Spaces, ' '));
00318     break;
00319   case FormatStyle::UT_Always: {
00320     unsigned FirstTabWidth =
00321         Style.TabWidth - WhitespaceStartColumn % Style.TabWidth;
00322     // Indent with tabs only when there's at least one full tab.
00323     if (FirstTabWidth + Style.TabWidth <= Spaces) {
00324       Spaces -= FirstTabWidth;
00325       Text.append("\t");
00326     }
00327     Text.append(std::string(Spaces / Style.TabWidth, '\t'));
00328     Text.append(std::string(Spaces % Style.TabWidth, ' '));
00329     break;
00330   }
00331   case FormatStyle::UT_ForIndentation:
00332     if (WhitespaceStartColumn == 0) {
00333       unsigned Indentation = IndentLevel * Style.IndentWidth;
00334       // This happens, e.g. when a line in a block comment is indented less than
00335       // the first one.
00336       if (Indentation > Spaces)
00337         Indentation = Spaces;
00338       unsigned Tabs = Indentation / Style.TabWidth;
00339       Text.append(std::string(Tabs, '\t'));
00340       Spaces -= Tabs * Style.TabWidth;
00341     }
00342     Text.append(std::string(Spaces, ' '));
00343     break;
00344   }
00345 }
00346 
00347 } // namespace format
00348 } // namespace clang