1 //===--- OperatorsRepresentationCheck.cpp - clang-tidy
2 //--------------------------===//
4 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5 // See https://llvm.org/LICENSE.txt for license information.
6 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
8 //===----------------------------------------------------------------------===//
10 #include "OperatorsRepresentationCheck.h"
11 #include "../utils/OptionsUtils.h"
12 #include "clang/AST/ASTContext.h"
13 #include "clang/ASTMatchers/ASTMatchFinder.h"
14 #include "clang/Lex/Lexer.h"
15 #include "llvm/ADT/STLExtras.h"
19 using namespace clang::ast_matchers
;
21 namespace clang::tidy::readability
{
23 static StringRef
getOperatorSpelling(SourceLocation Loc
, ASTContext
&Context
) {
27 SourceManager
&SM
= Context
.getSourceManager();
29 Loc
= SM
.getSpellingLoc(Loc
);
33 const CharSourceRange TokenRange
= CharSourceRange::getTokenRange(Loc
);
34 return Lexer::getSourceText(TokenRange
, SM
, Context
.getLangOpts());
39 AST_MATCHER_P2(BinaryOperator
, hasInvalidBinaryOperatorRepresentation
,
40 BinaryOperatorKind
, Kind
, llvm::StringRef
,
41 ExpectedRepresentation
) {
42 if (Node
.getOpcode() != Kind
|| ExpectedRepresentation
.empty())
46 getOperatorSpelling(Node
.getOperatorLoc(), Finder
->getASTContext());
47 return !Spelling
.empty() && Spelling
!= ExpectedRepresentation
;
50 AST_MATCHER_P2(UnaryOperator
, hasInvalidUnaryOperatorRepresentation
,
51 UnaryOperatorKind
, Kind
, llvm::StringRef
,
52 ExpectedRepresentation
) {
53 if (Node
.getOpcode() != Kind
|| ExpectedRepresentation
.empty())
57 getOperatorSpelling(Node
.getOperatorLoc(), Finder
->getASTContext());
58 return !Spelling
.empty() && Spelling
!= ExpectedRepresentation
;
61 AST_MATCHER_P2(CXXOperatorCallExpr
, hasInvalidOverloadedOperatorRepresentation
,
62 OverloadedOperatorKind
, Kind
, llvm::StringRef
,
63 ExpectedRepresentation
) {
64 if (Node
.getOperator() != Kind
|| ExpectedRepresentation
.empty())
68 getOperatorSpelling(Node
.getOperatorLoc(), Finder
->getASTContext());
69 return !Spelling
.empty() && Spelling
!= ExpectedRepresentation
;
74 constexpr std::array
<std::pair
<llvm::StringRef
, llvm::StringRef
>, 2U>
75 UnaryRepresentation
{{{"!", "not"}, {"~", "compl"}}};
77 constexpr std::array
<std::pair
<llvm::StringRef
, llvm::StringRef
>, 9U>
78 OperatorsRepresentation
{{{"&&", "and"},
88 static llvm::StringRef
translate(llvm::StringRef Value
) {
89 for (const auto &[Traditional
, Alternative
] : UnaryRepresentation
) {
90 if (Value
== Traditional
)
92 if (Value
== Alternative
)
96 for (const auto &[Traditional
, Alternative
] : OperatorsRepresentation
) {
97 if (Value
== Traditional
)
99 if (Value
== Alternative
)
105 static bool isNotOperatorStr(llvm::StringRef Value
) {
106 return translate(Value
).empty();
109 static bool isSeparator(char C
) noexcept
{
110 constexpr llvm::StringRef
Separators(" \t\r\n\0()<>{};,");
111 return Separators
.contains(C
);
114 static bool needEscaping(llvm::StringRef Operator
) {
115 switch (Operator
[0]) {
127 static llvm::StringRef
128 getRepresentation(const std::vector
<llvm::StringRef
> &Config
,
129 llvm::StringRef Traditional
, llvm::StringRef Alternative
) {
130 if (llvm::is_contained(Config
, Traditional
))
132 if (llvm::is_contained(Config
, Alternative
))
137 template <typename T
>
138 static bool isAnyOperatorEnabled(const std::vector
<llvm::StringRef
> &Config
,
139 const T
&Operators
) {
140 for (const auto &[traditional
, alternative
] : Operators
) {
141 if (!getRepresentation(Config
, traditional
, alternative
).empty())
147 OperatorsRepresentationCheck::OperatorsRepresentationCheck(
148 StringRef Name
, ClangTidyContext
*Context
)
149 : ClangTidyCheck(Name
, Context
),
151 utils::options::parseStringList(Options
.get("BinaryOperators", ""))),
152 OverloadedOperators(utils::options::parseStringList(
153 Options
.get("OverloadedOperators", ""))) {
154 llvm::erase_if(BinaryOperators
, isNotOperatorStr
);
155 llvm::erase_if(OverloadedOperators
, isNotOperatorStr
);
158 void OperatorsRepresentationCheck::storeOptions(
159 ClangTidyOptions::OptionMap
&Opts
) {
160 Options
.store(Opts
, "BinaryOperators",
161 utils::options::serializeStringList(BinaryOperators
));
162 Options
.store(Opts
, "OverloadedOperators",
163 utils::options::serializeStringList(OverloadedOperators
));
166 std::optional
<TraversalKind
>
167 OperatorsRepresentationCheck::getCheckTraversalKind() const {
168 return TK_IgnoreUnlessSpelledInSource
;
171 bool OperatorsRepresentationCheck::isLanguageVersionSupported(
172 const LangOptions
&LangOpts
) const {
173 return LangOpts
.CPlusPlus
;
176 void OperatorsRepresentationCheck::registerBinaryOperatorMatcher(
177 MatchFinder
*Finder
) {
178 if (!isAnyOperatorEnabled(BinaryOperators
, OperatorsRepresentation
))
183 unless(isExpansionInSystemHeader()),
184 anyOf(hasInvalidBinaryOperatorRepresentation(
185 BO_LAnd
, getRepresentation(BinaryOperators
, "&&", "and")),
186 hasInvalidBinaryOperatorRepresentation(
187 BO_LOr
, getRepresentation(BinaryOperators
, "||", "or")),
188 hasInvalidBinaryOperatorRepresentation(
189 BO_NE
, getRepresentation(BinaryOperators
, "!=", "not_eq")),
190 hasInvalidBinaryOperatorRepresentation(
191 BO_Xor
, getRepresentation(BinaryOperators
, "^", "xor")),
192 hasInvalidBinaryOperatorRepresentation(
193 BO_And
, getRepresentation(BinaryOperators
, "&", "bitand")),
194 hasInvalidBinaryOperatorRepresentation(
195 BO_Or
, getRepresentation(BinaryOperators
, "|", "bitor")),
196 hasInvalidBinaryOperatorRepresentation(
198 getRepresentation(BinaryOperators
, "&=", "and_eq")),
199 hasInvalidBinaryOperatorRepresentation(
201 getRepresentation(BinaryOperators
, "|=", "or_eq")),
202 hasInvalidBinaryOperatorRepresentation(
204 getRepresentation(BinaryOperators
, "^=", "xor_eq"))))
209 void OperatorsRepresentationCheck::registerUnaryOperatorMatcher(
210 MatchFinder
*Finder
) {
211 if (!isAnyOperatorEnabled(BinaryOperators
, UnaryRepresentation
))
216 unless(isExpansionInSystemHeader()),
217 anyOf(hasInvalidUnaryOperatorRepresentation(
218 UO_LNot
, getRepresentation(BinaryOperators
, "!", "not")),
219 hasInvalidUnaryOperatorRepresentation(
220 UO_Not
, getRepresentation(BinaryOperators
, "~", "compl"))))
225 void OperatorsRepresentationCheck::registerOverloadedOperatorMatcher(
226 MatchFinder
*Finder
) {
227 if (!isAnyOperatorEnabled(OverloadedOperators
, OperatorsRepresentation
) &&
228 !isAnyOperatorEnabled(OverloadedOperators
, UnaryRepresentation
))
233 unless(isExpansionInSystemHeader()),
235 hasInvalidOverloadedOperatorRepresentation(
237 getRepresentation(OverloadedOperators
, "&&", "and")),
238 hasInvalidOverloadedOperatorRepresentation(
240 getRepresentation(OverloadedOperators
, "||", "or")),
241 hasInvalidOverloadedOperatorRepresentation(
243 getRepresentation(OverloadedOperators
, "!", "not")),
244 hasInvalidOverloadedOperatorRepresentation(
246 getRepresentation(OverloadedOperators
, "!=", "not_eq")),
247 hasInvalidOverloadedOperatorRepresentation(
248 OO_Caret
, getRepresentation(OverloadedOperators
, "^", "xor")),
249 hasInvalidOverloadedOperatorRepresentation(
251 getRepresentation(OverloadedOperators
, "&", "bitand")),
252 hasInvalidOverloadedOperatorRepresentation(
254 getRepresentation(OverloadedOperators
, "|", "bitor")),
255 hasInvalidOverloadedOperatorRepresentation(
257 getRepresentation(OverloadedOperators
, "&=", "and_eq")),
258 hasInvalidOverloadedOperatorRepresentation(
260 getRepresentation(OverloadedOperators
, "|=", "or_eq")),
261 hasInvalidOverloadedOperatorRepresentation(
263 getRepresentation(OverloadedOperators
, "^=", "xor_eq")),
264 hasInvalidOverloadedOperatorRepresentation(
266 getRepresentation(OverloadedOperators
, "~", "compl"))))
267 .bind("overloaded_op"),
271 void OperatorsRepresentationCheck::registerMatchers(MatchFinder
*Finder
) {
272 registerBinaryOperatorMatcher(Finder
);
273 registerUnaryOperatorMatcher(Finder
);
274 registerOverloadedOperatorMatcher(Finder
);
277 void OperatorsRepresentationCheck::check(
278 const MatchFinder::MatchResult
&Result
) {
282 if (const auto *Op
= Result
.Nodes
.getNodeAs
<BinaryOperator
>("binary_op"))
283 Loc
= Op
->getOperatorLoc();
284 else if (const auto *Op
= Result
.Nodes
.getNodeAs
<UnaryOperator
>("unary_op"))
285 Loc
= Op
->getOperatorLoc();
286 else if (const auto *Op
=
287 Result
.Nodes
.getNodeAs
<CXXOperatorCallExpr
>("overloaded_op"))
288 Loc
= Op
->getOperatorLoc();
293 Loc
= Result
.SourceManager
->getSpellingLoc(Loc
);
294 if (Loc
.isInvalid() || Loc
.isMacroID())
297 const CharSourceRange TokenRange
= CharSourceRange::getTokenRange(Loc
);
298 if (TokenRange
.isInvalid())
301 StringRef Spelling
= Lexer::getSourceText(TokenRange
, *Result
.SourceManager
,
302 Result
.Context
->getLangOpts());
303 StringRef TranslatedSpelling
= translate(Spelling
);
305 if (TranslatedSpelling
.empty())
308 std::string FixSpelling
= TranslatedSpelling
.str();
310 StringRef SourceRepresentation
= "an alternative";
311 StringRef TargetRepresentation
= "a traditional";
312 if (needEscaping(TranslatedSpelling
)) {
313 SourceRepresentation
= "a traditional";
314 TargetRepresentation
= "an alternative";
316 StringRef SpellingEx
= Lexer::getSourceText(
317 CharSourceRange::getCharRange(
318 TokenRange
.getBegin().getLocWithOffset(-1),
319 TokenRange
.getBegin().getLocWithOffset(Spelling
.size() + 1U)),
320 *Result
.SourceManager
, Result
.Context
->getLangOpts());
321 if (SpellingEx
.empty() || !isSeparator(SpellingEx
.front()))
322 FixSpelling
.insert(FixSpelling
.begin(), ' ');
323 if (SpellingEx
.empty() || !isSeparator(SpellingEx
.back()))
324 FixSpelling
.push_back(' ');
329 "'%0' is %1 token spelling, consider using %2 token '%3' for consistency")
330 << Spelling
<< SourceRepresentation
<< TargetRepresentation
331 << TranslatedSpelling
332 << FixItHint::CreateReplacement(TokenRange
, FixSpelling
);
335 } // namespace clang::tidy::readability