Path: blob/main/contrib/llvm-project/clang/lib/Format/QualifierAlignmentFixer.cpp
35233 views
//===--- QualifierAlignmentFixer.cpp ----------------------------*- C++--*-===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7///8/// \file9/// This file implements QualifierAlignmentFixer, a TokenAnalyzer that10/// enforces either left or right const depending on the style.11///12//===----------------------------------------------------------------------===//1314#include "QualifierAlignmentFixer.h"15#include "FormatToken.h"16#include "llvm/Support/Debug.h"17#include "llvm/Support/Regex.h"1819#include <algorithm>20#include <optional>2122#define DEBUG_TYPE "format-qualifier-alignment-fixer"2324namespace clang {25namespace format {2627void addQualifierAlignmentFixerPasses(const FormatStyle &Style,28SmallVectorImpl<AnalyzerPass> &Passes) {29std::vector<std::string> LeftOrder;30std::vector<std::string> RightOrder;31std::vector<tok::TokenKind> ConfiguredQualifierTokens;32prepareLeftRightOrderingForQualifierAlignmentFixer(33Style.QualifierOrder, LeftOrder, RightOrder, ConfiguredQualifierTokens);3435// Handle the left and right alignment separately.36for (const auto &Qualifier : LeftOrder) {37Passes.emplace_back(38[&, Qualifier, ConfiguredQualifierTokens](const Environment &Env) {39return LeftRightQualifierAlignmentFixer(Env, Style, Qualifier,40ConfiguredQualifierTokens,41/*RightAlign=*/false)42.process();43});44}45for (const auto &Qualifier : RightOrder) {46Passes.emplace_back(47[&, Qualifier, ConfiguredQualifierTokens](const Environment &Env) {48return LeftRightQualifierAlignmentFixer(Env, Style, Qualifier,49ConfiguredQualifierTokens,50/*RightAlign=*/true)51.process();52});53}54}5556static void replaceToken(const SourceManager &SourceMgr,57tooling::Replacements &Fixes,58const CharSourceRange &Range, std::string NewText) {59auto Replacement = tooling::Replacement(SourceMgr, Range, NewText);60auto Err = Fixes.add(Replacement);6162if (Err) {63llvm::errs() << "Error while rearranging Qualifier : "64<< llvm::toString(std::move(Err)) << "\n";65}66}6768static void removeToken(const SourceManager &SourceMgr,69tooling::Replacements &Fixes,70const FormatToken *First) {71auto Range = CharSourceRange::getCharRange(First->getStartOfNonWhitespace(),72First->Tok.getEndLoc());73replaceToken(SourceMgr, Fixes, Range, "");74}7576static void insertQualifierAfter(const SourceManager &SourceMgr,77tooling::Replacements &Fixes,78const FormatToken *First,79const std::string &Qualifier) {80auto Range = CharSourceRange::getCharRange(First->Tok.getLocation(),81First->Tok.getEndLoc());8283std::string NewText{};84NewText += First->TokenText;85NewText += " " + Qualifier;86replaceToken(SourceMgr, Fixes, Range, NewText);87}8889static void insertQualifierBefore(const SourceManager &SourceMgr,90tooling::Replacements &Fixes,91const FormatToken *First,92const std::string &Qualifier) {93auto Range = CharSourceRange::getCharRange(First->getStartOfNonWhitespace(),94First->Tok.getEndLoc());9596std::string NewText = " " + Qualifier + " ";97NewText += First->TokenText;9899replaceToken(SourceMgr, Fixes, Range, NewText);100}101102static bool endsWithSpace(const std::string &s) {103if (s.empty())104return false;105return isspace(s.back());106}107108static bool startsWithSpace(const std::string &s) {109if (s.empty())110return false;111return isspace(s.front());112}113114static void rotateTokens(const SourceManager &SourceMgr,115tooling::Replacements &Fixes, const FormatToken *First,116const FormatToken *Last, bool Left) {117auto *End = Last;118auto *Begin = First;119if (!Left) {120End = Last->Next;121Begin = First->Next;122}123124std::string NewText;125// If we are rotating to the left we move the Last token to the front.126if (Left) {127NewText += Last->TokenText;128NewText += " ";129}130131// Then move through the other tokens.132auto *Tok = Begin;133while (Tok != End) {134if (!NewText.empty() && !endsWithSpace(NewText))135NewText += " ";136137NewText += Tok->TokenText;138Tok = Tok->Next;139}140141// If we are rotating to the right we move the first token to the back.142if (!Left) {143if (!NewText.empty() && !startsWithSpace(NewText))144NewText += " ";145NewText += First->TokenText;146}147148auto Range = CharSourceRange::getCharRange(First->getStartOfNonWhitespace(),149Last->Tok.getEndLoc());150151replaceToken(SourceMgr, Fixes, Range, NewText);152}153154static bool155isConfiguredQualifier(const FormatToken *const Tok,156const std::vector<tok::TokenKind> &Qualifiers) {157return Tok && llvm::is_contained(Qualifiers, Tok->Tok.getKind());158}159160static bool isQualifier(const FormatToken *const Tok) {161if (!Tok)162return false;163164switch (Tok->Tok.getKind()) {165case tok::kw_const:166case tok::kw_volatile:167case tok::kw_static:168case tok::kw_inline:169case tok::kw_constexpr:170case tok::kw_restrict:171case tok::kw_friend:172return true;173default:174return false;175}176}177178const FormatToken *LeftRightQualifierAlignmentFixer::analyzeRight(179const SourceManager &SourceMgr, const AdditionalKeywords &Keywords,180tooling::Replacements &Fixes, const FormatToken *const Tok,181const std::string &Qualifier, tok::TokenKind QualifierType) {182// We only need to think about streams that begin with a qualifier.183if (Tok->isNot(QualifierType))184return Tok;185// Don't concern yourself if nothing follows the qualifier.186if (!Tok->Next)187return Tok;188189// Skip qualifiers to the left to find what preceeds the qualifiers.190// Use isQualifier rather than isConfiguredQualifier to cover all qualifiers.191const FormatToken *PreviousCheck = Tok->getPreviousNonComment();192while (isQualifier(PreviousCheck))193PreviousCheck = PreviousCheck->getPreviousNonComment();194195// Examples given in order of ['type', 'const', 'volatile']196const bool IsRightQualifier = PreviousCheck && [PreviousCheck]() {197// The cases:198// `Foo() const` -> `Foo() const`199// `Foo() const final` -> `Foo() const final`200// `Foo() const override` -> `Foo() const final`201// `Foo() const volatile override` -> `Foo() const volatile override`202// `Foo() volatile const final` -> `Foo() const volatile final`203if (PreviousCheck->is(tok::r_paren))204return true;205206// The cases:207// `struct {} volatile const a;` -> `struct {} const volatile a;`208// `class {} volatile const a;` -> `class {} const volatile a;`209if (PreviousCheck->is(tok::r_brace))210return true;211212// The case:213// `template <class T> const Bar Foo()` ->214// `template <class T> Bar const Foo()`215// The cases:216// `Foo<int> const foo` -> `Foo<int> const foo`217// `Foo<int> volatile const` -> `Foo<int> const volatile`218// The case:219// ```220// template <class T>221// requires Concept1<T> && requires Concept2<T>222// const Foo f();223// ```224// ->225// ```226// template <class T>227// requires Concept1<T> && requires Concept2<T>228// Foo const f();229// ```230if (PreviousCheck->is(TT_TemplateCloser)) {231// If the token closes a template<> or requires clause, then it is a left232// qualifier and should be moved to the right.233return !(PreviousCheck->ClosesTemplateDeclaration ||234PreviousCheck->ClosesRequiresClause);235}236237// The case `Foo* const` -> `Foo* const`238// The case `Foo* volatile const` -> `Foo* const volatile`239// The case `int32_t const` -> `int32_t const`240// The case `auto volatile const` -> `auto const volatile`241if (PreviousCheck->isOneOf(TT_PointerOrReference, tok::identifier,242tok::kw_auto)) {243return true;244}245246return false;247}();248249// Find the last qualifier to the right.250const FormatToken *LastQual = Tok;251while (isQualifier(LastQual->getNextNonComment()))252LastQual = LastQual->getNextNonComment();253254// If this qualifier is to the right of a type or pointer do a partial sort255// and return.256if (IsRightQualifier) {257if (LastQual != Tok)258rotateTokens(SourceMgr, Fixes, Tok, LastQual, /*Left=*/false);259return Tok;260}261262const FormatToken *TypeToken = LastQual->getNextNonComment();263if (!TypeToken)264return Tok;265266// Stay safe and don't move past macros, also don't bother with sorting.267if (isPossibleMacro(TypeToken))268return Tok;269270// The case `const long long int volatile` -> `long long int const volatile`271// The case `long const long int volatile` -> `long long int const volatile`272// The case `long long volatile int const` -> `long long int const volatile`273// The case `const long long volatile int` -> `long long int const volatile`274if (TypeToken->isTypeName(LangOpts)) {275// The case `const decltype(foo)` -> `const decltype(foo)`276// The case `const typeof(foo)` -> `const typeof(foo)`277// The case `const _Atomic(foo)` -> `const _Atomic(foo)`278if (TypeToken->isOneOf(tok::kw_decltype, tok::kw_typeof, tok::kw__Atomic))279return Tok;280281const FormatToken *LastSimpleTypeSpecifier = TypeToken;282while (isQualifierOrType(LastSimpleTypeSpecifier->getNextNonComment(),283LangOpts)) {284LastSimpleTypeSpecifier = LastSimpleTypeSpecifier->getNextNonComment();285}286287rotateTokens(SourceMgr, Fixes, Tok, LastSimpleTypeSpecifier,288/*Left=*/false);289return LastSimpleTypeSpecifier;290}291292// The case `unsigned short const` -> `unsigned short const`293// The case:294// `unsigned short volatile const` -> `unsigned short const volatile`295if (PreviousCheck && PreviousCheck->isTypeName(LangOpts)) {296if (LastQual != Tok)297rotateTokens(SourceMgr, Fixes, Tok, LastQual, /*Left=*/false);298return Tok;299}300301// Skip the typename keyword.302// The case `const typename C::type` -> `typename C::type const`303if (TypeToken->is(tok::kw_typename))304TypeToken = TypeToken->getNextNonComment();305306// Skip the initial :: of a global-namespace type.307// The case `const ::...` -> `::... const`308if (TypeToken->is(tok::coloncolon)) {309// The case `const ::template Foo...` -> `::template Foo... const`310TypeToken = TypeToken->getNextNonComment();311if (TypeToken && TypeToken->is(tok::kw_template))312TypeToken = TypeToken->getNextNonComment();313}314315// Don't change declarations such as316// `foo(const struct Foo a);` -> `foo(const struct Foo a);`317// as they would currently change code such as318// `const struct my_struct_t {} my_struct;` -> `struct my_struct_t const {}319// my_struct;`320if (TypeToken->isOneOf(tok::kw_struct, tok::kw_class))321return Tok;322323if (TypeToken->isOneOf(tok::kw_auto, tok::identifier)) {324// The case `const auto` -> `auto const`325// The case `const Foo` -> `Foo const`326// The case `const ::Foo` -> `::Foo const`327// The case `const Foo *` -> `Foo const *`328// The case `const Foo &` -> `Foo const &`329// The case `const Foo &&` -> `Foo const &&`330// The case `const std::Foo &&` -> `std::Foo const &&`331// The case `const std::Foo<T> &&` -> `std::Foo<T> const &&`332// The case `const ::template Foo` -> `::template Foo const`333// The case `const T::template Foo` -> `T::template Foo const`334const FormatToken *Next = nullptr;335while ((Next = TypeToken->getNextNonComment()) &&336(Next->is(TT_TemplateOpener) ||337Next->startsSequence(tok::coloncolon, tok::identifier) ||338Next->startsSequence(tok::coloncolon, tok::kw_template,339tok::identifier))) {340if (Next->is(TT_TemplateOpener)) {341assert(Next->MatchingParen && "Missing template closer");342TypeToken = Next->MatchingParen;343} else if (Next->startsSequence(tok::coloncolon, tok::identifier)) {344TypeToken = Next->getNextNonComment();345} else {346TypeToken = Next->getNextNonComment()->getNextNonComment();347}348}349350if (Next->is(tok::kw_auto))351TypeToken = Next;352353// Place the Qualifier at the end of the list of qualifiers.354while (isQualifier(TypeToken->getNextNonComment())) {355// The case `volatile Foo::iter const` -> `Foo::iter const volatile`356TypeToken = TypeToken->getNextNonComment();357}358359insertQualifierAfter(SourceMgr, Fixes, TypeToken, Qualifier);360// Remove token and following whitespace.361auto Range = CharSourceRange::getCharRange(362Tok->getStartOfNonWhitespace(), Tok->Next->getStartOfNonWhitespace());363replaceToken(SourceMgr, Fixes, Range, "");364}365366return Tok;367}368369const FormatToken *LeftRightQualifierAlignmentFixer::analyzeLeft(370const SourceManager &SourceMgr, const AdditionalKeywords &Keywords,371tooling::Replacements &Fixes, const FormatToken *const Tok,372const std::string &Qualifier, tok::TokenKind QualifierType) {373// We only need to think about streams that begin with a qualifier.374if (Tok->isNot(QualifierType))375return Tok;376// Don't concern yourself if nothing preceeds the qualifier.377if (!Tok->getPreviousNonComment())378return Tok;379380// Skip qualifiers to the left to find what preceeds the qualifiers.381const FormatToken *TypeToken = Tok->getPreviousNonComment();382while (isQualifier(TypeToken))383TypeToken = TypeToken->getPreviousNonComment();384385// For left qualifiers preceeded by nothing, a template declaration, or *,&,&&386// we only perform sorting.387if (!TypeToken || TypeToken->isPointerOrReference() ||388TypeToken->ClosesRequiresClause || TypeToken->ClosesTemplateDeclaration) {389390// Don't sort past a non-configured qualifier token.391const FormatToken *FirstQual = Tok;392while (isConfiguredQualifier(FirstQual->getPreviousNonComment(),393ConfiguredQualifierTokens)) {394FirstQual = FirstQual->getPreviousNonComment();395}396397if (FirstQual != Tok)398rotateTokens(SourceMgr, Fixes, FirstQual, Tok, /*Left=*/true);399return Tok;400}401402// Stay safe and don't move past macros, also don't bother with sorting.403if (isPossibleMacro(TypeToken))404return Tok;405406// Examples given in order of ['const', 'volatile', 'type']407408// The case `volatile long long int const` -> `const volatile long long int`409// The case `volatile long long const int` -> `const volatile long long int`410// The case `const long long volatile int` -> `const volatile long long int`411// The case `long volatile long int const` -> `const volatile long long int`412if (TypeToken->isTypeName(LangOpts)) {413const FormatToken *LastSimpleTypeSpecifier = TypeToken;414while (isConfiguredQualifierOrType(415LastSimpleTypeSpecifier->getPreviousNonComment(),416ConfiguredQualifierTokens, LangOpts)) {417LastSimpleTypeSpecifier =418LastSimpleTypeSpecifier->getPreviousNonComment();419}420421rotateTokens(SourceMgr, Fixes, LastSimpleTypeSpecifier, Tok,422/*Left=*/true);423return Tok;424}425426if (TypeToken->isOneOf(tok::kw_auto, tok::identifier, TT_TemplateCloser)) {427const auto IsStartOfType = [](const FormatToken *const Tok) -> bool {428if (!Tok)429return true;430431// A template closer is not the start of a type.432// The case `?<> const` -> `const ?<>`433if (Tok->is(TT_TemplateCloser))434return false;435436const FormatToken *const Previous = Tok->getPreviousNonComment();437if (!Previous)438return true;439440// An identifier preceeded by :: is not the start of a type.441// The case `?::Foo const` -> `const ?::Foo`442if (Tok->is(tok::identifier) && Previous->is(tok::coloncolon))443return false;444445const FormatToken *const PrePrevious = Previous->getPreviousNonComment();446// An identifier preceeded by ::template is not the start of a type.447// The case `?::template Foo const` -> `const ?::template Foo`448if (Tok->is(tok::identifier) && Previous->is(tok::kw_template) &&449PrePrevious && PrePrevious->is(tok::coloncolon)) {450return false;451}452453if (Tok->endsSequence(tok::kw_auto, tok::identifier))454return false;455456return true;457};458459while (!IsStartOfType(TypeToken)) {460// The case `?<>`461if (TypeToken->is(TT_TemplateCloser)) {462assert(TypeToken->MatchingParen && "Missing template opener");463TypeToken = TypeToken->MatchingParen->getPreviousNonComment();464} else {465// The cases466// `::Foo`467// `?>::Foo`468// `?Bar::Foo`469// `::template Foo`470// `?>::template Foo`471// `?Bar::template Foo`472if (TypeToken->getPreviousNonComment()->is(tok::kw_template))473TypeToken = TypeToken->getPreviousNonComment();474475const FormatToken *const ColonColon =476TypeToken->getPreviousNonComment();477const FormatToken *const PreColonColon =478ColonColon->getPreviousNonComment();479if (PreColonColon &&480PreColonColon->isOneOf(TT_TemplateCloser, tok::identifier)) {481TypeToken = PreColonColon;482} else {483TypeToken = ColonColon;484}485}486}487488assert(TypeToken && "Should be auto or identifier");489490// Place the Qualifier at the start of the list of qualifiers.491const FormatToken *Previous = nullptr;492while ((Previous = TypeToken->getPreviousNonComment()) &&493(isConfiguredQualifier(Previous, ConfiguredQualifierTokens) ||494Previous->is(tok::kw_typename))) {495// The case `volatile Foo::iter const` -> `const volatile Foo::iter`496// The case `typename C::type const` -> `const typename C::type`497TypeToken = Previous;498}499500// Don't change declarations such as501// `foo(struct Foo const a);` -> `foo(struct Foo const a);`502if (!Previous || !Previous->isOneOf(tok::kw_struct, tok::kw_class)) {503insertQualifierBefore(SourceMgr, Fixes, TypeToken, Qualifier);504removeToken(SourceMgr, Fixes, Tok);505}506}507508return Tok;509}510511tok::TokenKind LeftRightQualifierAlignmentFixer::getTokenFromQualifier(512const std::string &Qualifier) {513// Don't let 'type' be an identifier, but steal typeof token.514return llvm::StringSwitch<tok::TokenKind>(Qualifier)515.Case("type", tok::kw_typeof)516.Case("const", tok::kw_const)517.Case("volatile", tok::kw_volatile)518.Case("static", tok::kw_static)519.Case("inline", tok::kw_inline)520.Case("constexpr", tok::kw_constexpr)521.Case("restrict", tok::kw_restrict)522.Case("friend", tok::kw_friend)523.Default(tok::identifier);524}525526LeftRightQualifierAlignmentFixer::LeftRightQualifierAlignmentFixer(527const Environment &Env, const FormatStyle &Style,528const std::string &Qualifier,529const std::vector<tok::TokenKind> &QualifierTokens, bool RightAlign)530: TokenAnalyzer(Env, Style), Qualifier(Qualifier), RightAlign(RightAlign),531ConfiguredQualifierTokens(QualifierTokens) {}532533std::pair<tooling::Replacements, unsigned>534LeftRightQualifierAlignmentFixer::analyze(535TokenAnnotator & /*Annotator*/,536SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,537FormatTokenLexer &Tokens) {538tooling::Replacements Fixes;539AffectedRangeMgr.computeAffectedLines(AnnotatedLines);540fixQualifierAlignment(AnnotatedLines, Tokens, Fixes);541return {Fixes, 0};542}543544void LeftRightQualifierAlignmentFixer::fixQualifierAlignment(545SmallVectorImpl<AnnotatedLine *> &AnnotatedLines, FormatTokenLexer &Tokens,546tooling::Replacements &Fixes) {547const AdditionalKeywords &Keywords = Tokens.getKeywords();548const SourceManager &SourceMgr = Env.getSourceManager();549tok::TokenKind QualifierToken = getTokenFromQualifier(Qualifier);550assert(QualifierToken != tok::identifier && "Unrecognised Qualifier");551552for (AnnotatedLine *Line : AnnotatedLines) {553fixQualifierAlignment(Line->Children, Tokens, Fixes);554if (!Line->Affected || Line->InPPDirective)555continue;556FormatToken *First = Line->First;557assert(First);558if (First->Finalized)559continue;560561const auto *Last = Line->Last;562563for (const auto *Tok = First; Tok && Tok != Last && Tok->Next;564Tok = Tok->Next) {565if (Tok->MustBreakBefore)566break;567if (Tok->is(tok::comment))568continue;569if (RightAlign) {570Tok = analyzeRight(SourceMgr, Keywords, Fixes, Tok, Qualifier,571QualifierToken);572} else {573Tok = analyzeLeft(SourceMgr, Keywords, Fixes, Tok, Qualifier,574QualifierToken);575}576}577}578}579580void prepareLeftRightOrderingForQualifierAlignmentFixer(581const std::vector<std::string> &Order, std::vector<std::string> &LeftOrder,582std::vector<std::string> &RightOrder,583std::vector<tok::TokenKind> &Qualifiers) {584585// Depending on the position of type in the order you need586// To iterate forward or backward through the order list as qualifier587// can push through each other.588// The Order list must define the position of "type" to signify589assert(llvm::is_contained(Order, "type") &&590"QualifierOrder must contain type");591// Split the Order list by type and reverse the left side.592593bool left = true;594for (const auto &s : Order) {595if (s == "type") {596left = false;597continue;598}599600tok::TokenKind QualifierToken =601LeftRightQualifierAlignmentFixer::getTokenFromQualifier(s);602if (QualifierToken != tok::kw_typeof && QualifierToken != tok::identifier)603Qualifiers.push_back(QualifierToken);604605if (left) {606// Reverse the order for left aligned items.607LeftOrder.insert(LeftOrder.begin(), s);608} else {609RightOrder.push_back(s);610}611}612}613614bool isQualifierOrType(const FormatToken *Tok, const LangOptions &LangOpts) {615return Tok && (Tok->isTypeName(LangOpts) || Tok->is(tok::kw_auto) ||616isQualifier(Tok));617}618619bool isConfiguredQualifierOrType(const FormatToken *Tok,620const std::vector<tok::TokenKind> &Qualifiers,621const LangOptions &LangOpts) {622return Tok && (Tok->isTypeName(LangOpts) || Tok->is(tok::kw_auto) ||623isConfiguredQualifier(Tok, Qualifiers));624}625626// If a token is an identifier and it's upper case, it could627// be a macro and hence we need to be able to ignore it.628bool isPossibleMacro(const FormatToken *Tok) {629if (!Tok)630return false;631if (Tok->isNot(tok::identifier))632return false;633if (Tok->TokenText.upper() == Tok->TokenText.str()) {634// T,K,U,V likely could be template arguments635return Tok->TokenText.size() != 1;636}637return false;638}639640} // namespace format641} // namespace clang642643644