aboutsummaryrefslogtreecommitdiff
path: root/clang-tools-extra/clang-tidy/readability/OperatorsRepresentationCheck.cpp
diff options
context:
space:
mode:
authorPiotr Zegar <piotr.zegar@nokia.com>2023-03-31 15:28:12 +0000
committerPiotr Zegar <me@piotrzegar.pl>2023-03-31 16:07:16 +0000
commita084854266ca60748982228a4c98d036bca5f762 (patch)
tree2b58066e9fd54e8ce1870ba807e7f6864a1151d4 /clang-tools-extra/clang-tidy/readability/OperatorsRepresentationCheck.cpp
parentf1c5c84ae1a87c6ea707ae3b3e524e9eeaf1d70a (diff)
downloadllvm-a084854266ca60748982228a4c98d036bca5f762.zip
llvm-a084854266ca60748982228a4c98d036bca5f762.tar.gz
llvm-a084854266ca60748982228a4c98d036bca5f762.tar.bz2
[clang-tidy] Add readability-operators-representation check
Check helps enforce consistent token representation for binary, unary and overloaded operators in C++ code. The check supports both traditional and alternative representations of operators. Reviewed By: carlosgalvezp Differential Revision: https://reviews.llvm.org/D144522
Diffstat (limited to 'clang-tools-extra/clang-tidy/readability/OperatorsRepresentationCheck.cpp')
-rw-r--r--clang-tools-extra/clang-tidy/readability/OperatorsRepresentationCheck.cpp335
1 files changed, 335 insertions, 0 deletions
diff --git a/clang-tools-extra/clang-tidy/readability/OperatorsRepresentationCheck.cpp b/clang-tools-extra/clang-tidy/readability/OperatorsRepresentationCheck.cpp
new file mode 100644
index 0000000..ab45dab
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/readability/OperatorsRepresentationCheck.cpp
@@ -0,0 +1,335 @@
+//===--- 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 llvm::is_contained(Separators, 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