Refactor clang-format's formatter.

Summary:
a) Pull out a class LevelIndentTracker whose responsibility is to keep track
   of the indent of levels across multiple annotated lines.
b) Put all responsibility for merging lines into the LineJoiner; make the
   LineJoiner iterate over the lines so we never operate on a line that might
   be merged later; this makes the interface safer to use.
c) Move formatting of the end-of-file whitespace into formatFirstToken.

Fix bugs that became obvious after the refactoring:
1. We would not format lines with offsets correctly inside nested blocks if
   only the outer expression was affected:
   int x = s({ // clang-format only this line
     class X {
       public:
    // ^ this starts at the non-modified indnent level; previously we would
    //   not fix this, now we correctly outdent it.
       void f();
     };
   });
2. We would incorrectly align comments across lines that do not have comments
   for lines with nested blocks:
   int expression; // with comment
   int x = s({
     int y; // comment
     int z; // we would incorrectly align this comment with the comment on
            // 'expression'
   });

llvm-svn: 237104
This commit is contained in:
Manuel Klimek 2015-05-12 09:23:57 +00:00
parent 108c325d6e
commit 3d3ea84a4f
3 changed files with 246 additions and 165 deletions

View File

@ -25,11 +25,134 @@ bool startsExternCBlock(const AnnotatedLine &Line) {
NextNext && NextNext->is(tok::l_brace);
}
/// \brief Tracks the indent level of \c AnnotatedLines across levels.
///
/// \c nextLine must be called for each \c AnnotatedLine, after which \c
/// getIndent() will return the indent for the last line \c nextLine was called
/// with.
/// If the line is not formatted (and thus the indent does not change), calling
/// \c adjustToUnmodifiedLine after the call to \c nextLine will cause
/// subsequent lines on the same level to be indented at the same level as the
/// given line.
class LevelIndentTracker {
public:
LevelIndentTracker(const FormatStyle &Style,
const AdditionalKeywords &Keywords, unsigned StartLevel,
int AdditionalIndent)
: Style(Style), Keywords(Keywords) {
for (unsigned i = 0; i != StartLevel; ++i)
IndentForLevel.push_back(Style.IndentWidth * i + AdditionalIndent);
}
/// \brief Returns the indent for the current line.
unsigned getIndent() const { return Indent; }
/// \brief Update the indent state given that \p Line is going to be formatted
/// next.
void nextLine(const AnnotatedLine &Line) {
Offset = getIndentOffset(*Line.First);
if (Line.InPPDirective) {
Indent = Line.Level * Style.IndentWidth;
} else {
while (IndentForLevel.size() <= Line.Level)
IndentForLevel.push_back(-1);
IndentForLevel.resize(Line.Level + 1);
Indent = getIndent(IndentForLevel, Line.Level);
}
if (static_cast<int>(Indent) + Offset >= 0)
Indent += Offset;
}
/// \brief Update the level indent to adapt to the given \p Line.
///
/// When a line is not formatted, we move the subsequent lines on the same
/// level to the same indent.
/// Note that \c nextLine must have been called before this method.
void adjustToUnmodifiedLine(const AnnotatedLine &Line) {
unsigned LevelIndent = Line.First->OriginalColumn;
if (static_cast<int>(LevelIndent) - Offset >= 0)
LevelIndent -= Offset;
if ((Line.First->isNot(tok::comment) || IndentForLevel[Line.Level] == -1) &&
!Line.InPPDirective)
IndentForLevel[Line.Level] = LevelIndent;
}
private:
/// \brief Get the offset of the line relatively to the level.
///
/// For example, 'public:' labels in classes are offset by 1 or 2
/// characters to the left from their level.
int getIndentOffset(const FormatToken &RootToken) {
if (Style.Language == FormatStyle::LK_Java ||
Style.Language == FormatStyle::LK_JavaScript)
return 0;
if (RootToken.isAccessSpecifier(false) ||
RootToken.isObjCAccessSpecifier() ||
(RootToken.is(Keywords.kw_signals) && RootToken.Next &&
RootToken.Next->is(tok::colon)))
return Style.AccessModifierOffset;
return 0;
}
/// \brief Get the indent of \p Level from \p IndentForLevel.
///
/// \p IndentForLevel must contain the indent for the level \c l
/// at \p IndentForLevel[l], or a value < 0 if the indent for
/// that level is unknown.
unsigned getIndent(ArrayRef<int> IndentForLevel, unsigned Level) {
if (IndentForLevel[Level] != -1)
return IndentForLevel[Level];
if (Level == 0)
return 0;
return getIndent(IndentForLevel, Level - 1) + Style.IndentWidth;
}
const FormatStyle &Style;
const AdditionalKeywords &Keywords;
/// \brief The indent in characters for each level.
std::vector<int> IndentForLevel;
/// \brief Offset of the current line relative to the indent level.
///
/// For example, the 'public' keywords is often indented with a negative
/// offset.
int Offset = 0;
/// \brief The current line's indent.
unsigned Indent = 0;
};
class LineJoiner {
public:
LineJoiner(const FormatStyle &Style, const AdditionalKeywords &Keywords)
: Style(Style), Keywords(Keywords) {}
LineJoiner(const FormatStyle &Style, const AdditionalKeywords &Keywords,
const SmallVectorImpl<AnnotatedLine *> &Lines)
: Style(Style), Keywords(Keywords), End(Lines.end()),
Next(Lines.begin()) {}
/// \brief Returns the next line, merging multiple lines into one if possible.
const AnnotatedLine *getNextMergedLine(bool DryRun,
LevelIndentTracker &IndentTracker) {
if (Next == End)
return nullptr;
const AnnotatedLine *Current = *Next;
IndentTracker.nextLine(*Current);
unsigned MergedLines =
tryFitMultipleLinesInOne(IndentTracker.getIndent(), Next, End);
if (MergedLines > 0 && Style.ColumnLimit == 0)
// Disallow line merging if there is a break at the start of one of the
// input lines.
for (unsigned i = 0; i < MergedLines; ++i)
if (Next[i + 1]->First->NewlinesBefore > 0)
MergedLines = 0;
if (!DryRun)
for (unsigned i = 0; i < MergedLines; ++i)
join(*Next[i], *Next[i + 1]);
Next = Next + MergedLines + 1;
return Current;
}
private:
/// \brief Calculates how many lines can be merged into 1 starting at \p I.
unsigned
tryFitMultipleLinesInOne(unsigned Indent,
@ -119,7 +242,6 @@ public:
return 0;
}
private:
unsigned
tryMergeSimplePPDirective(SmallVectorImpl<AnnotatedLine *>::const_iterator I,
SmallVectorImpl<AnnotatedLine *>::const_iterator E,
@ -304,8 +426,26 @@ private:
return false;
}
void join(AnnotatedLine &A, const AnnotatedLine &B) {
assert(!A.Last->Next);
assert(!B.First->Previous);
if (B.Affected)
A.Affected = true;
A.Last->Next = B.First;
B.First->Previous = A.Last;
B.First->CanBreakBefore = true;
unsigned LengthA = A.Last->TotalLength + B.First->SpacesRequiredBefore;
for (FormatToken *Tok = B.First; Tok; Tok = Tok->Next) {
Tok->TotalLength += LengthA;
A.Last = Tok;
}
}
const FormatStyle &Style;
const AdditionalKeywords &Keywords;
const SmallVectorImpl<AnnotatedLine*>::const_iterator End;
SmallVectorImpl<AnnotatedLine*>::const_iterator Next;
};
static void markFinalized(FormatToken *Tok) {
@ -655,7 +795,7 @@ unsigned
UnwrappedLineFormatter::format(const SmallVectorImpl<AnnotatedLine *> &Lines,
bool DryRun, int AdditionalIndent,
bool FixBadIndentation) {
LineJoiner Joiner(Style, Keywords);
LineJoiner Joiner(Style, Keywords, Lines);
// Try to look up already computed penalty in DryRun-mode.
std::pair<const SmallVectorImpl<AnnotatedLine *> *, unsigned> CacheKey(
@ -666,125 +806,77 @@ UnwrappedLineFormatter::format(const SmallVectorImpl<AnnotatedLine *> &Lines,
assert(!Lines.empty());
unsigned Penalty = 0;
std::vector<int> IndentForLevel;
for (unsigned i = 0, e = Lines[0]->Level; i != e; ++i)
IndentForLevel.push_back(Style.IndentWidth * i + AdditionalIndent);
LevelIndentTracker IndentTracker(Style, Keywords, Lines[0]->Level,
AdditionalIndent);
const AnnotatedLine *PreviousLine = nullptr;
for (SmallVectorImpl<AnnotatedLine *>::const_iterator I = Lines.begin(),
E = Lines.end();
I != E; ++I) {
const AnnotatedLine &TheLine = **I;
const FormatToken *FirstTok = TheLine.First;
int Offset = getIndentOffset(*FirstTok);
// Determine indent and try to merge multiple unwrapped lines.
unsigned Indent;
if (TheLine.InPPDirective) {
Indent = TheLine.Level * Style.IndentWidth;
} else {
while (IndentForLevel.size() <= TheLine.Level)
IndentForLevel.push_back(-1);
IndentForLevel.resize(TheLine.Level + 1);
Indent = getIndent(IndentForLevel, TheLine.Level);
}
unsigned LevelIndent = Indent;
if (static_cast<int>(Indent) + Offset >= 0)
Indent += Offset;
// Merge multiple lines if possible.
unsigned MergedLines = Joiner.tryFitMultipleLinesInOne(Indent, I, E);
if (MergedLines > 0 && Style.ColumnLimit == 0) {
// Disallow line merging if there is a break at the start of one of the
// input lines.
for (unsigned i = 0; i < MergedLines; ++i) {
if (I[i + 1]->First->NewlinesBefore > 0)
MergedLines = 0;
}
}
if (!DryRun) {
for (unsigned i = 0; i < MergedLines; ++i) {
join(*I[i], *I[i + 1]);
}
}
I += MergedLines;
const AnnotatedLine *NextLine = nullptr;
for (const AnnotatedLine *Line =
Joiner.getNextMergedLine(DryRun, IndentTracker);
Line; Line = NextLine) {
const AnnotatedLine &TheLine = *Line;
unsigned Indent = IndentTracker.getIndent();
bool FixIndentation =
FixBadIndentation && (LevelIndent != FirstTok->OriginalColumn);
FixBadIndentation && (Indent != TheLine.First->OriginalColumn);
bool ShouldFormat = TheLine.Affected || FixIndentation;
if (TheLine.First->is(tok::eof)) {
if (PreviousLine && PreviousLine->Affected && !DryRun) {
// Remove the file's trailing whitespace.
unsigned Newlines = std::min(FirstTok->NewlinesBefore, 1u);
Whitespaces->replaceWhitespace(*TheLine.First, Newlines,
/*IndentLevel=*/0, /*Spaces=*/0,
/*TargetColumn=*/0);
}
} else if (TheLine.Type != LT_Invalid && ShouldFormat) {
if (FirstTok->WhitespaceRange.isValid()) {
// We cannot format this line; if the reason is that the line had a
// parsing error, remember that.
if (ShouldFormat && TheLine.Type == LT_Invalid && IncompleteFormat)
*IncompleteFormat = true;
if (ShouldFormat && TheLine.Type != LT_Invalid) {
if (!DryRun)
formatFirstToken(*TheLine.First, PreviousLine, TheLine.Level, Indent,
TheLine.InPPDirective);
} else {
Indent = LevelIndent = FirstTok->OriginalColumn;
}
// If everything fits on a single line, just put it there.
unsigned ColumnLimit = Style.ColumnLimit;
if (I + 1 != E) {
AnnotatedLine *NextLine = I[1];
if (NextLine->InPPDirective && !NextLine->First->HasUnescapedNewline)
ColumnLimit = getColumnLimit(TheLine.InPPDirective);
}
NextLine = Joiner.getNextMergedLine(DryRun, IndentTracker);
unsigned ColumnLimit = getColumnLimit(TheLine.InPPDirective, NextLine);
bool FitsIntoOneLine =
TheLine.Last->TotalLength + Indent <= ColumnLimit ||
TheLine.Type == LT_ImportStatement;
if (TheLine.Last->TotalLength + Indent <= ColumnLimit ||
TheLine.Type == LT_ImportStatement) {
Penalty += NoLineBreakFormatter(Indenter, Whitespaces, Style, this)
.formatLine(TheLine, Indent, DryRun);
} else if (Style.ColumnLimit == 0) {
if (Style.ColumnLimit == 0)
NoColumnLimitLineFormatter(Indenter, Whitespaces, Style, this)
.formatLine(TheLine, Indent, DryRun);
} else {
else if (FitsIntoOneLine)
Penalty += NoLineBreakFormatter(Indenter, Whitespaces, Style, this)
.formatLine(TheLine, Indent, DryRun);
else
Penalty += OptimizingLineFormatter(Indenter, Whitespaces, Style, this)
.formatLine(TheLine, Indent, DryRun);
}
if (!TheLine.InPPDirective)
IndentForLevel[TheLine.Level] = LevelIndent;
} else if (TheLine.ChildrenAffected) {
} else {
// If no token in the current line is affected, we still need to format
// affected children.
if (TheLine.ChildrenAffected)
format(TheLine.Children, DryRun);
} else {
// Format the first token if necessary, and notify the WhitespaceManager
// about the unchanged whitespace.
for (FormatToken *Tok = TheLine.First; Tok; Tok = Tok->Next) {
if (Tok == TheLine.First && (Tok->NewlinesBefore > 0 || Tok->IsFirst)) {
unsigned LevelIndent = Tok->OriginalColumn;
if (!DryRun) {
// Remove trailing whitespace of the previous line.
if ((PreviousLine && PreviousLine->Affected) ||
TheLine.LeadingEmptyLinesAffected) {
formatFirstToken(*Tok, PreviousLine, TheLine.Level, LevelIndent,
TheLine.InPPDirective);
} else {
Whitespaces->addUntouchableToken(*Tok, TheLine.InPPDirective);
}
}
if (static_cast<int>(LevelIndent) - Offset >= 0)
LevelIndent -= Offset;
if ((Tok->isNot(tok::comment) ||
IndentForLevel[TheLine.Level] == -1) &&
!TheLine.InPPDirective)
IndentForLevel[TheLine.Level] = LevelIndent;
} else if (!DryRun) {
// Adapt following lines on the current indent level to the same level
// unless the current \c AnnotatedLine is not at the beginning of a line.
bool StartsNewLine =
TheLine.First->NewlinesBefore > 0 || TheLine.First->IsFirst;
if (StartsNewLine)
IndentTracker.adjustToUnmodifiedLine(TheLine);
if (!DryRun) {
bool ReformatLeadingWhitespace =
StartsNewLine && ((PreviousLine && PreviousLine->Affected) ||
TheLine.LeadingEmptyLinesAffected);
// Format the first token.
if (ReformatLeadingWhitespace)
formatFirstToken(*TheLine.First, PreviousLine, TheLine.Level,
TheLine.First->OriginalColumn,
TheLine.InPPDirective);
else
Whitespaces->addUntouchableToken(*TheLine.First,
TheLine.InPPDirective);
// Notify the WhitespaceManager about the unchanged whitespace.
for (FormatToken *Tok = TheLine.First->Next; Tok; Tok = Tok->Next)
Whitespaces->addUntouchableToken(*Tok, TheLine.InPPDirective);
}
NextLine = Joiner.getNextMergedLine(DryRun, IndentTracker);
}
}
if (TheLine.Type == LT_Invalid && ShouldFormat && IncompleteFormat)
*IncompleteFormat = true;
if (!DryRun)
markFinalized(TheLine.First);
PreviousLine = *I;
PreviousLine = &TheLine;
}
PenaltyCache[CacheKey] = Penalty;
return Penalty;
@ -795,6 +887,12 @@ void UnwrappedLineFormatter::formatFirstToken(FormatToken &RootToken,
unsigned IndentLevel,
unsigned Indent,
bool InPPDirective) {
if (RootToken.is(tok::eof)) {
unsigned Newlines = std::min(RootToken.NewlinesBefore, 1u);
Whitespaces->replaceWhitespace(RootToken, Newlines, /*IndentLevel=*/0,
/*Spaces=*/0, /*TargetColumn=*/0);
return;
}
unsigned Newlines =
std::min(RootToken.NewlinesBefore, Style.MaxEmptyLinesToKeep + 1);
// Remove empty lines before "}" where applicable.
@ -829,33 +927,17 @@ void UnwrappedLineFormatter::formatFirstToken(FormatToken &RootToken,
!RootToken.HasUnescapedNewline);
}
/// \brief Get the indent of \p Level from \p IndentForLevel.
///
/// \p IndentForLevel must contain the indent for the level \c l
/// at \p IndentForLevel[l], or a value < 0 if the indent for
/// that level is unknown.
unsigned UnwrappedLineFormatter::getIndent(ArrayRef<int> IndentForLevel,
unsigned Level) {
if (IndentForLevel[Level] != -1)
return IndentForLevel[Level];
if (Level == 0)
return 0;
return getIndent(IndentForLevel, Level - 1) + Style.IndentWidth;
}
void UnwrappedLineFormatter::join(AnnotatedLine &A, const AnnotatedLine &B) {
assert(!A.Last->Next);
assert(!B.First->Previous);
if (B.Affected)
A.Affected = true;
A.Last->Next = B.First;
B.First->Previous = A.Last;
B.First->CanBreakBefore = true;
unsigned LengthA = A.Last->TotalLength + B.First->SpacesRequiredBefore;
for (FormatToken *Tok = B.First; Tok; Tok = Tok->Next) {
Tok->TotalLength += LengthA;
A.Last = Tok;
}
unsigned
UnwrappedLineFormatter::getColumnLimit(bool InPPDirective,
const AnnotatedLine *NextLine) const {
// In preprocessor directives reserve two chars for trailing " \" if the
// next line continues the preprocessor directive.
bool ContinuesPPDirective =
InPPDirective && NextLine && NextLine->InPPDirective &&
// If there is an unescaped newline between this line and the next, the
// next line starts a new preprocessor directive.
!NextLine->First->HasUnescapedNewline;
return Style.ColumnLimit - (ContinuesPPDirective ? 2 : 0);
}
} // namespace format

View File

@ -44,41 +44,15 @@ public:
bool FixBadIndentation = false);
private:
/// \brief Get the offset of the line relatively to the level.
///
/// For example, 'public:' labels in classes are offset by 1 or 2
/// characters to the left from their level.
int getIndentOffset(const FormatToken &RootToken) {
if (Style.Language == FormatStyle::LK_Java ||
Style.Language == FormatStyle::LK_JavaScript)
return 0;
if (RootToken.isAccessSpecifier(false) ||
RootToken.isObjCAccessSpecifier() ||
(RootToken.is(Keywords.kw_signals) && RootToken.Next &&
RootToken.Next->is(tok::colon)))
return Style.AccessModifierOffset;
return 0;
}
/// \brief Add a new line and the required indent before the first Token
/// of the \c UnwrappedLine if there was no structural parsing error.
void formatFirstToken(FormatToken &RootToken,
const AnnotatedLine *PreviousLine, unsigned IndentLevel,
unsigned Indent, bool InPPDirective);
/// \brief Get the indent of \p Level from \p IndentForLevel.
///
/// \p IndentForLevel must contain the indent for the level \c l
/// at \p IndentForLevel[l], or a value < 0 if the indent for
/// that level is unknown.
unsigned getIndent(ArrayRef<int> IndentForLevel, unsigned Level);
void join(AnnotatedLine &A, const AnnotatedLine &B);
unsigned getColumnLimit(bool InPPDirective) const {
// In preprocessor directives reserve two chars for trailing " \"
return Style.ColumnLimit - (InPPDirective ? 2 : 0);
}
/// \brief Returns the column limit for a line, taking into account whether we
/// need an escaped newline due to a continued preprocessor directive.
unsigned getColumnLimit(bool InPPDirective, const AnnotatedLine *NextLine) const;
// Cache to store the penalty of formatting a vector of AnnotatedLines
// starting from a specific additional offset. Improves performance if there

View File

@ -2775,9 +2775,10 @@ TEST_F(FormatTest, MacroDefinitionsWithIncompleteCode) {
verifyFormat("#d, = };");
verifyFormat("#if \"a");
verifyIncompleteFormat("({\n"
"#define b }\\\n"
"#define b \\\n"
" } \\\n"
" a\n"
"a");
"a", getLLVMStyleWithColumns(15));
verifyFormat("#define A \\\n"
" { \\\n"
" {\n"
@ -3179,6 +3180,30 @@ TEST_F(FormatTest, LayoutBlockInsideParens) {
" if (a)\n"
" f();\n"
"});");
EXPECT_EQ("int longlongname; // comment\n"
"int x = f({\n"
" int x; // comment\n"
" int y; // comment\n"
"});",
format("int longlongname; // comment\n"
"int x = f({\n"
" int x; // comment\n"
" int y; // comment\n"
"});",
65, 0, getLLVMStyle()));
EXPECT_EQ("int s = f({\n"
" class X {\n"
" public:\n"
" void f();\n"
" };\n"
"});",
format("int s = f({\n"
" class X {\n"
" public:\n"
" void f();\n"
" };\n"
"});",
0, 0, getLLVMStyle()));
}
TEST_F(FormatTest, LayoutBlockInsideStatement) {