//===--- 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 #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 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(); }); } } std::pair QualifierAlignmentFixer::analyze( TokenAnnotator & /*Annotator*/, SmallVectorImpl & /*AnnotatedLines*/, FormatTokenLexer & /*Tokens*/) { auto Env = Environment::make(Code, FileName, Ranges, FirstStartColumn, NextStartColumn, LastStartColumn); if (!Env) return {}; std::optional CurrentCode; tooling::Replacements Fixes; 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); if (I + 1 < E) { CurrentCode = std::move(*NewCode); Env = Environment::make( *CurrentCode, FileName, tooling::calculateRangesAfterReplacements(Fixes, Ranges), FirstStartColumn, NextStartColumn, LastStartColumn); if (!Env) return {}; } } } // Don't make replacements that replace nothing. tooling::Replacements NonNoOpFixes; for (const tooling::Replacement &Fix : Fixes) { StringRef OriginalCode = Code.substr(Fix.getOffset(), Fix.getLength()); if (!OriginalCode.equals(Fix.getReplacementText())) { auto Err = NonNoOpFixes.add(Fix); if (Err) { llvm::errs() << "Error adding replacements : " << llvm::toString(std::move(Err)) << "\n"; } } } return {NonNoOpFixes, 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); } const FormatToken *LeftRightQualifierAlignmentFixer::analyzeRight( const SourceManager &SourceMgr, const AdditionalKeywords &Keywords, tooling::Replacements &Fixes, const 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; auto AnalyzeTemplate = [&](const FormatToken *Tok, const FormatToken *StartTemplate) -> const FormatToken * { // Read from the TemplateOpener to TemplateCloser. FormatToken *EndTemplate = StartTemplate->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; } return nullptr; }; 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_TemplateCloser)) { FormatToken *Closer = Tok->Next->Next; rotateTokens(SourceMgr, Fixes, Tok, Tok->Next, /*Left=*/false); Tok = Closer; return Tok; } else if (Tok->startsSequence(QualifierType, tok::identifier, TT_TemplateOpener)) { // `const ArrayRef a;` // `const ArrayRef &a;` const FormatToken *NewTok = AnalyzeTemplate(Tok, Tok->Next->Next); if (NewTok) return NewTok; } else if (Tok->startsSequence(QualifierType, tok::coloncolon, tok::identifier, TT_TemplateOpener)) { // `const ::ArrayRef a;` // `const ::ArrayRef &a;` const FormatToken *NewTok = AnalyzeTemplate(Tok, Tok->Next->Next->Next); if (NewTok) return NewTok; } else if (Tok->startsSequence(QualifierType, tok::identifier) || Tok->startsSequence(QualifierType, tok::coloncolon, 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 Foo &&` -> `Foo const &&` // The case `const std::Foo &&` -> `std::Foo const &&` // The case `const std::Foo &&` -> `std::Foo const &&` // However, `const Bar::*` remains the same. while (Next && Next->isOneOf(tok::identifier, tok::coloncolon) && !Next->startsSequence(tok::coloncolon, tok::star)) { 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)) { 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; } const FormatToken *LeftRightQualifierAlignmentFixer::analyzeLeft( const SourceManager &SourceMgr, const AdditionalKeywords &Keywords, tooling::Replacements &Fixes, const 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; const FormatToken *Qual = Tok; const 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); if (!Qual->Next) return Tok; 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; } } else if (Tok->startsSequence(tok::identifier, QualifierType, TT_TemplateCloser)) { FormatToken *Closer = Tok->Next->Next; rotateTokens(SourceMgr, Fixes, Tok, Tok->Next, /*Left=*/true); Tok = Closer; } } 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::coloncolon, tok::identifier) || Tok->is(tok::identifier)) && Tok->Next) { if (Tok->Previous && Tok->Previous->isOneOf(tok::star, tok::ampamp, tok::amp)) { return Tok; } const 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; if (Next->is(tok::comment) && Next->getNextNonComment()) Next = Next->getNextNonComment(); assert(Next->MatchingParen && "Missing template closer"); Next = Next->MatchingParen; // If the template closer is closing the requires clause, // then stop and go back to the TemplateOpener and do whatever is // inside the <>. if (Next->ClosesRequiresClause) return Next->MatchingParen; Next = Next->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)) { // Move the qualifier. 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)) { // Move 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 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; 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 (AnnotatedLine *Line : AnnotatedLines) { if (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->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. // 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 LeftRightQualifierAlignmentFixer::isQualifierOrType( const FormatToken *Tok, const std::vector &specifiedTypes) { return Tok && (Tok->isSimpleTypeSpecifier() || Tok->is(tok::kw_auto) || llvm::is_contained(specifiedTypes, Tok->Tok.getKind())); } // 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()) { // T,K,U,V likely could be template arguments return (Tok->TokenText.size() != 1); } return false; } } // namespace format } // namespace clang