xref: /llvm-project/clang-tools-extra/clang-tidy/modernize/MacroToEnumCheck.cpp (revision da95d926f6fce4ed9707c77908ad96624268f134)
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 
hasOnlyComments(SourceLocation Loc,const LangOptions & Options,StringRef Text)23 static bool hasOnlyComments(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(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 
getTokenName(const Token & Tok)76 static StringRef getTokenName(const Token &Tok) {
77   return Tok.is(tok::raw_identifier) ? Tok.getRawIdentifier()
78                                      : Tok.getIdentifierInfo()->getName();
79 }
80 
81 namespace {
82 
83 struct EnumMacro {
EnumMacroclang::tidy::modernize::__anon92bd59950111::EnumMacro84   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:
MacroToEnumCallbacks(MacroToEnumCheck * Check,const LangOptions & LangOptions,const SourceManager & SM)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 
InclusionDirective(SourceLocation HashLoc,const Token & IncludeTok,StringRef FileName,bool IsAngled,CharSourceRange FilenameRange,OptionalFileEntryRef File,StringRef SearchPath,StringRef RelativePath,const Module * SuggestedModule,bool ModuleImported,SrcMgr::CharacteristicKind FileType)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(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
If(SourceLocation Loc,SourceRange ConditionRange,ConditionValueKind ConditionValue)141   void If(SourceLocation Loc, SourceRange ConditionRange,
142           ConditionValueKind ConditionValue) override {
143     conditionStart(Loc);
144     checkCondition(ConditionRange);
145   }
Ifndef(SourceLocation Loc,const Token & MacroNameTok,const MacroDefinition & MD)146   void Ifndef(SourceLocation Loc, const Token &MacroNameTok,
147               const MacroDefinition &MD) override {
148     conditionStart(Loc);
149     checkName(MacroNameTok);
150   }
Ifdef(SourceLocation Loc,const Token & MacroNameTok,const MacroDefinition & MD)151   void Ifdef(SourceLocation Loc, const Token &MacroNameTok,
152              const MacroDefinition &MD) override {
153     conditionStart(Loc);
154     checkName(MacroNameTok);
155   }
Elif(SourceLocation Loc,SourceRange ConditionRange,ConditionValueKind ConditionValue,SourceLocation IfLoc)156   void Elif(SourceLocation Loc, SourceRange ConditionRange,
157             ConditionValueKind ConditionValue, SourceLocation IfLoc) override {
158     checkCondition(ConditionRange);
159   }
Elifdef(SourceLocation Loc,const Token & MacroNameTok,const MacroDefinition & MD)160   void Elifdef(SourceLocation Loc, const Token &MacroNameTok,
161                const MacroDefinition &MD) override {
162     checkName(MacroNameTok);
163   }
Elifdef(SourceLocation Loc,SourceRange ConditionRange,SourceLocation IfLoc)164   void Elifdef(SourceLocation Loc, SourceRange ConditionRange,
165       SourceLocation IfLoc) override {
166     PPCallbacks::Elifdef(Loc, ConditionRange, IfLoc);
167   }
Elifndef(SourceLocation Loc,const Token & MacroNameTok,const MacroDefinition & MD)168   void Elifndef(SourceLocation Loc, const Token &MacroNameTok,
169                 const MacroDefinition &MD) override {
170     checkName(MacroNameTok);
171   }
Elifndef(SourceLocation Loc,SourceRange ConditionRange,SourceLocation IfLoc)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:
newEnum()186   void newEnum() {
187     if (Enums.empty() || !Enums.back().empty())
188       Enums.emplace_back();
189   }
insideConditional() const190   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;
rememberLastMacroLocation(const MacroDirective * MD)197   void rememberLastMacroLocation(const MacroDirective *MD) {
198     CurrentFile->LastLine = SM.getSpellingLineNumber(MD->getLocation());
199     CurrentFile->LastMacroLocation = Lexer::getLocForEndOfToken(
200         MD->getMacroInfo()->getDefinitionEndLoc(), 0, SM, LangOpts);
201   }
clearLastMacroLocation()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 
isConsecutiveMacro(const MacroDirective * MD) const227 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(SM.getFileID(Loc), SM.getSpellingLineNumber(Loc), 1);
237   CharSourceRange BetweenMacros{
238       SourceRange{CurrentFile->LastMacroLocation, Define}, true};
239   CharSourceRange CharRange =
240       Lexer::makeFileCharRange(BetweenMacros, SM, LangOpts);
241   StringRef BetweenText = Lexer::getSourceText(CharRange, SM, LangOpts);
242   return hasOnlyComments(Define, LangOpts, BetweenText);
243 }
244 
clearCurrentEnum(SourceLocation Loc)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 
conditionStart(const SourceLocation & Loc)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 
checkCondition(SourceRange Range)261 void MacroToEnumCallbacks::checkCondition(SourceRange Range) {
262   CharSourceRange CharRange = Lexer::makeFileCharRange(
263       CharSourceRange::getTokenRange(Range), SM, LangOpts);
264   std::string Text = Lexer::getSourceText(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(Tok);
271     if (Tok.is(tok::raw_identifier) &&
272         Tok.getRawIdentifier().str() != "defined")
273       checkName(Tok);
274   }
275 }
276 
checkName(const Token & MacroNameTok)277 void MacroToEnumCallbacks::checkName(const Token &MacroNameTok) {
278   rememberExpressionName(MacroNameTok);
279 
280   StringRef Id = getTokenName(MacroNameTok);
281   llvm::erase_if(Enums, [&Id](const MacroList &MacroList) {
282     return llvm::any_of(MacroList, [&Id](const EnumMacro &Macro) {
283       return getTokenName(Macro.Name) == Id;
284     });
285   });
286 }
287 
rememberExpressionName(const Token & Tok)288 void MacroToEnumCallbacks::rememberExpressionName(const Token &Tok) {
289   std::string Id = getTokenName(Tok).str();
290   auto Pos = llvm::lower_bound(ExpressionNames, Id);
291   if (Pos == ExpressionNames.end() || *Pos != Id) {
292     ExpressionNames.insert(Pos, Id);
293   }
294 }
295 
rememberExpressionTokens(ArrayRef<Token> MacroTokens)296 void MacroToEnumCallbacks::rememberExpressionTokens(
297     ArrayRef<Token> MacroTokens) {
298   for (Token Tok : MacroTokens) {
299     if (Tok.isAnyIdentifier())
300       rememberExpressionName(Tok);
301   }
302 }
303 
FileChanged(SourceLocation Loc,FileChangeReason Reason,SrcMgr::CharacteristicKind FileType,FileID PrevFID)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 
isInitializer(ArrayRef<Token> MacroTokens)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.
MacroDefined(const Token & MacroNameTok,const MacroDirective * MD)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(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(MacroNameTok, 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.
MacroUndefined(const Token & MacroNameTok,const MacroDefinition & MD,const MacroDirective * Undef)369 void MacroToEnumCallbacks::MacroUndefined(const Token &MacroNameTok,
370                                           const MacroDefinition &MD,
371                                           const MacroDirective *Undef) {
372   rememberExpressionName(MacroNameTok);
373 
374   auto MatchesToken = [&MacroNameTok](const EnumMacro &Macro) {
375     return getTokenName(Macro.Name) == getTokenName(MacroNameTok);
376   };
377 
378   auto It = llvm::find_if(Enums, [MatchesToken](const MacroList &MacroList) {
379     return llvm::any_of(MacroList, MatchesToken);
380   });
381   if (It != Enums.end())
382     Enums.erase(It);
383 
384   clearLastMacroLocation();
385   CurrentFile->GuardScanner = IncludeGuard::None;
386 }
387 
Endif(SourceLocation Loc,SourceLocation IfLoc)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>
textEquals(const char (& Needle)[N],const char * HayStack)404 bool textEquals(const char (&Needle)[N], const char *HayStack) {
405   return StringRef{HayStack, N - 1} == Needle;
406 }
407 
len(const char (&)[N])408 template <size_t N> size_t len(const char (&)[N]) { return N - 1; }
409 
410 } // namespace
411 
PragmaDirective(SourceLocation Loc,PragmaIntroducerKind Introducer)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       Lexer::getLocForEndOfToken(Loc, 0, SM, LangOpts), &Invalid);
420   if (Invalid)
421     return;
422 
423   while (*Text && std::isspace(*Text))
424     ++Text;
425 
426   if (textEquals("pragma", Text))
427     return;
428 
429   Text += len("pragma");
430   while (*Text && std::isspace(*Text))
431     ++Text;
432 
433   if (textEquals("once", Text))
434     CurrentFile->GuardScanner = IncludeGuard::IfGuard;
435 }
436 
invalidateExpressionNames()437 void MacroToEnumCallbacks::invalidateExpressionNames() {
438   for (const std::string &Id : ExpressionNames) {
439     llvm::erase_if(Enums, [Id](const MacroList &MacroList) {
440       return llvm::any_of(MacroList, [&Id](const EnumMacro &Macro) {
441         return getTokenName(Macro.Name) == Id;
442       });
443     });
444   }
445 }
446 
EndOfMainFile()447 void MacroToEnumCallbacks::EndOfMainFile() {
448     invalidateExpressionNames();
449     issueDiagnostics();
450 }
451 
invalidateRange(SourceRange Range)452 void MacroToEnumCallbacks::invalidateRange(SourceRange Range) {
453   llvm::erase_if(Enums, [Range](const MacroList &MacroList) {
454     return llvm::any_of(MacroList, [Range](const EnumMacro &Macro) {
455       return Macro.Directive->getLocation() >= Range.getBegin() &&
456              Macro.Directive->getLocation() <= Range.getEnd();
457     });
458   });
459 }
460 
issueDiagnostics()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 
warnMacroEnum(const EnumMacro & Macro) const473 void MacroToEnumCallbacks::warnMacroEnum(const EnumMacro &Macro) const {
474   Check->diag(Macro.Directive->getLocation(),
475               "macro '%0' defines an integral constant; prefer an enum instead")
476       << getTokenName(Macro.Name);
477 }
478 
fixEnumMacro(const MacroList & MacroList) const479 void MacroToEnumCallbacks::fixEnumMacro(const MacroList &MacroList) const {
480   SourceLocation Begin =
481       MacroList.front().Directive->getMacroInfo()->getDefinitionLoc();
482   Begin = SM.translateLineCol(SM.getFileID(Begin),
483                               SM.getSpellingLineNumber(Begin), 1);
484   DiagnosticBuilder Diagnostic =
485       Check->diag(Begin, "replace macro with enum")
486       << FixItHint::CreateInsertion(Begin, "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         SM.getFileID(DefineEnd), SM.getSpellingLineNumber(DefineEnd), 1);
494     CharSourceRange DefineRange;
495     DefineRange.setBegin(DefineBegin);
496     DefineRange.setEnd(DefineEnd);
497     Diagnostic << FixItHint::CreateRemoval(DefineRange);
498 
499     SourceLocation NameEnd = Lexer::getLocForEndOfToken(
500         Macro.Directive->getMacroInfo()->getDefinitionLoc(), 0, SM, LangOpts);
501     Diagnostic << FixItHint::CreateInsertion(NameEnd, " =");
502 
503     SourceLocation ValueEnd = Lexer::getLocForEndOfToken(
504         Macro.Directive->getMacroInfo()->getDefinitionEndLoc(), 0, SM,
505         LangOpts);
506     if (I < MacroList.size() - 1)
507       Diagnostic << FixItHint::CreateInsertion(ValueEnd, ",");
508   }
509 
510   SourceLocation End = Lexer::getLocForEndOfToken(
511       MacroList.back().Directive->getMacroInfo()->getDefinitionEndLoc(), 0, SM,
512       LangOpts);
513   End = SM.translateLineCol(SM.getFileID(End),
514                             SM.getSpellingLineNumber(End) + 1, 1);
515   Diagnostic << FixItHint::CreateInsertion(End, "};\n");
516 }
517 
registerPPCallbacks(const SourceManager & SM,Preprocessor * PP,Preprocessor * ModuleExpanderPP)518 void MacroToEnumCheck::registerPPCallbacks(const SourceManager &SM,
519                                            Preprocessor *PP,
520                                            Preprocessor *ModuleExpanderPP) {
521   auto Callback = std::make_unique<MacroToEnumCallbacks>(this, getLangOpts(), SM);
522   PPCallback = Callback.get();
523   PP->addPPCallbacks(std::move(Callback));
524 }
525 
registerMatchers(ast_matchers::MatchFinder * Finder)526 void MacroToEnumCheck::registerMatchers(ast_matchers::MatchFinder *Finder) {
527   using namespace ast_matchers;
528   auto TopLevelDecl = hasParent(translationUnitDecl());
529   Finder->addMatcher(decl(TopLevelDecl).bind("top"), this);
530 }
531 
isValid(SourceRange Range)532 static bool isValid(SourceRange Range) {
533   return Range.getBegin().isValid() && Range.getEnd().isValid();
534 }
535 
empty(SourceRange Range)536 static bool empty(SourceRange Range) {
537   return Range.getBegin() == Range.getEnd();
538 }
539 
check(const ast_matchers::MatchFinder::MatchResult & Result)540 void MacroToEnumCheck::check(
541     const ast_matchers::MatchFinder::MatchResult &Result) {
542   auto *TLDecl = Result.Nodes.getNodeAs<Decl>("top");
543   if (TLDecl == nullptr)
544       return;
545 
546   SourceRange Range = TLDecl->getSourceRange();
547   if (auto *TemplateFn = Result.Nodes.getNodeAs<FunctionTemplateDecl>("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