--- /dev/null
+//===--- MacroToEnumCheck.cpp - clang-tidy --------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "MacroToEnumCheck.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Lex/Preprocessor.h"
+#include "llvm/ADT/STLExtras.h"
+#include <algorithm>
+#include <cassert>
+#include <cctype>
+#include <string>
+
+namespace clang {
+namespace tidy {
+namespace modernize {
+
+static bool hasOnlyComments(SourceLocation Loc, const LangOptions &Options,
+ StringRef Text) {
+ // Use a lexer to look for tokens; if we find something other than a single
+ // hash, then there were intervening tokens between macro definitions.
+ std::string Buffer{Text};
+ Lexer Lex(Loc, Options, Buffer.c_str(), Buffer.c_str(),
+ Buffer.c_str() + Buffer.size());
+ Token Tok;
+ bool SeenHash = false;
+ while (!Lex.LexFromRawLexer(Tok)) {
+ if (Tok.getKind() == tok::hash && !SeenHash) {
+ SeenHash = true;
+ continue;
+ }
+ return false;
+ }
+
+ // Everything in between was whitespace, so now just look for two blank lines,
+ // consisting of two consecutive EOL sequences, either '\n', '\r' or '\r\n'.
+ enum class WhiteSpace {
+ Nothing,
+ CR,
+ LF,
+ CRLF,
+ CRLFCR,
+ };
+
+ WhiteSpace State = WhiteSpace::Nothing;
+ for (char C : Text) {
+ switch (C) {
+ case '\r':
+ if (State == WhiteSpace::CR)
+ return false;
+
+ State = State == WhiteSpace::CRLF ? WhiteSpace::CRLFCR : WhiteSpace::CR;
+ break;
+
+ case '\n':
+ if (State == WhiteSpace::LF || State == WhiteSpace::CRLFCR)
+ return false;
+
+ State = State == WhiteSpace::CR ? WhiteSpace::CRLF : WhiteSpace::LF;
+ break;
+
+ default:
+ State = WhiteSpace::Nothing;
+ break;
+ }
+ }
+
+ return true;
+}
+
+// Validate that this literal token is a valid integer literal. A literal token
+// could be a floating-point token, which isn't acceptable as a value for an
+// enumeration. A floating-point token must either have a decimal point or an
+// exponent ('E' or 'P').
+static bool isIntegralConstant(const Token &Token) {
+ const char *Begin = Token.getLiteralData();
+ const char *End = Begin + Token.getLength();
+
+ // not a hexadecimal floating-point literal
+ if (Token.getLength() > 2 && Begin[0] == '0' && std::toupper(Begin[1]) == 'X')
+ return std::none_of(Begin + 2, End, [](char C) {
+ return C == '.' || std::toupper(C) == 'P';
+ });
+
+ // not a decimal floating-point literal
+ return std::none_of(
+ Begin, End, [](char C) { return C == '.' || std::toupper(C) == 'E'; });
+}
+
+namespace {
+
+struct EnumMacro {
+ EnumMacro(Token Name, const MacroDirective *Directive)
+ : Name(Name), Directive(Directive) {}
+
+ Token Name;
+ const MacroDirective *Directive;
+};
+
+using MacroList = SmallVector<EnumMacro>;
+
+enum class IncludeGuard { None, FileChanged, IfGuard, DefineGuard };
+
+struct FileState {
+ FileState()
+ : ConditionScopes(0), LastLine(0), GuardScanner(IncludeGuard::None) {}
+
+ int ConditionScopes;
+ unsigned int LastLine;
+ IncludeGuard GuardScanner;
+ SourceLocation LastMacroLocation;
+};
+
+class MacroToEnumCallbacks : public PPCallbacks {
+public:
+ MacroToEnumCallbacks(MacroToEnumCheck *Check, const LangOptions &LangOptions,
+ const SourceManager &SM)
+ : Check(Check), LangOpts(LangOptions), SM(SM) {}
+
+ void FileChanged(SourceLocation Loc, FileChangeReason Reason,
+ SrcMgr::CharacteristicKind FileType,
+ FileID PrevFID) override;
+
+ void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok,
+ StringRef FileName, bool IsAngled,
+ CharSourceRange FilenameRange, const FileEntry *File,
+ StringRef SearchPath, StringRef RelativePath,
+ const Module *Imported,
+ SrcMgr::CharacteristicKind FileType) override {
+ clearCurrentEnum(HashLoc);
+ }
+
+ // Keep track of macro definitions that look like enums.
+ void MacroDefined(const Token &MacroNameTok,
+ const MacroDirective *MD) override;
+
+ // Undefining an enum-like macro results in the enum set being dropped.
+ void MacroUndefined(const Token &MacroNameTok, const MacroDefinition &MD,
+ const MacroDirective *Undef) override;
+
+ // Conditional compilation clears any adjacent enum-like macros.
+ // Macros used in conditional expressions clear any adjacent enum-like
+ // macros.
+ // Include guards are either
+ // #if !defined(GUARD)
+ // or
+ // #ifndef GUARD
+ void If(SourceLocation Loc, SourceRange ConditionRange,
+ ConditionValueKind ConditionValue) override {
+ conditionStart(Loc);
+ checkCondition(ConditionRange);
+ }
+ void Ifndef(SourceLocation Loc, const Token &MacroNameTok,
+ const MacroDefinition &MD) override {
+ conditionStart(Loc);
+ checkName(MacroNameTok);
+ }
+ void Ifdef(SourceLocation Loc, const Token &MacroNameTok,
+ const MacroDefinition &MD) override {
+ conditionStart(Loc);
+ checkName(MacroNameTok);
+ }
+ void Elif(SourceLocation Loc, SourceRange ConditionRange,
+ ConditionValueKind ConditionValue, SourceLocation IfLoc) override {
+ checkCondition(ConditionRange);
+ }
+ void Elifdef(SourceLocation Loc, const Token &MacroNameTok,
+ const MacroDefinition &MD) override {
+ checkName(MacroNameTok);
+ }
+ void Elifdef(SourceLocation Loc, SourceRange ConditionRange,
+ SourceLocation IfLoc) override {
+ PPCallbacks::Elifdef(Loc, ConditionRange, IfLoc);
+ }
+ void Elifndef(SourceLocation Loc, const Token &MacroNameTok,
+ const MacroDefinition &MD) override {
+ checkName(MacroNameTok);
+ }
+ void Elifndef(SourceLocation Loc, SourceRange ConditionRange,
+ SourceLocation IfLoc) override {
+ PPCallbacks::Elifndef(Loc, ConditionRange, IfLoc);
+ }
+ void Endif(SourceLocation Loc, SourceLocation IfLoc) override;
+ void PragmaDirective(SourceLocation Loc,
+ PragmaIntroducerKind Introducer) override;
+
+ // After we've seen everything, issue warnings and fix-its.
+ void EndOfMainFile() override;
+
+private:
+ void newEnum() {
+ if (Enums.empty() || !Enums.back().empty())
+ Enums.emplace_back();
+ }
+ bool insideConditional() const {
+ return (CurrentFile->GuardScanner == IncludeGuard::DefineGuard &&
+ CurrentFile->ConditionScopes > 1) ||
+ (CurrentFile->GuardScanner != IncludeGuard::DefineGuard &&
+ CurrentFile->ConditionScopes > 0);
+ }
+ bool isConsecutiveMacro(const MacroDirective *MD) const;
+ void rememberLastMacroLocation(const MacroDirective *MD) {
+ CurrentFile->LastLine = SM.getSpellingLineNumber(MD->getLocation());
+ CurrentFile->LastMacroLocation = Lexer::getLocForEndOfToken(
+ MD->getMacroInfo()->getDefinitionEndLoc(), 0, SM, LangOpts);
+ }
+ void clearLastMacroLocation() {
+ CurrentFile->LastLine = 0;
+ CurrentFile->LastMacroLocation = SourceLocation{};
+ }
+ void clearCurrentEnum(SourceLocation Loc);
+ void conditionStart(const SourceLocation &Loc);
+ void checkCondition(SourceRange ConditionRange);
+ void checkName(const Token &MacroNameTok);
+ void warnMacroEnum(const EnumMacro &Macro) const;
+ void fixEnumMacro(const MacroList &MacroList) const;
+
+ MacroToEnumCheck *Check;
+ const LangOptions &LangOpts;
+ const SourceManager &SM;
+ SmallVector<MacroList> Enums;
+ SmallVector<FileState> Files;
+ FileState *CurrentFile = nullptr;
+};
+
+bool MacroToEnumCallbacks::isConsecutiveMacro(const MacroDirective *MD) const {
+ if (CurrentFile->LastMacroLocation.isInvalid())
+ return false;
+
+ SourceLocation Loc = MD->getLocation();
+ if (CurrentFile->LastLine + 1 == SM.getSpellingLineNumber(Loc))
+ return true;
+
+ SourceLocation Define =
+ SM.translateLineCol(SM.getFileID(Loc), SM.getSpellingLineNumber(Loc), 1);
+ CharSourceRange BetweenMacros{
+ SourceRange{CurrentFile->LastMacroLocation, Define}, true};
+ CharSourceRange CharRange =
+ Lexer::makeFileCharRange(BetweenMacros, SM, LangOpts);
+ StringRef BetweenText = Lexer::getSourceText(CharRange, SM, LangOpts);
+ return hasOnlyComments(Define, LangOpts, BetweenText);
+}
+
+void MacroToEnumCallbacks::clearCurrentEnum(SourceLocation Loc) {
+ // Only drop the most recent Enum set if the directive immediately follows.
+ if (!Enums.empty() && !Enums.back().empty() &&
+ SM.getSpellingLineNumber(Loc) == CurrentFile->LastLine + 1)
+ Enums.pop_back();
+
+ clearLastMacroLocation();
+}
+
+void MacroToEnumCallbacks::conditionStart(const SourceLocation &Loc) {
+ ++CurrentFile->ConditionScopes;
+ clearCurrentEnum(Loc);
+ if (CurrentFile->GuardScanner == IncludeGuard::FileChanged)
+ CurrentFile->GuardScanner = IncludeGuard::IfGuard;
+}
+
+void MacroToEnumCallbacks::checkCondition(SourceRange Range) {
+ CharSourceRange CharRange = Lexer::makeFileCharRange(
+ CharSourceRange::getTokenRange(Range), SM, LangOpts);
+ std::string Text = Lexer::getSourceText(CharRange, SM, LangOpts).str();
+ Lexer Lex(CharRange.getBegin(), LangOpts, Text.data(), Text.data(),
+ Text.data() + Text.size());
+ Token Tok;
+ bool End = false;
+ while (!End) {
+ End = Lex.LexFromRawLexer(Tok);
+ if (Tok.is(tok::raw_identifier) &&
+ Tok.getRawIdentifier().str() != "defined")
+ checkName(Tok);
+ }
+}
+
+void MacroToEnumCallbacks::checkName(const Token &MacroNameTok) {
+ std::string Id;
+ if (MacroNameTok.is(tok::raw_identifier))
+ Id = MacroNameTok.getRawIdentifier().str();
+ else if (MacroNameTok.is(tok::identifier))
+ Id = MacroNameTok.getIdentifierInfo()->getName().str();
+ else {
+ assert(false && "Expected either an identifier or raw identifier token");
+ return;
+ }
+
+ llvm::erase_if(Enums, [&Id](const MacroList &MacroList) {
+ return llvm::any_of(MacroList, [&Id](const EnumMacro &Macro) {
+ return Macro.Name.getIdentifierInfo()->getName().str() == Id;
+ });
+ });
+}
+
+void MacroToEnumCallbacks::FileChanged(SourceLocation Loc,
+ FileChangeReason Reason,
+ SrcMgr::CharacteristicKind FileType,
+ FileID PrevFID) {
+ newEnum();
+ if (Reason == EnterFile) {
+ Files.emplace_back();
+ if (!SM.isInMainFile(Loc))
+ Files.back().GuardScanner = IncludeGuard::FileChanged;
+ } else if (Reason == ExitFile) {
+ assert(CurrentFile->ConditionScopes == 0);
+ Files.pop_back();
+ }
+ CurrentFile = &Files.back();
+}
+
+void MacroToEnumCallbacks::MacroDefined(const Token &MacroNameTok,
+ const MacroDirective *MD) {
+ // Include guards are never candidates for becoming an enum.
+ if (CurrentFile->GuardScanner == IncludeGuard::IfGuard) {
+ CurrentFile->GuardScanner = IncludeGuard::DefineGuard;
+ return;
+ }
+
+ if (insideConditional())
+ return;
+
+ if (SM.getFilename(MD->getLocation()).empty())
+ return;
+
+ const MacroInfo *Info = MD->getMacroInfo();
+ if (Info->isFunctionLike() || Info->isBuiltinMacro() ||
+ Info->tokens().empty() || Info->tokens().size() > 2)
+ return;
+
+ // It can be +Lit, -Lit or just Lit.
+ Token Tok = Info->tokens().front();
+ if (Info->tokens().size() == 2) {
+ if (!Tok.isOneOf(tok::TokenKind::minus, tok::TokenKind::plus,
+ tok::TokenKind::tilde))
+ return;
+ Tok = Info->tokens().back();
+ }
+ if (!Tok.isLiteral() || isStringLiteral(Tok.getKind()) ||
+ !isIntegralConstant(Tok))
+ return;
+
+ if (!isConsecutiveMacro(MD))
+ newEnum();
+ Enums.back().emplace_back(MacroNameTok, MD);
+ rememberLastMacroLocation(MD);
+}
+
+// Any macro that is undefined removes all adjacent macros from consideration as
+// an enum and starts a new enum scan.
+void MacroToEnumCallbacks::MacroUndefined(const Token &MacroNameTok,
+ const MacroDefinition &MD,
+ const MacroDirective *Undef) {
+ auto MatchesToken = [&MacroNameTok](const EnumMacro &Macro) {
+ return Macro.Name.getIdentifierInfo()->getName() ==
+ MacroNameTok.getIdentifierInfo()->getName();
+ };
+
+ auto It = llvm::find_if(Enums, [MatchesToken](const MacroList &MacroList) {
+ return llvm::any_of(MacroList, MatchesToken);
+ });
+ if (It != Enums.end())
+ Enums.erase(It);
+
+ clearLastMacroLocation();
+ CurrentFile->GuardScanner = IncludeGuard::None;
+}
+
+void MacroToEnumCallbacks::Endif(SourceLocation Loc, SourceLocation IfLoc) {
+ // The if directive for the include guard isn't counted in the
+ // ConditionScopes.
+ if (CurrentFile->ConditionScopes == 0 &&
+ CurrentFile->GuardScanner == IncludeGuard::DefineGuard)
+ return;
+
+ // We don't need to clear the current enum because the start of the
+ // conditional block already took care of that.
+ assert(CurrentFile->ConditionScopes > 0);
+ --CurrentFile->ConditionScopes;
+}
+
+namespace {
+
+template <size_t N>
+bool textEquals(const char (&Needle)[N], const char *HayStack) {
+ return StringRef{HayStack, N - 1} == Needle;
+}
+
+template <size_t N> size_t len(const char (&)[N]) { return N - 1; }
+
+} // namespace
+
+void MacroToEnumCallbacks::PragmaDirective(SourceLocation Loc,
+ PragmaIntroducerKind Introducer) {
+ if (CurrentFile->GuardScanner != IncludeGuard::FileChanged)
+ return;
+
+ bool Invalid = false;
+ const char *Text = SM.getCharacterData(
+ Lexer::getLocForEndOfToken(Loc, 0, SM, LangOpts), &Invalid);
+ if (Invalid)
+ return;
+
+ while (*Text && std::isspace(*Text))
+ ++Text;
+
+ if (textEquals("pragma", Text))
+ return;
+
+ Text += len("pragma");
+ while (*Text && std::isspace(*Text))
+ ++Text;
+
+ if (textEquals("once", Text))
+ CurrentFile->GuardScanner = IncludeGuard::IfGuard;
+}
+
+void MacroToEnumCallbacks::EndOfMainFile() {
+ for (const MacroList &MacroList : Enums) {
+ if (MacroList.empty())
+ continue;
+
+ for (const EnumMacro &Macro : MacroList)
+ warnMacroEnum(Macro);
+
+ fixEnumMacro(MacroList);
+ }
+}
+
+void MacroToEnumCallbacks::warnMacroEnum(const EnumMacro &Macro) const {
+ Check->diag(Macro.Directive->getLocation(),
+ "macro %0 defines an integral constant; prefer an enum instead")
+ << Macro.Name.getIdentifierInfo();
+}
+
+void MacroToEnumCallbacks::fixEnumMacro(const MacroList &MacroList) const {
+ SourceLocation Begin =
+ MacroList.front().Directive->getMacroInfo()->getDefinitionLoc();
+ Begin = SM.translateLineCol(SM.getFileID(Begin),
+ SM.getSpellingLineNumber(Begin), 1);
+ DiagnosticBuilder Diagnostic =
+ Check->diag(Begin, "replace macro with enum")
+ << FixItHint::CreateInsertion(Begin, "enum {\n");
+
+ for (size_t I = 0u; I < MacroList.size(); ++I) {
+ const EnumMacro &Macro = MacroList[I];
+ SourceLocation DefineEnd =
+ Macro.Directive->getMacroInfo()->getDefinitionLoc();
+ SourceLocation DefineBegin = SM.translateLineCol(
+ SM.getFileID(DefineEnd), SM.getSpellingLineNumber(DefineEnd), 1);
+ CharSourceRange DefineRange;
+ DefineRange.setBegin(DefineBegin);
+ DefineRange.setEnd(DefineEnd);
+ Diagnostic << FixItHint::CreateRemoval(DefineRange);
+
+ SourceLocation NameEnd = Lexer::getLocForEndOfToken(
+ Macro.Directive->getMacroInfo()->getDefinitionLoc(), 0, SM, LangOpts);
+ Diagnostic << FixItHint::CreateInsertion(NameEnd, " =");
+
+ SourceLocation ValueEnd = Lexer::getLocForEndOfToken(
+ Macro.Directive->getMacroInfo()->getDefinitionEndLoc(), 0, SM,
+ LangOpts);
+ if (I < MacroList.size() - 1)
+ Diagnostic << FixItHint::CreateInsertion(ValueEnd, ",");
+ }
+
+ SourceLocation End = Lexer::getLocForEndOfToken(
+ MacroList.back().Directive->getMacroInfo()->getDefinitionEndLoc(), 0, SM,
+ LangOpts);
+ End = SM.translateLineCol(SM.getFileID(End),
+ SM.getSpellingLineNumber(End) + 1, 1);
+ Diagnostic << FixItHint::CreateInsertion(End, "};\n");
+}
+
+} // namespace
+
+void MacroToEnumCheck::registerPPCallbacks(const SourceManager &SM,
+ Preprocessor *PP,
+ Preprocessor *ModuleExpanderPP) {
+ PP->addPPCallbacks(
+ std::make_unique<MacroToEnumCallbacks>(this, getLangOpts(), SM));
+}
+
+} // namespace modernize
+} // namespace tidy
+} // namespace clang
--- /dev/null
+// RUN: %check_clang_tidy -std=c++14-or-later %s modernize-macro-to-enum %t -- -- -I%S/Inputs/modernize-macro-to-enum
+// C++14 or later required for binary literals.
+
+#if 1
+#include "modernize-macro-to-enum.h"
+
+// These macros are skipped due to being inside a conditional compilation block.
+#define GOO_RED 1
+#define GOO_GREEN 2
+#define GOO_BLUE 3
+
+#endif
+
+#define RED 0xFF0000
+#define GREEN 0x00FF00
+#define BLUE 0x0000FF
+// CHECK-MESSAGES: :[[@LINE-3]]:1: warning: replace macro with enum [modernize-macro-to-enum]
+// CHECK-MESSAGES: :[[@LINE-4]]:9: warning: macro 'RED' defines an integral constant; prefer an enum instead
+// CHECK-MESSAGES: :[[@LINE-4]]:9: warning: macro 'GREEN' defines an integral constant; prefer an enum instead
+// CHECK-MESSAGES: :[[@LINE-4]]:9: warning: macro 'BLUE' defines an integral constant; prefer an enum instead
+// CHECK-FIXES: enum {
+// CHECK-FIXES-NEXT: RED = 0xFF0000,
+// CHECK-FIXES-NEXT: GREEN = 0x00FF00,
+// CHECK-FIXES-NEXT: BLUE = 0x0000FF
+// CHECK-FIXES-NEXT: };
+
+// Verify that comments are preserved.
+#define CoordModeOrigin 0 /* relative to the origin */
+#define CoordModePrevious 1 /* relative to previous point */
+// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: replace macro with enum
+// CHECK-MESSAGES: :[[@LINE-3]]:9: warning: macro 'CoordModeOrigin' defines an integral constant; prefer an enum instead
+// CHECK-MESSAGES: :[[@LINE-3]]:9: warning: macro 'CoordModePrevious' defines an integral constant; prefer an enum instead
+// CHECK-FIXES: enum {
+// CHECK-FIXES-NEXT: CoordModeOrigin = 0, /* relative to the origin */
+// CHECK-FIXES-NEXT: CoordModePrevious = 1 /* relative to previous point */
+// CHECK-FIXES-NEXT: };
+
+// Verify that multiline comments are preserved.
+#define BadDrawable 9 /* parameter not a Pixmap or Window */
+#define BadAccess 10 /* depending on context:
+ - key/button already grabbed
+ - attempt to free an illegal
+ cmap entry
+ - attempt to store into a read-only
+ color map entry. */
+ // - attempt to modify the access control
+ // list from other than the local host.
+ //
+#define BadAlloc 11 /* insufficient resources */
+// CHECK-MESSAGES: :[[@LINE-11]]:1: warning: replace macro with enum
+// CHECK-MESSAGES: :[[@LINE-12]]:9: warning: macro 'BadDrawable' defines an integral constant; prefer an enum instead
+// CHECK-MESSAGES: :[[@LINE-12]]:9: warning: macro 'BadAccess' defines an integral constant; prefer an enum instead
+// CHECK-MESSAGES: :[[@LINE-4]]:9: warning: macro 'BadAlloc' defines an integral constant; prefer an enum instead
+// CHECK-FIXES: enum {
+// CHECK-FIXES-NEXT: BadDrawable = 9, /* parameter not a Pixmap or Window */
+// CHECK-FIXES-NEXT: BadAccess = 10, /* depending on context:
+// CHECK-FIXES-NEXT: - key/button already grabbed
+// CHECK-FIXES-NEXT: - attempt to free an illegal
+// CHECK-FIXES-NEXT: cmap entry
+// CHECK-FIXES-NEXT: - attempt to store into a read-only
+// CHECK-FIXES-NEXT: color map entry. */
+// CHECK-FIXES-NEXT: // - attempt to modify the access control
+// CHECK-FIXES-NEXT: // list from other than the local host.
+// CHECK-FIXES-NEXT: //
+// CHECK-FIXES-NEXT: BadAlloc = 11 /* insufficient resources */
+// CHECK-FIXES-NEXT: };
+
+// Undefining a macro invalidates adjacent macros
+// from being considered as an enum.
+#define REMOVED1 1
+#define REMOVED2 2
+#define REMOVED3 3
+#undef REMOVED2
+#define VALID1 1
+// CHECK-MESSAGES: :[[@LINE-1]]:1: warning: replace macro with enum
+// CHECK-MESSAGES: :[[@LINE-2]]:9: warning: macro 'VALID1' defines an integral constant; prefer an enum instead
+// CHECK-FIXES: enum {
+// CHECK-FIXES-NEXT: VALID1 = 1
+// CHECK-FIXES-NEXT: };
+
+#define UNDEF1 1
+#define UNDEF2 2
+#define UNDEF3 3
+
+// Undefining a macro later invalidates the set of possible adjacent macros
+// from being considered as an enum.
+#undef UNDEF2
+
+// Integral constants can have an optional sign
+#define SIGNED1 +1
+#define SIGNED2 -1
+// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: replace macro with enum
+// CHECK-MESSAGES: :[[@LINE-3]]:9: warning: macro 'SIGNED1' defines an integral constant; prefer an enum instead
+// CHECK-MESSAGES: :[[@LINE-3]]:9: warning: macro 'SIGNED2' defines an integral constant; prefer an enum instead
+// CHECK-FIXES: enum {
+// CHECK-FIXES-NEXT: SIGNED1 = +1,
+// CHECK-FIXES-NEXT: SIGNED2 = -1
+// CHECK-FIXES-NEXT: };
+
+// Integral constants with bitwise negated values
+#define UNOP1 ~0U
+#define UNOP2 ~1U
+// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: replace macro with enum
+// CHECK-MESSAGES: :[[@LINE-3]]:9: warning: macro 'UNOP1' defines an integral constant; prefer an enum instead
+// CHECK-MESSAGES: :[[@LINE-3]]:9: warning: macro 'UNOP2' defines an integral constant; prefer an enum instead
+// CHECK-FIXES: enum {
+// CHECK-FIXES-NEXT: UNOP1 = ~0U,
+// CHECK-FIXES-NEXT: UNOP2 = ~1U
+// CHECK-FIXES-NEXT: };
+
+// Integral constants in other bases and with suffixes are OK
+#define BASE1 0777 // octal
+#define BASE2 0xDEAD // hexadecimal
+#define BASE3 0b0011 // binary
+#define SUFFIX1 +1U
+#define SUFFIX2 -1L
+#define SUFFIX3 +1UL
+#define SUFFIX4 -1LL
+#define SUFFIX5 +1ULL
+// CHECK-MESSAGES: :[[@LINE-8]]:1: warning: replace macro with enum
+// CHECK-MESSAGES: :[[@LINE-9]]:9: warning: macro 'BASE1' defines an integral constant; prefer an enum instead
+// CHECK-MESSAGES: :[[@LINE-9]]:9: warning: macro 'BASE2' defines an integral constant; prefer an enum instead
+// CHECK-MESSAGES: :[[@LINE-9]]:9: warning: macro 'BASE3' defines an integral constant; prefer an enum instead
+// CHECK-MESSAGES: :[[@LINE-9]]:9: warning: macro 'SUFFIX1' defines an integral constant; prefer an enum instead
+// CHECK-MESSAGES: :[[@LINE-9]]:9: warning: macro 'SUFFIX2' defines an integral constant; prefer an enum instead
+// CHECK-MESSAGES: :[[@LINE-9]]:9: warning: macro 'SUFFIX3' defines an integral constant; prefer an enum instead
+// CHECK-MESSAGES: :[[@LINE-9]]:9: warning: macro 'SUFFIX4' defines an integral constant; prefer an enum instead
+// CHECK-MESSAGES: :[[@LINE-9]]:9: warning: macro 'SUFFIX5' defines an integral constant; prefer an enum instead
+// CHECK-FIXES: enum {
+// CHECK-FIXES-NEXT: BASE1 = 0777, // octal
+// CHECK-FIXES-NEXT: BASE2 = 0xDEAD, // hexadecimal
+// CHECK-FIXES-NEXT: BASE3 = 0b0011, // binary
+// CHECK-FIXES-NEXT: SUFFIX1 = +1U,
+// CHECK-FIXES-NEXT: SUFFIX2 = -1L,
+// CHECK-FIXES-NEXT: SUFFIX3 = +1UL,
+// CHECK-FIXES-NEXT: SUFFIX4 = -1LL,
+// CHECK-FIXES-NEXT: SUFFIX5 = +1ULL
+// CHECK-FIXES-NEXT: };
+
+// Macros appearing in conditional expressions can't be replaced
+// by enums.
+#define USE_FOO 1
+#define USE_BAR 0
+#define USE_IF 1
+#define USE_ELIF 1
+#define USE_IFDEF 1
+#define USE_IFNDEF 1
+
+#if defined(USE_FOO) && USE_FOO
+extern void foo();
+#else
+inline void foo() {}
+#endif
+
+#if USE_BAR
+extern void bar();
+#else
+inline void bar() {}
+#endif
+
+#if USE_IF
+inline void used_if() {}
+#endif
+
+#if 0
+#elif USE_ELIF
+inline void used_elif() {}
+#endif
+
+#ifdef USE_IFDEF
+inline void used_ifdef() {}
+#endif
+
+#ifndef USE_IFNDEF
+#else
+inline void used_ifndef() {}
+#endif
+
+// Regular conditional compilation blocks should leave previous
+// macro enums alone.
+#if 0
+#include <non-existent.h>
+#endif
+
+// Conditional compilation blocks invalidate adjacent macros
+// from being considered as an enum. Conditionally compiled
+// blocks could contain macros that should rightly be included
+// in the enum, but we can't explore multiple branches of a
+// conditionally compiled section in clang-tidy, only the active
+// branch based on compilation options.
+#define CONDITION1 1
+#define CONDITION2 2
+#if 0
+#define CONDITION3 3
+#else
+#define CONDITION3 -3
+#endif
+
+#define IFDEF1 1
+#define IFDEF2 2
+#ifdef FROB
+#define IFDEF3 3
+#endif
+
+#define IFNDEF1 1
+#define IFNDEF2 2
+#ifndef GOINK
+#define IFNDEF3 3
+#endif
+
+// These macros do not expand to integral constants.
+#define HELLO "Hello, "
+#define WORLD "World"
+#define EPS1 1.0F
+#define EPS2 1e5
+#define EPS3 1.
+
+#define DO_RED draw(RED)
+#define DO_GREEN draw(GREEN)
+#define DO_BLUE draw(BLUE)
+
+#define FN_RED(x) draw(RED | x)
+#define FN_GREEN(x) draw(GREEN | x)
+#define FN_BLUE(x) draw(BLUE | x)
+
+extern void draw(unsigned int Color);
+
+void f()
+{
+ draw(RED);
+ draw(GREEN);
+ draw(BLUE);
+ DO_RED;
+ DO_GREEN;
+ DO_BLUE;
+ FN_RED(0);
+ FN_GREEN(0);
+ FN_BLUE(0);
+}