Reland [clang-format] Fix overlapping whitespace replacements before PPDirective

If the first token of an annotated line already has a computed Newlines,
reuse it to avoid potential overlapping whitespace replacements before
preprocessor branching directives.

Fixes #62892.

Differential Revision: https://reviews.llvm.org/D151954
This commit is contained in:
Owen Pan 2023-06-14 15:06:17 -07:00
parent 1b8c7ee424
commit 441108ccba
3 changed files with 98 additions and 18 deletions

View File

@ -418,6 +418,12 @@ public:
/// and thereby e.g. leave an empty line between two function definitions. /// and thereby e.g. leave an empty line between two function definitions.
unsigned NewlinesBefore = 0; unsigned NewlinesBefore = 0;
/// The number of newlines immediately before the \c Token after formatting.
///
/// This is used to avoid overlapping whitespace replacements when \c Newlines
/// is recomputed for a finalized preprocessor branching directive.
int Newlines = -1;
/// The offset just past the last '\n' in this token's leading /// The offset just past the last '\n' in this token's leading
/// whitespace (relative to \c WhiteSpaceStart). 0 if there is no '\n'. /// whitespace (relative to \c WhiteSpaceStart). 0 if there is no '\n'.
unsigned LastNewlineOffset = 0; unsigned LastNewlineOffset = 0;

View File

@ -1418,22 +1418,13 @@ unsigned UnwrappedLineFormatter::format(
return Penalty; return Penalty;
} }
void UnwrappedLineFormatter::formatFirstToken( static auto computeNewlines(const AnnotatedLine &Line,
const AnnotatedLine &Line, const AnnotatedLine *PreviousLine, const AnnotatedLine *PreviousLine,
const AnnotatedLine *PrevPrevLine, const AnnotatedLine *PrevPrevLine,
const SmallVectorImpl<AnnotatedLine *> &Lines, unsigned Indent, const SmallVectorImpl<AnnotatedLine *> &Lines,
unsigned NewlineIndent) { const FormatStyle &Style) {
FormatToken &RootToken = *Line.First; const auto &RootToken = *Line.First;
if (RootToken.is(tok::eof)) { auto Newlines =
unsigned Newlines =
std::min(RootToken.NewlinesBefore,
Style.KeepEmptyLinesAtEOF ? Style.MaxEmptyLinesToKeep + 1 : 1);
unsigned TokenIndent = Newlines ? NewlineIndent : 0;
Whitespaces->replaceWhitespace(RootToken, Newlines, TokenIndent,
TokenIndent);
return;
}
unsigned Newlines =
std::min(RootToken.NewlinesBefore, Style.MaxEmptyLinesToKeep + 1); std::min(RootToken.NewlinesBefore, Style.MaxEmptyLinesToKeep + 1);
// Remove empty lines before "}" where applicable. // Remove empty lines before "}" where applicable.
if (RootToken.is(tok::r_brace) && if (RootToken.is(tok::r_brace) &&
@ -1512,7 +1503,32 @@ void UnwrappedLineFormatter::formatFirstToken(
} }
} }
if (Newlines) return Newlines;
}
void UnwrappedLineFormatter::formatFirstToken(
const AnnotatedLine &Line, const AnnotatedLine *PreviousLine,
const AnnotatedLine *PrevPrevLine,
const SmallVectorImpl<AnnotatedLine *> &Lines, unsigned Indent,
unsigned NewlineIndent) {
FormatToken &RootToken = *Line.First;
if (RootToken.is(tok::eof)) {
unsigned Newlines =
std::min(RootToken.NewlinesBefore,
Style.KeepEmptyLinesAtEOF ? Style.MaxEmptyLinesToKeep + 1 : 1);
unsigned TokenIndent = Newlines ? NewlineIndent : 0;
Whitespaces->replaceWhitespace(RootToken, Newlines, TokenIndent,
TokenIndent);
return;
}
if (RootToken.Newlines < 0) {
RootToken.Newlines =
computeNewlines(Line, PreviousLine, PrevPrevLine, Lines, Style);
assert(RootToken.Newlines >= 0);
}
if (RootToken.Newlines > 0)
Indent = NewlineIndent; Indent = NewlineIndent;
// Preprocessor directives get indented before the hash only if specified. In // Preprocessor directives get indented before the hash only if specified. In
@ -1524,7 +1540,7 @@ void UnwrappedLineFormatter::formatFirstToken(
Indent = 0; Indent = 0;
} }
Whitespaces->replaceWhitespace(RootToken, Newlines, Indent, Indent, Whitespaces->replaceWhitespace(RootToken, RootToken.Newlines, Indent, Indent,
/*IsAligned=*/false, /*IsAligned=*/false,
Line.InPPDirective && Line.InPPDirective &&
!RootToken.HasUnescapedNewline); !RootToken.HasUnescapedNewline);

View File

@ -12841,6 +12841,64 @@ TEST_F(FormatTest, FormatsAfterAccessModifiers) {
" void f() {}\n" " void f() {}\n"
"};\n", "};\n",
Style); Style);
verifyNoChange("struct foo {\n"
"#ifdef FOO\n"
"#else\n"
"private:\n"
"\n"
"#endif\n"
"};",
Style);
verifyFormat("struct foo {\n"
"#ifdef FOO\n"
"#else\n"
"private:\n"
"\n"
"#endif\n"
"};",
"struct foo {\n"
"#ifdef FOO\n"
"#else\n"
"private:\n"
"\n"
"\n"
"#endif\n"
"};",
Style);
verifyFormat("struct foo {\n"
"#ifdef FOO\n"
"private:\n"
"#else\n"
"#endif\n"
"};",
"struct foo {\n"
"#ifdef FOO\n"
"private:\n"
"\n"
"\n"
"#else\n"
"#endif\n"
"};",
Style);
verifyFormat("struct foo {\n"
"#if 0\n"
"#else\n"
"#endif\n"
"#ifdef FOO\n"
"private:\n"
"#endif\n"
"};",
"struct foo {\n"
"#if 0\n"
"#else\n"
"#endif\n"
"#ifdef FOO\n"
"private:\n"
"\n"
"\n"
"#endif\n"
"};",
Style);
Style.EmptyLineAfterAccessModifier = FormatStyle::ELAAMS_Always; Style.EmptyLineAfterAccessModifier = FormatStyle::ELAAMS_Always;
verifyFormat("struct foo {\n" verifyFormat("struct foo {\n"