clang-tools 19.0.0git
OperatorsRepresentationCheck.cpp
Go to the documentation of this file.
1//===--- OperatorsRepresentationCheck.cpp - clang-tidy
2//--------------------------===//
3//
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
7//
8//===----------------------------------------------------------------------===//
9
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"
16#include <array>
17#include <utility>
18
19using namespace clang::ast_matchers;
20
22
23static StringRef getOperatorSpelling(SourceLocation Loc, ASTContext &Context) {
24 if (Loc.isInvalid())
25 return {};
26
27 SourceManager &SM = Context.getSourceManager();
28
29 Loc = SM.getSpellingLoc(Loc);
30 if (Loc.isInvalid())
31 return {};
32
33 const CharSourceRange TokenRange = CharSourceRange::getTokenRange(Loc);
34 return Lexer::getSourceText(TokenRange, SM, Context.getLangOpts());
35}
36
37namespace {
38
39AST_MATCHER_P2(BinaryOperator, hasInvalidBinaryOperatorRepresentation,
40 BinaryOperatorKind, Kind, llvm::StringRef,
41 ExpectedRepresentation) {
42 if (Node.getOpcode() != Kind || ExpectedRepresentation.empty())
43 return false;
44
45 StringRef Spelling =
46 getOperatorSpelling(Node.getOperatorLoc(), Finder->getASTContext());
47 return !Spelling.empty() && Spelling != ExpectedRepresentation;
48}
49
50AST_MATCHER_P2(UnaryOperator, hasInvalidUnaryOperatorRepresentation,
51 UnaryOperatorKind, Kind, llvm::StringRef,
52 ExpectedRepresentation) {
53 if (Node.getOpcode() != Kind || ExpectedRepresentation.empty())
54 return false;
55
56 StringRef Spelling =
57 getOperatorSpelling(Node.getOperatorLoc(), Finder->getASTContext());
58 return !Spelling.empty() && Spelling != ExpectedRepresentation;
59}
60
61AST_MATCHER_P2(CXXOperatorCallExpr, hasInvalidOverloadedOperatorRepresentation,
62 OverloadedOperatorKind, Kind, llvm::StringRef,
63 ExpectedRepresentation) {
64 if (Node.getOperator() != Kind || ExpectedRepresentation.empty())
65 return false;
66
67 StringRef Spelling =
68 getOperatorSpelling(Node.getOperatorLoc(), Finder->getASTContext());
69 return !Spelling.empty() && Spelling != ExpectedRepresentation;
70}
71
72} // namespace
73
74constexpr std::array<std::pair<llvm::StringRef, llvm::StringRef>, 2U>
75 UnaryRepresentation{{{"!", "not"}, {"~", "compl"}}};
76
77constexpr std::array<std::pair<llvm::StringRef, llvm::StringRef>, 9U>
79 {"||", "or"},
80 {"^", "xor"},
81 {"&", "bitand"},
82 {"|", "bitor"},
83 {"&=", "and_eq"},
84 {"|=", "or_eq"},
85 {"!=", "not_eq"},
86 {"^=", "xor_eq"}}};
87
88static llvm::StringRef translate(llvm::StringRef Value) {
89 for (const auto &[Traditional, Alternative] : UnaryRepresentation) {
90 if (Value == Traditional)
91 return Alternative;
92 if (Value == Alternative)
93 return Traditional;
94 }
95
96 for (const auto &[Traditional, Alternative] : OperatorsRepresentation) {
97 if (Value == Traditional)
98 return Alternative;
99 if (Value == Alternative)
100 return Traditional;
101 }
102 return {};
103}
104
105static bool isNotOperatorStr(llvm::StringRef Value) {
106 return translate(Value).empty();
107}
108
109static bool isSeparator(char C) noexcept {
110 constexpr llvm::StringRef Separators(" \t\r\n\0()<>{};,");
111 return Separators.contains(C);
112}
113
114static bool needEscaping(llvm::StringRef Operator) {
115 switch (Operator[0]) {
116 case '&':
117 case '|':
118 case '!':
119 case '^':
120 case '~':
121 return false;
122 default:
123 return true;
124 }
125}
126
127static llvm::StringRef
128getRepresentation(const std::vector<llvm::StringRef> &Config,
129 llvm::StringRef Traditional, llvm::StringRef Alternative) {
130 if (llvm::is_contained(Config, Traditional))
131 return Traditional;
132 if (llvm::is_contained(Config, Alternative))
133 return Alternative;
134 return {};
135}
136
137template <typename T>
138static 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())
142 return true;
143 }
144 return false;
145}
146
148 StringRef Name, ClangTidyContext *Context)
149 : ClangTidyCheck(Name, Context),
150 BinaryOperators(
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);
156}
157
160 Options.store(Opts, "BinaryOperators",
161 utils::options::serializeStringList(BinaryOperators));
162 Options.store(Opts, "OverloadedOperators",
163 utils::options::serializeStringList(OverloadedOperators));
164}
165
166std::optional<TraversalKind>
168 return TK_IgnoreUnlessSpelledInSource;
169}
170
172 const LangOptions &LangOpts) const {
173 return LangOpts.CPlusPlus;
174}
175
176void OperatorsRepresentationCheck::registerBinaryOperatorMatcher(
177 MatchFinder *Finder) {
178 if (!isAnyOperatorEnabled(BinaryOperators, OperatorsRepresentation))
179 return;
180
181 Finder->addMatcher(
182 binaryOperator(
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(
197 BO_AndAssign,
198 getRepresentation(BinaryOperators, "&=", "and_eq")),
199 hasInvalidBinaryOperatorRepresentation(
200 BO_OrAssign,
201 getRepresentation(BinaryOperators, "|=", "or_eq")),
202 hasInvalidBinaryOperatorRepresentation(
203 BO_XorAssign,
204 getRepresentation(BinaryOperators, "^=", "xor_eq"))))
205 .bind("binary_op"),
206 this);
207}
208
209void OperatorsRepresentationCheck::registerUnaryOperatorMatcher(
210 MatchFinder *Finder) {
211 if (!isAnyOperatorEnabled(BinaryOperators, UnaryRepresentation))
212 return;
213
214 Finder->addMatcher(
215 unaryOperator(
216 unless(isExpansionInSystemHeader()),
217 anyOf(hasInvalidUnaryOperatorRepresentation(
218 UO_LNot, getRepresentation(BinaryOperators, "!", "not")),
219 hasInvalidUnaryOperatorRepresentation(
220 UO_Not, getRepresentation(BinaryOperators, "~", "compl"))))
221 .bind("unary_op"),
222 this);
223}
224
225void OperatorsRepresentationCheck::registerOverloadedOperatorMatcher(
226 MatchFinder *Finder) {
227 if (!isAnyOperatorEnabled(OverloadedOperators, OperatorsRepresentation) &&
228 !isAnyOperatorEnabled(OverloadedOperators, UnaryRepresentation))
229 return;
230
231 Finder->addMatcher(
232 cxxOperatorCallExpr(
233 unless(isExpansionInSystemHeader()),
234 anyOf(
235 hasInvalidOverloadedOperatorRepresentation(
236 OO_AmpAmp,
237 getRepresentation(OverloadedOperators, "&&", "and")),
238 hasInvalidOverloadedOperatorRepresentation(
239 OO_PipePipe,
240 getRepresentation(OverloadedOperators, "||", "or")),
241 hasInvalidOverloadedOperatorRepresentation(
242 OO_Exclaim,
243 getRepresentation(OverloadedOperators, "!", "not")),
244 hasInvalidOverloadedOperatorRepresentation(
245 OO_ExclaimEqual,
246 getRepresentation(OverloadedOperators, "!=", "not_eq")),
247 hasInvalidOverloadedOperatorRepresentation(
248 OO_Caret, getRepresentation(OverloadedOperators, "^", "xor")),
249 hasInvalidOverloadedOperatorRepresentation(
250 OO_Amp,
251 getRepresentation(OverloadedOperators, "&", "bitand")),
252 hasInvalidOverloadedOperatorRepresentation(
253 OO_Pipe,
254 getRepresentation(OverloadedOperators, "|", "bitor")),
255 hasInvalidOverloadedOperatorRepresentation(
256 OO_AmpEqual,
257 getRepresentation(OverloadedOperators, "&=", "and_eq")),
258 hasInvalidOverloadedOperatorRepresentation(
259 OO_PipeEqual,
260 getRepresentation(OverloadedOperators, "|=", "or_eq")),
261 hasInvalidOverloadedOperatorRepresentation(
262 OO_CaretEqual,
263 getRepresentation(OverloadedOperators, "^=", "xor_eq")),
264 hasInvalidOverloadedOperatorRepresentation(
265 OO_Tilde,
266 getRepresentation(OverloadedOperators, "~", "compl"))))
267 .bind("overloaded_op"),
268 this);
269}
270
272 registerBinaryOperatorMatcher(Finder);
273 registerUnaryOperatorMatcher(Finder);
274 registerOverloadedOperatorMatcher(Finder);
275}
276
278 const MatchFinder::MatchResult &Result) {
279
280 SourceLocation Loc;
281
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();
289
290 if (Loc.isInvalid())
291 return;
292
293 Loc = Result.SourceManager->getSpellingLoc(Loc);
294 if (Loc.isInvalid() || Loc.isMacroID())
295 return;
296
297 const CharSourceRange TokenRange = CharSourceRange::getTokenRange(Loc);
298 if (TokenRange.isInvalid())
299 return;
300
301 StringRef Spelling = Lexer::getSourceText(TokenRange, *Result.SourceManager,
302 Result.Context->getLangOpts());
303 StringRef TranslatedSpelling = translate(Spelling);
304
305 if (TranslatedSpelling.empty())
306 return;
307
308 std::string FixSpelling = TranslatedSpelling.str();
309
310 StringRef SourceRepresentation = "an alternative";
311 StringRef TargetRepresentation = "a traditional";
312 if (needEscaping(TranslatedSpelling)) {
313 SourceRepresentation = "a traditional";
314 TargetRepresentation = "an alternative";
315
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(' ');
325 }
326
327 diag(
328 Loc,
329 "'%0' is %1 token spelling, consider using %2 token '%3' for consistency")
330 << Spelling << SourceRepresentation << TargetRepresentation
331 << TranslatedSpelling
332 << FixItHint::CreateReplacement(TokenRange, FixSpelling);
333}
334
335} // namespace clang::tidy::readability
llvm::SmallString< 256U > Name
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))
const Criteria C
SourceLocation Loc
::clang::DynTypedNode Node
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Base class for all clang-tidy checks.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
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
ClangTidyChecks that register ASTMatchers should do the actual work in here.
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override
Override this to disable registering matchers and PP callbacks if an invalid language version is bein...
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
static bool isAnyOperatorEnabled(const std::vector< llvm::StringRef > &Config, const T &Operators)
static StringRef getOperatorSpelling(SourceLocation Loc, ASTContext &Context)
static bool isNotOperatorStr(llvm::StringRef Value)
constexpr std::array< std::pair< llvm::StringRef, llvm::StringRef >, 9U > OperatorsRepresentation
static llvm::StringRef getRepresentation(const std::vector< llvm::StringRef > &Config, llvm::StringRef Traditional, llvm::StringRef Alternative)
static llvm::StringRef translate(llvm::StringRef Value)
static bool isSeparator(char C) noexcept
constexpr std::array< std::pair< llvm::StringRef, llvm::StringRef >, 2U > UnaryRepresentation
static bool needEscaping(llvm::StringRef Operator)
std::string serializeStringList(ArrayRef< StringRef > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
llvm::StringMap< ClangTidyValue > OptionMap