clang-tools 23.0.0git
OperatorsRepresentationCheck.cpp
Go to the documentation of this file.
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-exception
6//
7//===----------------------------------------------------------------------===//
8
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
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, StringRef, ExpectedRepresentation) {
40 if (Node.getOpcode() != Kind || ExpectedRepresentation.empty())
41 return false;
42
43 const StringRef Spelling =
44 getOperatorSpelling(Node.getOperatorLoc(), Finder->getASTContext());
45 return !Spelling.empty() && Spelling != ExpectedRepresentation;
46}
47
48AST_MATCHER_P2(UnaryOperator, hasInvalidUnaryOperatorRepresentation,
49 UnaryOperatorKind, Kind, StringRef, ExpectedRepresentation) {
50 if (Node.getOpcode() != Kind || ExpectedRepresentation.empty())
51 return false;
52
53 const StringRef Spelling =
54 getOperatorSpelling(Node.getOperatorLoc(), Finder->getASTContext());
55 return !Spelling.empty() && Spelling != ExpectedRepresentation;
56}
57
58AST_MATCHER_P2(CXXOperatorCallExpr, hasInvalidOverloadedOperatorRepresentation,
59 OverloadedOperatorKind, Kind, StringRef,
60 ExpectedRepresentation) {
61 if (Node.getOperator() != Kind || ExpectedRepresentation.empty())
62 return false;
63
64 const StringRef Spelling =
65 getOperatorSpelling(Node.getOperatorLoc(), Finder->getASTContext());
66 return !Spelling.empty() && Spelling != ExpectedRepresentation;
67}
68
69} // namespace
70
71constexpr std::array<std::pair<StringRef, StringRef>, 2U> UnaryRepresentation{
72 {{"!", "not"}, {"~", "compl"}}};
73
74constexpr std::array<std::pair<StringRef, StringRef>, 9U>
76 {"||", "or"},
77 {"^", "xor"},
78 {"&", "bitand"},
79 {"|", "bitor"},
80 {"&=", "and_eq"},
81 {"|=", "or_eq"},
82 {"!=", "not_eq"},
83 {"^=", "xor_eq"}}};
84
85static StringRef translate(StringRef Value) {
86 for (const auto &[Traditional, Alternative] : UnaryRepresentation) {
87 if (Value == Traditional)
88 return Alternative;
89 if (Value == Alternative)
90 return Traditional;
91 }
92
93 for (const auto &[Traditional, Alternative] : OperatorsRepresentation) {
94 if (Value == Traditional)
95 return Alternative;
96 if (Value == Alternative)
97 return Traditional;
98 }
99 return {};
100}
101
102static bool isNotOperatorStr(StringRef Value) {
103 return translate(Value).empty();
104}
105
106static bool isSeparator(char C) noexcept {
107 constexpr StringRef Separators(" \t\r\n\0()<>{};,");
108 return Separators.contains(C);
109}
110
111static bool needEscaping(StringRef Operator) {
112 switch (Operator[0]) {
113 case '&':
114 case '|':
115 case '!':
116 case '^':
117 case '~':
118 return false;
119 default:
120 return true;
121 }
122}
123
124static StringRef getRepresentation(const std::vector<StringRef> &Config,
125 StringRef Traditional,
126 StringRef Alternative) {
127 if (llvm::is_contained(Config, Traditional))
128 return Traditional;
129 if (llvm::is_contained(Config, Alternative))
130 return Alternative;
131 return {};
132}
133
134template <typename T>
135static bool isAnyOperatorEnabled(const std::vector<StringRef> &Config,
136 const T &Operators) {
137 return llvm::any_of(Operators, [&](const auto &Op) {
138 return !getRepresentation(Config, Op.first, Op.second).empty();
139 });
140}
141
143 StringRef Name, ClangTidyContext *Context)
144 : ClangTidyCheck(Name, Context),
145 BinaryOperators(
146 utils::options::parseStringList(Options.get("BinaryOperators", ""))),
147 OverloadedOperators(utils::options::parseStringList(
148 Options.get("OverloadedOperators", ""))) {
149 llvm::erase_if(BinaryOperators, isNotOperatorStr);
150 llvm::erase_if(OverloadedOperators, isNotOperatorStr);
151}
152
155 Options.store(Opts, "BinaryOperators",
156 utils::options::serializeStringList(BinaryOperators));
157 Options.store(Opts, "OverloadedOperators",
158 utils::options::serializeStringList(OverloadedOperators));
159}
160
161std::optional<TraversalKind>
163 return TK_IgnoreUnlessSpelledInSource;
164}
165
167 const LangOptions &LangOpts) const {
168 return LangOpts.CPlusPlus;
169}
170
171void OperatorsRepresentationCheck::registerBinaryOperatorMatcher(
172 MatchFinder *Finder) {
173 if (!isAnyOperatorEnabled(BinaryOperators, OperatorsRepresentation))
174 return;
175
176 Finder->addMatcher(
177 binaryOperator(
178 anyOf(hasInvalidBinaryOperatorRepresentation(
179 BO_LAnd, getRepresentation(BinaryOperators, "&&", "and")),
180 hasInvalidBinaryOperatorRepresentation(
181 BO_LOr, getRepresentation(BinaryOperators, "||", "or")),
182 hasInvalidBinaryOperatorRepresentation(
183 BO_NE, getRepresentation(BinaryOperators, "!=", "not_eq")),
184 hasInvalidBinaryOperatorRepresentation(
185 BO_Xor, getRepresentation(BinaryOperators, "^", "xor")),
186 hasInvalidBinaryOperatorRepresentation(
187 BO_And, getRepresentation(BinaryOperators, "&", "bitand")),
188 hasInvalidBinaryOperatorRepresentation(
189 BO_Or, getRepresentation(BinaryOperators, "|", "bitor")),
190 hasInvalidBinaryOperatorRepresentation(
191 BO_AndAssign,
192 getRepresentation(BinaryOperators, "&=", "and_eq")),
193 hasInvalidBinaryOperatorRepresentation(
194 BO_OrAssign,
195 getRepresentation(BinaryOperators, "|=", "or_eq")),
196 hasInvalidBinaryOperatorRepresentation(
197 BO_XorAssign,
198 getRepresentation(BinaryOperators, "^=", "xor_eq"))))
199 .bind("binary_op"),
200 this);
201}
202
203void OperatorsRepresentationCheck::registerUnaryOperatorMatcher(
204 MatchFinder *Finder) {
205 if (!isAnyOperatorEnabled(BinaryOperators, UnaryRepresentation))
206 return;
207
208 Finder->addMatcher(
209 unaryOperator(
210 anyOf(hasInvalidUnaryOperatorRepresentation(
211 UO_LNot, getRepresentation(BinaryOperators, "!", "not")),
212 hasInvalidUnaryOperatorRepresentation(
213 UO_Not, getRepresentation(BinaryOperators, "~", "compl"))))
214 .bind("unary_op"),
215 this);
216}
217
218void OperatorsRepresentationCheck::registerOverloadedOperatorMatcher(
219 MatchFinder *Finder) {
220 if (!isAnyOperatorEnabled(OverloadedOperators, OperatorsRepresentation) &&
221 !isAnyOperatorEnabled(OverloadedOperators, UnaryRepresentation))
222 return;
223
224 Finder->addMatcher(
225 cxxOperatorCallExpr(
226 anyOf(
227 hasInvalidOverloadedOperatorRepresentation(
228 OO_AmpAmp,
229 getRepresentation(OverloadedOperators, "&&", "and")),
230 hasInvalidOverloadedOperatorRepresentation(
231 OO_PipePipe,
232 getRepresentation(OverloadedOperators, "||", "or")),
233 hasInvalidOverloadedOperatorRepresentation(
234 OO_Exclaim,
235 getRepresentation(OverloadedOperators, "!", "not")),
236 hasInvalidOverloadedOperatorRepresentation(
237 OO_ExclaimEqual,
238 getRepresentation(OverloadedOperators, "!=", "not_eq")),
239 hasInvalidOverloadedOperatorRepresentation(
240 OO_Caret, getRepresentation(OverloadedOperators, "^", "xor")),
241 hasInvalidOverloadedOperatorRepresentation(
242 OO_Amp,
243 getRepresentation(OverloadedOperators, "&", "bitand")),
244 hasInvalidOverloadedOperatorRepresentation(
245 OO_Pipe,
246 getRepresentation(OverloadedOperators, "|", "bitor")),
247 hasInvalidOverloadedOperatorRepresentation(
248 OO_AmpEqual,
249 getRepresentation(OverloadedOperators, "&=", "and_eq")),
250 hasInvalidOverloadedOperatorRepresentation(
251 OO_PipeEqual,
252 getRepresentation(OverloadedOperators, "|=", "or_eq")),
253 hasInvalidOverloadedOperatorRepresentation(
254 OO_CaretEqual,
255 getRepresentation(OverloadedOperators, "^=", "xor_eq")),
256 hasInvalidOverloadedOperatorRepresentation(
257 OO_Tilde,
258 getRepresentation(OverloadedOperators, "~", "compl"))))
259 .bind("overloaded_op"),
260 this);
261}
262
264 registerBinaryOperatorMatcher(Finder);
265 registerUnaryOperatorMatcher(Finder);
266 registerOverloadedOperatorMatcher(Finder);
267}
268
270 const MatchFinder::MatchResult &Result) {
271 SourceLocation Loc;
272
273 if (const auto *Op = Result.Nodes.getNodeAs<BinaryOperator>("binary_op"))
274 Loc = Op->getOperatorLoc();
275 else if (const auto *Op = Result.Nodes.getNodeAs<UnaryOperator>("unary_op"))
276 Loc = Op->getOperatorLoc();
277 else if (const auto *Op =
278 Result.Nodes.getNodeAs<CXXOperatorCallExpr>("overloaded_op"))
279 Loc = Op->getOperatorLoc();
280
281 if (Loc.isInvalid())
282 return;
283
284 Loc = Result.SourceManager->getSpellingLoc(Loc);
285 if (Loc.isInvalid() || Loc.isMacroID())
286 return;
287
288 const CharSourceRange TokenRange = CharSourceRange::getTokenRange(Loc);
289 if (TokenRange.isInvalid())
290 return;
291
292 const StringRef Spelling = Lexer::getSourceText(
293 TokenRange, *Result.SourceManager, Result.Context->getLangOpts());
294 const StringRef TranslatedSpelling = translate(Spelling);
295
296 if (TranslatedSpelling.empty())
297 return;
298
299 std::string FixSpelling = TranslatedSpelling.str();
300
301 StringRef SourceRepresentation = "an alternative";
302 StringRef TargetRepresentation = "a traditional";
303 if (needEscaping(TranslatedSpelling)) {
304 SourceRepresentation = "a traditional";
305 TargetRepresentation = "an alternative";
306
307 const StringRef SpellingEx = Lexer::getSourceText(
308 CharSourceRange::getCharRange(
309 TokenRange.getBegin().getLocWithOffset(-1),
310 TokenRange.getBegin().getLocWithOffset(Spelling.size() + 1U)),
311 *Result.SourceManager, Result.Context->getLangOpts());
312 if (SpellingEx.empty() || !isSeparator(SpellingEx.front()))
313 FixSpelling.insert(FixSpelling.begin(), ' ');
314 if (SpellingEx.empty() || !isSeparator(SpellingEx.back()))
315 FixSpelling.push_back(' ');
316 }
317
318 diag(
319 Loc,
320 "'%0' is %1 token spelling, consider using %2 token '%3' for consistency")
321 << Spelling << SourceRepresentation << TargetRepresentation
322 << TranslatedSpelling
323 << FixItHint::CreateReplacement(TokenRange, FixSpelling);
324}
325
326} // namespace clang::tidy::readability
static cl::opt< std::string > Config("config", desc(R"( Specifies a configuration in YAML/JSON format: -config="{Checks:' *', CheckOptions:{x:y}}" When the value is empty, clang-tidy will attempt to find a file named .clang-tidy for each source file in its parent directories. )"), cl::init(""), cl::cat(ClangTidyCategory))
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
std::optional< TraversalKind > getCheckTraversalKind() const override
OperatorsRepresentationCheck(StringRef Name, ClangTidyContext *Context)
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
static StringRef getOperatorSpelling(SourceLocation Loc, ASTContext &Context)
constexpr std::array< std::pair< StringRef, StringRef >, 9U > OperatorsRepresentation
static StringRef translate(StringRef Value)
static bool needEscaping(StringRef Operator)
static bool isNotOperatorStr(StringRef Value)
static bool isSeparator(char C) noexcept
static StringRef getRepresentation(const std::vector< StringRef > &Config, StringRef Traditional, StringRef Alternative)
constexpr std::array< std::pair< StringRef, StringRef >, 2U > UnaryRepresentation
static bool isAnyOperatorEnabled(const std::vector< StringRef > &Config, const T &Operators)
std::string serializeStringList(ArrayRef< StringRef > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
llvm::StringMap< ClangTidyValue > OptionMap