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