//===--- 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 #include 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, 2U> UnaryRepresentation{{{"!", "not"}, {"~", "compl"}}}; constexpr std::array, 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 &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 static bool isAnyOperatorEnabled(const std::vector &Config, const 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 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("binary_op")) Loc = Op->getOperatorLoc(); else if (const auto *Op = Result.Nodes.getNodeAs("unary_op")) Loc = Op->getOperatorLoc(); else if (const auto *Op = Result.Nodes.getNodeAs("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