WhitespaceManager.cpp revision 263508
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
18namespace clang {
19namespace format {
20
21bool
22WhitespaceManager::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
29WhitespaceManager::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
42void WhitespaceManager::reset() {
43  Changes.clear();
44  Replaces.clear();
45}
46
47void 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
59void 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
69void 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
89const 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
102void 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
127void 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
190void 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      Changes[i].StartOfTokenColumn = Column;
197    }
198  }
199}
200
201void 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);
210      } else {
211        alignEscapedNewlines(StartOfMacro + 1, i, MaxEndOfLine);
212        MaxEndOfLine = Style.AlignEscapedNewlinesLeft ? 0 : Style.ColumnLimit;
213        StartOfMacro = i;
214      }
215    }
216  }
217  alignEscapedNewlines(StartOfMacro + 1, Changes.size(), MaxEndOfLine);
218}
219
220void WhitespaceManager::alignEscapedNewlines(unsigned Start, unsigned End,
221                                             unsigned Column) {
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;
228      else
229        C.EscapedNewlineColumn = Column;
230    }
231  }
232}
233
234void 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);
242      else
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);
248    }
249  }
250}
251
252void WhitespaceManager::storeReplacement(const SourceRange &Range,
253                                         StringRef Text) {
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)
259    return;
260  Replaces.insert(tooling::Replacement(
261      SourceMgr, CharSourceRange::getCharRange(Range), Text));
262}
263
264void WhitespaceManager::appendNewlineText(std::string &Text,
265                                          unsigned Newlines) {
266  for (unsigned i = 0; i < Newlines; ++i)
267    Text.append(UseCRLF ? "\r\n" : "\n");
268}
269
270void WhitespaceManager::appendNewlineText(std::string &Text, unsigned Newlines,
271                                          unsigned PreviousEndOfTokenColumn,
272                                          unsigned EscapedNewlineColumn) {
273  if (Newlines > 0) {
274    unsigned Offset =
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");
279      Offset = 0;
280    }
281  }
282}
283
284void 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, ' '));
290    break;
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;
297      Text.append("\t");
298    }
299    Text.append(std::string(Spaces / Style.TabWidth, '\t'));
300    Text.append(std::string(Spaces % Style.TabWidth, ' '));
301    break;
302  }
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
307      // the first one.
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;
313    }
314    Text.append(std::string(Spaces, ' '));
315    break;
316  }
317}
318
319} // namespace format
320} // namespace clang
321