llvm-project/clang/lib/Format/QualifierAlignmentFixer.cpp
mydeveloperday a44ab17025 [clang-format] Add Left/Right Const fixer capability
Developers these days seem to argue over east vs west const like they used to argue over tabs vs whitespace or the various bracing style. These previous arguments were mainly eliminated with tools like `clang-format` that allowed those rules to become part of your style guide. Anyone who has been using clang-format in a large team over the last couple of years knows that we don't have those religious arguments any more, and code reviews are more productive.

https://www.youtube.com/watch?v=fv--IKZFVO8
https://mariusbancila.ro/blog/2018/11/23/join-the-east-const-revolution/
https://www.youtube.com/watch?v=z6s6bacI424

The purpose of this revision is to try to do the same for the East/West const discussion. Move the debate into the style guide and leave it there!

In addition to the new `ConstStyle: Right` or `ConstStyle: Left` there is an additional command-line argument `--const-style=left/right` which would allow an individual developer to switch the source back and forth to their own style for editing, and back to the committed style before commit. (you could imagine an IDE might offer such a switch)

The revision works by implementing a separate pass of the Annotated lines much like the SortIncludes and then create replacements for constant type declarations.

Differential Revision: https://reviews.llvm.org/D69764
2021-09-23 20:00:33 +01:00

456 lines
17 KiB
C++

//===--- 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 <algorithm>
#define DEBUG_TYPE "format-qualifier-alignment-fixer"
namespace clang {
namespace format {
QualifierAlignmentFixer::QualifierAlignmentFixer(
const Environment &Env, const FormatStyle &Style, StringRef &Code,
ArrayRef<tooling::Range> 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<std::string> LeftOrder;
std::vector<std::string> RightOrder;
std::vector<tok::TokenKind> 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<tooling::Replacements, unsigned> QualifierAlignmentFixer::analyze(
TokenAnnotator &Annotator, SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
FormatTokenLexer &Tokens) {
auto Env =
std::make_unique<Environment>(Code, FileName, Ranges, FirstStartColumn,
NextStartColumn, LastStartColumn);
llvm::Optional<std::string> CurrentCode = None;
tooling::Replacements Fixes;
unsigned Penalty = 0;
for (size_t I = 0, E = Passes.size(); I < E; ++I) {
std::pair<tooling::Replacements, unsigned> 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<Environment>(
*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<int> a; const ArrayRef<int> &a;
FormatToken *EndTemplate = Tok->Next->Next->MatchingParen;
if (EndTemplate) {
// Move to the end of any template class members e.g.
// `Foo<int>::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<T> &&` -> `std::Foo<T> 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<int>::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<T> const` -> `const std::Foo<T> &&`
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<int> a; const ArrayRef<int> &a;
assert(Next->MatchingParen && "Missing template closer");
Next = Next->MatchingParen->Next;
// Move to the end of any template class members e.g.
// `Foo<int>::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<tok::TokenKind>(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<tok::TokenKind> &QualifierTokens, bool RightAlign)
: TokenAnalyzer(Env, Style), Qualifier(Qualifier), RightAlign(RightAlign),
ConfiguredQualifierTokens(QualifierTokens) {}
std::pair<tooling::Replacements, unsigned>
LeftRightQualifierAlignmentFixer::analyze(
TokenAnnotator &Annotator, SmallVectorImpl<AnnotatedLine *> &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<std::string> &Order, std::vector<std::string> &LeftOrder,
std::vector<std::string> &RightOrder,
std::vector<tok::TokenKind> &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<tok::TokenKind> &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