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