• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
operator ()(const Change & C1,const Change & C2) const22 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 
Change(bool CreateReplacement,const SourceRange & OriginalWhitespaceRange,unsigned Spaces,unsigned StartOfTokenColumn,unsigned NewlinesBefore,StringRef PreviousLinePostfix,StringRef CurrentLinePrefix,tok::TokenKind Kind,bool ContinuesPPDirective)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 
replaceWhitespace(const FormatToken & Tok,unsigned Newlines,unsigned Spaces,unsigned StartOfTokenColumn,bool InPPDirective)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 
addUntouchableToken(const FormatToken & Tok,bool InPPDirective)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 
replaceWhitespaceInToken(const FormatToken & Tok,unsigned Offset,unsigned ReplaceChars,StringRef PreviousPostfix,StringRef CurrentPrefix,bool InPPDirective,unsigned Newlines,unsigned Spaces)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 
generateReplacements()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 
calculateLineBreakInformation()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 
alignTrailingComments()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 
alignTrailingComments(unsigned Start,unsigned End,unsigned Column)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 
alignEscapedNewlines()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 
alignEscapedNewlines(unsigned Start,unsigned End,unsigned Column)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 
generateChanges()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 
storeReplacement(const SourceRange & Range,StringRef Text)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 
getNewlineText(unsigned Newlines,unsigned Spaces)251 std::string WhitespaceManager::getNewlineText(unsigned Newlines,
252                                               unsigned Spaces) {
253   return std::string(Newlines, '\n') + getIndentText(Spaces);
254 }
255 
getNewlineText(unsigned Newlines,unsigned Spaces,unsigned PreviousEndOfTokenColumn,unsigned EscapedNewlineColumn)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 
getIndentText(unsigned Spaces)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