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
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"
16#include <array>
17#include <utility>
18
19using namespace clang::ast_matchers;
20
21namespace clang::tidy::readability {
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(R: Loc);
34 return Lexer::getSourceText(Range: TokenRange, SM, LangOpts: 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(Loc: Node.getOperatorLoc(), Context&: 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(Loc: Node.getOperatorLoc(), Context&: 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(Loc: Node.getOperatorLoc(), Context&: 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{._M_elems: {{"!", "not"}, {"~", "compl"}}};
76
77constexpr std::array<std::pair<llvm::StringRef, llvm::StringRef>, 9U>
78 OperatorsRepresentation{._M_elems: {{"&&", "and"},
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(Range: Config, Element: Traditional))
131 return Traditional;
132 if (llvm::is_contained(Range: Config, Element: 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
147OperatorsRepresentationCheck::OperatorsRepresentationCheck(
148 StringRef Name, ClangTidyContext *Context)
149 : ClangTidyCheck(Name, Context),
150 BinaryOperators(
151 utils::options::parseStringList(Option: Options.get(LocalName: "BinaryOperators", Default: ""))),
152 OverloadedOperators(utils::options::parseStringList(
153 Option: Options.get(LocalName: "OverloadedOperators", Default: ""))) {
154 llvm::erase_if(C&: BinaryOperators, P: isNotOperatorStr);
155 llvm::erase_if(C&: OverloadedOperators, P: isNotOperatorStr);
156}
157
158void OperatorsRepresentationCheck::storeOptions(
159 ClangTidyOptions::OptionMap &Opts) {
160 Options.store(Options&: Opts, LocalName: "BinaryOperators",
161 Value: utils::options::serializeStringList(Strings: BinaryOperators));
162 Options.store(Options&: Opts, LocalName: "OverloadedOperators",
163 Value: utils::options::serializeStringList(Strings: OverloadedOperators));
164}
165
166std::optional<TraversalKind>
167OperatorsRepresentationCheck::getCheckTraversalKind() const {
168 return TK_IgnoreUnlessSpelledInSource;
169}
170
171bool OperatorsRepresentationCheck::isLanguageVersionSupported(
172 const LangOptions &LangOpts) const {
173 return LangOpts.CPlusPlus;
174}
175
176void OperatorsRepresentationCheck::registerBinaryOperatorMatcher(
177 MatchFinder *Finder) {
178 if (!isAnyOperatorEnabled(Config: BinaryOperators, Operators: OperatorsRepresentation))
179 return;
180
181 Finder->addMatcher(
182 NodeMatch: binaryOperator(
183 unless(isExpansionInSystemHeader()),
184 anyOf(hasInvalidBinaryOperatorRepresentation(
185 Kind: BO_LAnd, ExpectedRepresentation: getRepresentation(Config: BinaryOperators, Traditional: "&&", Alternative: "and")),
186 hasInvalidBinaryOperatorRepresentation(
187 Kind: BO_LOr, ExpectedRepresentation: getRepresentation(Config: BinaryOperators, Traditional: "||", Alternative: "or")),
188 hasInvalidBinaryOperatorRepresentation(
189 Kind: BO_NE, ExpectedRepresentation: getRepresentation(Config: BinaryOperators, Traditional: "!=", Alternative: "not_eq")),
190 hasInvalidBinaryOperatorRepresentation(
191 Kind: BO_Xor, ExpectedRepresentation: getRepresentation(Config: BinaryOperators, Traditional: "^", Alternative: "xor")),
192 hasInvalidBinaryOperatorRepresentation(
193 Kind: BO_And, ExpectedRepresentation: getRepresentation(Config: BinaryOperators, Traditional: "&", Alternative: "bitand")),
194 hasInvalidBinaryOperatorRepresentation(
195 Kind: BO_Or, ExpectedRepresentation: getRepresentation(Config: BinaryOperators, Traditional: "|", Alternative: "bitor")),
196 hasInvalidBinaryOperatorRepresentation(
197 Kind: BO_AndAssign,
198 ExpectedRepresentation: getRepresentation(Config: BinaryOperators, Traditional: "&=", Alternative: "and_eq")),
199 hasInvalidBinaryOperatorRepresentation(
200 Kind: BO_OrAssign,
201 ExpectedRepresentation: getRepresentation(Config: BinaryOperators, Traditional: "|=", Alternative: "or_eq")),
202 hasInvalidBinaryOperatorRepresentation(
203 Kind: BO_XorAssign,
204 ExpectedRepresentation: getRepresentation(Config: BinaryOperators, Traditional: "^=", Alternative: "xor_eq"))))
205 .bind(ID: "binary_op"),
206 Action: this);
207}
208
209void OperatorsRepresentationCheck::registerUnaryOperatorMatcher(
210 MatchFinder *Finder) {
211 if (!isAnyOperatorEnabled(Config: BinaryOperators, Operators: UnaryRepresentation))
212 return;
213
214 Finder->addMatcher(
215 NodeMatch: unaryOperator(
216 unless(isExpansionInSystemHeader()),
217 anyOf(hasInvalidUnaryOperatorRepresentation(
218 Kind: UO_LNot, ExpectedRepresentation: getRepresentation(Config: BinaryOperators, Traditional: "!", Alternative: "not")),
219 hasInvalidUnaryOperatorRepresentation(
220 Kind: UO_Not, ExpectedRepresentation: getRepresentation(Config: BinaryOperators, Traditional: "~", Alternative: "compl"))))
221 .bind(ID: "unary_op"),
222 Action: this);
223}
224
225void OperatorsRepresentationCheck::registerOverloadedOperatorMatcher(
226 MatchFinder *Finder) {
227 if (!isAnyOperatorEnabled(Config: OverloadedOperators, Operators: OperatorsRepresentation) &&
228 !isAnyOperatorEnabled(Config: OverloadedOperators, Operators: UnaryRepresentation))
229 return;
230
231 Finder->addMatcher(
232 NodeMatch: cxxOperatorCallExpr(
233 unless(isExpansionInSystemHeader()),
234 anyOf(
235 hasInvalidOverloadedOperatorRepresentation(
236 Kind: OO_AmpAmp,
237 ExpectedRepresentation: getRepresentation(Config: OverloadedOperators, Traditional: "&&", Alternative: "and")),
238 hasInvalidOverloadedOperatorRepresentation(
239 Kind: OO_PipePipe,
240 ExpectedRepresentation: getRepresentation(Config: OverloadedOperators, Traditional: "||", Alternative: "or")),
241 hasInvalidOverloadedOperatorRepresentation(
242 Kind: OO_Exclaim,
243 ExpectedRepresentation: getRepresentation(Config: OverloadedOperators, Traditional: "!", Alternative: "not")),
244 hasInvalidOverloadedOperatorRepresentation(
245 Kind: OO_ExclaimEqual,
246 ExpectedRepresentation: getRepresentation(Config: OverloadedOperators, Traditional: "!=", Alternative: "not_eq")),
247 hasInvalidOverloadedOperatorRepresentation(
248 Kind: OO_Caret, ExpectedRepresentation: getRepresentation(Config: OverloadedOperators, Traditional: "^", Alternative: "xor")),
249 hasInvalidOverloadedOperatorRepresentation(
250 Kind: OO_Amp,
251 ExpectedRepresentation: getRepresentation(Config: OverloadedOperators, Traditional: "&", Alternative: "bitand")),
252 hasInvalidOverloadedOperatorRepresentation(
253 Kind: OO_Pipe,
254 ExpectedRepresentation: getRepresentation(Config: OverloadedOperators, Traditional: "|", Alternative: "bitor")),
255 hasInvalidOverloadedOperatorRepresentation(
256 Kind: OO_AmpEqual,
257 ExpectedRepresentation: getRepresentation(Config: OverloadedOperators, Traditional: "&=", Alternative: "and_eq")),
258 hasInvalidOverloadedOperatorRepresentation(
259 Kind: OO_PipeEqual,
260 ExpectedRepresentation: getRepresentation(Config: OverloadedOperators, Traditional: "|=", Alternative: "or_eq")),
261 hasInvalidOverloadedOperatorRepresentation(
262 Kind: OO_CaretEqual,
263 ExpectedRepresentation: getRepresentation(Config: OverloadedOperators, Traditional: "^=", Alternative: "xor_eq")),
264 hasInvalidOverloadedOperatorRepresentation(
265 Kind: OO_Tilde,
266 ExpectedRepresentation: getRepresentation(Config: OverloadedOperators, Traditional: "~", Alternative: "compl"))))
267 .bind(ID: "overloaded_op"),
268 Action: this);
269}
270
271void OperatorsRepresentationCheck::registerMatchers(MatchFinder *Finder) {
272 registerBinaryOperatorMatcher(Finder);
273 registerUnaryOperatorMatcher(Finder);
274 registerOverloadedOperatorMatcher(Finder);
275}
276
277void OperatorsRepresentationCheck::check(
278 const MatchFinder::MatchResult &Result) {
279
280 SourceLocation Loc;
281
282 if (const auto *Op = Result.Nodes.getNodeAs<BinaryOperator>(ID: "binary_op"))
283 Loc = Op->getOperatorLoc();
284 else if (const auto *Op = Result.Nodes.getNodeAs<UnaryOperator>(ID: "unary_op"))
285 Loc = Op->getOperatorLoc();
286 else if (const auto *Op =
287 Result.Nodes.getNodeAs<CXXOperatorCallExpr>(ID: "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(R: Loc);
298 if (TokenRange.isInvalid())
299 return;
300
301 StringRef Spelling = Lexer::getSourceText(Range: TokenRange, SM: *Result.SourceManager,
302 LangOpts: Result.Context->getLangOpts());
303 StringRef TranslatedSpelling = translate(Value: 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(Operator: TranslatedSpelling)) {
313 SourceRepresentation = "a traditional";
314 TargetRepresentation = "an alternative";
315
316 StringRef SpellingEx = Lexer::getSourceText(
317 Range: CharSourceRange::getCharRange(
318 B: TokenRange.getBegin().getLocWithOffset(Offset: -1),
319 E: TokenRange.getBegin().getLocWithOffset(Offset: Spelling.size() + 1U)),
320 SM: *Result.SourceManager, LangOpts: Result.Context->getLangOpts());
321 if (SpellingEx.empty() || !isSeparator(C: SpellingEx.front()))
322 FixSpelling.insert(p: FixSpelling.begin(), c: ' ');
323 if (SpellingEx.empty() || !isSeparator(C: SpellingEx.back()))
324 FixSpelling.push_back(c: ' ');
325 }
326
327 diag(
328 Loc,
329 Description: "'%0' is %1 token spelling, consider using %2 token '%3' for consistency")
330 << Spelling << SourceRepresentation << TargetRepresentation
331 << TranslatedSpelling
332 << FixItHint::CreateReplacement(RemoveRange: TokenRange, Code: FixSpelling);
333}
334
335} // namespace clang::tidy::readability
336

source code of clang-tools-extra/clang-tidy/readability/OperatorsRepresentationCheck.cpp