blob: a67b1cdc81f987d45bafcdc7eed80beeee0d320a [file] [log] [blame]
//===--- OperatorsRepresentationCheck.cpp - clang-tidy
//--------------------------===//
//
// 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 "OperatorsRepresentationCheck.h"
#include "../utils/OptionsUtils.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Lex/Lexer.h"
#include "llvm/ADT/STLExtras.h"
#include <array>
#include <utility>
using namespace clang::ast_matchers;
namespace clang::tidy::readability {
static StringRef getOperatorSpelling(SourceLocation Loc, ASTContext &Context) {
if (Loc.isInvalid())
return {};
SourceManager &SM = Context.getSourceManager();
Loc = SM.getSpellingLoc(Loc);
if (Loc.isInvalid())
return {};
const CharSourceRange TokenRange = CharSourceRange::getTokenRange(Loc);
return Lexer::getSourceText(TokenRange, SM, Context.getLangOpts());
}
namespace {
AST_MATCHER_P2(BinaryOperator, hasInvalidBinaryOperatorRepresentation,
BinaryOperatorKind, Kind, llvm::StringRef,
ExpectedRepresentation) {
if (Node.getOpcode() != Kind || ExpectedRepresentation.empty())
return false;
StringRef Spelling =
getOperatorSpelling(Node.getOperatorLoc(), Finder->getASTContext());
return !Spelling.empty() && Spelling != ExpectedRepresentation;
}
AST_MATCHER_P2(UnaryOperator, hasInvalidUnaryOperatorRepresentation,
UnaryOperatorKind, Kind, llvm::StringRef,
ExpectedRepresentation) {
if (Node.getOpcode() != Kind || ExpectedRepresentation.empty())
return false;
StringRef Spelling =
getOperatorSpelling(Node.getOperatorLoc(), Finder->getASTContext());
return !Spelling.empty() && Spelling != ExpectedRepresentation;
}
AST_MATCHER_P2(CXXOperatorCallExpr, hasInvalidOverloadedOperatorRepresentation,
OverloadedOperatorKind, Kind, llvm::StringRef,
ExpectedRepresentation) {
if (Node.getOperator() != Kind || ExpectedRepresentation.empty())
return false;
StringRef Spelling =
getOperatorSpelling(Node.getOperatorLoc(), Finder->getASTContext());
return !Spelling.empty() && Spelling != ExpectedRepresentation;
}
} // namespace
constexpr std::array<std::pair<llvm::StringRef, llvm::StringRef>, 2U>
UnaryRepresentation{{{"!", "not"}, {"~", "compl"}}};
constexpr std::array<std::pair<llvm::StringRef, llvm::StringRef>, 9U>
OperatorsRepresentation{{{"&&", "and"},
{"||", "or"},
{"^", "xor"},
{"&", "bitand"},
{"|", "bitor"},
{"&=", "and_eq"},
{"|=", "or_eq"},
{"!=", "not_eq"},
{"^=", "xor_eq"}}};
static llvm::StringRef translate(llvm::StringRef Value) {
for (const auto &[Traditional, Alternative] : UnaryRepresentation) {
if (Value == Traditional)
return Alternative;
if (Value == Alternative)
return Traditional;
}
for (const auto &[Traditional, Alternative] : OperatorsRepresentation) {
if (Value == Traditional)
return Alternative;
if (Value == Alternative)
return Traditional;
}
return {};
}
static bool isNotOperatorStr(llvm::StringRef Value) {
return translate(Value).empty();
}
static bool isSeparator(char C) noexcept {
constexpr llvm::StringRef Separators(" \t\r\n\0()<>{};,");
return Separators.contains(C);
}
static bool needEscaping(llvm::StringRef Operator) {
switch (Operator[0]) {
case '&':
case '|':
case '!':
case '^':
case '~':
return false;
default:
return true;
}
}
static llvm::StringRef
getRepresentation(const std::vector<llvm::StringRef> &Config,
llvm::StringRef Traditional, llvm::StringRef Alternative) {
if (llvm::is_contained(Config, Traditional))
return Traditional;
if (llvm::is_contained(Config, Alternative))
return Alternative;
return {};
}
template <typename T>
static bool isAnyOperatorEnabled(const std::vector<llvm::StringRef> &Config,
T &&Operators) {
for (const auto &[traditional, alternative] : Operators) {
if (!getRepresentation(Config, traditional, alternative).empty())
return true;
}
return false;
}
OperatorsRepresentationCheck::OperatorsRepresentationCheck(
StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
BinaryOperators(
utils::options::parseStringList(Options.get("BinaryOperators", ""))),
OverloadedOperators(utils::options::parseStringList(
Options.get("OverloadedOperators", ""))) {
llvm::erase_if(BinaryOperators, isNotOperatorStr);
llvm::erase_if(OverloadedOperators, isNotOperatorStr);
}
void OperatorsRepresentationCheck::storeOptions(
ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "BinaryOperators",
utils::options::serializeStringList(BinaryOperators));
Options.store(Opts, "OverloadedOperators",
utils::options::serializeStringList(OverloadedOperators));
}
std::optional<TraversalKind>
OperatorsRepresentationCheck::getCheckTraversalKind() const {
return TK_IgnoreUnlessSpelledInSource;
}
bool OperatorsRepresentationCheck::isLanguageVersionSupported(
const LangOptions &LangOpts) const {
return LangOpts.CPlusPlus;
}
void OperatorsRepresentationCheck::registerBinaryOperatorMatcher(
MatchFinder *Finder) {
if (!isAnyOperatorEnabled(BinaryOperators, OperatorsRepresentation))
return;
Finder->addMatcher(
binaryOperator(
unless(isExpansionInSystemHeader()),
anyOf(hasInvalidBinaryOperatorRepresentation(
BO_LAnd, getRepresentation(BinaryOperators, "&&", "and")),
hasInvalidBinaryOperatorRepresentation(
BO_LOr, getRepresentation(BinaryOperators, "||", "or")),
hasInvalidBinaryOperatorRepresentation(
BO_NE, getRepresentation(BinaryOperators, "!=", "not_eq")),
hasInvalidBinaryOperatorRepresentation(
BO_Xor, getRepresentation(BinaryOperators, "^", "xor")),
hasInvalidBinaryOperatorRepresentation(
BO_And, getRepresentation(BinaryOperators, "&", "bitand")),
hasInvalidBinaryOperatorRepresentation(
BO_Or, getRepresentation(BinaryOperators, "|", "bitor")),
hasInvalidBinaryOperatorRepresentation(
BO_AndAssign,
getRepresentation(BinaryOperators, "&=", "and_eq")),
hasInvalidBinaryOperatorRepresentation(
BO_OrAssign,
getRepresentation(BinaryOperators, "|=", "or_eq")),
hasInvalidBinaryOperatorRepresentation(
BO_XorAssign,
getRepresentation(BinaryOperators, "^=", "xor_eq"))))
.bind("binary_op"),
this);
}
void OperatorsRepresentationCheck::registerUnaryOperatorMatcher(
MatchFinder *Finder) {
if (!isAnyOperatorEnabled(BinaryOperators, UnaryRepresentation))
return;
Finder->addMatcher(
unaryOperator(
unless(isExpansionInSystemHeader()),
anyOf(hasInvalidUnaryOperatorRepresentation(
UO_LNot, getRepresentation(BinaryOperators, "!", "not")),
hasInvalidUnaryOperatorRepresentation(
UO_Not, getRepresentation(BinaryOperators, "~", "compl"))))
.bind("unary_op"),
this);
}
void OperatorsRepresentationCheck::registerOverloadedOperatorMatcher(
MatchFinder *Finder) {
if (!isAnyOperatorEnabled(OverloadedOperators, OperatorsRepresentation) &&
!isAnyOperatorEnabled(OverloadedOperators, UnaryRepresentation))
return;
Finder->addMatcher(
cxxOperatorCallExpr(
unless(isExpansionInSystemHeader()),
anyOf(
hasInvalidOverloadedOperatorRepresentation(
OO_AmpAmp,
getRepresentation(OverloadedOperators, "&&", "and")),
hasInvalidOverloadedOperatorRepresentation(
OO_PipePipe,
getRepresentation(OverloadedOperators, "||", "or")),
hasInvalidOverloadedOperatorRepresentation(
OO_Exclaim,
getRepresentation(OverloadedOperators, "!", "not")),
hasInvalidOverloadedOperatorRepresentation(
OO_ExclaimEqual,
getRepresentation(OverloadedOperators, "!=", "not_eq")),
hasInvalidOverloadedOperatorRepresentation(
OO_Caret, getRepresentation(OverloadedOperators, "^", "xor")),
hasInvalidOverloadedOperatorRepresentation(
OO_Amp,
getRepresentation(OverloadedOperators, "&", "bitand")),
hasInvalidOverloadedOperatorRepresentation(
OO_Pipe,
getRepresentation(OverloadedOperators, "|", "bitor")),
hasInvalidOverloadedOperatorRepresentation(
OO_AmpEqual,
getRepresentation(OverloadedOperators, "&=", "and_eq")),
hasInvalidOverloadedOperatorRepresentation(
OO_PipeEqual,
getRepresentation(OverloadedOperators, "|=", "or_eq")),
hasInvalidOverloadedOperatorRepresentation(
OO_CaretEqual,
getRepresentation(OverloadedOperators, "^=", "xor_eq")),
hasInvalidOverloadedOperatorRepresentation(
OO_Tilde,
getRepresentation(OverloadedOperators, "~", "compl"))))
.bind("overloaded_op"),
this);
}
void OperatorsRepresentationCheck::registerMatchers(MatchFinder *Finder) {
registerBinaryOperatorMatcher(Finder);
registerUnaryOperatorMatcher(Finder);
registerOverloadedOperatorMatcher(Finder);
}
void OperatorsRepresentationCheck::check(
const MatchFinder::MatchResult &Result) {
SourceLocation Loc;
if (const auto *Op = Result.Nodes.getNodeAs<BinaryOperator>("binary_op"))
Loc = Op->getOperatorLoc();
else if (const auto *Op = Result.Nodes.getNodeAs<UnaryOperator>("unary_op"))
Loc = Op->getOperatorLoc();
else if (const auto *Op =
Result.Nodes.getNodeAs<CXXOperatorCallExpr>("overloaded_op"))
Loc = Op->getOperatorLoc();
if (Loc.isInvalid())
return;
Loc = Result.SourceManager->getSpellingLoc(Loc);
if (Loc.isInvalid() || Loc.isMacroID())
return;
const CharSourceRange TokenRange = CharSourceRange::getTokenRange(Loc);
if (TokenRange.isInvalid())
return;
StringRef Spelling = Lexer::getSourceText(TokenRange, *Result.SourceManager,
Result.Context->getLangOpts());
StringRef TranslatedSpelling = translate(Spelling);
if (TranslatedSpelling.empty())
return;
std::string FixSpelling = TranslatedSpelling.str();
StringRef SourceRepresentation = "an alternative";
StringRef TargetRepresentation = "a traditional";
if (needEscaping(TranslatedSpelling)) {
SourceRepresentation = "a traditional";
TargetRepresentation = "an alternative";
StringRef SpellingEx = Lexer::getSourceText(
CharSourceRange::getCharRange(
TokenRange.getBegin().getLocWithOffset(-1),
TokenRange.getBegin().getLocWithOffset(Spelling.size() + 1U)),
*Result.SourceManager, Result.Context->getLangOpts());
if (SpellingEx.empty() || !isSeparator(SpellingEx.front()))
FixSpelling.insert(FixSpelling.begin(), ' ');
if (SpellingEx.empty() || !isSeparator(SpellingEx.back()))
FixSpelling.push_back(' ');
}
diag(
Loc,
"'%0' is %1 token spelling, consider using %2 token '%3' for consistency")
<< Spelling << SourceRepresentation << TargetRepresentation
<< TranslatedSpelling
<< FixItHint::CreateReplacement(TokenRange, FixSpelling);
}
} // namespace clang::tidy::readability