1 //===--- WhitespaceManager.cpp - Format C++ code --------------------------===//
3 // The LLVM Compiler Infrastructure
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
8 //===----------------------------------------------------------------------===//
11 /// \brief This file implements WhitespaceManager class.
13 //===----------------------------------------------------------------------===//
15 #include "WhitespaceManager.h"
16 #include "llvm/ADT/STLExtras.h"
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());
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),
42 void WhitespaceManager::reset() {
47 void WhitespaceManager::replaceWhitespace(FormatToken &Tok, unsigned Newlines,
48 unsigned IndentLevel, unsigned Spaces,
49 unsigned StartOfTokenColumn,
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));
59 void WhitespaceManager::addUntouchableToken(const FormatToken &Tok,
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));
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) {
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));
89 const tooling::Replacements &WhitespaceManager::generateReplacements() {
93 std::sort(Changes.begin(), Changes.end(), Change::IsBeforeInFile(SourceMgr));
94 calculateLineBreakInformation();
95 alignTrailingComments();
96 alignEscapedNewlines();
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();
114 Changes[i].PreviousEndOfTokenColumn =
115 Changes[i - 1].StartOfTokenColumn + Changes[i - 1].TokenLength;
117 Changes[i - 1].IsTrailingComment =
118 (Changes[i].NewlinesBefore > 0 || Changes[i].Kind == tok::eof) &&
119 Changes[i - 1].Kind == tok::comment;
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;
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()));
159 if (!Style.AlignTrailingComments || FollowsRBraceInColumn0) {
160 alignTrailingComments(StartOfSequence, i, MinColumn);
161 MinColumn = ChangeMinColumn;
162 MaxColumn = ChangeMinColumn;
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;
176 MinColumn = std::max(MinColumn, ChangeMinColumn);
177 MaxColumn = std::min(MaxColumn, ChangeMaxColumn);
180 (i == 0) || (Changes[i].NewlinesBefore > 1) ||
181 // Never start a sequence with a comment at the beginning of
183 (Changes[i].NewlinesBefore == 1 && StartOfSequence == i);
187 alignTrailingComments(StartOfSequence, Changes.size(), MinColumn);
190 void WhitespaceManager::alignTrailingComments(unsigned Start, unsigned End,
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 Changes[i].StartOfTokenColumn = Column;
201 void WhitespaceManager::alignEscapedNewlines() {
202 unsigned MaxEndOfLine =
203 Style.AlignEscapedNewlinesLeft ? 0 : Style.ColumnLimit;
204 unsigned StartOfMacro = 0;
205 for (unsigned i = 1, e = Changes.size(); i < e; ++i) {
206 Change &C = Changes[i];
207 if (C.NewlinesBefore > 0) {
208 if (C.ContinuesPPDirective) {
209 MaxEndOfLine = std::max(C.PreviousEndOfTokenColumn + 2, MaxEndOfLine);
211 alignEscapedNewlines(StartOfMacro + 1, i, MaxEndOfLine);
212 MaxEndOfLine = Style.AlignEscapedNewlinesLeft ? 0 : Style.ColumnLimit;
217 alignEscapedNewlines(StartOfMacro + 1, Changes.size(), MaxEndOfLine);
220 void WhitespaceManager::alignEscapedNewlines(unsigned Start, unsigned End,
222 for (unsigned i = Start; i < End; ++i) {
223 Change &C = Changes[i];
224 if (C.NewlinesBefore > 0) {
225 assert(C.ContinuesPPDirective);
226 if (C.PreviousEndOfTokenColumn + 1 > Column)
227 C.EscapedNewlineColumn = 0;
229 C.EscapedNewlineColumn = Column;
234 void WhitespaceManager::generateChanges() {
235 for (unsigned i = 0, e = Changes.size(); i != e; ++i) {
236 const Change &C = Changes[i];
237 if (C.CreateReplacement) {
238 std::string ReplacementText = C.PreviousLinePostfix;
239 if (C.ContinuesPPDirective)
240 appendNewlineText(ReplacementText, C.NewlinesBefore,
241 C.PreviousEndOfTokenColumn, C.EscapedNewlineColumn);
243 appendNewlineText(ReplacementText, C.NewlinesBefore);
244 appendIndentText(ReplacementText, C.IndentLevel, C.Spaces,
245 C.StartOfTokenColumn - C.Spaces);
246 ReplacementText.append(C.CurrentLinePrefix);
247 storeReplacement(C.OriginalWhitespaceRange, ReplacementText);
252 void WhitespaceManager::storeReplacement(const SourceRange &Range,
254 unsigned WhitespaceLength = SourceMgr.getFileOffset(Range.getEnd()) -
255 SourceMgr.getFileOffset(Range.getBegin());
256 // Don't create a replacement, if it does not change anything.
257 if (StringRef(SourceMgr.getCharacterData(Range.getBegin()),
258 WhitespaceLength) == Text)
260 Replaces.insert(tooling::Replacement(
261 SourceMgr, CharSourceRange::getCharRange(Range), Text));
264 void WhitespaceManager::appendNewlineText(std::string &Text,
266 for (unsigned i = 0; i < Newlines; ++i)
267 Text.append(UseCRLF ? "\r\n" : "\n");
270 void WhitespaceManager::appendNewlineText(std::string &Text, unsigned Newlines,
271 unsigned PreviousEndOfTokenColumn,
272 unsigned EscapedNewlineColumn) {
275 std::min<int>(EscapedNewlineColumn - 1, PreviousEndOfTokenColumn);
276 for (unsigned i = 0; i < Newlines; ++i) {
277 Text.append(std::string(EscapedNewlineColumn - Offset - 1, ' '));
278 Text.append(UseCRLF ? "\\\r\n" : "\\\n");
284 void WhitespaceManager::appendIndentText(std::string &Text,
285 unsigned IndentLevel, unsigned Spaces,
286 unsigned WhitespaceStartColumn) {
287 switch (Style.UseTab) {
288 case FormatStyle::UT_Never:
289 Text.append(std::string(Spaces, ' '));
291 case FormatStyle::UT_Always: {
292 unsigned FirstTabWidth =
293 Style.TabWidth - WhitespaceStartColumn % Style.TabWidth;
294 // Indent with tabs only when there's at least one full tab.
295 if (FirstTabWidth + Style.TabWidth <= Spaces) {
296 Spaces -= FirstTabWidth;
299 Text.append(std::string(Spaces / Style.TabWidth, '\t'));
300 Text.append(std::string(Spaces % Style.TabWidth, ' '));
303 case FormatStyle::UT_ForIndentation:
304 if (WhitespaceStartColumn == 0) {
305 unsigned Indentation = IndentLevel * Style.IndentWidth;
306 // This happens, e.g. when a line in a block comment is indented less than
308 if (Indentation > Spaces)
309 Indentation = Spaces;
310 unsigned Tabs = Indentation / Style.TabWidth;
311 Text.append(std::string(Tabs, '\t'));
312 Spaces -= Tabs * Style.TabWidth;
314 Text.append(std::string(Spaces, ' '));
319 } // namespace format