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(
46       Change(true, Tok.WhitespaceRange, Spaces, 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       // FIXME: Unify token adjustment, so we don't split it between
69       // BreakableToken and the WhitespaceManager. That would also allow us to
70       // correctly store a tok::TokenKind instead of rolling our own enum.
71       tok::unknown, InPPDirective && !Tok.IsFirst));
72 }
73 
74 const tooling::Replacements &WhitespaceManager::generateReplacements() {
75   if (Changes.empty())
76     return Replaces;
77 
78   std::sort(Changes.begin(), Changes.end(), Change::IsBeforeInFile(SourceMgr));
79   calculateLineBreakInformation();
80   alignTrailingComments();
81   alignEscapedNewlines();
82   generateChanges();
83 
84   return Replaces;
85 }
86 
87 void WhitespaceManager::calculateLineBreakInformation() {
88   Changes[0].PreviousEndOfTokenColumn = 0;
89   for (unsigned i = 1, e = Changes.size(); i != e; ++i) {
90     unsigned OriginalWhitespaceStart =
91         SourceMgr.getFileOffset(Changes[i].OriginalWhitespaceRange.getBegin());
92     unsigned PreviousOriginalWhitespaceEnd = SourceMgr.getFileOffset(
93         Changes[i - 1].OriginalWhitespaceRange.getEnd());
94     Changes[i - 1].TokenLength =
95         OriginalWhitespaceStart - PreviousOriginalWhitespaceEnd +
96         Changes[i].PreviousLinePostfix.size() +
97         Changes[i - 1].CurrentLinePrefix.size();
98 
99     Changes[i].PreviousEndOfTokenColumn =
100         Changes[i - 1].StartOfTokenColumn + Changes[i - 1].TokenLength;
101 
102     Changes[i - 1].IsTrailingComment =
103         (Changes[i].NewlinesBefore > 0 || Changes[i].Kind == tok::eof) &&
104         Changes[i - 1].Kind == tok::comment;
105   }
106   // FIXME: The last token is currently not always an eof token; in those
107   // cases, setting TokenLength of the last token to 0 is wrong.
108   Changes.back().TokenLength = 0;
109   Changes.back().IsTrailingComment = Changes.back().Kind == tok::comment;
110 }
111 
112 void WhitespaceManager::alignTrailingComments() {
113   unsigned MinColumn = 0;
114   unsigned MaxColumn = UINT_MAX;
115   unsigned StartOfSequence = 0;
116   bool BreakBeforeNext = false;
117   unsigned Newlines = 0;
118   for (unsigned i = 0, e = Changes.size(); i != e; ++i) {
119     unsigned ChangeMinColumn = Changes[i].StartOfTokenColumn;
120     // FIXME: Correctly handle ChangeMaxColumn in PP directives.
121     unsigned ChangeMaxColumn = Style.ColumnLimit - Changes[i].TokenLength;
122     Newlines += Changes[i].NewlinesBefore;
123     if (Changes[i].IsTrailingComment) {
124       bool WasAlignedWithStartOfNextLine =
125           // A comment on its own line.
126           Changes[i].NewlinesBefore == 1 &&
127           // Not the last line.
128           i + 1 != e &&
129           // The start of the next token was previously aligned with
130           // the start of this comment.
131           (SourceMgr.getSpellingColumnNumber(
132                Changes[i].OriginalWhitespaceRange.getEnd()) ==
133            SourceMgr.getSpellingColumnNumber(
134                Changes[i + 1].OriginalWhitespaceRange.getEnd())) &&
135           // Which is not a comment itself.
136           Changes[i + 1].Kind != tok::comment;
137       if (BreakBeforeNext || Newlines > 1 ||
138           (ChangeMinColumn > MaxColumn || ChangeMaxColumn < MinColumn) ||
139           // Break the comment sequence if the previous line did not end
140           // in a trailing comment.
141           (Changes[i].NewlinesBefore == 1 && i > 0 &&
142            !Changes[i - 1].IsTrailingComment) ||
143           WasAlignedWithStartOfNextLine) {
144         alignTrailingComments(StartOfSequence, i, MinColumn);
145         MinColumn = ChangeMinColumn;
146         MaxColumn = ChangeMaxColumn;
147         StartOfSequence = i;
148       } else {
149         MinColumn = std::max(MinColumn, ChangeMinColumn);
150         MaxColumn = std::min(MaxColumn, ChangeMaxColumn);
151       }
152       BreakBeforeNext =
153           (i == 0) || (Changes[i].NewlinesBefore > 1) ||
154           // Never start a sequence with a comment at the beginning of
155           // the line.
156           (Changes[i].NewlinesBefore == 1 && StartOfSequence == i);
157       Newlines = 0;
158     }
159   }
160   alignTrailingComments(StartOfSequence, Changes.size(), MinColumn);
161 }
162 
163 void WhitespaceManager::alignTrailingComments(unsigned Start, unsigned End,
164                                               unsigned Column) {
165   for (unsigned i = Start; i != End; ++i) {
166     if (Changes[i].IsTrailingComment) {
167       assert(Column >= Changes[i].StartOfTokenColumn);
168       Changes[i].Spaces += Column - Changes[i].StartOfTokenColumn;
169       Changes[i].StartOfTokenColumn = Column;
170     }
171   }
172 }
173 
174 void WhitespaceManager::alignEscapedNewlines() {
175   unsigned MaxEndOfLine = 0;
176   unsigned StartOfMacro = 0;
177   for (unsigned i = 1, e = Changes.size(); i < e; ++i) {
178     Change &C = Changes[i];
179     if (C.NewlinesBefore > 0) {
180       if (C.ContinuesPPDirective) {
181         if (Style.AlignEscapedNewlinesLeft)
182           MaxEndOfLine = std::max(C.PreviousEndOfTokenColumn + 2, MaxEndOfLine);
183         else
184           MaxEndOfLine = Style.ColumnLimit;
185       } else {
186         alignEscapedNewlines(StartOfMacro + 1, i, MaxEndOfLine);
187         MaxEndOfLine = 0;
188         StartOfMacro = i;
189       }
190     }
191   }
192   alignEscapedNewlines(StartOfMacro + 1, Changes.size(), MaxEndOfLine);
193 }
194 
195 void WhitespaceManager::alignEscapedNewlines(unsigned Start, unsigned End,
196                                              unsigned Column) {
197   for (unsigned i = Start; i < End; ++i) {
198     Change &C = Changes[i];
199     if (C.NewlinesBefore > 0) {
200       assert(C.ContinuesPPDirective);
201       if (C.PreviousEndOfTokenColumn + 1 > Column)
202         C.EscapedNewlineColumn = 0;
203       else
204         C.EscapedNewlineColumn = Column;
205     }
206   }
207 }
208 
209 void WhitespaceManager::generateChanges() {
210   for (unsigned i = 0, e = Changes.size(); i != e; ++i) {
211     const Change &C = Changes[i];
212     if (C.CreateReplacement) {
213       std::string ReplacementText =
214           C.PreviousLinePostfix +
215           (C.ContinuesPPDirective
216                ? getNewlineText(C.NewlinesBefore, C.Spaces,
217                                 C.PreviousEndOfTokenColumn,
218                                 C.EscapedNewlineColumn)
219                : getNewlineText(C.NewlinesBefore, C.Spaces)) +
220           C.CurrentLinePrefix;
221       storeReplacement(C.OriginalWhitespaceRange, ReplacementText);
222     }
223   }
224 }
225 
226 void WhitespaceManager::storeReplacement(const SourceRange &Range,
227                                          StringRef Text) {
228   unsigned WhitespaceLength = SourceMgr.getFileOffset(Range.getEnd()) -
229                               SourceMgr.getFileOffset(Range.getBegin());
230   // Don't create a replacement, if it does not change anything.
231   if (StringRef(SourceMgr.getCharacterData(Range.getBegin()),
232                 WhitespaceLength) ==
233       Text)
234     return;
235   Replaces.insert(tooling::Replacement(
236       SourceMgr, CharSourceRange::getCharRange(Range), Text));
237 }
238 
239 std::string WhitespaceManager::getNewlineText(unsigned Newlines,
240                                               unsigned Spaces) {
241   return std::string(Newlines, '\n') + getIndentText(Spaces);
242 }
243 
244 std::string WhitespaceManager::getNewlineText(unsigned Newlines,
245                                               unsigned Spaces,
246                                               unsigned PreviousEndOfTokenColumn,
247                                               unsigned EscapedNewlineColumn) {
248   std::string NewlineText;
249   if (Newlines > 0) {
250     unsigned Offset =
251         std::min<int>(EscapedNewlineColumn - 1, PreviousEndOfTokenColumn);
252     for (unsigned i = 0; i < Newlines; ++i) {
253       NewlineText += std::string(EscapedNewlineColumn - Offset - 1, ' ');
254       NewlineText += "\\\n";
255       Offset = 0;
256     }
257   }
258   return NewlineText + getIndentText(Spaces);
259 }
260 
261 std::string WhitespaceManager::getIndentText(unsigned Spaces) {
262   if (!Style.UseTab)
263     return std::string(Spaces, ' ');
264 
265   return std::string(Spaces / Style.IndentWidth, '\t') +
266          std::string(Spaces % Style.IndentWidth, ' ');
267 }
268 
269 } // namespace format
270 } // namespace clang
271