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 WhitespaceManager::Change::IsBeforeInFile::
22 operator()(const Change &C1, const Change &C2) const {
23   return SourceMgr.isBeforeInTranslationUnit(
24       C1.OriginalWhitespaceRange.getBegin(),
25       C2.OriginalWhitespaceRange.getBegin());
26 }
27 
28 WhitespaceManager::Change::Change(const FormatToken &Tok,
29                                   bool CreateReplacement,
30                                   SourceRange OriginalWhitespaceRange,
31                                   int Spaces, unsigned StartOfTokenColumn,
32                                   unsigned NewlinesBefore,
33                                   StringRef PreviousLinePostfix,
34                                   StringRef CurrentLinePrefix,
35                                   bool ContinuesPPDirective, bool IsInsideToken)
36     : Tok(&Tok), CreateReplacement(CreateReplacement),
37       OriginalWhitespaceRange(OriginalWhitespaceRange),
38       StartOfTokenColumn(StartOfTokenColumn), NewlinesBefore(NewlinesBefore),
39       PreviousLinePostfix(PreviousLinePostfix),
40       CurrentLinePrefix(CurrentLinePrefix),
41       ContinuesPPDirective(ContinuesPPDirective), Spaces(Spaces),
42       IsInsideToken(IsInsideToken), IsTrailingComment(false), TokenLength(0),
43       PreviousEndOfTokenColumn(0), EscapedNewlineColumn(0),
44       StartOfBlockComment(nullptr), IndentationOffset(0) {}
45 
46 void WhitespaceManager::replaceWhitespace(FormatToken &Tok, unsigned Newlines,
47                                           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(Tok, /*CreateReplacement=*/true,
54                            Tok.WhitespaceRange, Spaces, StartOfTokenColumn,
55                            Newlines, "", "", InPPDirective && !Tok.IsFirst,
56                            /*IsInsideToken=*/false));
57 }
58 
59 void WhitespaceManager::addUntouchableToken(const FormatToken &Tok,
60                                             bool InPPDirective) {
61   if (Tok.Finalized)
62     return;
63   Changes.push_back(Change(Tok, /*CreateReplacement=*/false,
64                            Tok.WhitespaceRange, /*Spaces=*/0,
65                            Tok.OriginalColumn, Tok.NewlinesBefore, "", "",
66                            InPPDirective && !Tok.IsFirst,
67                            /*IsInsideToken=*/false));
68 }
69 
70 void WhitespaceManager::replaceWhitespaceInToken(
71     const FormatToken &Tok, unsigned Offset, unsigned ReplaceChars,
72     StringRef PreviousPostfix, StringRef CurrentPrefix, bool InPPDirective,
73     unsigned Newlines, int Spaces) {
74   if (Tok.Finalized)
75     return;
76   SourceLocation Start = Tok.getStartOfNonWhitespace().getLocWithOffset(Offset);
77   Changes.push_back(
78       Change(Tok, /*CreateReplacement=*/true,
79              SourceRange(Start, Start.getLocWithOffset(ReplaceChars)), Spaces,
80              std::max(0, Spaces), Newlines, PreviousPostfix, CurrentPrefix,
81              InPPDirective && !Tok.IsFirst, /*IsInsideToken=*/true));
82 }
83 
84 const tooling::Replacements &WhitespaceManager::generateReplacements() {
85   if (Changes.empty())
86     return Replaces;
87 
88   std::sort(Changes.begin(), Changes.end(), Change::IsBeforeInFile(SourceMgr));
89   calculateLineBreakInformation();
90   alignConsecutiveDeclarations();
91   alignConsecutiveAssignments();
92   alignTrailingComments();
93   alignEscapedNewlines();
94   generateChanges();
95 
96   return Replaces;
97 }
98 
99 void WhitespaceManager::calculateLineBreakInformation() {
100   Changes[0].PreviousEndOfTokenColumn = 0;
101   Change *LastOutsideTokenChange = &Changes[0];
102   for (unsigned i = 1, e = Changes.size(); i != e; ++i) {
103     unsigned OriginalWhitespaceStart =
104         SourceMgr.getFileOffset(Changes[i].OriginalWhitespaceRange.getBegin());
105     unsigned PreviousOriginalWhitespaceEnd = SourceMgr.getFileOffset(
106         Changes[i - 1].OriginalWhitespaceRange.getEnd());
107     Changes[i - 1].TokenLength = OriginalWhitespaceStart -
108                                  PreviousOriginalWhitespaceEnd +
109                                  Changes[i].PreviousLinePostfix.size() +
110                                  Changes[i - 1].CurrentLinePrefix.size();
111 
112     // If there are multiple changes in this token, sum up all the changes until
113     // the end of the line.
114     if (Changes[i - 1].IsInsideToken)
115       LastOutsideTokenChange->TokenLength +=
116           Changes[i - 1].TokenLength + Changes[i - 1].Spaces;
117     else
118       LastOutsideTokenChange = &Changes[i - 1];
119 
120     Changes[i].PreviousEndOfTokenColumn =
121         Changes[i - 1].StartOfTokenColumn + Changes[i - 1].TokenLength;
122 
123     Changes[i - 1].IsTrailingComment =
124         (Changes[i].NewlinesBefore > 0 || Changes[i].Tok->is(tok::eof) ||
125          (Changes[i].IsInsideToken && Changes[i].Tok->is(tok::comment))) &&
126         Changes[i - 1].Tok->is(tok::comment) &&
127         // FIXME: This is a dirty hack. The problem is that
128         // BreakableLineCommentSection does comment reflow changes and here is
129         // the aligning of trailing comments. Consider the case where we reflow
130         // the second line up in this example:
131         //
132         // // line 1
133         // // line 2
134         //
135         // That amounts to 2 changes by BreakableLineCommentSection:
136         //  - the first, delimited by (), for the whitespace between the tokens,
137         //  - and second, delimited by [], for the whitespace at the beginning
138         //  of the second token:
139         //
140         // // line 1(
141         // )[// ]line 2
142         //
143         // So in the end we have two changes like this:
144         //
145         // // line1()[ ]line 2
146         //
147         // Note that the OriginalWhitespaceStart of the second change is the
148         // same as the PreviousOriginalWhitespaceEnd of the first change.
149         // In this case, the below check ensures that the second change doesn't
150         // get treated as a trailing comment change here, since this might
151         // trigger additional whitespace to be wrongly inserted before "line 2"
152         // by the comment aligner here.
153         //
154         // For a proper solution we need a mechanism to say to WhitespaceManager
155         // that a particular change breaks the current sequence of trailing
156         // comments.
157         OriginalWhitespaceStart != PreviousOriginalWhitespaceEnd;
158   }
159   // FIXME: The last token is currently not always an eof token; in those
160   // cases, setting TokenLength of the last token to 0 is wrong.
161   Changes.back().TokenLength = 0;
162   Changes.back().IsTrailingComment = Changes.back().Tok->is(tok::comment);
163 
164   const WhitespaceManager::Change *LastBlockComment = nullptr;
165   for (auto &Change : Changes) {
166     // Reset the IsTrailingComment flag for changes inside of trailing comments
167     // so they don't get realigned later. Comment line breaks however still need
168     // to be aligned.
169     if (Change.IsInsideToken && Change.NewlinesBefore == 0)
170       Change.IsTrailingComment = false;
171     Change.StartOfBlockComment = nullptr;
172     Change.IndentationOffset = 0;
173     if (Change.Tok->is(tok::comment)) {
174       if (Change.Tok->is(TT_LineComment) || !Change.IsInsideToken)
175         LastBlockComment = &Change;
176       else {
177         if ((Change.StartOfBlockComment = LastBlockComment))
178           Change.IndentationOffset =
179               Change.StartOfTokenColumn -
180               Change.StartOfBlockComment->StartOfTokenColumn;
181       }
182     } else {
183       LastBlockComment = nullptr;
184     }
185   }
186 }
187 
188 // Align a single sequence of tokens, see AlignTokens below.
189 template <typename F>
190 static void
191 AlignTokenSequence(unsigned Start, unsigned End, unsigned Column, F &&Matches,
192                    SmallVector<WhitespaceManager::Change, 16> &Changes) {
193   bool FoundMatchOnLine = false;
194   int Shift = 0;
195   for (unsigned i = Start; i != End; ++i) {
196     if (Changes[i].NewlinesBefore > 0) {
197       FoundMatchOnLine = false;
198       Shift = 0;
199     }
200 
201     // If this is the first matching token to be aligned, remember by how many
202     // spaces it has to be shifted, so the rest of the changes on the line are
203     // shifted by the same amount
204     if (!FoundMatchOnLine && Matches(Changes[i])) {
205       FoundMatchOnLine = true;
206       Shift = Column - Changes[i].StartOfTokenColumn;
207       Changes[i].Spaces += Shift;
208     }
209 
210     assert(Shift >= 0);
211     Changes[i].StartOfTokenColumn += Shift;
212     if (i + 1 != Changes.size())
213       Changes[i + 1].PreviousEndOfTokenColumn += Shift;
214   }
215 }
216 
217 // Walk through all of the changes and find sequences of matching tokens to
218 // align. To do so, keep track of the lines and whether or not a matching token
219 // was found on a line. If a matching token is found, extend the current
220 // sequence. If the current line cannot be part of a sequence, e.g. because
221 // there is an empty line before it or it contains only non-matching tokens,
222 // finalize the previous sequence.
223 template <typename F>
224 static void AlignTokens(const FormatStyle &Style, F &&Matches,
225                         SmallVector<WhitespaceManager::Change, 16> &Changes) {
226   unsigned MinColumn = 0;
227   unsigned MaxColumn = UINT_MAX;
228 
229   // Line number of the start and the end of the current token sequence.
230   unsigned StartOfSequence = 0;
231   unsigned EndOfSequence = 0;
232 
233   // Keep track of the nesting level of matching tokens, i.e. the number of
234   // surrounding (), [], or {}. We will only align a sequence of matching
235   // token that share the same scope depth.
236   //
237   // FIXME: This could use FormatToken::NestingLevel information, but there is
238   // an outstanding issue wrt the brace scopes.
239   unsigned NestingLevelOfLastMatch = 0;
240   unsigned NestingLevel = 0;
241 
242   // Keep track of the number of commas before the matching tokens, we will only
243   // align a sequence of matching tokens if they are preceded by the same number
244   // of commas.
245   unsigned CommasBeforeLastMatch = 0;
246   unsigned CommasBeforeMatch = 0;
247 
248   // Whether a matching token has been found on the current line.
249   bool FoundMatchOnLine = false;
250 
251   // Aligns a sequence of matching tokens, on the MinColumn column.
252   //
253   // Sequences start from the first matching token to align, and end at the
254   // first token of the first line that doesn't need to be aligned.
255   //
256   // We need to adjust the StartOfTokenColumn of each Change that is on a line
257   // containing any matching token to be aligned and located after such token.
258   auto AlignCurrentSequence = [&] {
259     if (StartOfSequence > 0 && StartOfSequence < EndOfSequence)
260       AlignTokenSequence(StartOfSequence, EndOfSequence, MinColumn, Matches,
261                          Changes);
262     MinColumn = 0;
263     MaxColumn = UINT_MAX;
264     StartOfSequence = 0;
265     EndOfSequence = 0;
266   };
267 
268   for (unsigned i = 0, e = Changes.size(); i != e; ++i) {
269     if (Changes[i].NewlinesBefore != 0) {
270       CommasBeforeMatch = 0;
271       EndOfSequence = i;
272       // If there is a blank line, or if the last line didn't contain any
273       // matching token, the sequence ends here.
274       if (Changes[i].NewlinesBefore > 1 || !FoundMatchOnLine)
275         AlignCurrentSequence();
276 
277       FoundMatchOnLine = false;
278     }
279 
280     if (Changes[i].Tok->is(tok::comma)) {
281       ++CommasBeforeMatch;
282     } else if (Changes[i].Tok->isOneOf(tok::r_brace, tok::r_paren,
283                                        tok::r_square)) {
284       --NestingLevel;
285     } else if (Changes[i].Tok->isOneOf(tok::l_brace, tok::l_paren,
286                                        tok::l_square)) {
287       // We want sequences to skip over child scopes if possible, but not the
288       // other way around.
289       NestingLevelOfLastMatch = std::min(NestingLevelOfLastMatch, NestingLevel);
290       ++NestingLevel;
291     }
292 
293     if (!Matches(Changes[i]))
294       continue;
295 
296     // If there is more than one matching token per line, or if the number of
297     // preceding commas, or the scope depth, do not match anymore, end the
298     // sequence.
299     if (FoundMatchOnLine || CommasBeforeMatch != CommasBeforeLastMatch ||
300         NestingLevel != NestingLevelOfLastMatch)
301       AlignCurrentSequence();
302 
303     CommasBeforeLastMatch = CommasBeforeMatch;
304     NestingLevelOfLastMatch = NestingLevel;
305     FoundMatchOnLine = true;
306 
307     if (StartOfSequence == 0)
308       StartOfSequence = i;
309 
310     unsigned ChangeMinColumn = Changes[i].StartOfTokenColumn;
311     int LineLengthAfter = -Changes[i].Spaces;
312     for (unsigned j = i; j != e && Changes[j].NewlinesBefore == 0; ++j)
313       LineLengthAfter += Changes[j].Spaces + Changes[j].TokenLength;
314     unsigned ChangeMaxColumn = Style.ColumnLimit - LineLengthAfter;
315 
316     // If we are restricted by the maximum column width, end the sequence.
317     if (ChangeMinColumn > MaxColumn || ChangeMaxColumn < MinColumn ||
318         CommasBeforeLastMatch != CommasBeforeMatch) {
319       AlignCurrentSequence();
320       StartOfSequence = i;
321     }
322 
323     MinColumn = std::max(MinColumn, ChangeMinColumn);
324     MaxColumn = std::min(MaxColumn, ChangeMaxColumn);
325   }
326 
327   EndOfSequence = Changes.size();
328   AlignCurrentSequence();
329 }
330 
331 void WhitespaceManager::alignConsecutiveAssignments() {
332   if (!Style.AlignConsecutiveAssignments)
333     return;
334 
335   AlignTokens(Style,
336               [&](const Change &C) {
337                 // Do not align on equal signs that are first on a line.
338                 if (C.NewlinesBefore > 0)
339                   return false;
340 
341                 // Do not align on equal signs that are last on a line.
342                 if (&C != &Changes.back() && (&C + 1)->NewlinesBefore > 0)
343                   return false;
344 
345                 return C.Tok->is(tok::equal);
346               },
347               Changes);
348 }
349 
350 void WhitespaceManager::alignConsecutiveDeclarations() {
351   if (!Style.AlignConsecutiveDeclarations)
352     return;
353 
354   // FIXME: Currently we don't handle properly the PointerAlignment: Right
355   // The * and & are not aligned and are left dangling. Something has to be done
356   // about it, but it raises the question of alignment of code like:
357   //   const char* const* v1;
358   //   float const* v2;
359   //   SomeVeryLongType const& v3;
360 
361   AlignTokens(Style,
362               [](Change const &C) {
363                 return C.Tok->isOneOf(TT_StartOfName,
364                                       TT_FunctionDeclarationName);
365               },
366               Changes);
367 }
368 
369 void WhitespaceManager::alignTrailingComments() {
370   unsigned MinColumn = 0;
371   unsigned MaxColumn = UINT_MAX;
372   unsigned StartOfSequence = 0;
373   bool BreakBeforeNext = false;
374   unsigned Newlines = 0;
375   for (unsigned i = 0, e = Changes.size(); i != e; ++i) {
376     if (Changes[i].StartOfBlockComment)
377       continue;
378     Newlines += Changes[i].NewlinesBefore;
379     if (!Changes[i].IsTrailingComment)
380       continue;
381 
382     unsigned ChangeMinColumn = Changes[i].StartOfTokenColumn;
383     unsigned ChangeMaxColumn = Style.ColumnLimit - Changes[i].TokenLength;
384 
385     // If we don't create a replacement for this change, we have to consider
386     // it to be immovable.
387     if (!Changes[i].CreateReplacement)
388       ChangeMaxColumn = ChangeMinColumn;
389 
390     if (i + 1 != e && Changes[i + 1].ContinuesPPDirective)
391       ChangeMaxColumn -= 2;
392     // If this comment follows an } in column 0, it probably documents the
393     // closing of a namespace and we don't want to align it.
394     bool FollowsRBraceInColumn0 = i > 0 && Changes[i].NewlinesBefore == 0 &&
395                                   Changes[i - 1].Tok->is(tok::r_brace) &&
396                                   Changes[i - 1].StartOfTokenColumn == 0;
397     bool WasAlignedWithStartOfNextLine = false;
398     if (Changes[i].NewlinesBefore == 1) { // A comment on its own line.
399       unsigned CommentColumn = SourceMgr.getSpellingColumnNumber(
400           Changes[i].OriginalWhitespaceRange.getEnd());
401       for (unsigned j = i + 1; j != e; ++j) {
402         if (Changes[j].Tok->is(tok::comment))
403           continue;
404 
405         unsigned NextColumn = SourceMgr.getSpellingColumnNumber(
406             Changes[j].OriginalWhitespaceRange.getEnd());
407         // The start of the next token was previously aligned with the
408         // start of this comment.
409         WasAlignedWithStartOfNextLine =
410             CommentColumn == NextColumn ||
411             CommentColumn == NextColumn + Style.IndentWidth;
412         break;
413       }
414     }
415     if (!Style.AlignTrailingComments || FollowsRBraceInColumn0) {
416       alignTrailingComments(StartOfSequence, i, MinColumn);
417       MinColumn = ChangeMinColumn;
418       MaxColumn = ChangeMinColumn;
419       StartOfSequence = i;
420     } else if (BreakBeforeNext || Newlines > 1 ||
421                (ChangeMinColumn > MaxColumn || ChangeMaxColumn < MinColumn) ||
422                // Break the comment sequence if the previous line did not end
423                // in a trailing comment.
424                (Changes[i].NewlinesBefore == 1 && i > 0 &&
425                 !Changes[i - 1].IsTrailingComment) ||
426                WasAlignedWithStartOfNextLine) {
427       alignTrailingComments(StartOfSequence, i, MinColumn);
428       MinColumn = ChangeMinColumn;
429       MaxColumn = ChangeMaxColumn;
430       StartOfSequence = i;
431     } else {
432       MinColumn = std::max(MinColumn, ChangeMinColumn);
433       MaxColumn = std::min(MaxColumn, ChangeMaxColumn);
434     }
435     BreakBeforeNext =
436         (i == 0) || (Changes[i].NewlinesBefore > 1) ||
437         // Never start a sequence with a comment at the beginning of
438         // the line.
439         (Changes[i].NewlinesBefore == 1 && StartOfSequence == i);
440     Newlines = 0;
441   }
442   alignTrailingComments(StartOfSequence, Changes.size(), MinColumn);
443 }
444 
445 void WhitespaceManager::alignTrailingComments(unsigned Start, unsigned End,
446                                               unsigned Column) {
447   for (unsigned i = Start; i != End; ++i) {
448     int Shift = 0;
449     if (Changes[i].IsTrailingComment) {
450       Shift = Column - Changes[i].StartOfTokenColumn;
451     }
452     if (Changes[i].StartOfBlockComment) {
453       Shift = Changes[i].IndentationOffset +
454               Changes[i].StartOfBlockComment->StartOfTokenColumn -
455               Changes[i].StartOfTokenColumn;
456     }
457     assert(Shift >= 0);
458     Changes[i].Spaces += Shift;
459     if (i + 1 != Changes.size())
460       Changes[i + 1].PreviousEndOfTokenColumn += Shift;
461     Changes[i].StartOfTokenColumn += Shift;
462   }
463 }
464 
465 void WhitespaceManager::alignEscapedNewlines() {
466   unsigned MaxEndOfLine =
467       Style.AlignEscapedNewlinesLeft ? 0 : Style.ColumnLimit;
468   unsigned StartOfMacro = 0;
469   for (unsigned i = 1, e = Changes.size(); i < e; ++i) {
470     Change &C = Changes[i];
471     if (C.NewlinesBefore > 0) {
472       if (C.ContinuesPPDirective) {
473         MaxEndOfLine = std::max(C.PreviousEndOfTokenColumn + 2, MaxEndOfLine);
474       } else {
475         alignEscapedNewlines(StartOfMacro + 1, i, MaxEndOfLine);
476         MaxEndOfLine = Style.AlignEscapedNewlinesLeft ? 0 : Style.ColumnLimit;
477         StartOfMacro = i;
478       }
479     }
480   }
481   alignEscapedNewlines(StartOfMacro + 1, Changes.size(), MaxEndOfLine);
482 }
483 
484 void WhitespaceManager::alignEscapedNewlines(unsigned Start, unsigned End,
485                                              unsigned Column) {
486   for (unsigned i = Start; i < End; ++i) {
487     Change &C = Changes[i];
488     if (C.NewlinesBefore > 0) {
489       assert(C.ContinuesPPDirective);
490       if (C.PreviousEndOfTokenColumn + 1 > Column)
491         C.EscapedNewlineColumn = 0;
492       else
493         C.EscapedNewlineColumn = Column;
494     }
495   }
496 }
497 
498 void WhitespaceManager::generateChanges() {
499   for (unsigned i = 0, e = Changes.size(); i != e; ++i) {
500     const Change &C = Changes[i];
501     if (i > 0) {
502       assert(Changes[i - 1].OriginalWhitespaceRange.getBegin() !=
503                  C.OriginalWhitespaceRange.getBegin() &&
504              "Generating two replacements for the same location");
505     }
506     if (C.CreateReplacement) {
507       std::string ReplacementText = C.PreviousLinePostfix;
508       if (C.ContinuesPPDirective)
509         appendNewlineText(ReplacementText, C.NewlinesBefore,
510                           C.PreviousEndOfTokenColumn, C.EscapedNewlineColumn);
511       else
512         appendNewlineText(ReplacementText, C.NewlinesBefore);
513       appendIndentText(ReplacementText, C.Tok->IndentLevel,
514                        std::max(0, C.Spaces),
515                        C.StartOfTokenColumn - std::max(0, C.Spaces));
516       ReplacementText.append(C.CurrentLinePrefix);
517       storeReplacement(C.OriginalWhitespaceRange, ReplacementText);
518     }
519   }
520 }
521 
522 void WhitespaceManager::storeReplacement(SourceRange Range,
523                                          StringRef Text) {
524   unsigned WhitespaceLength = SourceMgr.getFileOffset(Range.getEnd()) -
525                               SourceMgr.getFileOffset(Range.getBegin());
526   // Don't create a replacement, if it does not change anything.
527   if (StringRef(SourceMgr.getCharacterData(Range.getBegin()),
528                 WhitespaceLength) == Text)
529     return;
530   auto Err = Replaces.add(tooling::Replacement(
531       SourceMgr, CharSourceRange::getCharRange(Range), Text));
532   // FIXME: better error handling. For now, just print an error message in the
533   // release version.
534   if (Err) {
535     llvm::errs() << llvm::toString(std::move(Err)) << "\n";
536     assert(false);
537   }
538 }
539 
540 void WhitespaceManager::appendNewlineText(std::string &Text,
541                                           unsigned Newlines) {
542   for (unsigned i = 0; i < Newlines; ++i)
543     Text.append(UseCRLF ? "\r\n" : "\n");
544 }
545 
546 void WhitespaceManager::appendNewlineText(std::string &Text, unsigned Newlines,
547                                           unsigned PreviousEndOfTokenColumn,
548                                           unsigned EscapedNewlineColumn) {
549   if (Newlines > 0) {
550     unsigned Offset =
551         std::min<int>(EscapedNewlineColumn - 1, PreviousEndOfTokenColumn);
552     for (unsigned i = 0; i < Newlines; ++i) {
553       Text.append(EscapedNewlineColumn - Offset - 1, ' ');
554       Text.append(UseCRLF ? "\\\r\n" : "\\\n");
555       Offset = 0;
556     }
557   }
558 }
559 
560 void WhitespaceManager::appendIndentText(std::string &Text,
561                                          unsigned IndentLevel, unsigned Spaces,
562                                          unsigned WhitespaceStartColumn) {
563   switch (Style.UseTab) {
564   case FormatStyle::UT_Never:
565     Text.append(Spaces, ' ');
566     break;
567   case FormatStyle::UT_Always: {
568     unsigned FirstTabWidth =
569         Style.TabWidth - WhitespaceStartColumn % Style.TabWidth;
570     // Indent with tabs only when there's at least one full tab.
571     if (FirstTabWidth + Style.TabWidth <= Spaces) {
572       Spaces -= FirstTabWidth;
573       Text.append("\t");
574     }
575     Text.append(Spaces / Style.TabWidth, '\t');
576     Text.append(Spaces % Style.TabWidth, ' ');
577     break;
578   }
579   case FormatStyle::UT_ForIndentation:
580     if (WhitespaceStartColumn == 0) {
581       unsigned Indentation = IndentLevel * Style.IndentWidth;
582       // This happens, e.g. when a line in a block comment is indented less than
583       // the first one.
584       if (Indentation > Spaces)
585         Indentation = Spaces;
586       unsigned Tabs = Indentation / Style.TabWidth;
587       Text.append(Tabs, '\t');
588       Spaces -= Tabs * Style.TabWidth;
589     }
590     Text.append(Spaces, ' ');
591     break;
592   case FormatStyle::UT_ForContinuationAndIndentation:
593     if (WhitespaceStartColumn == 0) {
594       unsigned Tabs = Spaces / Style.TabWidth;
595       Text.append(Tabs, '\t');
596       Spaces -= Tabs * Style.TabWidth;
597     }
598     Text.append(Spaces, ' ');
599     break;
600   }
601 }
602 
603 } // namespace format
604 } // namespace clang
605