| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,200 @@ | ||
| //===--- EnumInitialValueCheck.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 "EnumInitialValueCheck.h" | ||
| #include "../utils/LexerUtils.h" | ||
| #include "clang/AST/Decl.h" | ||
| #include "clang/ASTMatchers/ASTMatchFinder.h" | ||
| #include "clang/ASTMatchers/ASTMatchers.h" | ||
| #include "clang/Basic/Diagnostic.h" | ||
| #include "clang/Basic/SourceLocation.h" | ||
| #include "llvm/ADT/STLExtras.h" | ||
| #include "llvm/ADT/SmallString.h" | ||
|
|
||
| using namespace clang::ast_matchers; | ||
|
|
||
| namespace clang::tidy::readability { | ||
|
|
||
| static bool isNoneEnumeratorsInitialized(const EnumDecl &Node) { | ||
| return llvm::all_of(Node.enumerators(), [](const EnumConstantDecl *ECD) { | ||
| return ECD->getInitExpr() == nullptr; | ||
| }); | ||
| } | ||
|
|
||
| static bool isOnlyFirstEnumeratorInitialized(const EnumDecl &Node) { | ||
| bool IsFirst = true; | ||
| for (const EnumConstantDecl *ECD : Node.enumerators()) { | ||
| if ((IsFirst && ECD->getInitExpr() == nullptr) || | ||
| (!IsFirst && ECD->getInitExpr() != nullptr)) | ||
| return false; | ||
| IsFirst = false; | ||
| } | ||
| return !IsFirst; | ||
| } | ||
|
|
||
| static bool areAllEnumeratorsInitialized(const EnumDecl &Node) { | ||
| return llvm::all_of(Node.enumerators(), [](const EnumConstantDecl *ECD) { | ||
| return ECD->getInitExpr() != nullptr; | ||
| }); | ||
| } | ||
|
|
||
| /// Check if \p Enumerator is initialized with a (potentially negated) \c | ||
| /// IntegerLiteral. | ||
| static bool isInitializedByLiteral(const EnumConstantDecl *Enumerator) { | ||
| const Expr *const Init = Enumerator->getInitExpr(); | ||
| if (!Init) | ||
| return false; | ||
| return Init->isIntegerConstantExpr(Enumerator->getASTContext()); | ||
| } | ||
|
|
||
| static void cleanInitialValue(DiagnosticBuilder &Diag, | ||
| const EnumConstantDecl *ECD, | ||
| const SourceManager &SM, | ||
| const LangOptions &LangOpts) { | ||
| const SourceRange InitExprRange = ECD->getInitExpr()->getSourceRange(); | ||
| if (InitExprRange.isInvalid() || InitExprRange.getBegin().isMacroID() || | ||
| InitExprRange.getEnd().isMacroID()) | ||
| return; | ||
| std::optional<Token> EqualToken = utils::lexer::findNextTokenSkippingComments( | ||
| ECD->getLocation(), SM, LangOpts); | ||
| if (!EqualToken.has_value() || | ||
| EqualToken.value().getKind() != tok::TokenKind::equal) | ||
| return; | ||
| const SourceLocation EqualLoc{EqualToken->getLocation()}; | ||
| if (EqualLoc.isInvalid() || EqualLoc.isMacroID()) | ||
| return; | ||
| Diag << FixItHint::CreateRemoval(EqualLoc) | ||
| << FixItHint::CreateRemoval(InitExprRange); | ||
| return; | ||
| } | ||
|
|
||
| namespace { | ||
|
|
||
| AST_MATCHER(EnumDecl, isMacro) { | ||
| SourceLocation Loc = Node.getBeginLoc(); | ||
| return Loc.isMacroID(); | ||
| } | ||
|
|
||
| AST_MATCHER(EnumDecl, hasConsistentInitialValues) { | ||
| return isNoneEnumeratorsInitialized(Node) || | ||
| isOnlyFirstEnumeratorInitialized(Node) || | ||
| areAllEnumeratorsInitialized(Node); | ||
| } | ||
|
|
||
| AST_MATCHER(EnumDecl, hasZeroInitialValueForFirstEnumerator) { | ||
| const EnumDecl::enumerator_range Enumerators = Node.enumerators(); | ||
| if (Enumerators.empty()) | ||
| return false; | ||
| const EnumConstantDecl *ECD = *Enumerators.begin(); | ||
| return isOnlyFirstEnumeratorInitialized(Node) && | ||
| isInitializedByLiteral(ECD) && ECD->getInitVal().isZero(); | ||
| } | ||
|
|
||
| /// Excludes bitfields because enumerators initialized with the result of a | ||
| /// bitwise operator on enumeration values or any other expr that is not a | ||
| /// potentially negative integer literal. | ||
| /// Enumerations where it is not directly clear if they are used with | ||
| /// bitmask, evident when enumerators are only initialized with (potentially | ||
| /// negative) integer literals, are ignored. This is also the case when all | ||
| /// enumerators are powers of two (e.g., 0, 1, 2). | ||
| AST_MATCHER(EnumDecl, hasSequentialInitialValues) { | ||
| const EnumDecl::enumerator_range Enumerators = Node.enumerators(); | ||
| if (Enumerators.empty()) | ||
| return false; | ||
| const EnumConstantDecl *const FirstEnumerator = *Node.enumerator_begin(); | ||
| llvm::APSInt PrevValue = FirstEnumerator->getInitVal(); | ||
| if (!isInitializedByLiteral(FirstEnumerator)) | ||
| return false; | ||
| bool AllEnumeratorsArePowersOfTwo = true; | ||
| for (const EnumConstantDecl *Enumerator : llvm::drop_begin(Enumerators)) { | ||
| const llvm::APSInt NewValue = Enumerator->getInitVal(); | ||
| if (NewValue != ++PrevValue) | ||
| return false; | ||
| if (!isInitializedByLiteral(Enumerator)) | ||
| return false; | ||
| PrevValue = NewValue; | ||
| AllEnumeratorsArePowersOfTwo &= NewValue.isPowerOf2(); | ||
| } | ||
| return !AllEnumeratorsArePowersOfTwo; | ||
| } | ||
|
|
||
| } // namespace | ||
|
|
||
| EnumInitialValueCheck::EnumInitialValueCheck(StringRef Name, | ||
| ClangTidyContext *Context) | ||
| : ClangTidyCheck(Name, Context), | ||
| AllowExplicitZeroFirstInitialValue( | ||
| Options.get("AllowExplicitZeroFirstInitialValue", true)), | ||
| AllowExplicitSequentialInitialValues( | ||
| Options.get("AllowExplicitSequentialInitialValues", true)) {} | ||
|
|
||
| void EnumInitialValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { | ||
| Options.store(Opts, "AllowExplicitZeroFirstInitialValue", | ||
| AllowExplicitZeroFirstInitialValue); | ||
| Options.store(Opts, "AllowExplicitSequentialInitialValues", | ||
| AllowExplicitSequentialInitialValues); | ||
| } | ||
|
|
||
| void EnumInitialValueCheck::registerMatchers(MatchFinder *Finder) { | ||
| Finder->addMatcher( | ||
| enumDecl(unless(isMacro()), unless(hasConsistentInitialValues())) | ||
| .bind("inconsistent"), | ||
| this); | ||
| if (!AllowExplicitZeroFirstInitialValue) | ||
| Finder->addMatcher( | ||
| enumDecl(hasZeroInitialValueForFirstEnumerator()).bind("zero_first"), | ||
| this); | ||
| if (!AllowExplicitSequentialInitialValues) | ||
| Finder->addMatcher(enumDecl(unless(isMacro()), hasSequentialInitialValues()) | ||
| .bind("sequential"), | ||
| this); | ||
| } | ||
|
|
||
| void EnumInitialValueCheck::check(const MatchFinder::MatchResult &Result) { | ||
| if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("inconsistent")) { | ||
| DiagnosticBuilder Diag = | ||
| diag(Enum->getBeginLoc(), | ||
| "inital values in enum %0 are not consistent, consider explicit " | ||
| "initialization of all, none or only the first enumerator") | ||
| << Enum; | ||
| for (const EnumConstantDecl *ECD : Enum->enumerators()) | ||
| if (ECD->getInitExpr() == nullptr) { | ||
| const SourceLocation EndLoc = Lexer::getLocForEndOfToken( | ||
| ECD->getLocation(), 0, *Result.SourceManager, getLangOpts()); | ||
| if (EndLoc.isMacroID()) | ||
| continue; | ||
| llvm::SmallString<8> Str{" = "}; | ||
| ECD->getInitVal().toString(Str); | ||
| Diag << FixItHint::CreateInsertion(EndLoc, Str); | ||
| } | ||
| return; | ||
| } | ||
|
|
||
| if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("zero_first")) { | ||
| const EnumConstantDecl *ECD = *Enum->enumerator_begin(); | ||
| const SourceLocation Loc = ECD->getLocation(); | ||
| if (Loc.isInvalid() || Loc.isMacroID()) | ||
| return; | ||
| DiagnosticBuilder Diag = diag(Loc, "zero initial value for the first " | ||
| "enumerator in %0 can be disregarded") | ||
| << Enum; | ||
| cleanInitialValue(Diag, ECD, *Result.SourceManager, getLangOpts()); | ||
| return; | ||
| } | ||
| if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("sequential")) { | ||
| DiagnosticBuilder Diag = | ||
| diag(Enum->getBeginLoc(), | ||
| "sequential initial value in %0 can be ignored") | ||
| << Enum; | ||
| for (const EnumConstantDecl *ECD : llvm::drop_begin(Enum->enumerators())) | ||
| cleanInitialValue(Diag, ECD, *Result.SourceManager, getLangOpts()); | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| } // namespace clang::tidy::readability |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| //===--- EnumInitialValueCheck.h - clang-tidy -------------------*- C++ -*-===// | ||
| // | ||
| // 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 | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_ENUMINITIALVALUECHECK_H | ||
| #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_ENUMINITIALVALUECHECK_H | ||
|
|
||
| #include "../ClangTidyCheck.h" | ||
|
|
||
| namespace clang::tidy::readability { | ||
|
|
||
| /// Enforces consistent style for enumerators' initialization, covering three | ||
| /// styles: none, first only, or all initialized explicitly. | ||
| /// | ||
| /// For the user-facing documentation see: | ||
| /// http://clang.llvm.org/extra/clang-tidy/checks/readability/enum-initial-value.html | ||
| class EnumInitialValueCheck : public ClangTidyCheck { | ||
| public: | ||
| EnumInitialValueCheck(StringRef Name, ClangTidyContext *Context); | ||
| void storeOptions(ClangTidyOptions::OptionMap &Opts) override; | ||
| void registerMatchers(ast_matchers::MatchFinder *Finder) override; | ||
| void check(const ast_matchers::MatchFinder::MatchResult &Result) override; | ||
| std::optional<TraversalKind> getCheckTraversalKind() const override { | ||
| return TK_IgnoreUnlessSpelledInSource; | ||
| } | ||
|
|
||
| private: | ||
| const bool AllowExplicitZeroFirstInitialValue; | ||
| const bool AllowExplicitSequentialInitialValues; | ||
| }; | ||
|
|
||
| } // namespace clang::tidy::readability | ||
|
|
||
| #endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_ENUMINITIALVALUECHECK_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,168 @@ | ||
| //===--- BracesAroundStatement.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 | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| /// | ||
| /// \file | ||
| /// This file provides utilities to put braces around a statement. | ||
| /// | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "BracesAroundStatement.h" | ||
| #include "../utils/LexerUtils.h" | ||
| #include "LexerUtils.h" | ||
| #include "clang/AST/ASTContext.h" | ||
| #include "clang/Basic/CharInfo.h" | ||
| #include "clang/Basic/LangOptions.h" | ||
| #include "clang/Lex/Lexer.h" | ||
|
|
||
| namespace clang::tidy::utils { | ||
|
|
||
| BraceInsertionHints::operator bool() const { return DiagnosticPos.isValid(); } | ||
|
|
||
| bool BraceInsertionHints::offersFixIts() const { | ||
| return OpeningBracePos.isValid() && ClosingBracePos.isValid(); | ||
| } | ||
|
|
||
| unsigned BraceInsertionHints::resultingCompoundLineExtent( | ||
| const SourceManager &SourceMgr) const { | ||
| return SourceMgr.getSpellingLineNumber(ClosingBracePos) - | ||
| SourceMgr.getSpellingLineNumber(OpeningBracePos); | ||
| } | ||
|
|
||
| FixItHint BraceInsertionHints::openingBraceFixIt() const { | ||
| return OpeningBracePos.isValid() | ||
| ? FixItHint::CreateInsertion(OpeningBracePos, " {") | ||
| : FixItHint(); | ||
| } | ||
|
|
||
| FixItHint BraceInsertionHints::closingBraceFixIt() const { | ||
| return ClosingBracePos.isValid() | ||
| ? FixItHint::CreateInsertion(ClosingBracePos, ClosingBrace) | ||
| : FixItHint(); | ||
| } | ||
|
|
||
| static tok::TokenKind getTokenKind(SourceLocation Loc, const SourceManager &SM, | ||
| const LangOptions &LangOpts) { | ||
| Token Tok; | ||
| SourceLocation Beginning = Lexer::GetBeginningOfToken(Loc, SM, LangOpts); | ||
| const bool Invalid = Lexer::getRawToken(Beginning, Tok, SM, LangOpts); | ||
| assert(!Invalid && "Expected a valid token."); | ||
|
|
||
| if (Invalid) | ||
| return tok::NUM_TOKENS; | ||
|
|
||
| return Tok.getKind(); | ||
| } | ||
|
|
||
| static SourceLocation findEndLocation(const Stmt &S, const SourceManager &SM, | ||
| const LangOptions &LangOpts) { | ||
| SourceLocation Loc = lexer::getUnifiedEndLoc(S, SM, LangOpts); | ||
| if (!Loc.isValid()) | ||
| return Loc; | ||
|
|
||
| // Start searching right after S. | ||
| Loc = Loc.getLocWithOffset(1); | ||
|
|
||
| for (;;) { | ||
| assert(Loc.isValid()); | ||
| while (isHorizontalWhitespace(*SM.getCharacterData(Loc))) { | ||
| Loc = Loc.getLocWithOffset(1); | ||
| } | ||
|
|
||
| if (isVerticalWhitespace(*SM.getCharacterData(Loc))) { | ||
| // EOL, insert brace before. | ||
| break; | ||
| } | ||
| tok::TokenKind TokKind = getTokenKind(Loc, SM, LangOpts); | ||
| if (TokKind != tok::comment) { | ||
| // Non-comment token, insert brace before. | ||
| break; | ||
| } | ||
|
|
||
| SourceLocation TokEndLoc = Lexer::getLocForEndOfToken(Loc, 0, SM, LangOpts); | ||
| SourceRange TokRange(Loc, TokEndLoc); | ||
| StringRef Comment = Lexer::getSourceText( | ||
| CharSourceRange::getTokenRange(TokRange), SM, LangOpts); | ||
| if (Comment.starts_with("/*") && Comment.contains('\n')) { | ||
| // Multi-line block comment, insert brace before. | ||
| break; | ||
| } | ||
| // else: Trailing comment, insert brace after the newline. | ||
|
|
||
| // Fast-forward current token. | ||
| Loc = TokEndLoc; | ||
| } | ||
| return Loc; | ||
| } | ||
|
|
||
| BraceInsertionHints getBraceInsertionsHints(const Stmt *const S, | ||
| const LangOptions &LangOpts, | ||
| const SourceManager &SM, | ||
| SourceLocation StartLoc, | ||
| SourceLocation EndLocHint) { | ||
| // 1) If there's a corresponding "else" or "while", the check inserts "} " | ||
| // right before that token. | ||
| // 2) If there's a multi-line block comment starting on the same line after | ||
| // the location we're inserting the closing brace at, or there's a non-comment | ||
| // token, the check inserts "\n}" right before that token. | ||
| // 3) Otherwise the check finds the end of line (possibly after some block or | ||
| // line comments) and inserts "\n}" right before that EOL. | ||
| if (!S || isa<CompoundStmt>(S)) { | ||
| // Already inside braces. | ||
| return {}; | ||
| } | ||
|
|
||
| // When TreeTransform, Stmt in constexpr IfStmt will be transform to NullStmt. | ||
| // This NullStmt can be detected according to beginning token. | ||
| const SourceLocation StmtBeginLoc = S->getBeginLoc(); | ||
| if (isa<NullStmt>(S) && StmtBeginLoc.isValid() && | ||
| getTokenKind(StmtBeginLoc, SM, LangOpts) == tok::l_brace) | ||
| return {}; | ||
|
|
||
| if (StartLoc.isInvalid()) | ||
| return {}; | ||
|
|
||
| // Convert StartLoc to file location, if it's on the same macro expansion | ||
| // level as the start of the statement. We also need file locations for | ||
| // Lexer::getLocForEndOfToken working properly. | ||
| StartLoc = Lexer::makeFileCharRange( | ||
| CharSourceRange::getCharRange(StartLoc, S->getBeginLoc()), SM, | ||
| LangOpts) | ||
| .getBegin(); | ||
| if (StartLoc.isInvalid()) | ||
| return {}; | ||
| StartLoc = Lexer::getLocForEndOfToken(StartLoc, 0, SM, LangOpts); | ||
|
|
||
| // StartLoc points at the location of the opening brace to be inserted. | ||
| SourceLocation EndLoc; | ||
| std::string ClosingInsertion; | ||
| if (EndLocHint.isValid()) { | ||
| EndLoc = EndLocHint; | ||
| ClosingInsertion = "} "; | ||
| } else { | ||
| EndLoc = findEndLocation(*S, SM, LangOpts); | ||
| ClosingInsertion = "\n}"; | ||
| } | ||
|
|
||
| assert(StartLoc.isValid()); | ||
|
|
||
| // Change only if StartLoc and EndLoc are on the same macro expansion level. | ||
| // This will also catch invalid EndLoc. | ||
| // Example: LLVM_DEBUG( for(...) do_something() ); | ||
| // In this case fix-it cannot be provided as the semicolon which is not | ||
| // visible here is part of the macro. Adding braces here would require adding | ||
| // another semicolon. | ||
| if (Lexer::makeFileCharRange( | ||
| CharSourceRange::getTokenRange(SourceRange( | ||
| SM.getSpellingLoc(StartLoc), SM.getSpellingLoc(EndLoc))), | ||
| SM, LangOpts) | ||
| .isInvalid()) | ||
| return {StartLoc}; | ||
| return {StartLoc, EndLoc, ClosingInsertion}; | ||
| } | ||
|
|
||
| } // namespace clang::tidy::utils |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| //===--- BracesAroundStatement.h - clang-tidy ------- -----------*- C++ -*-===// | ||
| // | ||
| // 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 | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| /// | ||
| /// \file | ||
| /// This file provides utilities to put braces around a statement. | ||
| /// | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "clang/AST/Stmt.h" | ||
| #include "clang/Basic/Diagnostic.h" | ||
| #include "clang/Basic/SourceLocation.h" | ||
| #include "clang/Basic/SourceManager.h" | ||
|
|
||
| namespace clang::tidy::utils { | ||
|
|
||
| /// A provider of fix-it hints to insert opening and closing braces. An instance | ||
| /// of this type is the result of calling `getBraceInsertionsHints` below. | ||
| struct BraceInsertionHints { | ||
| /// The position of a potential diagnostic. It coincides with the position of | ||
| /// the opening brace to insert, but can also just be the place to show a | ||
| /// diagnostic in case braces cannot be inserted automatically. | ||
| SourceLocation DiagnosticPos; | ||
|
|
||
| /// Constructor for a no-hint. | ||
| BraceInsertionHints() = default; | ||
|
|
||
| /// Constructor for a valid hint that cannot insert braces automatically. | ||
| BraceInsertionHints(SourceLocation DiagnosticPos) | ||
| : DiagnosticPos(DiagnosticPos) {} | ||
|
|
||
| /// Constructor for a hint offering fix-its for brace insertion. Both | ||
| /// positions must be valid. | ||
| BraceInsertionHints(SourceLocation OpeningBracePos, | ||
| SourceLocation ClosingBracePos, std::string ClosingBrace) | ||
| : DiagnosticPos(OpeningBracePos), OpeningBracePos(OpeningBracePos), | ||
| ClosingBracePos(ClosingBracePos), ClosingBrace(ClosingBrace) { | ||
| assert(offersFixIts()); | ||
| } | ||
|
|
||
| /// Indicates whether the hint provides at least the position of a diagnostic. | ||
| operator bool() const; | ||
|
|
||
| /// Indicates whether the hint provides fix-its to insert braces. | ||
| bool offersFixIts() const; | ||
|
|
||
| /// The number of lines between the inserted opening brace and its closing | ||
| /// counterpart. | ||
| unsigned resultingCompoundLineExtent(const SourceManager &SourceMgr) const; | ||
|
|
||
| /// Fix-it to insert an opening brace. | ||
| FixItHint openingBraceFixIt() const; | ||
|
|
||
| /// Fix-it to insert a closing brace. | ||
| FixItHint closingBraceFixIt() const; | ||
|
|
||
| private: | ||
| SourceLocation OpeningBracePos; | ||
| SourceLocation ClosingBracePos; | ||
| std::string ClosingBrace; | ||
| }; | ||
|
|
||
| /// Create fix-it hints for braces that wrap the given statement when applied. | ||
| /// The algorithm computing them respects comment before and after the statement | ||
| /// and adds line breaks before the braces accordingly. | ||
| BraceInsertionHints | ||
| getBraceInsertionsHints(const Stmt *const S, const LangOptions &LangOpts, | ||
| const SourceManager &SM, SourceLocation StartLoc, | ||
| SourceLocation EndLocHint = SourceLocation()); | ||
|
|
||
| } // namespace clang::tidy::utils |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| .. title:: clang-tidy - readability-enum-initial-value | ||
|
|
||
| readability-enum-initial-value | ||
| ============================== | ||
|
|
||
| Enforces consistent style for enumerators' initialization, covering three | ||
| styles: none, first only, or all initialized explicitly. | ||
|
|
||
| When adding new enumerations, inconsistent initial value will cause potential | ||
| enumeration value conflicts. | ||
|
|
||
| In an enumeration, the following three cases are accepted. | ||
| 1. none of enumerators are explicit initialized. | ||
| 2. the first enumerator is explicit initialized. | ||
| 3. all of enumerators are explicit initialized. | ||
|
|
||
| .. code-block:: c++ | ||
|
|
||
| // valid, none of enumerators are initialized. | ||
| enum A { | ||
| e0, | ||
| e1, | ||
| e2, | ||
| }; | ||
|
|
||
| // valid, the first enumerator is initialized. | ||
| enum A { | ||
| e0 = 0, | ||
| e1, | ||
| e2, | ||
| }; | ||
|
|
||
| // valid, all of enumerators are initialized. | ||
| enum A { | ||
| e0 = 0, | ||
| e1 = 1, | ||
| e2 = 2, | ||
| }; | ||
|
|
||
| // invalid, e1 is not explicit initialized. | ||
| enum A { | ||
| e0 = 0, | ||
| e1, | ||
| e2 = 2, | ||
| }; | ||
|
|
||
| Options | ||
| ------- | ||
|
|
||
| .. option:: AllowExplicitZeroFirstInitialValue | ||
|
|
||
| If set to `false`, the first enumerator must not be explicitly initialized. | ||
| See examples below. Default is `true`. | ||
|
|
||
| .. code-block:: c++ | ||
|
|
||
| enum A { | ||
| e0 = 0, // not allowed if AllowExplicitZeroFirstInitialValue is false | ||
| e1, | ||
| e2, | ||
| }; | ||
|
|
||
|
|
||
| .. option:: AllowExplicitSequentialInitialValues | ||
|
|
||
| If set to `false`, sequential initializations are not allowed. | ||
| See examples below. Default is `true`. | ||
|
|
||
| .. code-block:: c++ | ||
|
|
||
| enum A { | ||
| e0 = 1, // not allowed if AllowExplicitSequentialInitialValues is false | ||
| e1 = 2, | ||
| e2 = 3, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| // RUN: %check_clang_tidy %s readability-enum-initial-value %t | ||
| // RUN: %check_clang_tidy -check-suffix=ENABLE %s readability-enum-initial-value %t -- \ | ||
| // RUN: -config='{CheckOptions: { \ | ||
| // RUN: readability-enum-initial-value.AllowExplicitZeroFirstInitialValue: false, \ | ||
| // RUN: readability-enum-initial-value.AllowExplicitSequentialInitialValues: false, \ | ||
| // RUN: }}' | ||
|
|
||
| enum EError { | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:1: warning: inital values in enum 'EError' are not consistent | ||
| // CHECK-MESSAGES-ENABLE: :[[@LINE-2]]:1: warning: inital values in enum 'EError' are not consistent | ||
| EError_a = 1, | ||
| EError_b, | ||
| // CHECK-FIXES: EError_b = 2, | ||
| EError_c = 3, | ||
| }; | ||
|
|
||
| enum ENone { | ||
| ENone_a, | ||
| ENone_b, | ||
| EENone_c, | ||
| }; | ||
|
|
||
| enum EFirst { | ||
| EFirst_a = 1, | ||
| EFirst_b, | ||
| EFirst_c, | ||
| }; | ||
|
|
||
| enum EAll { | ||
| EAll_a = 1, | ||
| EAll_b = 2, | ||
| EAll_c = 4, | ||
| }; | ||
|
|
||
| #define ENUMERATOR_1 EMacro1_b | ||
| enum EMacro1 { | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:1: warning: inital values in enum 'EMacro1' are not consistent | ||
| // CHECK-MESSAGES-ENABLE: :[[@LINE-2]]:1: warning: inital values in enum 'EMacro1' are not consistent | ||
| EMacro1_a = 1, | ||
| ENUMERATOR_1, | ||
| // CHECK-FIXES: ENUMERATOR_1 = 2, | ||
| EMacro1_c = 3, | ||
| }; | ||
|
|
||
|
|
||
| #define ENUMERATOR_2 EMacro2_b = 2 | ||
| enum EMacro2 { | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:1: warning: inital values in enum 'EMacro2' are not consistent | ||
| // CHECK-MESSAGES-ENABLE: :[[@LINE-2]]:1: warning: inital values in enum 'EMacro2' are not consistent | ||
| EMacro2_a = 1, | ||
| ENUMERATOR_2, | ||
| EMacro2_c, | ||
| // CHECK-FIXES: EMacro2_c = 3, | ||
| }; | ||
|
|
||
| enum EnumZeroFirstInitialValue { | ||
| EnumZeroFirstInitialValue_0 = 0, | ||
| // CHECK-MESSAGES-ENABLE: :[[@LINE-1]]:3: warning: zero initial value for the first enumerator in 'EnumZeroFirstInitialValue' can be disregarded | ||
| // CHECK-FIXES-ENABLE: EnumZeroFirstInitialValue_0 , | ||
| EnumZeroFirstInitialValue_1, | ||
| EnumZeroFirstInitialValue_2, | ||
| }; | ||
|
|
||
| enum EnumZeroFirstInitialValueWithComment { | ||
| EnumZeroFirstInitialValueWithComment_0 = /* == */ 0, | ||
| // CHECK-MESSAGES-ENABLE: :[[@LINE-1]]:3: warning: zero initial value for the first enumerator in 'EnumZeroFirstInitialValueWithComment' can be disregarded | ||
| // CHECK-FIXES-ENABLE: EnumZeroFirstInitialValueWithComment_0 /* == */ , | ||
| EnumZeroFirstInitialValueWithComment_1, | ||
| EnumZeroFirstInitialValueWithComment_2, | ||
| }; | ||
|
|
||
| enum EnumSequentialInitialValue { | ||
| // CHECK-MESSAGES-ENABLE: :[[@LINE-1]]:1: warning: sequential initial value in 'EnumSequentialInitialValue' can be ignored | ||
| EnumSequentialInitialValue_0 = 2, | ||
| // CHECK-FIXES-ENABLE: EnumSequentialInitialValue_0 = 2, | ||
| EnumSequentialInitialValue_1 = 3, | ||
| // CHECK-FIXES-ENABLE: EnumSequentialInitialValue_1 , | ||
| EnumSequentialInitialValue_2 = 4, | ||
| // CHECK-FIXES-ENABLE: EnumSequentialInitialValue_2 , | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| // RUN: %check_clang_tidy %s readability-enum-initial-value %t | ||
|
|
||
| enum class EError { | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:1: warning: inital values in enum 'EError' are not consistent | ||
| EError_a = 1, | ||
| EError_b, | ||
| // CHECK-FIXES: EError_b = 2, | ||
| EError_c = 3, | ||
| }; | ||
|
|
||
| enum class ENone { | ||
| ENone_a, | ||
| ENone_b, | ||
| EENone_c, | ||
| }; | ||
|
|
||
| enum class EFirst { | ||
| EFirst_a = 1, | ||
| EFirst_b, | ||
| EFirst_c, | ||
| }; | ||
|
|
||
| enum class EAll { | ||
| EAll_a = 1, | ||
| EAll_b = 2, | ||
| EAll_c = 3, | ||
| }; |