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 WhitespaceManager::Change::IsBeforeInFile::
22operator()(const Change &C1, const Change &C2) const {
23  return SourceMgr.isBeforeInTranslationUnit(
24      C1.OriginalWhitespaceRange.getBegin(),
25      C2.OriginalWhitespaceRange.getBegin());
26}
27
28WhitespaceManager::Change::Change(
29    bool CreateReplacement, const SourceRange &OriginalWhitespaceRange,
30    unsigned IndentLevel, int Spaces, unsigned StartOfTokenColumn,
31    unsigned NewlinesBefore, StringRef PreviousLinePostfix,
32    StringRef CurrentLinePrefix, tok::TokenKind Kind, bool ContinuesPPDirective)
33    : CreateReplacement(CreateReplacement),
34      OriginalWhitespaceRange(OriginalWhitespaceRange),
35      StartOfTokenColumn(StartOfTokenColumn), NewlinesBefore(NewlinesBefore),
36      PreviousLinePostfix(PreviousLinePostfix),
37      CurrentLinePrefix(CurrentLinePrefix), Kind(Kind),
38      ContinuesPPDirective(ContinuesPPDirective), IndentLevel(IndentLevel),
39      Spaces(Spaces) {}
40
41void WhitespaceManager::reset() {
42  Changes.clear();
43  Replaces.clear();
44}
45
46void WhitespaceManager::replaceWhitespace(FormatToken &Tok, unsigned Newlines,
47                                          unsigned IndentLevel, unsigned Spaces,
48                                          unsigned StartOfTokenColumn,
49                                          bool InPPDirective) {
50  if (Tok.Finalized)
51    return;
52  Tok.Decision = (Newlines > 0) ? FD_Break : FD_Continue;
53  Changes.push_back(Change(true, Tok.WhitespaceRange, IndentLevel, Spaces,
54                           StartOfTokenColumn, Newlines, "", "",
55                           Tok.Tok.getKind(), InPPDirective && !Tok.IsFirst));
56}
57
58void WhitespaceManager::addUntouchableToken(const FormatToken &Tok,
59                                            bool InPPDirective) {
60  if (Tok.Finalized)
61    return;
62  Changes.push_back(Change(false, Tok.WhitespaceRange, /*IndentLevel=*/0,
63                           /*Spaces=*/0, Tok.OriginalColumn, Tok.NewlinesBefore,
64                           "", "", Tok.Tok.getKind(),
65                           InPPDirective && !Tok.IsFirst));
66}
67
68void WhitespaceManager::replaceWhitespaceInToken(
69    const FormatToken &Tok, unsigned Offset, unsigned ReplaceChars,
70    StringRef PreviousPostfix, StringRef CurrentPrefix, bool InPPDirective,
71    unsigned Newlines, unsigned IndentLevel, int Spaces) {
72  if (Tok.Finalized)
73    return;
74  SourceLocation Start = Tok.getStartOfNonWhitespace().getLocWithOffset(Offset);
75  Changes.push_back(Change(
76      true, SourceRange(Start, Start.getLocWithOffset(ReplaceChars)),
77      IndentLevel, Spaces, std::max(0, Spaces), Newlines, PreviousPostfix,
78      CurrentPrefix,
79      // If we don't add a newline this change doesn't start a comment. Thus,
80      // when we align line comments, we don't need to treat this change as one.
81      // FIXME: We still need to take this change in account to properly
82      // calculate the new length of the comment and to calculate the changes
83      // for which to do the alignment when aligning comments.
84      Tok.Type == TT_LineComment && Newlines > 0 ? tok::comment : tok::unknown,
85      InPPDirective && !Tok.IsFirst));
86}
87
88const tooling::Replacements &WhitespaceManager::generateReplacements() {
89  if (Changes.empty())
90    return Replaces;
91
92  std::sort(Changes.begin(), Changes.end(), Change::IsBeforeInFile(SourceMgr));
93  calculateLineBreakInformation();
94  alignTrailingComments();
95  alignEscapedNewlines();
96  generateChanges();
97
98  return Replaces;
99}
100
101void WhitespaceManager::calculateLineBreakInformation() {
102  Changes[0].PreviousEndOfTokenColumn = 0;
103  for (unsigned i = 1, e = Changes.size(); i != e; ++i) {
104    unsigned OriginalWhitespaceStart =
105        SourceMgr.getFileOffset(Changes[i].OriginalWhitespaceRange.getBegin());
106    unsigned PreviousOriginalWhitespaceEnd = SourceMgr.getFileOffset(
107        Changes[i - 1].OriginalWhitespaceRange.getEnd());
108    Changes[i - 1].TokenLength = OriginalWhitespaceStart -
109                                 PreviousOriginalWhitespaceEnd +
110                                 Changes[i].PreviousLinePostfix.size() +
111                                 Changes[i - 1].CurrentLinePrefix.size();
112
113    Changes[i].PreviousEndOfTokenColumn =
114        Changes[i - 1].StartOfTokenColumn + Changes[i - 1].TokenLength;
115
116    Changes[i - 1].IsTrailingComment =
117        (Changes[i].NewlinesBefore > 0 || Changes[i].Kind == tok::eof) &&
118        Changes[i - 1].Kind == tok::comment;
119  }
120  // FIXME: The last token is currently not always an eof token; in those
121  // cases, setting TokenLength of the last token to 0 is wrong.
122  Changes.back().TokenLength = 0;
123  Changes.back().IsTrailingComment = Changes.back().Kind == tok::comment;
124
125  const WhitespaceManager::Change *LastBlockComment = nullptr;
126  for (auto &Change : Changes) {
127    Change.StartOfBlockComment = nullptr;
128    Change.IndentationOffset = 0;
129    if (Change.Kind == tok::comment) {
130      LastBlockComment = &Change;
131    } else if (Change.Kind == tok::unknown) {
132      if ((Change.StartOfBlockComment = LastBlockComment))
133        Change.IndentationOffset =
134            Change.StartOfTokenColumn -
135            Change.StartOfBlockComment->StartOfTokenColumn;
136    } else {
137      LastBlockComment = nullptr;
138    }
139  }
140}
141
142void WhitespaceManager::alignTrailingComments() {
143  unsigned MinColumn = 0;
144  unsigned MaxColumn = UINT_MAX;
145  unsigned StartOfSequence = 0;
146  bool BreakBeforeNext = false;
147  unsigned Newlines = 0;
148  for (unsigned i = 0, e = Changes.size(); i != e; ++i) {
149    if (Changes[i].StartOfBlockComment)
150      continue;
151    Newlines += Changes[i].NewlinesBefore;
152    if (!Changes[i].IsTrailingComment)
153      continue;
154
155    unsigned ChangeMinColumn = Changes[i].StartOfTokenColumn;
156    unsigned ChangeMaxColumn = Style.ColumnLimit - Changes[i].TokenLength;
157    if (i + 1 != e && Changes[i + 1].ContinuesPPDirective)
158      ChangeMaxColumn -= 2;
159    // If this comment follows an } in column 0, it probably documents the
160    // closing of a namespace and we don't want to align it.
161    bool FollowsRBraceInColumn0 = i > 0 && Changes[i].NewlinesBefore == 0 &&
162                                  Changes[i - 1].Kind == tok::r_brace &&
163                                  Changes[i - 1].StartOfTokenColumn == 0;
164    bool WasAlignedWithStartOfNextLine = false;
165    if (Changes[i].NewlinesBefore == 1) { // A comment on its own line.
166      for (unsigned j = i + 1; j != e; ++j) {
167        if (Changes[j].Kind != tok::comment) { // Skip over comments.
168          // The start of the next token was previously aligned with the
169          // start of this comment.
170          WasAlignedWithStartOfNextLine =
171              (SourceMgr.getSpellingColumnNumber(
172                   Changes[i].OriginalWhitespaceRange.getEnd()) ==
173               SourceMgr.getSpellingColumnNumber(
174                   Changes[j].OriginalWhitespaceRange.getEnd()));
175          break;
176        }
177      }
178    }
179    if (!Style.AlignTrailingComments || FollowsRBraceInColumn0) {
180      alignTrailingComments(StartOfSequence, i, MinColumn);
181      MinColumn = ChangeMinColumn;
182      MaxColumn = ChangeMinColumn;
183      StartOfSequence = i;
184    } else if (BreakBeforeNext || Newlines > 1 ||
185               (ChangeMinColumn > MaxColumn || ChangeMaxColumn < MinColumn) ||
186               // Break the comment sequence if the previous line did not end
187               // in a trailing comment.
188               (Changes[i].NewlinesBefore == 1 && i > 0 &&
189                !Changes[i - 1].IsTrailingComment) ||
190               WasAlignedWithStartOfNextLine) {
191      alignTrailingComments(StartOfSequence, i, MinColumn);
192      MinColumn = ChangeMinColumn;
193      MaxColumn = ChangeMaxColumn;
194      StartOfSequence = i;
195    } else {
196      MinColumn = std::max(MinColumn, ChangeMinColumn);
197      MaxColumn = std::min(MaxColumn, ChangeMaxColumn);
198    }
199    BreakBeforeNext =
200        (i == 0) || (Changes[i].NewlinesBefore > 1) ||
201        // Never start a sequence with a comment at the beginning of
202        // the line.
203        (Changes[i].NewlinesBefore == 1 && StartOfSequence == i);
204    Newlines = 0;
205  }
206  alignTrailingComments(StartOfSequence, Changes.size(), MinColumn);
207}
208
209void WhitespaceManager::alignTrailingComments(unsigned Start, unsigned End,
210                                              unsigned Column) {
211  for (unsigned i = Start; i != End; ++i) {
212    int Shift = 0;
213    if (Changes[i].IsTrailingComment) {
214      Shift = Column - Changes[i].StartOfTokenColumn;
215    }
216    if (Changes[i].StartOfBlockComment) {
217      Shift = Changes[i].IndentationOffset +
218              Changes[i].StartOfBlockComment->StartOfTokenColumn -
219              Changes[i].StartOfTokenColumn;
220    }
221    assert(Shift >= 0);
222    Changes[i].Spaces += Shift;
223    if (i + 1 != End)
224      Changes[i + 1].PreviousEndOfTokenColumn += Shift;
225    Changes[i].StartOfTokenColumn += Shift;
226  }
227}
228
229void WhitespaceManager::alignEscapedNewlines() {
230  unsigned MaxEndOfLine =
231      Style.AlignEscapedNewlinesLeft ? 0 : Style.ColumnLimit;
232  unsigned StartOfMacro = 0;
233  for (unsigned i = 1, e = Changes.size(); i < e; ++i) {
234    Change &C = Changes[i];
235    if (C.NewlinesBefore > 0) {
236      if (C.ContinuesPPDirective) {
237        MaxEndOfLine = std::max(C.PreviousEndOfTokenColumn + 2, MaxEndOfLine);
238      } else {
239        alignEscapedNewlines(StartOfMacro + 1, i, MaxEndOfLine);
240        MaxEndOfLine = Style.AlignEscapedNewlinesLeft ? 0 : Style.ColumnLimit;
241        StartOfMacro = i;
242      }
243    }
244  }
245  alignEscapedNewlines(StartOfMacro + 1, Changes.size(), MaxEndOfLine);
246}
247
248void WhitespaceManager::alignEscapedNewlines(unsigned Start, unsigned End,
249                                             unsigned Column) {
250  for (unsigned i = Start; i < End; ++i) {
251    Change &C = Changes[i];
252    if (C.NewlinesBefore > 0) {
253      assert(C.ContinuesPPDirective);
254      if (C.PreviousEndOfTokenColumn + 1 > Column)
255        C.EscapedNewlineColumn = 0;
256      else
257        C.EscapedNewlineColumn = Column;
258    }
259  }
260}
261
262void WhitespaceManager::generateChanges() {
263  for (unsigned i = 0, e = Changes.size(); i != e; ++i) {
264    const Change &C = Changes[i];
265    if (C.CreateReplacement) {
266      std::string ReplacementText = C.PreviousLinePostfix;
267      if (C.ContinuesPPDirective)
268        appendNewlineText(ReplacementText, C.NewlinesBefore,
269                          C.PreviousEndOfTokenColumn, C.EscapedNewlineColumn);
270      else
271        appendNewlineText(ReplacementText, C.NewlinesBefore);
272      appendIndentText(ReplacementText, C.IndentLevel, std::max(0, C.Spaces),
273                       C.StartOfTokenColumn - std::max(0, C.Spaces));
274      ReplacementText.append(C.CurrentLinePrefix);
275      storeReplacement(C.OriginalWhitespaceRange, ReplacementText);
276    }
277  }
278}
279
280void WhitespaceManager::storeReplacement(const SourceRange &Range,
281                                         StringRef Text) {
282  unsigned WhitespaceLength = SourceMgr.getFileOffset(Range.getEnd()) -
283                              SourceMgr.getFileOffset(Range.getBegin());
284  // Don't create a replacement, if it does not change anything.
285  if (StringRef(SourceMgr.getCharacterData(Range.getBegin()),
286                WhitespaceLength) == Text)
287    return;
288  Replaces.insert(tooling::Replacement(
289      SourceMgr, CharSourceRange::getCharRange(Range), Text));
290}
291
292void WhitespaceManager::appendNewlineText(std::string &Text,
293                                          unsigned Newlines) {
294  for (unsigned i = 0; i < Newlines; ++i)
295    Text.append(UseCRLF ? "\r\n" : "\n");
296}
297
298void WhitespaceManager::appendNewlineText(std::string &Text, unsigned Newlines,
299                                          unsigned PreviousEndOfTokenColumn,
300                                          unsigned EscapedNewlineColumn) {
301  if (Newlines > 0) {
302    unsigned Offset =
303        std::min<int>(EscapedNewlineColumn - 1, PreviousEndOfTokenColumn);
304    for (unsigned i = 0; i < Newlines; ++i) {
305      Text.append(std::string(EscapedNewlineColumn - Offset - 1, ' '));
306      Text.append(UseCRLF ? "\\\r\n" : "\\\n");
307      Offset = 0;
308    }
309  }
310}
311
312void WhitespaceManager::appendIndentText(std::string &Text,
313                                         unsigned IndentLevel, unsigned Spaces,
314                                         unsigned WhitespaceStartColumn) {
315  switch (Style.UseTab) {
316  case FormatStyle::UT_Never:
317    Text.append(std::string(Spaces, ' '));
318    break;
319  case FormatStyle::UT_Always: {
320    unsigned FirstTabWidth =
321        Style.TabWidth - WhitespaceStartColumn % Style.TabWidth;
322    // Indent with tabs only when there's at least one full tab.
323    if (FirstTabWidth + Style.TabWidth <= Spaces) {
324      Spaces -= FirstTabWidth;
325      Text.append("\t");
326    }
327    Text.append(std::string(Spaces / Style.TabWidth, '\t'));
328    Text.append(std::string(Spaces % Style.TabWidth, ' '));
329    break;
330  }
331  case FormatStyle::UT_ForIndentation:
332    if (WhitespaceStartColumn == 0) {
333      unsigned Indentation = IndentLevel * Style.IndentWidth;
334      // This happens, e.g. when a line in a block comment is indented less than
335      // the first one.
336      if (Indentation > Spaces)
337        Indentation = Spaces;
338      unsigned Tabs = Indentation / Style.TabWidth;
339      Text.append(std::string(Tabs, '\t'));
340      Spaces -= Tabs * Style.TabWidth;
341    }
342    Text.append(std::string(Spaces, ' '));
343    break;
344  }
345}
346
347} // namespace format
348} // namespace clang
349