forked from OSchip/llvm-project
[clang-format] Preserve whitespace in selected macros
Summary: https://bugs.llvm.org/show_bug.cgi?id=46383 When the c preprocessor stringizes tokens, the generated string literals are affected by the whitespace. This means clang-format can affect codegen silently, adding spaces and newlines to strings. Practically speaking, the vast majority of cases will be harmless, only affecting single identifiers or debug macros. In the interest of doing no harm in other cases though, this introduces a blacklist option 'WhitespaceSensitiveMacros', which contains a list of names of function-like macros whose contents should not be touched by clang-format, period. Clang-format can't automatically detect these without a real compile context, so users will have to specify it explicitly (it still beats clang-format off'ing at every invocation). Defaults include "STRINGIZE", "PP_STRINGIZE", and "BOOST_PP_STRINGIZE". Subscribers: kristof.beyls, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D82620
This commit is contained in:
parent
3b95d8346d
commit
0c332a7784
|
@ -2694,6 +2694,23 @@ the configuration (without a prefix: ``Auto``).
|
|||
Use tabs whenever we need to fill whitespace that spans at least from
|
||||
one tab stop to the next one.
|
||||
|
||||
**WhitespaceSensitiveMacros** (``std::vector<std::string>``)
|
||||
A vector of macros which are whitespace-sensitive and should not be touched.
|
||||
|
||||
These are expected to be macros of the form:
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
STRINGIZE(...)
|
||||
|
||||
In the .clang-format configuration file, this can be configured like:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
WhitespaceSensitiveMacros: ['STRINGIZE', 'PP_STRINGIZE']
|
||||
|
||||
For example: BOOST_PP_STRINGIZE.
|
||||
|
||||
|
||||
|
||||
.. END_FORMAT_STYLE_OPTIONS
|
||||
|
|
|
@ -1425,6 +1425,17 @@ struct FormatStyle {
|
|||
/// For example: TESTSUITE
|
||||
std::vector<std::string> NamespaceMacros;
|
||||
|
||||
/// A vector of macros which are whitespace-sensitive and shouldn't be
|
||||
/// touched.
|
||||
///
|
||||
/// These are expected to be macros of the form:
|
||||
/// \code
|
||||
/// STRINGIZE(...)
|
||||
/// \endcode
|
||||
///
|
||||
/// For example: STRINGIZE
|
||||
std::vector<std::string> WhitespaceSensitiveMacros;
|
||||
|
||||
tooling::IncludeStyle IncludeStyle;
|
||||
|
||||
/// Indent case labels one level from the switch statement.
|
||||
|
|
|
@ -599,6 +599,8 @@ template <> struct MappingTraits<FormatStyle> {
|
|||
IO.mapOptional("TypenameMacros", Style.TypenameMacros);
|
||||
IO.mapOptional("UseCRLF", Style.UseCRLF);
|
||||
IO.mapOptional("UseTab", Style.UseTab);
|
||||
IO.mapOptional("WhitespaceSensitiveMacros",
|
||||
Style.WhitespaceSensitiveMacros);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -933,6 +935,9 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
|
|||
LLVMStyle.SortUsingDeclarations = true;
|
||||
LLVMStyle.StatementMacros.push_back("Q_UNUSED");
|
||||
LLVMStyle.StatementMacros.push_back("QT_REQUIRE_VERSION");
|
||||
LLVMStyle.WhitespaceSensitiveMacros.push_back("STRINGIZE");
|
||||
LLVMStyle.WhitespaceSensitiveMacros.push_back("PP_STRINGIZE");
|
||||
LLVMStyle.WhitespaceSensitiveMacros.push_back("BOOST_PP_STRINGIZE");
|
||||
|
||||
// Defaults that differ when not C++.
|
||||
if (Language == FormatStyle::LK_TableGen) {
|
||||
|
|
|
@ -102,6 +102,7 @@ namespace format {
|
|||
TYPE(TrailingUnaryOperator) \
|
||||
TYPE(TypenameMacro) \
|
||||
TYPE(UnaryOperator) \
|
||||
TYPE(UntouchableMacroFunc) \
|
||||
TYPE(CSharpStringLiteral) \
|
||||
TYPE(CSharpNamedArgumentColon) \
|
||||
TYPE(CSharpNullable) \
|
||||
|
|
|
@ -43,6 +43,11 @@ FormatTokenLexer::FormatTokenLexer(const SourceManager &SourceMgr, FileID ID,
|
|||
Macros.insert({&IdentTable.get(TypenameMacro), TT_TypenameMacro});
|
||||
for (const std::string &NamespaceMacro : Style.NamespaceMacros)
|
||||
Macros.insert({&IdentTable.get(NamespaceMacro), TT_NamespaceMacro});
|
||||
for (const std::string &WhitespaceSensitiveMacro :
|
||||
Style.WhitespaceSensitiveMacros) {
|
||||
Macros.insert(
|
||||
{&IdentTable.get(WhitespaceSensitiveMacro), TT_UntouchableMacroFunc});
|
||||
}
|
||||
}
|
||||
|
||||
ArrayRef<FormatToken *> FormatTokenLexer::lex() {
|
||||
|
|
|
@ -160,6 +160,27 @@ private:
|
|||
return false;
|
||||
}
|
||||
|
||||
bool parseUntouchableParens() {
|
||||
while (CurrentToken) {
|
||||
CurrentToken->Finalized = true;
|
||||
switch (CurrentToken->Tok.getKind()) {
|
||||
case tok::l_paren:
|
||||
next();
|
||||
if (!parseUntouchableParens())
|
||||
return false;
|
||||
continue;
|
||||
case tok::r_paren:
|
||||
next();
|
||||
return true;
|
||||
default:
|
||||
// no-op
|
||||
break;
|
||||
}
|
||||
next();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool parseParens(bool LookForDecls = false) {
|
||||
if (!CurrentToken)
|
||||
return false;
|
||||
|
@ -171,6 +192,11 @@ private:
|
|||
Contexts.back().ColonIsForRangeExpr =
|
||||
Contexts.size() == 2 && Contexts[0].ColonIsForRangeExpr;
|
||||
|
||||
if (Left->Previous && Left->Previous->is(TT_UntouchableMacroFunc)) {
|
||||
Left->Finalized = true;
|
||||
return parseUntouchableParens();
|
||||
}
|
||||
|
||||
bool StartsObjCMethodExpr = false;
|
||||
if (FormatToken *MaybeSel = Left->Previous) {
|
||||
// @selector( starts a selector.
|
||||
|
@ -1311,7 +1337,7 @@ private:
|
|||
TT_TypenameMacro, TT_FunctionLBrace, TT_ImplicitStringLiteral,
|
||||
TT_InlineASMBrace, TT_JsFatArrow, TT_LambdaArrow, TT_NamespaceMacro,
|
||||
TT_OverloadedOperator, TT_RegexLiteral, TT_TemplateString,
|
||||
TT_ObjCStringLiteral))
|
||||
TT_ObjCStringLiteral, TT_UntouchableMacroFunc))
|
||||
CurrentToken->setType(TT_Unknown);
|
||||
CurrentToken->Role.reset();
|
||||
CurrentToken->MatchingParen = nullptr;
|
||||
|
@ -3970,7 +3996,7 @@ void TokenAnnotator::printDebugInfo(const AnnotatedLine &Line) {
|
|||
<< " C=" << Tok->CanBreakBefore
|
||||
<< " T=" << getTokenTypeName(Tok->getType())
|
||||
<< " S=" << Tok->SpacesRequiredBefore
|
||||
<< " B=" << Tok->BlockParameterCount
|
||||
<< " F=" << Tok->Finalized << " B=" << Tok->BlockParameterCount
|
||||
<< " BK=" << Tok->BlockKind << " P=" << Tok->SplitPenalty
|
||||
<< " Name=" << Tok->Tok.getName() << " L=" << Tok->TotalLength
|
||||
<< " PPK=" << Tok->PackingKind << " FakeLParens=";
|
||||
|
|
|
@ -14021,6 +14021,19 @@ TEST_F(FormatTest, ParsesConfiguration) {
|
|||
CHECK_PARSE("NamespaceMacros: [TESTSUITE, SUITE]", NamespaceMacros,
|
||||
std::vector<std::string>({"TESTSUITE", "SUITE"}));
|
||||
|
||||
Style.WhitespaceSensitiveMacros.clear();
|
||||
CHECK_PARSE("WhitespaceSensitiveMacros: [STRINGIZE]",
|
||||
WhitespaceSensitiveMacros, std::vector<std::string>{"STRINGIZE"});
|
||||
CHECK_PARSE("WhitespaceSensitiveMacros: [STRINGIZE, ASSERT]",
|
||||
WhitespaceSensitiveMacros,
|
||||
std::vector<std::string>({"STRINGIZE", "ASSERT"}));
|
||||
Style.WhitespaceSensitiveMacros.clear();
|
||||
CHECK_PARSE("WhitespaceSensitiveMacros: ['STRINGIZE']",
|
||||
WhitespaceSensitiveMacros, std::vector<std::string>{"STRINGIZE"});
|
||||
CHECK_PARSE("WhitespaceSensitiveMacros: ['STRINGIZE', 'ASSERT']",
|
||||
WhitespaceSensitiveMacros,
|
||||
std::vector<std::string>({"STRINGIZE", "ASSERT"}));
|
||||
|
||||
Style.IncludeStyle.IncludeCategories.clear();
|
||||
std::vector<tooling::IncludeStyle::IncludeCategory> ExpectedCategories = {
|
||||
{"abc/.*", 2, 0}, {".*", 1, 0}};
|
||||
|
@ -16530,6 +16543,36 @@ TEST_F(FormatTest, OperatorPassedAsAFunctionPtr) {
|
|||
verifyFormat("foo(operator, , -42);", Style);
|
||||
}
|
||||
|
||||
TEST_F(FormatTest, WhitespaceSensitiveMacros) {
|
||||
FormatStyle Style = getLLVMStyle();
|
||||
Style.WhitespaceSensitiveMacros.push_back("FOO");
|
||||
|
||||
// Don't use the helpers here, since 'mess up' will change the whitespace
|
||||
// and these are all whitespace sensitive by definition
|
||||
EXPECT_EQ("FOO(String-ized&Messy+But(: :Still)=Intentional);",
|
||||
format("FOO(String-ized&Messy+But(: :Still)=Intentional);", Style));
|
||||
EXPECT_EQ(
|
||||
"FOO(String-ized&Messy+But\\(: :Still)=Intentional);",
|
||||
format("FOO(String-ized&Messy+But\\(: :Still)=Intentional);", Style));
|
||||
EXPECT_EQ("FOO(String-ized&Messy+But,: :Still=Intentional);",
|
||||
format("FOO(String-ized&Messy+But,: :Still=Intentional);", Style));
|
||||
EXPECT_EQ("FOO(String-ized&Messy+But,: :\n"
|
||||
" Still=Intentional);",
|
||||
format("FOO(String-ized&Messy+But,: :\n"
|
||||
" Still=Intentional);",
|
||||
Style));
|
||||
Style.AlignConsecutiveAssignments = true;
|
||||
EXPECT_EQ("FOO(String-ized=&Messy+But,: :\n"
|
||||
" Still=Intentional);",
|
||||
format("FOO(String-ized=&Messy+But,: :\n"
|
||||
" Still=Intentional);",
|
||||
Style));
|
||||
|
||||
Style.ColumnLimit = 21;
|
||||
EXPECT_EQ("FOO(String-ized&Messy+But: :Still=Intentional);",
|
||||
format("FOO(String-ized&Messy+But: :Still=Intentional);", Style));
|
||||
}
|
||||
|
||||
TEST_F(FormatTest, VeryLongNamespaceCommentSplit) {
|
||||
// These tests are not in NamespaceFixer because that doesn't
|
||||
// test its interaction with line wrapping
|
||||
|
|
Loading…
Reference in New Issue