1 | //===--- MacroToEnumCheck.cpp - clang-tidy --------------------------------===// |
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 | |
9 | #include "MacroToEnumCheck.h" |
10 | #include "IntegralLiteralExpressionMatcher.h" |
11 | |
12 | #include "clang/AST/ASTContext.h" |
13 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
14 | #include "clang/Lex/Preprocessor.h" |
15 | #include "llvm/ADT/STLExtras.h" |
16 | #include <algorithm> |
17 | #include <cassert> |
18 | #include <cctype> |
19 | #include <string> |
20 | |
21 | namespace clang::tidy::modernize { |
22 | |
23 | static bool (SourceLocation Loc, const LangOptions &Options, |
24 | StringRef Text) { |
25 | // Use a lexer to look for tokens; if we find something other than a single |
26 | // hash, then there were intervening tokens between macro definitions. |
27 | std::string Buffer{Text}; |
28 | Lexer Lex(Loc, Options, Buffer.c_str(), Buffer.c_str(), |
29 | Buffer.c_str() + Buffer.size()); |
30 | Token Tok; |
31 | bool SeenHash = false; |
32 | while (!Lex.LexFromRawLexer(Result&: Tok)) { |
33 | if (Tok.getKind() == tok::hash && !SeenHash) { |
34 | SeenHash = true; |
35 | continue; |
36 | } |
37 | return false; |
38 | } |
39 | |
40 | // Everything in between was whitespace, so now just look for two blank lines, |
41 | // consisting of two consecutive EOL sequences, either '\n', '\r' or '\r\n'. |
42 | enum class WhiteSpace { |
43 | Nothing, |
44 | CR, |
45 | LF, |
46 | CRLF, |
47 | CRLFCR, |
48 | }; |
49 | |
50 | WhiteSpace State = WhiteSpace::Nothing; |
51 | for (char C : Text) { |
52 | switch (C) { |
53 | case '\r': |
54 | if (State == WhiteSpace::CR) |
55 | return false; |
56 | |
57 | State = State == WhiteSpace::CRLF ? WhiteSpace::CRLFCR : WhiteSpace::CR; |
58 | break; |
59 | |
60 | case '\n': |
61 | if (State == WhiteSpace::LF || State == WhiteSpace::CRLFCR) |
62 | return false; |
63 | |
64 | State = State == WhiteSpace::CR ? WhiteSpace::CRLF : WhiteSpace::LF; |
65 | break; |
66 | |
67 | default: |
68 | State = WhiteSpace::Nothing; |
69 | break; |
70 | } |
71 | } |
72 | |
73 | return true; |
74 | } |
75 | |
76 | static StringRef getTokenName(const Token &Tok) { |
77 | return Tok.is(K: tok::raw_identifier) ? Tok.getRawIdentifier() |
78 | : Tok.getIdentifierInfo()->getName(); |
79 | } |
80 | |
81 | namespace { |
82 | |
83 | struct EnumMacro { |
84 | EnumMacro(Token Name, const MacroDirective *Directive) |
85 | : Name(Name), Directive(Directive) {} |
86 | |
87 | Token Name; |
88 | const MacroDirective *Directive; |
89 | }; |
90 | |
91 | using MacroList = SmallVector<EnumMacro>; |
92 | |
93 | enum class IncludeGuard { None, FileChanged, IfGuard, DefineGuard }; |
94 | |
95 | struct FileState { |
96 | FileState() = default; |
97 | |
98 | int ConditionScopes = 0; |
99 | unsigned int LastLine = 0; |
100 | IncludeGuard GuardScanner = IncludeGuard::None; |
101 | SourceLocation LastMacroLocation; |
102 | }; |
103 | |
104 | } // namespace |
105 | |
106 | class MacroToEnumCallbacks : public PPCallbacks { |
107 | public: |
108 | MacroToEnumCallbacks(MacroToEnumCheck *Check, const LangOptions &LangOptions, |
109 | const SourceManager &SM) |
110 | : Check(Check), LangOpts(LangOptions), SM(SM) {} |
111 | |
112 | void FileChanged(SourceLocation Loc, FileChangeReason Reason, |
113 | SrcMgr::CharacteristicKind FileType, |
114 | FileID PrevFID) override; |
115 | |
116 | void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok, |
117 | StringRef FileName, bool IsAngled, |
118 | CharSourceRange FilenameRange, |
119 | OptionalFileEntryRef File, StringRef SearchPath, |
120 | StringRef RelativePath, const Module *SuggestedModule, |
121 | bool ModuleImported, |
122 | SrcMgr::CharacteristicKind FileType) override { |
123 | clearCurrentEnum(Loc: HashLoc); |
124 | } |
125 | |
126 | // Keep track of macro definitions that look like enums. |
127 | void MacroDefined(const Token &MacroNameTok, |
128 | const MacroDirective *MD) override; |
129 | |
130 | // Undefining an enum-like macro results in the enum set being dropped. |
131 | void MacroUndefined(const Token &MacroNameTok, const MacroDefinition &MD, |
132 | const MacroDirective *Undef) override; |
133 | |
134 | // Conditional compilation clears any adjacent enum-like macros. |
135 | // Macros used in conditional expressions clear any adjacent enum-like |
136 | // macros. |
137 | // Include guards are either |
138 | // #if !defined(GUARD) |
139 | // or |
140 | // #ifndef GUARD |
141 | void If(SourceLocation Loc, SourceRange ConditionRange, |
142 | ConditionValueKind ConditionValue) override { |
143 | conditionStart(Loc); |
144 | checkCondition(ConditionRange); |
145 | } |
146 | void Ifndef(SourceLocation Loc, const Token &MacroNameTok, |
147 | const MacroDefinition &MD) override { |
148 | conditionStart(Loc); |
149 | checkName(MacroNameTok); |
150 | } |
151 | void Ifdef(SourceLocation Loc, const Token &MacroNameTok, |
152 | const MacroDefinition &MD) override { |
153 | conditionStart(Loc); |
154 | checkName(MacroNameTok); |
155 | } |
156 | void Elif(SourceLocation Loc, SourceRange ConditionRange, |
157 | ConditionValueKind ConditionValue, SourceLocation IfLoc) override { |
158 | checkCondition(ConditionRange); |
159 | } |
160 | void Elifdef(SourceLocation Loc, const Token &MacroNameTok, |
161 | const MacroDefinition &MD) override { |
162 | checkName(MacroNameTok); |
163 | } |
164 | void Elifdef(SourceLocation Loc, SourceRange ConditionRange, |
165 | SourceLocation IfLoc) override { |
166 | PPCallbacks::Elifdef(Loc, ConditionRange, IfLoc); |
167 | } |
168 | void Elifndef(SourceLocation Loc, const Token &MacroNameTok, |
169 | const MacroDefinition &MD) override { |
170 | checkName(MacroNameTok); |
171 | } |
172 | void Elifndef(SourceLocation Loc, SourceRange ConditionRange, |
173 | SourceLocation IfLoc) override { |
174 | PPCallbacks::Elifndef(Loc, ConditionRange, IfLoc); |
175 | } |
176 | void Endif(SourceLocation Loc, SourceLocation IfLoc) override; |
177 | void PragmaDirective(SourceLocation Loc, |
178 | PragmaIntroducerKind Introducer) override; |
179 | |
180 | // After we've seen everything, issue warnings and fix-its. |
181 | void EndOfMainFile() override; |
182 | |
183 | void invalidateRange(SourceRange Range); |
184 | |
185 | private: |
186 | void newEnum() { |
187 | if (Enums.empty() || !Enums.back().empty()) |
188 | Enums.emplace_back(); |
189 | } |
190 | bool insideConditional() const { |
191 | return (CurrentFile->GuardScanner == IncludeGuard::DefineGuard && |
192 | CurrentFile->ConditionScopes > 1) || |
193 | (CurrentFile->GuardScanner != IncludeGuard::DefineGuard && |
194 | CurrentFile->ConditionScopes > 0); |
195 | } |
196 | bool isConsecutiveMacro(const MacroDirective *MD) const; |
197 | void rememberLastMacroLocation(const MacroDirective *MD) { |
198 | CurrentFile->LastLine = SM.getSpellingLineNumber(Loc: MD->getLocation()); |
199 | CurrentFile->LastMacroLocation = Lexer::getLocForEndOfToken( |
200 | Loc: MD->getMacroInfo()->getDefinitionEndLoc(), Offset: 0, SM, LangOpts); |
201 | } |
202 | void clearLastMacroLocation() { |
203 | CurrentFile->LastLine = 0; |
204 | CurrentFile->LastMacroLocation = SourceLocation{}; |
205 | } |
206 | void clearCurrentEnum(SourceLocation Loc); |
207 | void conditionStart(const SourceLocation &Loc); |
208 | void checkCondition(SourceRange ConditionRange); |
209 | void checkName(const Token &MacroNameTok); |
210 | void rememberExpressionName(const Token &Tok); |
211 | void rememberExpressionTokens(ArrayRef<Token> MacroTokens); |
212 | void invalidateExpressionNames(); |
213 | void issueDiagnostics(); |
214 | void warnMacroEnum(const EnumMacro &Macro) const; |
215 | void fixEnumMacro(const MacroList &MacroList) const; |
216 | bool isInitializer(ArrayRef<Token> MacroTokens); |
217 | |
218 | MacroToEnumCheck *Check; |
219 | const LangOptions &LangOpts; |
220 | const SourceManager &SM; |
221 | SmallVector<MacroList> Enums; |
222 | SmallVector<FileState> Files; |
223 | std::vector<std::string> ExpressionNames; |
224 | FileState *CurrentFile = nullptr; |
225 | }; |
226 | |
227 | bool MacroToEnumCallbacks::isConsecutiveMacro(const MacroDirective *MD) const { |
228 | if (CurrentFile->LastMacroLocation.isInvalid()) |
229 | return false; |
230 | |
231 | SourceLocation Loc = MD->getLocation(); |
232 | if (CurrentFile->LastLine + 1 == SM.getSpellingLineNumber(Loc)) |
233 | return true; |
234 | |
235 | SourceLocation Define = |
236 | SM.translateLineCol(FID: SM.getFileID(SpellingLoc: Loc), Line: SM.getSpellingLineNumber(Loc), Col: 1); |
237 | CharSourceRange BetweenMacros{ |
238 | SourceRange{CurrentFile->LastMacroLocation, Define}, true}; |
239 | CharSourceRange CharRange = |
240 | Lexer::makeFileCharRange(Range: BetweenMacros, SM, LangOpts); |
241 | StringRef BetweenText = Lexer::getSourceText(Range: CharRange, SM, LangOpts); |
242 | return hasOnlyComments(Loc: Define, Options: LangOpts, Text: BetweenText); |
243 | } |
244 | |
245 | void MacroToEnumCallbacks::clearCurrentEnum(SourceLocation Loc) { |
246 | // Only drop the most recent Enum set if the directive immediately follows. |
247 | if (!Enums.empty() && !Enums.back().empty() && |
248 | SM.getSpellingLineNumber(Loc) == CurrentFile->LastLine + 1) |
249 | Enums.pop_back(); |
250 | |
251 | clearLastMacroLocation(); |
252 | } |
253 | |
254 | void MacroToEnumCallbacks::conditionStart(const SourceLocation &Loc) { |
255 | ++CurrentFile->ConditionScopes; |
256 | clearCurrentEnum(Loc); |
257 | if (CurrentFile->GuardScanner == IncludeGuard::FileChanged) |
258 | CurrentFile->GuardScanner = IncludeGuard::IfGuard; |
259 | } |
260 | |
261 | void MacroToEnumCallbacks::checkCondition(SourceRange Range) { |
262 | CharSourceRange CharRange = Lexer::makeFileCharRange( |
263 | Range: CharSourceRange::getTokenRange(R: Range), SM, LangOpts); |
264 | std::string Text = Lexer::getSourceText(Range: CharRange, SM, LangOpts).str(); |
265 | Lexer Lex(CharRange.getBegin(), LangOpts, Text.data(), Text.data(), |
266 | Text.data() + Text.size()); |
267 | Token Tok; |
268 | bool End = false; |
269 | while (!End) { |
270 | End = Lex.LexFromRawLexer(Result&: Tok); |
271 | if (Tok.is(K: tok::raw_identifier) && |
272 | Tok.getRawIdentifier().str() != "defined" ) |
273 | checkName(MacroNameTok: Tok); |
274 | } |
275 | } |
276 | |
277 | void MacroToEnumCallbacks::checkName(const Token &MacroNameTok) { |
278 | rememberExpressionName(Tok: MacroNameTok); |
279 | |
280 | StringRef Id = getTokenName(Tok: MacroNameTok); |
281 | llvm::erase_if(C&: Enums, P: [&Id](const MacroList &MacroList) { |
282 | return llvm::any_of(Range: MacroList, P: [&Id](const EnumMacro &Macro) { |
283 | return getTokenName(Tok: Macro.Name) == Id; |
284 | }); |
285 | }); |
286 | } |
287 | |
288 | void MacroToEnumCallbacks::rememberExpressionName(const Token &Tok) { |
289 | std::string Id = getTokenName(Tok).str(); |
290 | auto Pos = llvm::lower_bound(Range&: ExpressionNames, Value&: Id); |
291 | if (Pos == ExpressionNames.end() || *Pos != Id) { |
292 | ExpressionNames.insert(position: Pos, x: Id); |
293 | } |
294 | } |
295 | |
296 | void MacroToEnumCallbacks::rememberExpressionTokens( |
297 | ArrayRef<Token> MacroTokens) { |
298 | for (Token Tok : MacroTokens) { |
299 | if (Tok.isAnyIdentifier()) |
300 | rememberExpressionName(Tok); |
301 | } |
302 | } |
303 | |
304 | void MacroToEnumCallbacks::FileChanged(SourceLocation Loc, |
305 | FileChangeReason Reason, |
306 | SrcMgr::CharacteristicKind FileType, |
307 | FileID PrevFID) { |
308 | newEnum(); |
309 | if (Reason == EnterFile) { |
310 | Files.emplace_back(); |
311 | if (!SM.isInMainFile(Loc)) |
312 | Files.back().GuardScanner = IncludeGuard::FileChanged; |
313 | } else if (Reason == ExitFile) { |
314 | assert(CurrentFile->ConditionScopes == 0); |
315 | Files.pop_back(); |
316 | } |
317 | CurrentFile = &Files.back(); |
318 | } |
319 | |
320 | bool MacroToEnumCallbacks::isInitializer(ArrayRef<Token> MacroTokens) |
321 | { |
322 | IntegralLiteralExpressionMatcher Matcher(MacroTokens, LangOpts.C99 == 0); |
323 | bool Matched = Matcher.match(); |
324 | bool isC = !LangOpts.CPlusPlus; |
325 | if (isC && (Matcher.largestLiteralSize() != LiteralSize::Int && |
326 | Matcher.largestLiteralSize() != LiteralSize::UnsignedInt)) |
327 | return false; |
328 | |
329 | return Matched; |
330 | } |
331 | |
332 | |
333 | // Any defined but rejected macro is scanned for identifiers that |
334 | // are to be excluded as enums. |
335 | void MacroToEnumCallbacks::MacroDefined(const Token &MacroNameTok, |
336 | const MacroDirective *MD) { |
337 | // Include guards are never candidates for becoming an enum. |
338 | if (CurrentFile->GuardScanner == IncludeGuard::IfGuard) { |
339 | CurrentFile->GuardScanner = IncludeGuard::DefineGuard; |
340 | return; |
341 | } |
342 | |
343 | if (insideConditional()) |
344 | return; |
345 | |
346 | if (SM.getFilename(SpellingLoc: MD->getLocation()).empty()) |
347 | return; |
348 | |
349 | const MacroInfo *Info = MD->getMacroInfo(); |
350 | ArrayRef<Token> MacroTokens = Info->tokens(); |
351 | if (Info->isBuiltinMacro() || MacroTokens.empty()) |
352 | return; |
353 | if (Info->isFunctionLike()) { |
354 | rememberExpressionTokens(MacroTokens); |
355 | return; |
356 | } |
357 | |
358 | if (!isInitializer(MacroTokens)) |
359 | return; |
360 | |
361 | if (!isConsecutiveMacro(MD)) |
362 | newEnum(); |
363 | Enums.back().emplace_back(Args: MacroNameTok, Args&: MD); |
364 | rememberLastMacroLocation(MD); |
365 | } |
366 | |
367 | // Any macro that is undefined removes all adjacent macros from consideration as |
368 | // an enum and starts a new enum scan. |
369 | void MacroToEnumCallbacks::MacroUndefined(const Token &MacroNameTok, |
370 | const MacroDefinition &MD, |
371 | const MacroDirective *Undef) { |
372 | rememberExpressionName(Tok: MacroNameTok); |
373 | |
374 | auto MatchesToken = [&MacroNameTok](const EnumMacro &Macro) { |
375 | return getTokenName(Tok: Macro.Name) == getTokenName(Tok: MacroNameTok); |
376 | }; |
377 | |
378 | auto It = llvm::find_if(Range&: Enums, P: [MatchesToken](const MacroList &MacroList) { |
379 | return llvm::any_of(Range: MacroList, P: MatchesToken); |
380 | }); |
381 | if (It != Enums.end()) |
382 | Enums.erase(CI: It); |
383 | |
384 | clearLastMacroLocation(); |
385 | CurrentFile->GuardScanner = IncludeGuard::None; |
386 | } |
387 | |
388 | void MacroToEnumCallbacks::Endif(SourceLocation Loc, SourceLocation IfLoc) { |
389 | // The if directive for the include guard isn't counted in the |
390 | // ConditionScopes. |
391 | if (CurrentFile->ConditionScopes == 0 && |
392 | CurrentFile->GuardScanner == IncludeGuard::DefineGuard) |
393 | return; |
394 | |
395 | // We don't need to clear the current enum because the start of the |
396 | // conditional block already took care of that. |
397 | assert(CurrentFile->ConditionScopes > 0); |
398 | --CurrentFile->ConditionScopes; |
399 | } |
400 | |
401 | namespace { |
402 | |
403 | template <size_t N> |
404 | bool textEquals(const char (&Needle)[N], const char *HayStack) { |
405 | return StringRef{HayStack, N - 1} == Needle; |
406 | } |
407 | |
408 | template <size_t N> size_t len(const char (&)[N]) { return N - 1; } |
409 | |
410 | } // namespace |
411 | |
412 | void MacroToEnumCallbacks::PragmaDirective(SourceLocation Loc, |
413 | PragmaIntroducerKind Introducer) { |
414 | if (CurrentFile->GuardScanner != IncludeGuard::FileChanged) |
415 | return; |
416 | |
417 | bool Invalid = false; |
418 | const char *Text = SM.getCharacterData( |
419 | SL: Lexer::getLocForEndOfToken(Loc, Offset: 0, SM, LangOpts), Invalid: &Invalid); |
420 | if (Invalid) |
421 | return; |
422 | |
423 | while (*Text && std::isspace(*Text)) |
424 | ++Text; |
425 | |
426 | if (textEquals(Needle: "pragma" , HayStack: Text)) |
427 | return; |
428 | |
429 | Text += len("pragma" ); |
430 | while (*Text && std::isspace(*Text)) |
431 | ++Text; |
432 | |
433 | if (textEquals(Needle: "once" , HayStack: Text)) |
434 | CurrentFile->GuardScanner = IncludeGuard::IfGuard; |
435 | } |
436 | |
437 | void MacroToEnumCallbacks::invalidateExpressionNames() { |
438 | for (const std::string &Id : ExpressionNames) { |
439 | llvm::erase_if(C&: Enums, P: [Id](const MacroList &MacroList) { |
440 | return llvm::any_of(Range: MacroList, P: [&Id](const EnumMacro &Macro) { |
441 | return getTokenName(Tok: Macro.Name) == Id; |
442 | }); |
443 | }); |
444 | } |
445 | } |
446 | |
447 | void MacroToEnumCallbacks::EndOfMainFile() { |
448 | invalidateExpressionNames(); |
449 | issueDiagnostics(); |
450 | } |
451 | |
452 | void MacroToEnumCallbacks::invalidateRange(SourceRange Range) { |
453 | llvm::erase_if(C&: Enums, P: [Range](const MacroList &MacroList) { |
454 | return llvm::any_of(Range: MacroList, P: [Range](const EnumMacro &Macro) { |
455 | return Macro.Directive->getLocation() >= Range.getBegin() && |
456 | Macro.Directive->getLocation() <= Range.getEnd(); |
457 | }); |
458 | }); |
459 | } |
460 | |
461 | void MacroToEnumCallbacks::issueDiagnostics() { |
462 | for (const MacroList &MacroList : Enums) { |
463 | if (MacroList.empty()) |
464 | continue; |
465 | |
466 | for (const EnumMacro &Macro : MacroList) |
467 | warnMacroEnum(Macro); |
468 | |
469 | fixEnumMacro(MacroList); |
470 | } |
471 | } |
472 | |
473 | void MacroToEnumCallbacks::warnMacroEnum(const EnumMacro &Macro) const { |
474 | Check->diag(Loc: Macro.Directive->getLocation(), |
475 | Description: "macro '%0' defines an integral constant; prefer an enum instead" ) |
476 | << getTokenName(Tok: Macro.Name); |
477 | } |
478 | |
479 | void MacroToEnumCallbacks::fixEnumMacro(const MacroList &MacroList) const { |
480 | SourceLocation Begin = |
481 | MacroList.front().Directive->getMacroInfo()->getDefinitionLoc(); |
482 | Begin = SM.translateLineCol(FID: SM.getFileID(SpellingLoc: Begin), |
483 | Line: SM.getSpellingLineNumber(Loc: Begin), Col: 1); |
484 | DiagnosticBuilder Diagnostic = |
485 | Check->diag(Loc: Begin, Description: "replace macro with enum" ) |
486 | << FixItHint::CreateInsertion(InsertionLoc: Begin, Code: "enum {\n" ); |
487 | |
488 | for (size_t I = 0U; I < MacroList.size(); ++I) { |
489 | const EnumMacro &Macro = MacroList[I]; |
490 | SourceLocation DefineEnd = |
491 | Macro.Directive->getMacroInfo()->getDefinitionLoc(); |
492 | SourceLocation DefineBegin = SM.translateLineCol( |
493 | FID: SM.getFileID(SpellingLoc: DefineEnd), Line: SM.getSpellingLineNumber(Loc: DefineEnd), Col: 1); |
494 | CharSourceRange DefineRange; |
495 | DefineRange.setBegin(DefineBegin); |
496 | DefineRange.setEnd(DefineEnd); |
497 | Diagnostic << FixItHint::CreateRemoval(RemoveRange: DefineRange); |
498 | |
499 | SourceLocation NameEnd = Lexer::getLocForEndOfToken( |
500 | Loc: Macro.Directive->getMacroInfo()->getDefinitionLoc(), Offset: 0, SM, LangOpts); |
501 | Diagnostic << FixItHint::CreateInsertion(InsertionLoc: NameEnd, Code: " =" ); |
502 | |
503 | SourceLocation ValueEnd = Lexer::getLocForEndOfToken( |
504 | Loc: Macro.Directive->getMacroInfo()->getDefinitionEndLoc(), Offset: 0, SM, |
505 | LangOpts); |
506 | if (I < MacroList.size() - 1) |
507 | Diagnostic << FixItHint::CreateInsertion(InsertionLoc: ValueEnd, Code: "," ); |
508 | } |
509 | |
510 | SourceLocation End = Lexer::getLocForEndOfToken( |
511 | Loc: MacroList.back().Directive->getMacroInfo()->getDefinitionEndLoc(), Offset: 0, SM, |
512 | LangOpts); |
513 | End = SM.translateLineCol(FID: SM.getFileID(SpellingLoc: End), |
514 | Line: SM.getSpellingLineNumber(Loc: End) + 1, Col: 1); |
515 | Diagnostic << FixItHint::CreateInsertion(InsertionLoc: End, Code: "};\n" ); |
516 | } |
517 | |
518 | void MacroToEnumCheck::registerPPCallbacks(const SourceManager &SM, |
519 | Preprocessor *PP, |
520 | Preprocessor *ModuleExpanderPP) { |
521 | auto Callback = std::make_unique<MacroToEnumCallbacks>(args: this, args: getLangOpts(), args: SM); |
522 | PPCallback = Callback.get(); |
523 | PP->addPPCallbacks(C: std::move(Callback)); |
524 | } |
525 | |
526 | void MacroToEnumCheck::registerMatchers(ast_matchers::MatchFinder *Finder) { |
527 | using namespace ast_matchers; |
528 | auto TopLevelDecl = hasParent(translationUnitDecl()); |
529 | Finder->addMatcher(NodeMatch: decl(TopLevelDecl).bind(ID: "top" ), Action: this); |
530 | } |
531 | |
532 | static bool isValid(SourceRange Range) { |
533 | return Range.getBegin().isValid() && Range.getEnd().isValid(); |
534 | } |
535 | |
536 | static bool empty(SourceRange Range) { |
537 | return Range.getBegin() == Range.getEnd(); |
538 | } |
539 | |
540 | void MacroToEnumCheck::check( |
541 | const ast_matchers::MatchFinder::MatchResult &Result) { |
542 | auto *TLDecl = Result.Nodes.getNodeAs<Decl>(ID: "top" ); |
543 | if (TLDecl == nullptr) |
544 | return; |
545 | |
546 | SourceRange Range = TLDecl->getSourceRange(); |
547 | if (auto *TemplateFn = Result.Nodes.getNodeAs<FunctionTemplateDecl>(ID: "top" )) { |
548 | if (TemplateFn->isThisDeclarationADefinition() && TemplateFn->hasBody()) |
549 | Range = SourceRange{TemplateFn->getBeginLoc(), |
550 | TemplateFn->getUnderlyingDecl()->getBodyRBrace()}; |
551 | } |
552 | |
553 | if (isValid(Range) && !empty(Range)) |
554 | PPCallback->invalidateRange(Range); |
555 | } |
556 | |
557 | } // namespace clang::tidy::modernize |
558 | |