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