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