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       // 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 
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 
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 =
98         OriginalWhitespaceStart - 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 
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       bool WasAlignedWithStartOfNextLine =
128           // A comment on its own line.
129           Changes[i].NewlinesBefore == 1 &&
130           // Not the last line.
131           i + 1 != e &&
132           // The start of the next token was previously aligned with
133           // the start of this comment.
134           (SourceMgr.getSpellingColumnNumber(
135                Changes[i].OriginalWhitespaceRange.getEnd()) ==
136            SourceMgr.getSpellingColumnNumber(
137                Changes[i + 1].OriginalWhitespaceRange.getEnd())) &&
138           // Which is not a comment itself.
139           Changes[i + 1].Kind != tok::comment;
140       if (BreakBeforeNext || Newlines > 1 ||
141           (ChangeMinColumn > MaxColumn || ChangeMaxColumn < MinColumn) ||
142           // Break the comment sequence if the previous line did not end
143           // in a trailing comment.
144           (Changes[i].NewlinesBefore == 1 && i > 0 &&
145            !Changes[i - 1].IsTrailingComment) ||
146           WasAlignedWithStartOfNextLine) {
147         alignTrailingComments(StartOfSequence, i, MinColumn);
148         MinColumn = ChangeMinColumn;
149         MaxColumn = ChangeMaxColumn;
150         StartOfSequence = i;
151       } else {
152         MinColumn = std::max(MinColumn, ChangeMinColumn);
153         MaxColumn = std::min(MaxColumn, ChangeMaxColumn);
154       }
155       BreakBeforeNext =
156           (i == 0) || (Changes[i].NewlinesBefore > 1) ||
157           // Never start a sequence with a comment at the beginning of
158           // the line.
159           (Changes[i].NewlinesBefore == 1 && StartOfSequence == i);
160       Newlines = 0;
161     }
162   }
163   alignTrailingComments(StartOfSequence, Changes.size(), MinColumn);
164 }
165 
166 void WhitespaceManager::alignTrailingComments(unsigned Start, unsigned End,
167                                               unsigned Column) {
168   for (unsigned i = Start; i != End; ++i) {
169     if (Changes[i].IsTrailingComment) {
170       assert(Column >= Changes[i].StartOfTokenColumn);
171       Changes[i].Spaces += Column - Changes[i].StartOfTokenColumn;
172       Changes[i].StartOfTokenColumn = Column;
173     }
174   }
175 }
176 
177 void WhitespaceManager::alignEscapedNewlines() {
178   unsigned MaxEndOfLine = 0;
179   unsigned StartOfMacro = 0;
180   for (unsigned i = 1, e = Changes.size(); i < e; ++i) {
181     Change &C = Changes[i];
182     if (C.NewlinesBefore > 0) {
183       if (C.ContinuesPPDirective) {
184         if (Style.AlignEscapedNewlinesLeft)
185           MaxEndOfLine = std::max(C.PreviousEndOfTokenColumn + 2, MaxEndOfLine);
186         else
187           MaxEndOfLine = Style.ColumnLimit;
188       } else {
189         alignEscapedNewlines(StartOfMacro + 1, i, MaxEndOfLine);
190         MaxEndOfLine = 0;
191         StartOfMacro = i;
192       }
193     }
194   }
195   alignEscapedNewlines(StartOfMacro + 1, Changes.size(), MaxEndOfLine);
196 }
197 
198 void WhitespaceManager::alignEscapedNewlines(unsigned Start, unsigned End,
199                                              unsigned Column) {
200   for (unsigned i = Start; i < End; ++i) {
201     Change &C = Changes[i];
202     if (C.NewlinesBefore > 0) {
203       assert(C.ContinuesPPDirective);
204       if (C.PreviousEndOfTokenColumn + 1 > Column)
205         C.EscapedNewlineColumn = 0;
206       else
207         C.EscapedNewlineColumn = Column;
208     }
209   }
210 }
211 
212 void WhitespaceManager::generateChanges() {
213   for (unsigned i = 0, e = Changes.size(); i != e; ++i) {
214     const Change &C = Changes[i];
215     if (C.CreateReplacement) {
216       std::string ReplacementText =
217           C.PreviousLinePostfix +
218           (C.ContinuesPPDirective
219                ? getNewlineText(C.NewlinesBefore, C.Spaces,
220                                 C.PreviousEndOfTokenColumn,
221                                 C.EscapedNewlineColumn)
222                : getNewlineText(C.NewlinesBefore, C.Spaces)) +
223           C.CurrentLinePrefix;
224       storeReplacement(C.OriginalWhitespaceRange, ReplacementText);
225     }
226   }
227 }
228 
229 void WhitespaceManager::storeReplacement(const SourceRange &Range,
230                                          StringRef Text) {
231   unsigned WhitespaceLength = SourceMgr.getFileOffset(Range.getEnd()) -
232                               SourceMgr.getFileOffset(Range.getBegin());
233   // Don't create a replacement, if it does not change anything.
234   if (StringRef(SourceMgr.getCharacterData(Range.getBegin()),
235                 WhitespaceLength) ==
236       Text)
237     return;
238   Replaces.insert(tooling::Replacement(
239       SourceMgr, CharSourceRange::getCharRange(Range), Text));
240 }
241 
242 std::string WhitespaceManager::getNewlineText(unsigned Newlines,
243                                               unsigned Spaces) {
244   return std::string(Newlines, '\n') + getIndentText(Spaces);
245 }
246 
247 std::string WhitespaceManager::getNewlineText(unsigned Newlines,
248                                               unsigned Spaces,
249                                               unsigned PreviousEndOfTokenColumn,
250                                               unsigned EscapedNewlineColumn) {
251   std::string NewlineText;
252   if (Newlines > 0) {
253     unsigned Offset =
254         std::min<int>(EscapedNewlineColumn - 1, PreviousEndOfTokenColumn);
255     for (unsigned i = 0; i < Newlines; ++i) {
256       NewlineText += std::string(EscapedNewlineColumn - Offset - 1, ' ');
257       NewlineText += "\\\n";
258       Offset = 0;
259     }
260   }
261   return NewlineText + getIndentText(Spaces);
262 }
263 
264 std::string WhitespaceManager::getIndentText(unsigned Spaces) {
265   if (!Style.UseTab)
266     return std::string(Spaces, ' ');
267 
268   return std::string(Spaces / Style.IndentWidth, '\t') +
269          std::string(Spaces % Style.IndentWidth, ' ');
270 }
271 
272 } // namespace format
273 } // namespace clang
274