334 lines · cpp
1//===----------------------------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "OperatorsRepresentationCheck.h"10#include "../utils/OptionsUtils.h"11#include "clang/AST/ASTContext.h"12#include "clang/ASTMatchers/ASTMatchFinder.h"13#include "clang/Lex/Lexer.h"14#include "llvm/ADT/STLExtras.h"15#include <array>16#include <utility>17 18using namespace clang::ast_matchers;19 20namespace clang::tidy::readability {21 22static StringRef getOperatorSpelling(SourceLocation Loc, ASTContext &Context) {23 if (Loc.isInvalid())24 return {};25 26 const SourceManager &SM = Context.getSourceManager();27 28 Loc = SM.getSpellingLoc(Loc);29 if (Loc.isInvalid())30 return {};31 32 const CharSourceRange TokenRange = CharSourceRange::getTokenRange(Loc);33 return Lexer::getSourceText(TokenRange, SM, Context.getLangOpts());34}35 36namespace {37 38AST_MATCHER_P2(BinaryOperator, hasInvalidBinaryOperatorRepresentation,39 BinaryOperatorKind, Kind, llvm::StringRef,40 ExpectedRepresentation) {41 if (Node.getOpcode() != Kind || ExpectedRepresentation.empty())42 return false;43 44 const StringRef Spelling =45 getOperatorSpelling(Node.getOperatorLoc(), Finder->getASTContext());46 return !Spelling.empty() && Spelling != ExpectedRepresentation;47}48 49AST_MATCHER_P2(UnaryOperator, hasInvalidUnaryOperatorRepresentation,50 UnaryOperatorKind, Kind, llvm::StringRef,51 ExpectedRepresentation) {52 if (Node.getOpcode() != Kind || ExpectedRepresentation.empty())53 return false;54 55 const StringRef Spelling =56 getOperatorSpelling(Node.getOperatorLoc(), Finder->getASTContext());57 return !Spelling.empty() && Spelling != ExpectedRepresentation;58}59 60AST_MATCHER_P2(CXXOperatorCallExpr, hasInvalidOverloadedOperatorRepresentation,61 OverloadedOperatorKind, Kind, llvm::StringRef,62 ExpectedRepresentation) {63 if (Node.getOperator() != Kind || ExpectedRepresentation.empty())64 return false;65 66 const StringRef Spelling =67 getOperatorSpelling(Node.getOperatorLoc(), Finder->getASTContext());68 return !Spelling.empty() && Spelling != ExpectedRepresentation;69}70 71} // namespace72 73constexpr std::array<std::pair<llvm::StringRef, llvm::StringRef>, 2U>74 UnaryRepresentation{{{"!", "not"}, {"~", "compl"}}};75 76constexpr std::array<std::pair<llvm::StringRef, llvm::StringRef>, 9U>77 OperatorsRepresentation{{{"&&", "and"},78 {"||", "or"},79 {"^", "xor"},80 {"&", "bitand"},81 {"|", "bitor"},82 {"&=", "and_eq"},83 {"|=", "or_eq"},84 {"!=", "not_eq"},85 {"^=", "xor_eq"}}};86 87static llvm::StringRef translate(llvm::StringRef Value) {88 for (const auto &[Traditional, Alternative] : UnaryRepresentation) {89 if (Value == Traditional)90 return Alternative;91 if (Value == Alternative)92 return Traditional;93 }94 95 for (const auto &[Traditional, Alternative] : OperatorsRepresentation) {96 if (Value == Traditional)97 return Alternative;98 if (Value == Alternative)99 return Traditional;100 }101 return {};102}103 104static bool isNotOperatorStr(llvm::StringRef Value) {105 return translate(Value).empty();106}107 108static bool isSeparator(char C) noexcept {109 constexpr llvm::StringRef Separators(" \t\r\n\0()<>{};,");110 return Separators.contains(C);111}112 113static bool needEscaping(llvm::StringRef Operator) {114 switch (Operator[0]) {115 case '&':116 case '|':117 case '!':118 case '^':119 case '~':120 return false;121 default:122 return true;123 }124}125 126static llvm::StringRef127getRepresentation(const std::vector<llvm::StringRef> &Config,128 llvm::StringRef Traditional, llvm::StringRef Alternative) {129 if (llvm::is_contained(Config, Traditional))130 return Traditional;131 if (llvm::is_contained(Config, Alternative))132 return Alternative;133 return {};134}135 136template <typename T>137static bool isAnyOperatorEnabled(const std::vector<llvm::StringRef> &Config,138 const T &Operators) {139 for (const auto &[traditional, alternative] : Operators) {140 if (!getRepresentation(Config, traditional, alternative).empty())141 return true;142 }143 return false;144}145 146OperatorsRepresentationCheck::OperatorsRepresentationCheck(147 StringRef Name, ClangTidyContext *Context)148 : ClangTidyCheck(Name, Context),149 BinaryOperators(150 utils::options::parseStringList(Options.get("BinaryOperators", ""))),151 OverloadedOperators(utils::options::parseStringList(152 Options.get("OverloadedOperators", ""))) {153 llvm::erase_if(BinaryOperators, isNotOperatorStr);154 llvm::erase_if(OverloadedOperators, isNotOperatorStr);155}156 157void OperatorsRepresentationCheck::storeOptions(158 ClangTidyOptions::OptionMap &Opts) {159 Options.store(Opts, "BinaryOperators",160 utils::options::serializeStringList(BinaryOperators));161 Options.store(Opts, "OverloadedOperators",162 utils::options::serializeStringList(OverloadedOperators));163}164 165std::optional<TraversalKind>166OperatorsRepresentationCheck::getCheckTraversalKind() const {167 return TK_IgnoreUnlessSpelledInSource;168}169 170bool OperatorsRepresentationCheck::isLanguageVersionSupported(171 const LangOptions &LangOpts) const {172 return LangOpts.CPlusPlus;173}174 175void OperatorsRepresentationCheck::registerBinaryOperatorMatcher(176 MatchFinder *Finder) {177 if (!isAnyOperatorEnabled(BinaryOperators, OperatorsRepresentation))178 return;179 180 Finder->addMatcher(181 binaryOperator(182 unless(isExpansionInSystemHeader()),183 anyOf(hasInvalidBinaryOperatorRepresentation(184 BO_LAnd, getRepresentation(BinaryOperators, "&&", "and")),185 hasInvalidBinaryOperatorRepresentation(186 BO_LOr, getRepresentation(BinaryOperators, "||", "or")),187 hasInvalidBinaryOperatorRepresentation(188 BO_NE, getRepresentation(BinaryOperators, "!=", "not_eq")),189 hasInvalidBinaryOperatorRepresentation(190 BO_Xor, getRepresentation(BinaryOperators, "^", "xor")),191 hasInvalidBinaryOperatorRepresentation(192 BO_And, getRepresentation(BinaryOperators, "&", "bitand")),193 hasInvalidBinaryOperatorRepresentation(194 BO_Or, getRepresentation(BinaryOperators, "|", "bitor")),195 hasInvalidBinaryOperatorRepresentation(196 BO_AndAssign,197 getRepresentation(BinaryOperators, "&=", "and_eq")),198 hasInvalidBinaryOperatorRepresentation(199 BO_OrAssign,200 getRepresentation(BinaryOperators, "|=", "or_eq")),201 hasInvalidBinaryOperatorRepresentation(202 BO_XorAssign,203 getRepresentation(BinaryOperators, "^=", "xor_eq"))))204 .bind("binary_op"),205 this);206}207 208void OperatorsRepresentationCheck::registerUnaryOperatorMatcher(209 MatchFinder *Finder) {210 if (!isAnyOperatorEnabled(BinaryOperators, UnaryRepresentation))211 return;212 213 Finder->addMatcher(214 unaryOperator(215 unless(isExpansionInSystemHeader()),216 anyOf(hasInvalidUnaryOperatorRepresentation(217 UO_LNot, getRepresentation(BinaryOperators, "!", "not")),218 hasInvalidUnaryOperatorRepresentation(219 UO_Not, getRepresentation(BinaryOperators, "~", "compl"))))220 .bind("unary_op"),221 this);222}223 224void OperatorsRepresentationCheck::registerOverloadedOperatorMatcher(225 MatchFinder *Finder) {226 if (!isAnyOperatorEnabled(OverloadedOperators, OperatorsRepresentation) &&227 !isAnyOperatorEnabled(OverloadedOperators, UnaryRepresentation))228 return;229 230 Finder->addMatcher(231 cxxOperatorCallExpr(232 unless(isExpansionInSystemHeader()),233 anyOf(234 hasInvalidOverloadedOperatorRepresentation(235 OO_AmpAmp,236 getRepresentation(OverloadedOperators, "&&", "and")),237 hasInvalidOverloadedOperatorRepresentation(238 OO_PipePipe,239 getRepresentation(OverloadedOperators, "||", "or")),240 hasInvalidOverloadedOperatorRepresentation(241 OO_Exclaim,242 getRepresentation(OverloadedOperators, "!", "not")),243 hasInvalidOverloadedOperatorRepresentation(244 OO_ExclaimEqual,245 getRepresentation(OverloadedOperators, "!=", "not_eq")),246 hasInvalidOverloadedOperatorRepresentation(247 OO_Caret, getRepresentation(OverloadedOperators, "^", "xor")),248 hasInvalidOverloadedOperatorRepresentation(249 OO_Amp,250 getRepresentation(OverloadedOperators, "&", "bitand")),251 hasInvalidOverloadedOperatorRepresentation(252 OO_Pipe,253 getRepresentation(OverloadedOperators, "|", "bitor")),254 hasInvalidOverloadedOperatorRepresentation(255 OO_AmpEqual,256 getRepresentation(OverloadedOperators, "&=", "and_eq")),257 hasInvalidOverloadedOperatorRepresentation(258 OO_PipeEqual,259 getRepresentation(OverloadedOperators, "|=", "or_eq")),260 hasInvalidOverloadedOperatorRepresentation(261 OO_CaretEqual,262 getRepresentation(OverloadedOperators, "^=", "xor_eq")),263 hasInvalidOverloadedOperatorRepresentation(264 OO_Tilde,265 getRepresentation(OverloadedOperators, "~", "compl"))))266 .bind("overloaded_op"),267 this);268}269 270void OperatorsRepresentationCheck::registerMatchers(MatchFinder *Finder) {271 registerBinaryOperatorMatcher(Finder);272 registerUnaryOperatorMatcher(Finder);273 registerOverloadedOperatorMatcher(Finder);274}275 276void OperatorsRepresentationCheck::check(277 const MatchFinder::MatchResult &Result) {278 SourceLocation Loc;279 280 if (const auto *Op = Result.Nodes.getNodeAs<BinaryOperator>("binary_op"))281 Loc = Op->getOperatorLoc();282 else if (const auto *Op = Result.Nodes.getNodeAs<UnaryOperator>("unary_op"))283 Loc = Op->getOperatorLoc();284 else if (const auto *Op =285 Result.Nodes.getNodeAs<CXXOperatorCallExpr>("overloaded_op"))286 Loc = Op->getOperatorLoc();287 288 if (Loc.isInvalid())289 return;290 291 Loc = Result.SourceManager->getSpellingLoc(Loc);292 if (Loc.isInvalid() || Loc.isMacroID())293 return;294 295 const CharSourceRange TokenRange = CharSourceRange::getTokenRange(Loc);296 if (TokenRange.isInvalid())297 return;298 299 const StringRef Spelling = Lexer::getSourceText(300 TokenRange, *Result.SourceManager, Result.Context->getLangOpts());301 const StringRef TranslatedSpelling = translate(Spelling);302 303 if (TranslatedSpelling.empty())304 return;305 306 std::string FixSpelling = TranslatedSpelling.str();307 308 StringRef SourceRepresentation = "an alternative";309 StringRef TargetRepresentation = "a traditional";310 if (needEscaping(TranslatedSpelling)) {311 SourceRepresentation = "a traditional";312 TargetRepresentation = "an alternative";313 314 const StringRef SpellingEx = Lexer::getSourceText(315 CharSourceRange::getCharRange(316 TokenRange.getBegin().getLocWithOffset(-1),317 TokenRange.getBegin().getLocWithOffset(Spelling.size() + 1U)),318 *Result.SourceManager, Result.Context->getLangOpts());319 if (SpellingEx.empty() || !isSeparator(SpellingEx.front()))320 FixSpelling.insert(FixSpelling.begin(), ' ');321 if (SpellingEx.empty() || !isSeparator(SpellingEx.back()))322 FixSpelling.push_back(' ');323 }324 325 diag(326 Loc,327 "'%0' is %1 token spelling, consider using %2 token '%3' for consistency")328 << Spelling << SourceRepresentation << TargetRepresentation329 << TranslatedSpelling330 << FixItHint::CreateReplacement(TokenRange, FixSpelling);331}332 333} // namespace clang::tidy::readability334