diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -3225,6 +3225,78 @@ +**QualifierAlignment** (``QualifierAlignmentStyle``) + Different ways to arrange const/volatile qualifiers. + + .. warning:: + + ``QualifierAlignment`` COULD lead to incorrect code generation. + + Possible values: + + * ``QAS_Leave`` (in configuration: ``Leave``) + Don't change specifiers/qualifier to either Left or Right alignment + (default) + + .. code-block:: c++ + + int const a; + const int *a; + + * ``QAS_Left`` (in configuration: ``Left``) + Change specifiers/qualifiers to be Left aligned. + + .. code-block:: c++ + + const int a; + const int *a; + + * ``QAS_Right`` (in configuration: ``Right``) + Change specifiers/qualifiers to be Right aligned. + + .. code-block:: c++ + + int const a; + int const *a; + + * ``QAS_Custom`` (in configuration: ``Custom``) + Change specifiers/qualifiers to be aligned based on QualfierOrder. + With: + + .. code-block:: yaml + + QualifierOrder: ['inline', 'static' , '', 'const'] + + + .. code-block:: c++ + + + int const a; + int const *a; + + + +**QualifierOrder** (``std::vector``) + The Order in which the qualifiers appear. + Order is a an array can contain any of the following + + * const + * inline + * static + * constexpr + * volatile + * restrict + * type + + Note: it MUST contain 'type'. + Items to the left of type will be aligned in the order supplied. + Items to the right of type will be aligned in the order supplied. + + + .. code-block:: yaml + + QualifierOrder: ['inline', 'static', 'type', 'const', 'volatile' ] + **RawStringFormats** (``std::vector``) Defines hints for detecting supported languages code blocks in raw strings. diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -189,6 +189,16 @@ - Option ``AllowShortEnumsOnASingleLine: false`` has been improved, it now correctly places the opening brace according to ``BraceWrapping.AfterEnum``. +- Option ``QualifierAligment`` has been added in order to auto-arrange the + positioning of specifiers/qualifiers + `const` `volatile` `static` `inline` `constexpr` `restrict` + in variable and parameter declarations to be either ``Right`` aligned + or ``Left`` aligned or ``Custom`` using ``QualifierOrder``. + +- Option ``QualifierOrder`` has been added to allow the order + `const` `volatile` `static` `inline` `constexpr` `restrict` + to be controlled relative to the `type`. + libclang -------- diff --git a/clang/docs/tools/dump_format_style.py b/clang/docs/tools/dump_format_style.py --- a/clang/docs/tools/dump_format_style.py +++ b/clang/docs/tools/dump_format_style.py @@ -117,6 +117,14 @@ endcode_match = re.match(r'^/// +\\endcode$', line) if endcode_match: return '' + + match = re.match(r'^/// \\warning$', line) + if match: + return '\n.. warning:: \n\n' + + endwarning_match = re.match(r'^/// +\\endwarning$', line) + if endwarning_match: + return '' return line[4:] + '\n' def read_options(header): diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -40,7 +40,11 @@ Success = 0, Error, Unsuitable, - BinPackTrailingCommaConflict + BinPackTrailingCommaConflict, + InvalidQualifierSpecified, + DuplicateQualifierSpecified, + MissingQualifierType, + MissingQualifierOrder }; class ParseErrorCategory final : public std::error_category { public: @@ -1819,6 +1823,67 @@ /// \endcode std::string CommentPragmas; + /// Different const/volatile qualifier alignment styles. + enum QualifierAlignmentStyle { + /// Don't change specifiers/qualifier to either Left or Right alignment + /// (default) + /// \code + /// int const a; + /// const int *a; + /// \endcode + QAS_Leave, + /// Change specifiers/qualifiers to be Left aligned. + /// \code + /// const int a; + /// const int *a; + /// \endcode + QAS_Left, + /// Change specifiers/qualifiers to be Right aligned. + /// \code + /// int const a; + /// int const *a; + /// \endcode + QAS_Right, + /// Change specifiers/qualifiers to be aligned based on QualfierOrder. + /// With: + /// \code{.yaml} + /// QualifierOrder: ['inline', 'static' , '', 'const'] + /// \endcode + /// + /// \code + /// + /// int const a; + /// int const *a; + /// \endcode + QAS_Custom + }; + + /// Different ways to arrange const/volatile qualifiers. + /// \warning + /// ``QualifierAlignment`` COULD lead to incorrect code generation. + /// \endwarning + QualifierAlignmentStyle QualifierAlignment; + + /// The Order in which the qualifiers appear. + /// Order is a an array can contain any of the following + /// + /// * const + /// * inline + /// * static + /// * constexpr + /// * volatile + /// * restrict + /// * type + /// + /// Note: it MUST contain 'type'. + /// Items to the left of type will be aligned in the order supplied. + /// Items to the right of type will be aligned in the order supplied. + /// + /// \code{.yaml} + /// QualifierOrder: ['inline', 'static', 'type', 'const', 'volatile' ] + /// \endcode + std::vector QualifierOrder; + /// Different ways to break inheritance list. enum BreakInheritanceListStyle : unsigned char { /// Break inheritance list before the colon and after the commas. @@ -3496,6 +3561,8 @@ PenaltyBreakTemplateDeclaration == R.PenaltyBreakTemplateDeclaration && PointerAlignment == R.PointerAlignment && + QualifierAlignment == R.QualifierAlignment && + QualifierOrder == R.QualifierOrder && RawStringFormats == R.RawStringFormats && ReferenceAlignment == R.ReferenceAlignment && ShortNamespaceLines == R.ShortNamespaceLines && diff --git a/clang/lib/Format/CMakeLists.txt b/clang/lib/Format/CMakeLists.txt --- a/clang/lib/Format/CMakeLists.txt +++ b/clang/lib/Format/CMakeLists.txt @@ -9,6 +9,7 @@ FormatTokenLexer.cpp MacroExpander.cpp NamespaceEndCommentsFixer.cpp + QualifierAlignmentFixer.cpp SortJavaScriptImports.cpp TokenAnalyzer.cpp TokenAnnotator.cpp diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -19,6 +19,7 @@ #include "FormatInternal.h" #include "FormatTokenLexer.h" #include "NamespaceEndCommentsFixer.h" +#include "QualifierAlignmentFixer.h" #include "SortJavaScriptImports.h" #include "TokenAnalyzer.h" #include "TokenAnnotator.h" @@ -126,6 +127,16 @@ } }; +template <> +struct ScalarEnumerationTraits { + static void enumeration(IO &IO, FormatStyle::QualifierAlignmentStyle &Value) { + IO.enumCase(Value, "Leave", FormatStyle::QAS_Leave); + IO.enumCase(Value, "Left", FormatStyle::QAS_Left); + IO.enumCase(Value, "Right", FormatStyle::QAS_Right); + IO.enumCase(Value, "Custom", FormatStyle::QAS_Custom); + } +}; + template <> struct ScalarEnumerationTraits { static void enumeration(IO &IO, FormatStyle::ShortFunctionStyle &Value) { IO.enumCase(Value, "None", FormatStyle::SFS_None); @@ -641,6 +652,17 @@ IO.mapOptional("BreakStringLiterals", Style.BreakStringLiterals); IO.mapOptional("ColumnLimit", Style.ColumnLimit); IO.mapOptional("CommentPragmas", Style.CommentPragmas); + IO.mapOptional("QualifierAlignment", Style.QualifierAlignment); + + // Default Order for Left/Right based Qualifier alignment. + if (Style.QualifierAlignment == FormatStyle::QAS_Right) { + Style.QualifierOrder = {"type", "const", "volatile"}; + } else if (Style.QualifierAlignment == FormatStyle::QAS_Left) { + Style.QualifierOrder = {"const", "volatile", "type"}; + } else if (Style.QualifierAlignment == FormatStyle::QAS_Custom) { + IO.mapOptional("QualifierOrder", Style.QualifierOrder); + } + IO.mapOptional("CompactNamespaces", Style.CompactNamespaces); IO.mapOptional("ConstructorInitializerIndentWidth", Style.ConstructorInitializerIndentWidth); @@ -905,6 +927,14 @@ return "Unsuitable"; case ParseError::BinPackTrailingCommaConflict: return "trailing comma insertion cannot be used with bin packing"; + case ParseError::InvalidQualifierSpecified: + return "Invalid qualifier specified in QualifierOrder"; + case ParseError::DuplicateQualifierSpecified: + return "Duplicate qualifier specified in QualfierOrder"; + case ParseError::MissingQualifierType: + return "Missing type in QualfierOrder"; + case ParseError::MissingQualifierOrder: + return "Missing QualfierOrder"; } llvm_unreachable("unexpected parse error"); } @@ -1078,6 +1108,10 @@ LLVMStyle.ConstructorInitializerIndentWidth = 4; LLVMStyle.ContinuationIndentWidth = 4; LLVMStyle.Cpp11BracedListStyle = true; + + // Off by default Qualifier ordering + LLVMStyle.QualifierAlignment = FormatStyle::QAS_Leave; + LLVMStyle.DeriveLineEnding = true; LLVMStyle.DerivePointerAlignment = false; LLVMStyle.EmptyLineAfterAccessModifier = FormatStyle::ELAAMS_Never; @@ -1512,6 +1546,37 @@ return true; } +ParseError validateQualifierOrder(FormatStyle *Style) { + // If its empty then it means don't do anything. + if (Style->QualifierOrder.empty()) + return ParseError::MissingQualifierOrder; + + // Ensure the list contains only currently valid qualifiers. + for (const auto &Qualifier : Style->QualifierOrder) { + if (Qualifier == "type") + continue; + auto token = + LeftRightQualifierAlignmentFixer::getTokenFromQualifier(Qualifier); + if (token == tok::identifier) + return ParseError::InvalidQualifierSpecified; + } + // Ensure the list is unqiue (no duplicates). + std::set UniqueQualifiers(Style->QualifierOrder.begin(), + Style->QualifierOrder.end()); + if (Style->QualifierOrder.size() != UniqueQualifiers.size()) { + LLVM_DEBUG(llvm::dbgs() + << "Duplicate Qualifiers " << Style->QualifierOrder.size() + << " vs " << UniqueQualifiers.size() << "\n"); + return ParseError::DuplicateQualifierSpecified; + } + + auto type = std::find(Style->QualifierOrder.begin(), + Style->QualifierOrder.end(), "type"); + if (type == Style->QualifierOrder.end()) + return ParseError::MissingQualifierType; + return ParseError::Success; +} + std::error_code parseConfiguration(llvm::MemoryBufferRef Config, FormatStyle *Style, bool AllowUnknownOptions, llvm::SourceMgr::DiagHandlerTy DiagHandler, @@ -1573,6 +1638,8 @@ // See comment on FormatStyle::TSC_Wrapped. return make_error_code(ParseError::BinPackTrailingCommaConflict); } + if (Style->QualifierAlignment != FormatStyle::QAS_Leave) + return make_error_code(validateQualifierOrder(Style)); return make_error_code(ParseError::Success); } @@ -2894,6 +2961,15 @@ AnalyzerPass; SmallVector Passes; + if (Style.isCpp() && Style.QualifierAlignment != FormatStyle::QAS_Leave) { + Passes.emplace_back([&](const Environment &Env) { + return QualifierAlignmentFixer(Env, Expanded, Code, Ranges, + FirstStartColumn, NextStartColumn, + LastStartColumn, FileName) + .process(); + }); + } + if (Style.Language == FormatStyle::LK_Cpp) { if (Style.FixNamespaceComments) Passes.emplace_back([&](const Environment &Env) { diff --git a/clang/lib/Format/QualifierAlignmentFixer.h b/clang/lib/Format/QualifierAlignmentFixer.h new file mode 100644 --- /dev/null +++ b/clang/lib/Format/QualifierAlignmentFixer.h @@ -0,0 +1,98 @@ +//===--- LeftRightQualifierAlignmentFixer.h ------------------------------*- C++ +//-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file declares LeftRightQualifierAlignmentFixer, a TokenAnalyzer that +/// enforces either east or west const depending on the style. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_FORMAT_QUALIFIERALIGNMENTFIXER_H +#define LLVM_CLANG_LIB_FORMAT_QUALIFIERALIGNMENTFIXER_H + +#include "TokenAnalyzer.h" + +namespace clang { +namespace format { + +typedef std::function( + const Environment &)> + AnalyzerPass; + +class QualifierAlignmentFixer : public TokenAnalyzer { + // Left to Right ordering requires multiple passes + SmallVector Passes; + StringRef &Code; + ArrayRef Ranges; + unsigned FirstStartColumn; + unsigned NextStartColumn; + unsigned LastStartColumn; + StringRef FileName; + +public: + QualifierAlignmentFixer(const Environment &Env, const FormatStyle &Style, + StringRef &Code, ArrayRef Ranges, + unsigned FirstStartColumn, unsigned NextStartColumn, + unsigned LastStartColumn, StringRef FileName); + + std::pair + analyze(TokenAnnotator &Annotator, + SmallVectorImpl &AnnotatedLines, + FormatTokenLexer &Tokens) override; + + static void PrepareLeftRightOrdering(const std::vector &Order, + std::vector &LeftOrder, + std::vector &RightOrder, + std::vector &Qualifiers); +}; + +class LeftRightQualifierAlignmentFixer : public TokenAnalyzer { + std::string Qualifier; + bool RightAlign; + SmallVector QualifierTokens; + std::vector ConfiguredQualifierTokens; + +public: + LeftRightQualifierAlignmentFixer( + const Environment &Env, const FormatStyle &Style, + const std::string &Qualifier, + const std::vector &ConfiguredQualifierTokens, + bool RightAlign); + + std::pair + analyze(TokenAnnotator &Annotator, + SmallVectorImpl &AnnotatedLines, + FormatTokenLexer &Tokens) override; + + static tok::TokenKind getTokenFromQualifier(const std::string &Qualifier); + + FormatToken *analyzeRight(const SourceManager &SourceMgr, + const AdditionalKeywords &Keywords, + tooling::Replacements &Fixes, FormatToken *Tok, + const std::string &Qualifier, + tok::TokenKind QualifierType); + + FormatToken *analyzeLeft(const SourceManager &SourceMgr, + const AdditionalKeywords &Keywords, + tooling::Replacements &Fixes, FormatToken *Tok, + const std::string &Qualifier, + tok::TokenKind QualifierType); + + // is the Token a simple or qualifier type + static bool isQualifierOrType(const FormatToken *Tok, + const std::vector &Qualifiers); + + // is the Token likely a Macro + static bool isPossibleMacro(const FormatToken *Tok); +}; + +} // end namespace format +} // end namespace clang + +#endif diff --git a/clang/lib/Format/QualifierAlignmentFixer.cpp b/clang/lib/Format/QualifierAlignmentFixer.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/Format/QualifierAlignmentFixer.cpp @@ -0,0 +1,455 @@ +//===--- LeftRightQualifierAlignmentFixer.cpp -------------------*- C++--*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file implements LeftRightQualifierAlignmentFixer, a TokenAnalyzer that +/// enforces either left or right const depending on the style. +/// +//===----------------------------------------------------------------------===// + +#include "QualifierAlignmentFixer.h" +#include "FormatToken.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/Regex.h" + +#include + +#define DEBUG_TYPE "format-qualifier-alignment-fixer" + +namespace clang { +namespace format { + +QualifierAlignmentFixer::QualifierAlignmentFixer( + const Environment &Env, const FormatStyle &Style, StringRef &Code, + ArrayRef Ranges, unsigned FirstStartColumn, + unsigned NextStartColumn, unsigned LastStartColumn, StringRef FileName) + : TokenAnalyzer(Env, Style), Code(Code), Ranges(Ranges), + FirstStartColumn(FirstStartColumn), NextStartColumn(NextStartColumn), + LastStartColumn(LastStartColumn), FileName(FileName) { + std::vector LeftOrder; + std::vector RightOrder; + std::vector ConfiguredQualifierTokens; + PrepareLeftRightOrdering(Style.QualifierOrder, LeftOrder, RightOrder, + ConfiguredQualifierTokens); + + // Handle the left and right Alignment Seperately + for (const auto &Qualifier : LeftOrder) { + Passes.emplace_back( + [&, Qualifier, ConfiguredQualifierTokens](const Environment &Env) { + return LeftRightQualifierAlignmentFixer(Env, Style, Qualifier, + ConfiguredQualifierTokens, + /*RightAlign=*/false) + .process(); + }); + } + for (const auto &Qualifier : RightOrder) { + Passes.emplace_back( + [&, Qualifier, ConfiguredQualifierTokens](const Environment &Env) { + return LeftRightQualifierAlignmentFixer(Env, Style, Qualifier, + ConfiguredQualifierTokens, + /*RightAlign=*/true) + .process(); + }); + } +} + +std::pair QualifierAlignmentFixer::analyze( + TokenAnnotator &Annotator, SmallVectorImpl &AnnotatedLines, + FormatTokenLexer &Tokens) { + + auto Env = + std::make_unique(Code, FileName, Ranges, FirstStartColumn, + NextStartColumn, LastStartColumn); + llvm::Optional CurrentCode = None; + tooling::Replacements Fixes; + unsigned Penalty = 0; + for (size_t I = 0, E = Passes.size(); I < E; ++I) { + std::pair PassFixes = Passes[I](*Env); + auto NewCode = applyAllReplacements( + CurrentCode ? StringRef(*CurrentCode) : Code, PassFixes.first); + if (NewCode) { + Fixes = Fixes.merge(PassFixes.first); + Penalty += PassFixes.second; + if (I + 1 < E) { + CurrentCode = std::move(*NewCode); + Env = std::make_unique( + *CurrentCode, FileName, + tooling::calculateRangesAfterReplacements(Fixes, Ranges), + FirstStartColumn, NextStartColumn, LastStartColumn); + } + } + } + return {Fixes, 0}; +} + +static void replaceToken(const SourceManager &SourceMgr, + tooling::Replacements &Fixes, + const CharSourceRange &Range, std::string NewText) { + auto Replacement = tooling::Replacement(SourceMgr, Range, NewText); + auto Err = Fixes.add(Replacement); + + if (Err) + llvm::errs() << "Error while rearranging Qualifier : " + << llvm::toString(std::move(Err)) << "\n"; +} + +static void removeToken(const SourceManager &SourceMgr, + tooling::Replacements &Fixes, + const FormatToken *First) { + auto Range = CharSourceRange::getCharRange(First->getStartOfNonWhitespace(), + First->Tok.getEndLoc()); + replaceToken(SourceMgr, Fixes, Range, ""); +} + +static void insertQualifierAfter(const SourceManager &SourceMgr, + tooling::Replacements &Fixes, + const FormatToken *First, + const std::string &Qualifier) { + FormatToken *Next = First->Next; + if (!Next) + return; + auto Range = CharSourceRange::getCharRange(Next->getStartOfNonWhitespace(), + Next->Tok.getEndLoc()); + + std::string NewText = " " + Qualifier + " "; + NewText += Next->TokenText; + replaceToken(SourceMgr, Fixes, Range, NewText); +} + +static void insertQualifierBefore(const SourceManager &SourceMgr, + tooling::Replacements &Fixes, + const FormatToken *First, + const std::string &Qualifier) { + auto Range = CharSourceRange::getCharRange(First->getStartOfNonWhitespace(), + First->Tok.getEndLoc()); + + std::string NewText = " " + Qualifier + " "; + NewText += First->TokenText; + + replaceToken(SourceMgr, Fixes, Range, NewText); +} + +static bool endsWithSpace(const std::string &s) { + if (s.empty()) { + return false; + } + return isspace(s.back()); +} + +static bool startsWithSpace(const std::string &s) { + if (s.empty()) { + return false; + } + return isspace(s.front()); +} + +static void rotateTokens(const SourceManager &SourceMgr, + tooling::Replacements &Fixes, const FormatToken *First, + const FormatToken *Last, bool Left) { + auto *End = Last; + auto *Begin = First; + if (!Left) { + End = Last->Next; + Begin = First->Next; + } + + std::string NewText; + // If we are rotating to the left we move the Last token to the front. + if (Left) { + NewText += Last->TokenText; + NewText += " "; + } + + // Then move through the other tokens. + auto *Tok = Begin; + while (Tok != End) { + if (!NewText.empty() && !endsWithSpace(NewText)) { + NewText += " "; + } + + NewText += Tok->TokenText; + Tok = Tok->Next; + } + + // If we are rotating to the right we move the first token to the back. + if (!Left) { + if (!NewText.empty() && !startsWithSpace(NewText)) { + NewText += " "; + } + NewText += First->TokenText; + } + + auto Range = CharSourceRange::getCharRange(First->getStartOfNonWhitespace(), + Last->Tok.getEndLoc()); + + replaceToken(SourceMgr, Fixes, Range, NewText); +} + +FormatToken *LeftRightQualifierAlignmentFixer::analyzeRight( + const SourceManager &SourceMgr, const AdditionalKeywords &Keywords, + tooling::Replacements &Fixes, FormatToken *Tok, + const std::string &Qualifier, tok::TokenKind QualifierType) { + // We only need to think about streams that begin with a qualifier. + if (!Tok->is(QualifierType)) + return Tok; + // Don't concern yourself if nothing follows the qualifier. + if (!Tok->Next) + return Tok; + if (LeftRightQualifierAlignmentFixer::isPossibleMacro(Tok->Next)) + return Tok; + + FormatToken *Qual = Tok->Next; + FormatToken *LastQual = Qual; + while (Qual && isQualifierOrType(Qual, ConfiguredQualifierTokens)) { + LastQual = Qual; + Qual = Qual->Next; + } + if (LastQual && Qual != LastQual) { + rotateTokens(SourceMgr, Fixes, Tok, LastQual, /*Left=*/false); + Tok = LastQual; + } else if (Tok->startsSequence(QualifierType, tok::identifier, + TT_TemplateOpener)) { + // Read from the TemplateOpener to + // TemplateCloser as in const ArrayRef a; const ArrayRef &a; + FormatToken *EndTemplate = Tok->Next->Next->MatchingParen; + if (EndTemplate) { + // Move to the end of any template class members e.g. + // `Foo::iterator`. + if (EndTemplate->startsSequence(TT_TemplateCloser, tok::coloncolon, + tok::identifier)) + EndTemplate = EndTemplate->Next->Next; + } + if (EndTemplate && EndTemplate->Next && + !EndTemplate->Next->isOneOf(tok::equal, tok::l_paren)) { + insertQualifierAfter(SourceMgr, Fixes, EndTemplate, Qualifier); + // Remove the qualifier. + removeToken(SourceMgr, Fixes, Tok); + return Tok; + } + } else if (Tok->startsSequence(QualifierType, tok::identifier)) { + FormatToken *Next = Tok->Next; + // The case `const Foo` -> `Foo const` + // The case `const Foo *` -> `Foo const *` + // The case `const Foo &` -> `Foo const &` + // The case `const Foo &&` -> `Foo const &&` + // The case `const std::Foo &&` -> `std::Foo const &&` + // The case `const std::Foo &&` -> `std::Foo const &&` + while (Next && Next->isOneOf(tok::identifier, tok::coloncolon)) { + Next = Next->Next; + } + if (Next && Next->is(TT_TemplateOpener)) { + Next = Next->MatchingParen; + // Move to the end of any template class members e.g. + // `Foo::iterator`. + if (Next && Next->startsSequence(TT_TemplateCloser, tok::coloncolon, + tok::identifier)) { + Next = Next->Next->Next; + return Tok; + } + assert(Next && "Missing template opener"); + Next = Next->Next; + } + if (Next && Next->isOneOf(tok::star, tok::amp, tok::ampamp) && + !Tok->Next->isOneOf(Keywords.kw_override, Keywords.kw_final)) { + if (Next->Previous && !Next->Previous->is(QualifierType)) { + insertQualifierAfter(SourceMgr, Fixes, Next->Previous, Qualifier); + removeToken(SourceMgr, Fixes, Tok); + } + return Next; + } + } + + return Tok; +} + +FormatToken *LeftRightQualifierAlignmentFixer::analyzeLeft( + const SourceManager &SourceMgr, const AdditionalKeywords &Keywords, + tooling::Replacements &Fixes, FormatToken *Tok, + const std::string &Qualifier, tok::TokenKind QualifierType) { + // if Tok is an identifier and possibly a macro then don't convert. + if (LeftRightQualifierAlignmentFixer::isPossibleMacro(Tok)) + return Tok; + + FormatToken *Qual = Tok; + FormatToken *LastQual = Qual; + while (Qual && isQualifierOrType(Qual, ConfiguredQualifierTokens)) { + LastQual = Qual; + Qual = Qual->Next; + if (Qual && Qual->is(QualifierType)) + break; + } + + if (!Qual) { + return Tok; + } + + if (LastQual && Qual != LastQual && Qual->is(QualifierType)) { + rotateTokens(SourceMgr, Fixes, Tok, Qual, /*Left=*/true); + Tok = Qual->Next; + } else if (Tok->startsSequence(tok::identifier, QualifierType)) { + if (Tok->Next->Next && Tok->Next->Next->isOneOf(tok::identifier, tok::star, + tok::amp, tok::ampamp)) { + // Don't swap `::iterator const` to `::const iterator`. + if (!Tok->Previous || + (Tok->Previous && !Tok->Previous->is(tok::coloncolon))) { + rotateTokens(SourceMgr, Fixes, Tok, Tok->Next, /*Left=*/true); + Tok = Tok->Next; + } + } + } + if (Tok->is(TT_TemplateOpener) && Tok->Next && + (Tok->Next->is(tok::identifier) || Tok->Next->isSimpleTypeSpecifier()) && + Tok->Next->Next && Tok->Next->Next->is(QualifierType)) { + rotateTokens(SourceMgr, Fixes, Tok->Next, Tok->Next->Next, /*Left=*/true); + } + if (Tok->startsSequence(tok::identifier) && Tok->Next) { + if (Tok->Previous && + Tok->Previous->isOneOf(tok::star, tok::ampamp, tok::amp)) { + return Tok; + } + FormatToken *Next = Tok->Next; + // The case `std::Foo const` -> `const std::Foo &&` + while (Next && Next->isOneOf(tok::identifier, tok::coloncolon)) + Next = Next->Next; + if (Next && Next->Previous && + Next->Previous->startsSequence(tok::identifier, TT_TemplateOpener)) { + // Read from to the end of the TemplateOpener to + // TemplateCloser const ArrayRef a; const ArrayRef &a; + assert(Next->MatchingParen && "Missing template closer"); + Next = Next->MatchingParen->Next; + + // Move to the end of any template class members e.g. + // `Foo::iterator`. + if (Next && Next->startsSequence(tok::coloncolon, tok::identifier)) + Next = Next->Next->Next; + if (Next && Next->is(QualifierType)) { + // Remove the const. + insertQualifierBefore(SourceMgr, Fixes, Tok, Qualifier); + removeToken(SourceMgr, Fixes, Next); + return Next; + } + } + if (Next && Next->Next && + Next->Next->isOneOf(tok::amp, tok::ampamp, tok::star)) { + if (Next->is(QualifierType)) { + // Remove the qualifier. + insertQualifierBefore(SourceMgr, Fixes, Tok, Qualifier); + removeToken(SourceMgr, Fixes, Next); + return Next; + } + } + } + return Tok; +} + +tok::TokenKind LeftRightQualifierAlignmentFixer::getTokenFromQualifier( + const std::string &Qualifier) { + // don't let 'type' be an indentifier steal typeof token + return llvm::StringSwitch(Qualifier) + .Case("type", tok::kw_typeof) + .Case("const", tok::kw_const) + .Case("volatile", tok::kw_volatile) + .Case("static", tok::kw_static) + .Case("inline", tok::kw_inline) + .Case("constexpr", tok::kw_constexpr) + .Case("restrict", tok::kw_restrict) + .Default(tok::identifier); +} + +LeftRightQualifierAlignmentFixer::LeftRightQualifierAlignmentFixer( + const Environment &Env, const FormatStyle &Style, + const std::string &Qualifier, + const std::vector &QualifierTokens, bool RightAlign) + : TokenAnalyzer(Env, Style), Qualifier(Qualifier), RightAlign(RightAlign), + ConfiguredQualifierTokens(QualifierTokens) {} + +std::pair +LeftRightQualifierAlignmentFixer::analyze( + TokenAnnotator &Annotator, SmallVectorImpl &AnnotatedLines, + FormatTokenLexer &Tokens) { + tooling::Replacements Fixes; + const AdditionalKeywords &Keywords = Tokens.getKeywords(); + const SourceManager &SourceMgr = Env.getSourceManager(); + AffectedRangeMgr.computeAffectedLines(AnnotatedLines); + + tok::TokenKind QualifierToken = getTokenFromQualifier(Qualifier); + assert(QualifierToken != tok::identifier && "Unrecognised Qualifier"); + + for (size_t I = 0, E = AnnotatedLines.size(); I != E; ++I) { + FormatToken *First = AnnotatedLines[I]->First; + const auto *Last = AnnotatedLines[I]->Last; + + for (auto *Tok = First; Tok && Tok != Last && Tok->Next; Tok = Tok->Next) { + if (Tok->is(tok::comment)) + continue; + if (RightAlign) + Tok = analyzeRight(SourceMgr, Keywords, Fixes, Tok, Qualifier, + QualifierToken); + else + Tok = analyzeLeft(SourceMgr, Keywords, Fixes, Tok, Qualifier, + QualifierToken); + } + } + return {Fixes, 0}; +} + +void QualifierAlignmentFixer::PrepareLeftRightOrdering( + const std::vector &Order, std::vector &LeftOrder, + std::vector &RightOrder, + std::vector &Qualifiers) { + + // Depending on the position of type in the order you need + // To iterate forward or backward through the order list as qualifier + // can push through each other. + auto type = std::find(Order.begin(), Order.end(), "type"); + // The Order list must define the position of "type" to signify + assert(type != Order.end() && "QualifierOrder must contain type"); + // Split the Order list by type and reverse the left side. + + bool left = true; + for (const auto &s : Order) { + if (s == "type") { + left = false; + continue; + } + + tok::TokenKind QualifierToken = + LeftRightQualifierAlignmentFixer::getTokenFromQualifier(s); + if (QualifierToken != tok::kw_typeof && QualifierToken != tok::identifier) { + Qualifiers.push_back(QualifierToken); + } + + if (left) + // Reverse the order for left aligned items. + LeftOrder.insert(LeftOrder.begin(), s); + else + RightOrder.push_back(s); + } +} + +bool LeftRightQualifierAlignmentFixer::isQualifierOrType( + const FormatToken *Tok, const std::vector &specifiedTypes) { + return Tok && (Tok->isSimpleTypeSpecifier() || Tok->is(tok::kw_auto) || + (std::find(specifiedTypes.begin(), specifiedTypes.end(), + Tok->Tok.getKind()) != specifiedTypes.end())); +} + +// If a token is an identifier and it's upper case, it could +// be a macro and hence we need to be able to ignore it. +bool LeftRightQualifierAlignmentFixer::isPossibleMacro(const FormatToken *Tok) { + if (!Tok) + return false; + if (!Tok->is(tok::identifier)) + return false; + if (Tok->TokenText.upper() == Tok->TokenText.str()) + return true; + return false; +} + +} // namespace format +} // namespace clang diff --git a/clang/tools/clang-format/ClangFormat.cpp b/clang/tools/clang-format/ClangFormat.cpp --- a/clang/tools/clang-format/ClangFormat.cpp +++ b/clang/tools/clang-format/ClangFormat.cpp @@ -19,6 +19,7 @@ #include "clang/Basic/Version.h" #include "clang/Format/Format.h" #include "clang/Rewrite/Core/Rewriter.h" +#include "llvm/ADT/StringSwitch.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/InitLLVM.h" @@ -104,6 +105,13 @@ "SortIncludes style flag"), cl::cat(ClangFormatCategory)); +static cl::opt QualifierAlignment( + "qualifier-aligment", + cl::desc( + "If set, overrides the cvqualifier style behavior determined by the " + "QualifierAlignment style flag"), + cl::init(""), cl::cat(ClangFormatCategory)); + static cl::opt Verbose("verbose", cl::desc("If set, shows the list of processed files"), cl::cat(ClangFormatCategory)); @@ -402,6 +410,14 @@ return true; } + StringRef ConstAlignment = QualifierAlignment; + + FormatStyle->QualifierAlignment = + StringSwitch(ConstAlignment.lower()) + .Case("right", FormatStyle::QAS_Right) + .Case("left", FormatStyle::QAS_Left) + .Default(FormatStyle->QualifierAlignment); + if (SortIncludes.getNumOccurrences() != 0) { if (SortIncludes) FormatStyle->SortIncludes = FormatStyle::SI_CaseSensitive; diff --git a/clang/unittests/Format/CMakeLists.txt b/clang/unittests/Format/CMakeLists.txt --- a/clang/unittests/Format/CMakeLists.txt +++ b/clang/unittests/Format/CMakeLists.txt @@ -18,6 +18,7 @@ FormatTestTextProto.cpp MacroExpanderTest.cpp NamespaceEndCommentsFixerTest.cpp + QualifierFixerTest.cpp SortImportsTestJS.cpp SortImportsTestJava.cpp SortIncludesTest.cpp diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp --- a/clang/unittests/Format/FormatTest.cpp +++ b/clang/unittests/Format/FormatTest.cpp @@ -18325,6 +18325,26 @@ CHECK_PARSE("ContinuationIndentWidth: 11", ContinuationIndentWidth, 11u); CHECK_PARSE("CommentPragmas: '// abc$'", CommentPragmas, "// abc$"); + Style.QualifierAlignment = FormatStyle::QAS_Right; + CHECK_PARSE("QualifierAlignment: Leave", QualifierAlignment, + FormatStyle::QAS_Leave); + CHECK_PARSE("QualifierAlignment: Right", QualifierAlignment, + FormatStyle::QAS_Right); + CHECK_PARSE("QualifierAlignment: Left", QualifierAlignment, + FormatStyle::QAS_Left); + CHECK_PARSE("QualifierAlignment: Custom", QualifierAlignment, + FormatStyle::QAS_Custom); + + Style.QualifierOrder.clear(); + CHECK_PARSE("QualifierOrder: [ const, volatile, type ]", QualifierOrder, + std::vector({"const", "volatile", "type"})); + Style.QualifierOrder.clear(); + CHECK_PARSE("QualifierOrder: [const, type]", QualifierOrder, + std::vector({"const", "type"})); + Style.QualifierOrder.clear(); + CHECK_PARSE("QualifierOrder: [volatile, type]", QualifierOrder, + std::vector({"volatile", "type"})); + Style.AlignConsecutiveAssignments = FormatStyle::ACS_Consecutive; CHECK_PARSE("AlignConsecutiveAssignments: None", AlignConsecutiveAssignments, FormatStyle::ACS_None); @@ -18992,8 +19012,6 @@ EXPECT_EQ(FormatStyle::LK_Cpp, Style.Language); } -#undef CHECK_PARSE - TEST_F(FormatTest, UsesLanguageForBasedOnStyle) { FormatStyle Style = {}; Style.Language = FormatStyle::LK_JavaScript; @@ -22294,6 +22312,7 @@ "}"; EXPECT_EQ(Code, format(Code, Style)); } + } // namespace } // namespace format } // namespace clang diff --git a/clang/unittests/Format/QualifierFixerTest.cpp b/clang/unittests/Format/QualifierFixerTest.cpp new file mode 100755 --- /dev/null +++ b/clang/unittests/Format/QualifierFixerTest.cpp @@ -0,0 +1,810 @@ +//===- unittest/Format/QualifierFixerTest.cpp - Formatting unit tests -----===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/Format/Format.h" + +#include "FormatTestUtils.h" +#include "TestLexer.h" +#include "gtest/gtest.h" + +#include "../../lib/Format/QualifierAlignmentFixer.h" + +#define DEBUG_TYPE "format-qualifier-fixer-test" + +using testing::ScopedTrace; + +namespace clang { +namespace format { +namespace { + +#define CHECK_PARSE(TEXT, FIELD, VALUE) \ + EXPECT_NE(VALUE, Style.FIELD) << "Initial value already the same!"; \ + EXPECT_EQ(0, parseConfiguration(TEXT, &Style).value()); \ + EXPECT_EQ(VALUE, Style.FIELD) << "Unexpected value after parsing!" + +#define FAIL_PARSE(TEXT, FIELD, VALUE) \ + EXPECT_NE(0, parseConfiguration(TEXT, &Style).value()); \ + EXPECT_EQ(VALUE, Style.FIELD) << "Unexpected value after parsing!" + +class QualifierFixerTest : public ::testing::Test { +protected: + enum StatusCheck { SC_ExpectComplete, SC_ExpectIncomplete, SC_DoNotCheck }; + + TokenList annotate(llvm::StringRef Code, + const FormatStyle &Style = getLLVMStyle()) { + return TestLexer(Allocator, Buffers, Style).annotate(Code); + } + llvm::SpecificBumpPtrAllocator Allocator; + std::vector> Buffers; + + std::string format(llvm::StringRef Code, + const FormatStyle &Style = getLLVMStyle(), + StatusCheck CheckComplete = SC_ExpectComplete) { + LLVM_DEBUG(llvm::errs() << "---\n"); + LLVM_DEBUG(llvm::errs() << Code << "\n\n"); + std::vector Ranges(1, tooling::Range(0, Code.size())); + FormattingAttemptStatus Status; + tooling::Replacements Replaces = + reformat(Style, Code, Ranges, "", &Status); + if (CheckComplete != SC_DoNotCheck) { + bool ExpectedCompleteFormat = CheckComplete == SC_ExpectComplete; + EXPECT_EQ(ExpectedCompleteFormat, Status.FormatComplete) + << Code << "\n\n"; + } + ReplacementCount = Replaces.size(); + auto Result = applyAllReplacements(Code, Replaces); + EXPECT_TRUE(static_cast(Result)); + LLVM_DEBUG(llvm::errs() << "\n" << *Result << "\n\n"); + return *Result; + } + + FormatStyle getStyleWithColumns(FormatStyle Style, unsigned ColumnLimit) { + Style.ColumnLimit = ColumnLimit; + return Style; + } + + FormatStyle getLLVMStyleWithColumns(unsigned ColumnLimit) { + return getStyleWithColumns(getLLVMStyle(), ColumnLimit); + } + + void _verifyFormat(const char *File, int Line, llvm::StringRef Expected, + llvm::StringRef Code, + const FormatStyle &Style = getLLVMStyle()) { + ScopedTrace t(File, Line, ::testing::Message() << Code.str()); + EXPECT_EQ(Expected.str(), format(Expected, Style)) + << "Expected code is not stable"; + EXPECT_EQ(Expected.str(), format(Code, Style)); + if (Style.Language == FormatStyle::LK_Cpp) { + // Objective-C++ is a superset of C++, so everything checked for C++ + // needs to be checked for Objective-C++ as well. + FormatStyle ObjCStyle = Style; + ObjCStyle.Language = FormatStyle::LK_ObjC; + EXPECT_EQ(Expected.str(), format(test::messUp(Code), ObjCStyle)); + } + } + + void _verifyFormat(const char *File, int Line, llvm::StringRef Code, + const FormatStyle &Style = getLLVMStyle()) { + _verifyFormat(File, Line, Code, test::messUp(Code), Style); + } + + void _verifyIncompleteFormat(const char *File, int Line, llvm::StringRef Code, + const FormatStyle &Style = getLLVMStyle()) { + ScopedTrace t(File, Line, ::testing::Message() << Code.str()); + EXPECT_EQ(Code.str(), + format(test::messUp(Code), Style, SC_ExpectIncomplete)); + } + + void _verifyIndependentOfContext(const char *File, int Line, + llvm::StringRef Text, + const FormatStyle &Style = getLLVMStyle()) { + _verifyFormat(File, Line, Text, Style); + _verifyFormat(File, Line, llvm::Twine("void f() { " + Text + " }").str(), + Style); + } + + /// \brief Verify that clang-format does not crash on the given input. + void verifyNoCrash(llvm::StringRef Code, + const FormatStyle &Style = getLLVMStyle()) { + format(Code, Style, SC_DoNotCheck); + } + + int ReplacementCount; +}; + +#define verifyFormat(...) _verifyFormat(__FILE__, __LINE__, __VA_ARGS__) + +} // namespace + +TEST_F(QualifierFixerTest, RotateTokens) { + // TODO add test + EXPECT_EQ(LeftRightQualifierAlignmentFixer::getTokenFromQualifier("const"), + tok::kw_const); + EXPECT_EQ(LeftRightQualifierAlignmentFixer::getTokenFromQualifier("volatile"), + tok::kw_volatile); + EXPECT_EQ(LeftRightQualifierAlignmentFixer::getTokenFromQualifier("inline"), + tok::kw_inline); + EXPECT_EQ(LeftRightQualifierAlignmentFixer::getTokenFromQualifier("static"), + tok::kw_static); + EXPECT_EQ(LeftRightQualifierAlignmentFixer::getTokenFromQualifier("restrict"), + tok::kw_restrict); +} + +TEST_F(QualifierFixerTest, FailQualifierInvalidConfiguration) { + FormatStyle Style = {}; + Style.Language = FormatStyle::LK_Cpp; + FAIL_PARSE("QualifierAlignment: Custom\n" + "QualifierOrder: [const, volatile, apples, type]", + QualifierOrder, + std::vector({"const", "volatile", "apples", "type"})); +} + +TEST_F(QualifierFixerTest, FailQualifierDuplicateConfiguration) { + FormatStyle Style = {}; + Style.Language = FormatStyle::LK_Cpp; + FAIL_PARSE("QualifierAlignment: Custom\n" + "QualifierOrder: [const, volatile, const, type]", + QualifierOrder, + std::vector({"const", "volatile", "const", "type"})); +} + +TEST_F(QualifierFixerTest, FailQualifierMissingType) { + FormatStyle Style = {}; + Style.Language = FormatStyle::LK_Cpp; + FAIL_PARSE("QualifierAlignment: Custom\n" + "QualifierOrder: [const, volatile ]", + QualifierOrder, + std::vector({ + "const", + "volatile", + })); +} + +TEST_F(QualifierFixerTest, FailQualifierEmptyOrder) { + FormatStyle Style = {}; + Style.Language = FormatStyle::LK_Cpp; + FAIL_PARSE("QualifierAlignment: Custom\nQualifierOrder: []", QualifierOrder, + std::vector({})); +} + +TEST_F(QualifierFixerTest, FailQualifierMissingOrder) { + FormatStyle Style = {}; + Style.Language = FormatStyle::LK_Cpp; + FAIL_PARSE("QualifierAlignment: Custom", QualifierOrder, + std::vector()); +} + +TEST_F(QualifierFixerTest, QualifierLeft) { + FormatStyle Style = {}; + Style.Language = FormatStyle::LK_Cpp; + CHECK_PARSE("QualifierAlignment: Left", QualifierOrder, + std::vector({"const", "volatile", "type"})); +} + +TEST_F(QualifierFixerTest, QualifierRight) { + FormatStyle Style = {}; + Style.Language = FormatStyle::LK_Cpp; + CHECK_PARSE("QualifierAlignment: Right", QualifierOrder, + std::vector({"type", "const", "volatile"})); +} + +TEST_F(QualifierFixerTest, QualifiersCustomOrder) { + FormatStyle Style = getLLVMStyle(); + Style.QualifierAlignment = FormatStyle::QAS_Left; + Style.QualifierOrder = {"inline", "constexpr", "static", + "const", "volatile", "type"}; + + verifyFormat("const volatile int a;", "const volatile int a;", Style); + verifyFormat("const volatile int a;", "volatile const int a;", Style); + verifyFormat("const volatile int a;", "int const volatile a;", Style); + verifyFormat("const volatile int a;", "int volatile const a;", Style); + verifyFormat("const volatile int a;", "const int volatile a;", Style); + + verifyFormat("static const volatile int a;", "const static int volatile a;", + Style); + verifyFormat("inline static const volatile int a;", + "const static inline int volatile a;", Style); + + verifyFormat("constexpr static int a;", "static constexpr int a;", Style); + verifyFormat("constexpr static int A;", "static constexpr int A;", Style); + verifyFormat("constexpr static int Bar;", "static constexpr int Bar;", Style); + verifyFormat("constexpr static LPINT Bar;", "static constexpr LPINT Bar;", + Style); + verifyFormat("const const int a;", "const int const a;", Style); +} + +TEST_F(QualifierFixerTest, LeftRightQualifier) { + FormatStyle Style = getLLVMStyle(); + + // keep the const style unaltered + verifyFormat("const int a;", Style); + verifyFormat("const int *a;", Style); + verifyFormat("const int &a;", Style); + verifyFormat("const int &&a;", Style); + verifyFormat("int const b;", Style); + verifyFormat("int const *b;", Style); + verifyFormat("int const &b;", Style); + verifyFormat("int const &&b;", Style); + verifyFormat("int const *b const;", Style); + verifyFormat("int *const c;", Style); + + verifyFormat("const Foo a;", Style); + verifyFormat("const Foo *a;", Style); + verifyFormat("const Foo &a;", Style); + verifyFormat("const Foo &&a;", Style); + verifyFormat("Foo const b;", Style); + verifyFormat("Foo const *b;", Style); + verifyFormat("Foo const &b;", Style); + verifyFormat("Foo const &&b;", Style); + verifyFormat("Foo const *b const;", Style); + + verifyFormat("LLVM_NODISCARD const int &Foo();", Style); + verifyFormat("LLVM_NODISCARD int const &Foo();", Style); + + verifyFormat("volatile const int *restrict;", Style); + verifyFormat("const volatile int *restrict;", Style); + verifyFormat("const int volatile *restrict;", Style); +} + +TEST_F(QualifierFixerTest, RightQualifier) { + FormatStyle Style = getLLVMStyle(); + Style.QualifierAlignment = FormatStyle::QAS_Right; + Style.QualifierOrder = {"type", "const", "volatile"}; + + verifyFormat("int const a;", Style); + verifyFormat("int const *a;", Style); + verifyFormat("int const &a;", Style); + verifyFormat("int const &&a;", Style); + verifyFormat("int const b;", Style); + verifyFormat("int const *b;", Style); + verifyFormat("int const &b;", Style); + verifyFormat("int const &&b;", Style); + verifyFormat("int const *b const;", Style); + verifyFormat("int *const c;", Style); + + verifyFormat("Foo const a;", Style); + verifyFormat("Foo const *a;", Style); + verifyFormat("Foo const &a;", Style); + verifyFormat("Foo const &&a;", Style); + verifyFormat("Foo const b;", Style); + verifyFormat("Foo const *b;", Style); + verifyFormat("Foo const &b;", Style); + verifyFormat("Foo const &&b;", Style); + verifyFormat("Foo const *b const;", Style); + verifyFormat("Foo *const b;", Style); + verifyFormat("Foo const *const b;", Style); + verifyFormat("auto const v = get_value();", Style); + verifyFormat("long long const &a;", Style); + verifyFormat("unsigned char const *a;", Style); + verifyFormat("int main(int const argc, char const *const *const argv)", + Style); + + verifyFormat("LLVM_NODISCARD int const &Foo();", Style); + verifyFormat("SourceRange getSourceRange() const override LLVM_READONLY", + Style); + verifyFormat("void foo() const override;", Style); + verifyFormat("void foo() const override LLVM_READONLY;", Style); + verifyFormat("void foo() const final;", Style); + verifyFormat("void foo() const final LLVM_READONLY;", Style); + verifyFormat("void foo() const LLVM_READONLY;", Style); + + verifyFormat( + "template explicit Action(Action const &action);", + Style); + verifyFormat( + "template explicit Action(Action const &action);", + "template explicit Action(const Action& action);", + Style); + verifyFormat( + "template explicit Action(Action const &action);", + "template \nexplicit Action(const Action& action);", + Style); + + verifyFormat("int const a;", "const int a;", Style); + verifyFormat("int const *a;", "const int *a;", Style); + verifyFormat("int const &a;", "const int &a;", Style); + verifyFormat("foo(int const &a)", "foo(const int &a)", Style); + verifyFormat("unsigned char *a;", "unsigned char *a;", Style); + verifyFormat("unsigned char const *a;", "const unsigned char *a;", Style); + verifyFormat("vector args1", + "vector args1", Style); + verifyFormat("unsigned int const &get_nu() const", + "const unsigned int &get_nu() const", Style); + verifyFormat("Foo const &a", "const Foo &a", Style); + verifyFormat("Foo::iterator const &a", "const Foo::iterator &a", + Style); + + verifyFormat("Foo(int a, " + "unsigned b, // c-style args\n" + " Bar const &c);", + "Foo(int a, " + "unsigned b, // c-style args\n" + " const Bar &c);", + Style); + + verifyFormat("int const volatile;", "volatile const int;", Style); + verifyFormat("int const volatile;", "const volatile int;", Style); + verifyFormat("int const volatile;", "const int volatile;", Style); + verifyFormat("int const volatile *restrict;", "volatile const int *restrict;", + Style); + verifyFormat("int const volatile *restrict;", "const volatile int *restrict;", + Style); + verifyFormat("int const volatile *restrict;", "const int volatile *restrict;", + Style); + + verifyFormat("static int const bat;", "static const int bat;", Style); + verifyFormat("static int const bat;", "static int const bat;", Style); + + verifyFormat("int const Foo::bat = 0;", "const int Foo::bat = 0;", + Style); + verifyFormat("int const Foo::bat = 0;", "int const Foo::bat = 0;", + Style); + verifyFormat("void fn(Foo const &i);", "void fn(const Foo &i);", Style); + verifyFormat("int const Foo::fn() {", "int const Foo::fn() {", + Style); + verifyFormat("Foo> const *p;", "const Foo> *p;", Style); + verifyFormat( + "Foo> const *p = const_cast> const *>(&ffi);", + "const Foo> *p = const_cast> *>(&ffi);", + Style); + + verifyFormat("void fn(Foo const &i);", "void fn(const Foo &i);", Style); + verifyFormat("void fns(ns::S const &s);", "void fns(const ns::S &s);", Style); + verifyFormat("void fn(ns::Foo const &i);", "void fn(const ns::Foo &i);", + Style); + verifyFormat("void fns(ns::ns2::S const &s);", + "void fns(const ns::ns2::S &s);", Style); + verifyFormat("void fn(ns::Foo> const &i);", + "void fn(const ns::Foo> &i);", Style); + verifyFormat("void fn(ns::ns2::Foo> const &i);", + "void fn(const ns::ns2::Foo> &i);", Style); + verifyFormat("void fn(ns::ns2::Foo> const &i);", + "void fn(const ns::ns2::Foo> &i);", Style); + + verifyFormat("LocalScope const *Scope = nullptr;", + "const LocalScope* Scope = nullptr;", Style); + verifyFormat("struct DOTGraphTraits", + "struct DOTGraphTraits", Style); + + verifyFormat( + "bool tools::addXRayRuntime(ToolChain const &TC, ArgList const &Args) {", + "bool tools::addXRayRuntime(const ToolChain&TC, const ArgList &Args) {", + Style); + verifyFormat("Foo const> P;", "Foo> P;", Style); + verifyFormat("Foo const> P;\n", "Foo> P;\n", Style); + verifyFormat("Foo const> P;\n#if 0\n#else\n#endif", + "Foo> P;\n#if 0\n#else\n#endif", Style); + + verifyFormat("auto const i = 0;", "const auto i = 0;", Style); + verifyFormat("auto const &ir = i;", "const auto &ir = i;", Style); + verifyFormat("auto const *ip = &i;", "const auto *ip = &i;", Style); + + verifyFormat("Foo const> P;\n#if 0\n#else\n#endif", + "Foo> P;\n#if 0\n#else\n#endif", Style); + + verifyFormat("Bar const> P;\n#if 0\n#else\n#endif", + "Bar const> P;\n#if 0\n#else\n#endif", Style); + + verifyFormat("Baz const> P;\n#if 0\n#else\n#endif", + "Baz> P;\n#if 0\n#else\n#endif", Style); + + // verifyFormat("#if 0\nBoo const> P;\n#else\n#endif", + // "#if 0\nBoo> P;\n#else\n#endif", Style); + + verifyFormat("int const P;\n#if 0\n#else\n#endif", + "const int P;\n#if 0\n#else\n#endif", Style); + + verifyFormat("unsigned long const a;", "const unsigned long a;", Style); + verifyFormat("unsigned long long const a;", "const unsigned long long a;", + Style); + + // don't adjust macros + verifyFormat("const INTPTR a;", "const INTPTR a;", Style); +} + +TEST_F(QualifierFixerTest, LeftQualifier) { + FormatStyle Style = getLLVMStyle(); + Style.QualifierAlignment = FormatStyle::QAS_Left; + Style.QualifierOrder = {"inline", "static", "const", "volatile", "type"}; + + verifyFormat("const int a;", Style); + verifyFormat("const int *a;", Style); + verifyFormat("const int &a;", Style); + verifyFormat("const int &&a;", Style); + verifyFormat("const int b;", Style); + verifyFormat("const int *b;", Style); + verifyFormat("const int &b;", Style); + verifyFormat("const int &&b;", Style); + verifyFormat("const int *b const;", Style); + verifyFormat("int *const c;", Style); + + verifyFormat("const Foo a;", Style); + verifyFormat("const Foo *a;", Style); + verifyFormat("const Foo &a;", Style); + verifyFormat("const Foo &&a;", Style); + verifyFormat("const Foo b;", Style); + verifyFormat("const Foo *b;", Style); + verifyFormat("const Foo &b;", Style); + verifyFormat("const Foo &&b;", Style); + verifyFormat("const Foo *b const;", Style); + verifyFormat("Foo *const b;", Style); + verifyFormat("const Foo *const b;", Style); + + verifyFormat("LLVM_NODISCARD const int &Foo();", Style); + + verifyFormat("const char a[];", Style); + verifyFormat("const auto v = get_value();", Style); + verifyFormat("const long long &a;", Style); + verifyFormat("const unsigned char *a;", Style); + verifyFormat("const unsigned char *a;", "unsigned char const *a;", Style); + verifyFormat("const Foo &a", "Foo const &a", Style); + verifyFormat("const Foo::iterator &a", "Foo::iterator const &a", + Style); + + verifyFormat("const int a;", "int const a;", Style); + verifyFormat("const int *a;", "int const *a;", Style); + verifyFormat("const int &a;", "int const &a;", Style); + verifyFormat("foo(const int &a)", "foo(int const &a)", Style); + verifyFormat("unsigned char *a;", "unsigned char *a;", Style); + verifyFormat("const unsigned int &get_nu() const", + "unsigned int const &get_nu() const", Style); + + verifyFormat("const volatile int;", "volatile const int;", Style); + verifyFormat("const volatile int;", "const volatile int;", Style); + verifyFormat("const volatile int;", "const int volatile;", Style); + + verifyFormat("const volatile int *restrict;", "volatile const int *restrict;", + Style); + verifyFormat("const volatile int *restrict;", "const volatile int *restrict;", + Style); + verifyFormat("const volatile int *restrict;", "const int volatile *restrict;", + Style); + + verifyFormat("SourceRange getSourceRange() const override LLVM_READONLY;", + Style); + + verifyFormat("void foo() const override;", Style); + verifyFormat("void foo() const override LLVM_READONLY;", Style); + verifyFormat("void foo() const final;", Style); + verifyFormat("void foo() const final LLVM_READONLY;", Style); + verifyFormat("void foo() const LLVM_READONLY;", Style); + + verifyFormat( + "template explicit Action(const Action &action);", + Style); + verifyFormat( + "template explicit Action(const Action &action);", + "template explicit Action(Action const &action);", + Style); + + verifyFormat("static const int bat;", "static const int bat;", Style); + verifyFormat("static const int bat;", "static int const bat;", Style); + + verifyFormat("static const int Foo::bat = 0;", + "static const int Foo::bat = 0;", Style); + verifyFormat("static const int Foo::bat = 0;", + "static int const Foo::bat = 0;", Style); + + verifyFormat("void fn(const Foo &i);"); + + verifyFormat("const int Foo::bat = 0;", "const int Foo::bat = 0;", + Style); + verifyFormat("const int Foo::bat = 0;", "int const Foo::bat = 0;", + Style); + verifyFormat("void fn(const Foo &i);", "void fn( Foo const &i);", + Style); + verifyFormat("const int Foo::fn() {", "int const Foo::fn() {", + Style); + verifyFormat("const Foo> *p;", "Foo> const *p;", Style); + verifyFormat( + "const Foo> *p = const_cast> *>(&ffi);", + "const Foo> *p = const_cast> const *>(&ffi);", + Style); + + verifyFormat("void fn(const Foo &i);", "void fn(Foo const &i);", Style); + verifyFormat("void fns(const ns::S &s);", "void fns(ns::S const &s);", Style); + verifyFormat("void fn(const ns::Foo &i);", "void fn(ns::Foo const &i);", + Style); + verifyFormat("void fns(const ns::ns2::S &s);", + "void fns(ns::ns2::S const &s);", Style); + verifyFormat("void fn(const ns::Foo> &i);", + "void fn(ns::Foo> const &i);", Style); + verifyFormat("void fn(const ns::ns2::Foo> &i);", + "void fn(ns::ns2::Foo> const &i);", Style); + verifyFormat("void fn(const ns::ns2::Foo> &i);", + "void fn(ns::ns2::Foo> const &i);", Style); + + verifyFormat("const auto i = 0;", "auto const i = 0;", Style); + verifyFormat("const auto &ir = i;", "auto const &ir = i;", Style); + verifyFormat("const auto *ip = &i;", "auto const *ip = &i;", Style); + + verifyFormat("Foo> P;\n#if 0\n#else\n#endif", + "Foo const> P;\n#if 0\n#else\n#endif", Style); + + verifyFormat("Foo> P;\n#if 0\n#else\n#endif", + "Foo> P;\n#if 0\n#else\n#endif", Style); + + verifyFormat("const int P;\n#if 0\n#else\n#endif", + "int const P;\n#if 0\n#else\n#endif", Style); + + verifyFormat("const unsigned long a;", "unsigned long const a;", Style); + verifyFormat("const unsigned long long a;", "unsigned long long const a;", + Style); + + verifyFormat("const long long unsigned a;", "long const long unsigned a;", + Style); + + verifyFormat("const std::Foo", "const std::Foo", Style); + verifyFormat("const std::Foo<>", "const std::Foo<>", Style); + verifyFormat("const std::Foo < int", "const std::Foo", "const std::Foo", Style); + + // don't adjust macros + verifyFormat("INTPTR const a;", "INTPTR const a;", Style); +} + +TEST_F(QualifierFixerTest, ConstVolatileQualifiersOrder) { + FormatStyle Style = getLLVMStyle(); + Style.QualifierAlignment = FormatStyle::QAS_Left; + Style.QualifierOrder = {"inline", "static", "const", "volatile", "type"}; + + // The Default + EXPECT_EQ(Style.QualifierOrder.size(), 5); + + verifyFormat("const volatile int a;", "const volatile int a;", Style); + verifyFormat("const volatile int a;", "volatile const int a;", Style); + verifyFormat("const volatile int a;", "int const volatile a;", Style); + verifyFormat("const volatile int a;", "int volatile const a;", Style); + verifyFormat("const volatile int a;", "const int volatile a;", Style); + + Style.QualifierAlignment = FormatStyle::QAS_Right; + Style.QualifierOrder = {"type", "const", "volatile"}; + + verifyFormat("int const volatile a;", "const volatile int a;", Style); + verifyFormat("int const volatile a;", "volatile const int a;", Style); + verifyFormat("int const volatile a;", "int const volatile a;", Style); + verifyFormat("int const volatile a;", "int volatile const a;", Style); + verifyFormat("int const volatile a;", "const int volatile a;", Style); + + Style.QualifierAlignment = FormatStyle::QAS_Left; + Style.QualifierOrder = {"volatile", "const", "type"}; + + verifyFormat("volatile const int a;", "const volatile int a;", Style); + verifyFormat("volatile const int a;", "volatile const int a;", Style); + verifyFormat("volatile const int a;", "int const volatile a;", Style); + verifyFormat("volatile const int a;", "int volatile const a;", Style); + verifyFormat("volatile const int a;", "const int volatile a;", Style); + + Style.QualifierAlignment = FormatStyle::QAS_Right; + Style.QualifierOrder = {"type", "volatile", "const"}; + + verifyFormat("int volatile const a;", "const volatile int a;", Style); + verifyFormat("int volatile const a;", "volatile const int a;", Style); + verifyFormat("int volatile const a;", "int const volatile a;", Style); + verifyFormat("int volatile const a;", "int volatile const a;", Style); + verifyFormat("int volatile const a;", "const int volatile a;", Style); + + Style.QualifierAlignment = FormatStyle::QAS_Custom; + Style.QualifierOrder = {"type", "volatile", "const"}; + + verifyFormat("int volatile const a;", "const volatile int a;", Style); + verifyFormat("int volatile const a;", "volatile const int a;", Style); + verifyFormat("int volatile const a;", "int const volatile a;", Style); + verifyFormat("int volatile const a;", "int volatile const a;", Style); + verifyFormat("int volatile const a;", "const int volatile a;", Style); +} + +TEST_F(QualifierFixerTest, InlineStatics) { + FormatStyle Style = getLLVMStyle(); + Style.QualifierAlignment = FormatStyle::QAS_Left; + Style.QualifierOrder = {"inline", "static", "const", "volatile", "type"}; + EXPECT_EQ(Style.QualifierOrder.size(), 5); + + verifyFormat("inline static const volatile int a;", + "const inline static volatile int a;", Style); + verifyFormat("inline static const volatile int a;", + "volatile inline static const int a;", Style); + verifyFormat("inline static const volatile int a;", + "int const inline static volatile a;", Style); + verifyFormat("inline static const volatile int a;", + "int volatile inline static const a;", Style); + verifyFormat("inline static const volatile int a;", + "const int inline static volatile a;", Style); +} + +TEST_F(QualifierFixerTest, AmpEqual) { + FormatStyle Style = getLLVMStyle(); + Style.QualifierAlignment = FormatStyle::QAS_Custom; + Style.QualifierOrder = {"static", "type", "const"}; + EXPECT_EQ(Style.QualifierOrder.size(), 3); + + verifyFormat("foo(std::string const & = std::string()) const", + "foo(const std::string & = std::string()) const", Style); + verifyFormat("foo(std::string const & = std::string())", + "foo(const std::string & = std::string())", Style); +} + +TEST_F(QualifierFixerTest, MoveConstBeyondTypeSmall) { + + FormatStyle Style = getLLVMStyle(); + Style.QualifierAlignment = FormatStyle::QAS_Custom; + Style.QualifierOrder = {"type", "const"}; + EXPECT_EQ(Style.QualifierOrder.size(), 2); + + verifyFormat("int const a;", "const int a;", Style); + verifyFormat("int const *a;", "const int*a;", Style); + verifyFormat("int const *a;", "const int *a;", Style); + verifyFormat("int const &a;", "const int &a;", Style); + verifyFormat("int const &&a;", "const int &&a;", Style); +} + +TEST_F(QualifierFixerTest, MoveConstBeforeTypeSmall) { + + FormatStyle Style = getLLVMStyle(); + Style.QualifierAlignment = FormatStyle::QAS_Custom; + Style.QualifierOrder = {"const", "type"}; + EXPECT_EQ(Style.QualifierOrder.size(), 2); + + verifyFormat("const int a;", "int const a;", Style); + verifyFormat("const int *a;", "int const *a;", Style); + verifyFormat("const int *a const;", "int const *a const;", Style); + + verifyFormat("const int a = foo();", "int const a = foo();", Style); + verifyFormat("const int *a = foo();", "int const *a = foo();", Style); + verifyFormat("const int *a const = foo();", "int const *a const = foo();", + Style); + + verifyFormat("const auto a = foo();", "auto const a = foo();", Style); + verifyFormat("const auto *a = foo();", "auto const *a = foo();", Style); + verifyFormat("const auto *a const = foo();", "auto const *a const = foo();", + Style); +} + +TEST_F(QualifierFixerTest, MoveConstBeyondType) { + + FormatStyle Style = getLLVMStyle(); + Style.QualifierAlignment = FormatStyle::QAS_Custom; + Style.QualifierOrder = {"static", "inline", "type", "const", "volatile"}; + EXPECT_EQ(Style.QualifierOrder.size(), 5); + + verifyFormat("static inline int const volatile a;", + "const inline static volatile int a;", Style); + verifyFormat("static inline int const volatile a;", + "volatile inline static const int a;", Style); + verifyFormat("static inline int const volatile a;", + "int const inline static volatile a;", Style); + verifyFormat("static inline int const volatile a;", + "int volatile inline static const a;", Style); + verifyFormat("static inline int const volatile a;", + "const int inline static volatile a;", Style); + + verifyFormat("static inline int const volatile *a const;", + "const int inline static volatile *a const;", Style); +} + +TEST_F(QualifierFixerTest, PrepareLeftRightOrdering) { + FormatStyle Style = getLLVMStyle(); + Style.QualifierAlignment = FormatStyle::QAS_Custom; + Style.QualifierOrder = {"static", "inline", "type", "const", "volatile"}; + + std::vector Left; + std::vector Right; + std::vector ConfiguredTokens; + QualifierAlignmentFixer::PrepareLeftRightOrdering(Style.QualifierOrder, Left, + Right, ConfiguredTokens); + + EXPECT_EQ(Left.size(), 2); + EXPECT_EQ(Right.size(), 2); + + std::vector LeftResult = {"inline", "static"}; + std::vector RightResult = {"const", "volatile"}; + EXPECT_EQ(Left, LeftResult); + EXPECT_EQ(Right, RightResult); +} + +TEST_F(QualifierFixerTest, IsQualifierType) { + + std::vector ConfiguredTokens; + ConfiguredTokens.push_back(tok::kw_const); + ConfiguredTokens.push_back(tok::kw_static); + ConfiguredTokens.push_back(tok::kw_inline); + ConfiguredTokens.push_back(tok::kw_restrict); + ConfiguredTokens.push_back(tok::kw_constexpr); + + auto Tokens = + annotate("const static inline auto restrict int double long constexpr"); + + EXPECT_TRUE(LeftRightQualifierAlignmentFixer::isQualifierOrType( + Tokens[0], ConfiguredTokens)); + EXPECT_TRUE(LeftRightQualifierAlignmentFixer::isQualifierOrType( + Tokens[1], ConfiguredTokens)); + EXPECT_TRUE(LeftRightQualifierAlignmentFixer::isQualifierOrType( + Tokens[2], ConfiguredTokens)); + EXPECT_TRUE(LeftRightQualifierAlignmentFixer::isQualifierOrType( + Tokens[3], ConfiguredTokens)); + EXPECT_TRUE(LeftRightQualifierAlignmentFixer::isQualifierOrType( + Tokens[4], ConfiguredTokens)); + EXPECT_TRUE(LeftRightQualifierAlignmentFixer::isQualifierOrType( + Tokens[5], ConfiguredTokens)); + EXPECT_TRUE(LeftRightQualifierAlignmentFixer::isQualifierOrType( + Tokens[6], ConfiguredTokens)); + EXPECT_TRUE(LeftRightQualifierAlignmentFixer::isQualifierOrType( + Tokens[7], ConfiguredTokens)); + EXPECT_TRUE(LeftRightQualifierAlignmentFixer::isQualifierOrType( + Tokens[8], ConfiguredTokens)); + + auto NotTokens = annotate("for while do Foo Bar "); + + EXPECT_FALSE(LeftRightQualifierAlignmentFixer::isQualifierOrType( + NotTokens[0], ConfiguredTokens)); + EXPECT_FALSE(LeftRightQualifierAlignmentFixer::isQualifierOrType( + NotTokens[1], ConfiguredTokens)); + EXPECT_FALSE(LeftRightQualifierAlignmentFixer::isQualifierOrType( + NotTokens[2], ConfiguredTokens)); + EXPECT_FALSE(LeftRightQualifierAlignmentFixer::isQualifierOrType( + NotTokens[3], ConfiguredTokens)); + EXPECT_FALSE(LeftRightQualifierAlignmentFixer::isQualifierOrType( + NotTokens[4], ConfiguredTokens)); + EXPECT_FALSE(LeftRightQualifierAlignmentFixer::isQualifierOrType( + NotTokens[5], ConfiguredTokens)); +} + +TEST_F(QualifierFixerTest, IsMacro) { + + auto Tokens = annotate("INT INTPR Foo int"); + + EXPECT_TRUE(LeftRightQualifierAlignmentFixer::isPossibleMacro(Tokens[0])); + EXPECT_TRUE(LeftRightQualifierAlignmentFixer::isPossibleMacro(Tokens[1])); + EXPECT_FALSE(LeftRightQualifierAlignmentFixer::isPossibleMacro(Tokens[2])); + EXPECT_FALSE(LeftRightQualifierAlignmentFixer::isPossibleMacro(Tokens[3])); +} + +TEST_F(QualifierFixerTest, OverlappingQualifier) { + + FormatStyle Style = getLLVMStyle(); + Style.QualifierAlignment = FormatStyle::QAS_Left; + Style.QualifierOrder = {"const", "type"}; + + verifyFormat("Foo(const Bar &name);", "Foo(Bar const &name);", Style); +} + +TEST_F(QualifierFixerTest, DontPushQualifierThroughNonSpecifiedTypes) { + + FormatStyle Style = getLLVMStyle(); + Style.QualifierAlignment = FormatStyle::QAS_Left; + Style.QualifierOrder = {"const", "type"}; + + verifyFormat("inline static const int a;", Style); + + Style.QualifierOrder = {"static", "const", "type"}; + + verifyFormat("inline static const int a;", Style); + verifyFormat("static inline const int a;", "static inline const int a;", + Style); + + verifyFormat("static const int a;", "const static int a;", Style); +} + +TEST_F(QualifierFixerTest, UnsignedQualifier) { + + FormatStyle Style = getLLVMStyle(); + Style.QualifierAlignment = FormatStyle::QAS_Left; + Style.QualifierOrder = {"const", "type"}; + + verifyFormat("Foo(const unsigned char *bytes)", + "Foo(unsigned const char *bytes)", Style); + + Style.QualifierAlignment = FormatStyle::QAS_Right; + Style.QualifierOrder = {"type", "const"}; + + verifyFormat("Foo(unsigned char const *bytes)", + "Foo(unsigned const char *bytes)", Style); +} + +} // namespace format +} // namespace clang