//===--- QualifierAlignmentFixer.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 QualifierAlignmentFixer, 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 #include #define DEBUG_TYPE "format-qualifier-alignment-fixer" namespace clang { namespace format { void addQualifierAlignmentFixerPasses(const FormatStyle &Style, SmallVectorImpl &Passes) { std::vector LeftOrder; std::vector RightOrder; std::vector ConfiguredQualifierTokens; prepareLeftRightOrderingForQualifierAlignmentFixer( Style.QualifierOrder, LeftOrder, RightOrder, ConfiguredQualifierTokens); // Handle the left and right alignment separately. 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(); }); } } 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) { auto Range = CharSourceRange::getCharRange(First->Tok.getLocation(), First->Tok.getEndLoc()); std::string NewText{}; NewText += First->TokenText; NewText += " " + Qualifier; 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); } static bool isConfiguredQualifier(const FormatToken *const Tok, const std::vector &Qualifiers) { return Tok && llvm::is_contained(Qualifiers, Tok->Tok.getKind()); } static bool isQualifier(const FormatToken *const Tok) { if (!Tok) return false; switch (Tok->Tok.getKind()) { case tok::kw_const: case tok::kw_volatile: case tok::kw_static: case tok::kw_inline: case tok::kw_constexpr: case tok::kw_restrict: case tok::kw_friend: return true; default: return false; } } const FormatToken *LeftRightQualifierAlignmentFixer::analyzeRight( const SourceManager &SourceMgr, const AdditionalKeywords &Keywords, tooling::Replacements &Fixes, const FormatToken *const Tok, const std::string &Qualifier, tok::TokenKind QualifierType) { // We only need to think about streams that begin with a qualifier. if (Tok->isNot(QualifierType)) return Tok; // Don't concern yourself if nothing follows the qualifier. if (!Tok->Next) return Tok; // Skip qualifiers to the left to find what preceeds the qualifiers. // Use isQualifier rather than isConfiguredQualifier to cover all qualifiers. const FormatToken *PreviousCheck = Tok->getPreviousNonComment(); while (isQualifier(PreviousCheck)) PreviousCheck = PreviousCheck->getPreviousNonComment(); // Examples given in order of ['type', 'const', 'volatile'] const bool IsRightQualifier = PreviousCheck && [PreviousCheck]() { // The cases: // `Foo() const` -> `Foo() const` // `Foo() const final` -> `Foo() const final` // `Foo() const override` -> `Foo() const final` // `Foo() const volatile override` -> `Foo() const volatile override` // `Foo() volatile const final` -> `Foo() const volatile final` if (PreviousCheck->is(tok::r_paren)) return true; // The cases: // `struct {} volatile const a;` -> `struct {} const volatile a;` // `class {} volatile const a;` -> `class {} const volatile a;` if (PreviousCheck->is(tok::r_brace)) return true; // The case: // `template const Bar Foo()` -> // `template Bar const Foo()` // The cases: // `Foo const foo` -> `Foo const foo` // `Foo volatile const` -> `Foo const volatile` // The case: // ``` // template // requires Concept1 && requires Concept2 // const Foo f(); // ``` // -> // ``` // template // requires Concept1 && requires Concept2 // Foo const f(); // ``` if (PreviousCheck->is(TT_TemplateCloser)) { // If the token closes a template<> or requires clause, then it is a left // qualifier and should be moved to the right. return !(PreviousCheck->ClosesTemplateDeclaration || PreviousCheck->ClosesRequiresClause); } // The case `Foo* const` -> `Foo* const` // The case `Foo* volatile const` -> `Foo* const volatile` // The case `int32_t const` -> `int32_t const` // The case `auto volatile const` -> `auto const volatile` if (PreviousCheck->isOneOf(TT_PointerOrReference, tok::identifier, tok::kw_auto)) { return true; } return false; }(); // Find the last qualifier to the right. const FormatToken *LastQual = Tok; while (isQualifier(LastQual->getNextNonComment())) LastQual = LastQual->getNextNonComment(); // If this qualifier is to the right of a type or pointer do a partial sort // and return. if (IsRightQualifier) { if (LastQual != Tok) rotateTokens(SourceMgr, Fixes, Tok, LastQual, /*Left=*/false); return Tok; } const FormatToken *TypeToken = LastQual->getNextNonComment(); if (!TypeToken) return Tok; // Stay safe and don't move past macros, also don't bother with sorting. if (isPossibleMacro(TypeToken)) return Tok; // The case `const long long int volatile` -> `long long int const volatile` // The case `long const long int volatile` -> `long long int const volatile` // The case `long long volatile int const` -> `long long int const volatile` // The case `const long long volatile int` -> `long long int const volatile` if (TypeToken->isTypeName(LangOpts)) { // The case `const decltype(foo)` -> `const decltype(foo)` // The case `const typeof(foo)` -> `const typeof(foo)` // The case `const _Atomic(foo)` -> `const _Atomic(foo)` if (TypeToken->isOneOf(tok::kw_decltype, tok::kw_typeof, tok::kw__Atomic)) return Tok; const FormatToken *LastSimpleTypeSpecifier = TypeToken; while (isQualifierOrType(LastSimpleTypeSpecifier->getNextNonComment(), LangOpts)) { LastSimpleTypeSpecifier = LastSimpleTypeSpecifier->getNextNonComment(); } rotateTokens(SourceMgr, Fixes, Tok, LastSimpleTypeSpecifier, /*Left=*/false); return LastSimpleTypeSpecifier; } // The case `unsigned short const` -> `unsigned short const` // The case: // `unsigned short volatile const` -> `unsigned short const volatile` if (PreviousCheck && PreviousCheck->isTypeName(LangOpts)) { if (LastQual != Tok) rotateTokens(SourceMgr, Fixes, Tok, LastQual, /*Left=*/false); return Tok; } // Skip the typename keyword. // The case `const typename C::type` -> `typename C::type const` if (TypeToken->is(tok::kw_typename)) TypeToken = TypeToken->getNextNonComment(); // Skip the initial :: of a global-namespace type. // The case `const ::...` -> `::... const` if (TypeToken->is(tok::coloncolon)) { // The case `const ::template Foo...` -> `::template Foo... const` TypeToken = TypeToken->getNextNonComment(); if (TypeToken && TypeToken->is(tok::kw_template)) TypeToken = TypeToken->getNextNonComment(); } // Don't change declarations such as // `foo(const struct Foo a);` -> `foo(const struct Foo a);` // as they would currently change code such as // `const struct my_struct_t {} my_struct;` -> `struct my_struct_t const {} // my_struct;` if (TypeToken->isOneOf(tok::kw_struct, tok::kw_class)) return Tok; if (TypeToken->isOneOf(tok::kw_auto, tok::identifier)) { // The case `const auto` -> `auto const` // 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 Foo &&` -> `Foo const &&` // The case `const std::Foo &&` -> `std::Foo const &&` // The case `const std::Foo &&` -> `std::Foo const &&` // The case `const ::template Foo` -> `::template Foo const` // The case `const T::template Foo` -> `T::template Foo const` const FormatToken *Next = nullptr; while ((Next = TypeToken->getNextNonComment()) && (Next->is(TT_TemplateOpener) || Next->startsSequence(tok::coloncolon, tok::identifier) || Next->startsSequence(tok::coloncolon, tok::kw_template, tok::identifier))) { if (Next->is(TT_TemplateOpener)) { assert(Next->MatchingParen && "Missing template closer"); TypeToken = Next->MatchingParen; } else if (Next->startsSequence(tok::coloncolon, tok::identifier)) { TypeToken = Next->getNextNonComment(); } else { TypeToken = Next->getNextNonComment()->getNextNonComment(); } } if (Next->is(tok::kw_auto)) TypeToken = Next; // Place the Qualifier at the end of the list of qualifiers. while (isQualifier(TypeToken->getNextNonComment())) { // The case `volatile Foo::iter const` -> `Foo::iter const volatile` TypeToken = TypeToken->getNextNonComment(); } insertQualifierAfter(SourceMgr, Fixes, TypeToken, Qualifier); // Remove token and following whitespace. auto Range = CharSourceRange::getCharRange( Tok->getStartOfNonWhitespace(), Tok->Next->getStartOfNonWhitespace()); replaceToken(SourceMgr, Fixes, Range, ""); } return Tok; } const FormatToken *LeftRightQualifierAlignmentFixer::analyzeLeft( const SourceManager &SourceMgr, const AdditionalKeywords &Keywords, tooling::Replacements &Fixes, const FormatToken *const Tok, const std::string &Qualifier, tok::TokenKind QualifierType) { // We only need to think about streams that begin with a qualifier. if (Tok->isNot(QualifierType)) return Tok; // Don't concern yourself if nothing preceeds the qualifier. if (!Tok->getPreviousNonComment()) return Tok; // Skip qualifiers to the left to find what preceeds the qualifiers. const FormatToken *TypeToken = Tok->getPreviousNonComment(); while (isQualifier(TypeToken)) TypeToken = TypeToken->getPreviousNonComment(); // For left qualifiers preceeded by nothing, a template declaration, or *,&,&& // we only perform sorting. if (!TypeToken || TypeToken->isPointerOrReference() || TypeToken->ClosesRequiresClause || TypeToken->ClosesTemplateDeclaration) { // Don't sort past a non-configured qualifier token. const FormatToken *FirstQual = Tok; while (isConfiguredQualifier(FirstQual->getPreviousNonComment(), ConfiguredQualifierTokens)) { FirstQual = FirstQual->getPreviousNonComment(); } if (FirstQual != Tok) rotateTokens(SourceMgr, Fixes, FirstQual, Tok, /*Left=*/true); return Tok; } // Stay safe and don't move past macros, also don't bother with sorting. if (isPossibleMacro(TypeToken)) return Tok; // Examples given in order of ['const', 'volatile', 'type'] // The case `volatile long long int const` -> `const volatile long long int` // The case `volatile long long const int` -> `const volatile long long int` // The case `const long long volatile int` -> `const volatile long long int` // The case `long volatile long int const` -> `const volatile long long int` if (TypeToken->isTypeName(LangOpts)) { const FormatToken *LastSimpleTypeSpecifier = TypeToken; while (isConfiguredQualifierOrType( LastSimpleTypeSpecifier->getPreviousNonComment(), ConfiguredQualifierTokens, LangOpts)) { LastSimpleTypeSpecifier = LastSimpleTypeSpecifier->getPreviousNonComment(); } rotateTokens(SourceMgr, Fixes, LastSimpleTypeSpecifier, Tok, /*Left=*/true); return Tok; } if (TypeToken->isOneOf(tok::kw_auto, tok::identifier, TT_TemplateCloser)) { const auto IsStartOfType = [](const FormatToken *const Tok) -> bool { if (!Tok) return true; // A template closer is not the start of a type. // The case `?<> const` -> `const ?<>` if (Tok->is(TT_TemplateCloser)) return false; const FormatToken *const Previous = Tok->getPreviousNonComment(); if (!Previous) return true; // An identifier preceeded by :: is not the start of a type. // The case `?::Foo const` -> `const ?::Foo` if (Tok->is(tok::identifier) && Previous->is(tok::coloncolon)) return false; const FormatToken *const PrePrevious = Previous->getPreviousNonComment(); // An identifier preceeded by ::template is not the start of a type. // The case `?::template Foo const` -> `const ?::template Foo` if (Tok->is(tok::identifier) && Previous->is(tok::kw_template) && PrePrevious && PrePrevious->is(tok::coloncolon)) { return false; } if (Tok->endsSequence(tok::kw_auto, tok::identifier)) return false; return true; }; while (!IsStartOfType(TypeToken)) { // The case `?<>` if (TypeToken->is(TT_TemplateCloser)) { assert(TypeToken->MatchingParen && "Missing template opener"); TypeToken = TypeToken->MatchingParen->getPreviousNonComment(); } else { // The cases // `::Foo` // `?>::Foo` // `?Bar::Foo` // `::template Foo` // `?>::template Foo` // `?Bar::template Foo` if (TypeToken->getPreviousNonComment()->is(tok::kw_template)) TypeToken = TypeToken->getPreviousNonComment(); const FormatToken *const ColonColon = TypeToken->getPreviousNonComment(); const FormatToken *const PreColonColon = ColonColon->getPreviousNonComment(); if (PreColonColon && PreColonColon->isOneOf(TT_TemplateCloser, tok::identifier)) { TypeToken = PreColonColon; } else { TypeToken = ColonColon; } } } assert(TypeToken && "Should be auto or identifier"); // Place the Qualifier at the start of the list of qualifiers. const FormatToken *Previous = nullptr; while ((Previous = TypeToken->getPreviousNonComment()) && (isConfiguredQualifier(Previous, ConfiguredQualifierTokens) || Previous->is(tok::kw_typename))) { // The case `volatile Foo::iter const` -> `const volatile Foo::iter` // The case `typename C::type const` -> `const typename C::type` TypeToken = Previous; } // Don't change declarations such as // `foo(struct Foo const a);` -> `foo(struct Foo const a);` if (!Previous || !Previous->isOneOf(tok::kw_struct, tok::kw_class)) { insertQualifierBefore(SourceMgr, Fixes, TypeToken, Qualifier); removeToken(SourceMgr, Fixes, Tok); } } return Tok; } tok::TokenKind LeftRightQualifierAlignmentFixer::getTokenFromQualifier( const std::string &Qualifier) { // Don't let 'type' be an identifier, but 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) .Case("friend", tok::kw_friend) .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; AffectedRangeMgr.computeAffectedLines(AnnotatedLines); fixQualifierAlignment(AnnotatedLines, Tokens, Fixes); return {Fixes, 0}; } void LeftRightQualifierAlignmentFixer::fixQualifierAlignment( SmallVectorImpl &AnnotatedLines, FormatTokenLexer &Tokens, tooling::Replacements &Fixes) { const AdditionalKeywords &Keywords = Tokens.getKeywords(); const SourceManager &SourceMgr = Env.getSourceManager(); tok::TokenKind QualifierToken = getTokenFromQualifier(Qualifier); assert(QualifierToken != tok::identifier && "Unrecognised Qualifier"); for (AnnotatedLine *Line : AnnotatedLines) { fixQualifierAlignment(Line->Children, Tokens, Fixes); if (!Line->Affected || Line->InPPDirective) continue; FormatToken *First = Line->First; assert(First); if (First->Finalized) continue; const auto *Last = Line->Last; for (const auto *Tok = First; Tok && Tok != Last && Tok->Next; Tok = Tok->Next) { if (Tok->MustBreakBefore) break; 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); } } } } void prepareLeftRightOrderingForQualifierAlignmentFixer( 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. // The Order list must define the position of "type" to signify assert(llvm::is_contained(Order, "type") && "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 isQualifierOrType(const FormatToken *Tok, const LangOptions &LangOpts) { return Tok && (Tok->isTypeName(LangOpts) || Tok->is(tok::kw_auto) || isQualifier(Tok)); } bool isConfiguredQualifierOrType(const FormatToken *Tok, const std::vector &Qualifiers, const LangOptions &LangOpts) { return Tok && (Tok->isTypeName(LangOpts) || Tok->is(tok::kw_auto) || isConfiguredQualifier(Tok, Qualifiers)); } // 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 isPossibleMacro(const FormatToken *Tok) { if (!Tok) return false; if (Tok->isNot(tok::identifier)) return false; if (Tok->TokenText.upper() == Tok->TokenText.str()) { // T,K,U,V likely could be template arguments return Tok->TokenText.size() != 1; } return false; } } // namespace format } // namespace clang