[clang-tools-extra] [clang-tidy] Add new check bugprone-tagged-union-member-count (PR #89925)

via cfe-commits cfe-commits at lists.llvm.org
Fri Jul 19 08:47:22 PDT 2024


=?utf-8?b?R8OhYm9yIFTDs3RodsOhcmk=?= <tigbrcode at protonmail.com>,
=?utf-8?b?R8OhYm9yIFTDs3RodsOhcmk=?= <tigbrcode at protonmail.com>
Message-ID:
In-Reply-To: <llvm.org/llvm/llvm-project/pull/89925 at github.com>


https://github.com/tigbr updated https://github.com/llvm/llvm-project/pull/89925

>From 0dff511a0f63480dea527b6e823dcf230755f312 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A1bor=20T=C3=B3thv=C3=A1ri?= <tigbrcode at protonmail.com>
Date: Sun, 3 Mar 2024 22:30:32 +0100
Subject: [PATCH 1/3] [clang-tidy] Add new check
 bugprone-tagged-union-member-count

This patch introduces a new check to find mismatches between the number
of data members in a union and the number enum values present in
variant-like structures.

Variant-like types can look something like this:

```c++
struct variant {
    enum {
        tag1,
        tag2,
    } kind;
    union {
        int i;
        char c;
  } data;
};
```

The kind data member of the variant is supposed to tell which data
member of the union is valid, however if there are fewer enum values
than union members, then it is likely a mistake.

The opposite is not that obvious, because it might be fine to have more
enum values than union data members, but for the time being I am curious
how many real bugs can be caught if we give a warning regardless.

This patch also contains a heuristic where we try to guess whether the
last enum constant is actually supposed to be a tag value for the
variant or whether it is just holding how many enum constants have been
created.

I am still collecting data on how many bugs this check can catch on open
source software and I will update the pull-request with the results.
---
 .../bugprone/BugproneTidyModule.cpp           |   3 +
 .../clang-tidy/bugprone/CMakeLists.txt        |   1 +
 .../bugprone/TaggedUnionMemberCountCheck.cpp  | 125 ++++++++++++
 .../bugprone/TaggedUnionMemberCountCheck.h    |  31 +++
 clang-tools-extra/docs/ReleaseNotes.rst       |   6 +
 .../bugprone/tagged-union-member-count.rst    |  66 +++++++
 .../docs/clang-tidy/checks/list.rst           |   1 +
 .../bugprone/tagged-union-member-count.cpp    | 181 ++++++++++++++++++
 8 files changed, 414 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.h
 create mode 100644 clang-tools-extra/docs/clang-tidy/checks/bugprone/tagged-union-member-count.rst
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.cpp

diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
index 2931325d8b579..d9e094e11bdf0 100644
--- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
@@ -76,6 +76,7 @@
 #include "SuspiciousStringviewDataUsageCheck.h"
 #include "SwappedArgumentsCheck.h"
 #include "SwitchMissingDefaultCaseCheck.h"
+#include "TaggedUnionMemberCountCheck.h"
 #include "TerminatingContinueCheck.h"
 #include "ThrowKeywordMissingCheck.h"
 #include "TooSmallLoopVariableCheck.h"
@@ -223,6 +224,8 @@ class BugproneModule : public ClangTidyModule {
         "bugprone-suspicious-stringview-data-usage");
     CheckFactories.registerCheck<SwappedArgumentsCheck>(
         "bugprone-swapped-arguments");
+    CheckFactories.registerCheck<TaggedUnionMemberCountCheck>(
+        "bugprone-tagged-union-member-count");
     CheckFactories.registerCheck<TerminatingContinueCheck>(
         "bugprone-terminating-continue");
     CheckFactories.registerCheck<ThrowKeywordMissingCheck>(
diff --git a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
index 081ba67efe153..5e7fa08aece02 100644
--- a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
@@ -71,6 +71,7 @@ add_clang_library(clangTidyBugproneModule
   SuspiciousSemicolonCheck.cpp
   SuspiciousStringCompareCheck.cpp
   SwappedArgumentsCheck.cpp
+  TaggedUnionMemberCountCheck.cpp
   TerminatingContinueCheck.cpp
   ThrowKeywordMissingCheck.cpp
   TooSmallLoopVariableCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.cpp
new file mode 100644
index 0000000000000..6a7b5a92902c8
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.cpp
@@ -0,0 +1,125 @@
+//===--- TaggedUnionMemberCountCheck.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 "TaggedUnionMemberCountCheck.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+
+#include "clang/AST/PrettyPrinter.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/Support/Casting.h"
+#include <limits>
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::bugprone {
+
+void TaggedUnionMemberCountCheck::registerMatchers(MatchFinder *Finder) {
+  Finder->addMatcher(
+      recordDecl(
+          allOf(isStruct(),
+                has(fieldDecl(hasType(recordDecl(isUnion()).bind("union")))),
+                has(fieldDecl(hasType(enumDecl().bind("tags"))))))
+          .bind("root"),
+      this);
+}
+
+static bool hasMultipleUnionsOrEnums(const RecordDecl *rec) {
+  int tags = 0;
+  int unions = 0;
+  for (const FieldDecl *r : rec->fields()) {
+    TypeSourceInfo *info = r->getTypeSourceInfo();
+    QualType qualtype = info->getType();
+    const Type *type = qualtype.getTypePtr();
+    if (type->isUnionType())
+      unions += 1;
+    else if (type->isEnumeralType())
+      tags += 1;
+    if (tags > 1 || unions > 1)
+      return true;
+  }
+  return false;
+}
+
+static int64_t getNumberOfValidEnumValues(const EnumDecl *ed) {
+  int64_t maxTagValue = std::numeric_limits<int64_t>::min();
+  int64_t minTagValue = std::numeric_limits<int64_t>::max();
+
+  // Heuristic for counter enum constants.
+  //
+  //   enum tag_with_counter {
+  //     tag1,
+  //     tag2,
+  //     tag_count, <-- Searching for these enum constants
+  //   };
+  //
+  // The 'ce' prefix is used to abbreviate counterEnum.
+  // The final tag count is decreased by 1 if and only if:
+  // 1. The number of counting enum constants = 1,
+  int ceCount = 0;
+  // 2. The counting enum constant is the last enum constant that is defined,
+  int ceFirstIndex = 0;
+  // 3. The value of the counting enum constant is the largest out of every enum constant.
+  int64_t ceValue = 0;
+
+  int64_t enumConstantsCount = 0;
+  for (auto En : llvm::enumerate(ed->enumerators())) {
+    enumConstantsCount += 1;
+
+    int64_t enumValue = En.value()->getInitVal().getExtValue();
+    StringRef enumName = En.value()->getName();
+
+    if (enumValue > maxTagValue)
+      maxTagValue = enumValue;
+    if (enumValue < minTagValue)
+      minTagValue = enumValue;
+
+    if (enumName.ends_with_insensitive("count")) {
+      if (ceCount == 0) {
+        ceFirstIndex = En.index();
+      }
+      ceValue = enumValue;
+      ceCount += 1;
+    }
+  }
+
+  int64_t validValuesCount = maxTagValue - minTagValue + 1;
+  if (ceCount == 1 &&
+      ceFirstIndex == enumConstantsCount - 1 &&
+      ceValue == maxTagValue) {
+    validValuesCount -= 1;
+  }
+  return validValuesCount;
+}
+
+void TaggedUnionMemberCountCheck::check(
+    const MatchFinder::MatchResult &Result) {
+  const auto *root = Result.Nodes.getNodeAs<RecordDecl>("root");
+  const auto *unionMatch = Result.Nodes.getNodeAs<RecordDecl>("union");
+  const auto *tagMatch = Result.Nodes.getNodeAs<EnumDecl>("tags");
+
+  if (hasMultipleUnionsOrEnums(root))
+    return;
+
+  int64_t unionMemberCount = llvm::range_size(unionMatch->fields());
+  int64_t tagCount = getNumberOfValidEnumValues(tagMatch);
+
+  // FIXME: Maybe a emit a note when a counter enum constant was found.
+  if (unionMemberCount > tagCount) {
+    diag(root->getLocation(), "Tagged union has more data members than tags! "
+                              "Data members: %0 Tags: %1")
+        << unionMemberCount << tagCount;
+  } else if (unionMemberCount < tagCount) {
+    diag(root->getLocation(), "Tagged union has fewer data members than tags! "
+                              "Data members: %0 Tags: %1")
+        << unionMemberCount << tagCount;
+  }
+}
+
+} // namespace clang::tidy::bugprone
diff --git a/clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.h b/clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.h
new file mode 100644
index 0000000000000..bde42da23ff38
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.h
@@ -0,0 +1,31 @@
+//===--- TaggedUnionMemberCountCheck.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_BUGPRONE_TAGGEDUNIONMEMBERCOUNTCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_TAGGEDUNIONMEMBERCOUNTCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::bugprone {
+
+// Gives warnings for tagged unions, where the number of tags is
+// different from the number of data members inside the union.
+//
+// For the user-facing documentation see:
+// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/tagged-union-member-count.html
+class TaggedUnionMemberCountCheck : public ClangTidyCheck {
+public:
+  TaggedUnionMemberCountCheck(StringRef Name, ClangTidyContext *Context)
+      : ClangTidyCheck(Name, Context) {}
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+};
+
+} // namespace clang::tidy::bugprone
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_TAGGEDUNIONMEMBERCOUNTCHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 78b09d23d4427..9f9069a8d0892 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -129,6 +129,12 @@ New checks
   Replaces certain conditional statements with equivalent calls to
   ``std::min`` or ``std::max``.
 
+- New :doc:`bugprone-tagged-union-member-count
+  <clang-tidy/checks/bugprone/tagged-union-member-count>` check.
+
+  Warns about suspicious looking tagged unions where the number of enum
+  constants and the number of union data members are not equal.
+
 New check aliases
 ^^^^^^^^^^^^^^^^^
 
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/tagged-union-member-count.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/tagged-union-member-count.rst
new file mode 100644
index 0000000000000..7eac93c6c9602
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/tagged-union-member-count.rst
@@ -0,0 +1,66 @@
+.. title:: clang-tidy - bugprone-tagged-union-member-count
+
+bugprone-tagged-union-member-count
+==================================
+
+Gives warnings for tagged unions, where the number of tags is
+different from the number of data members inside the union.
+
+A struct or a class is considered to be a tagged union if it has
+exactly one union data member and exactly one enum data member and
+any number of other data members that are neither unions or enums.
+
+Example
+-------
+
+.. code-block:: c++
+
+  enum tags2 {
+    tag1,
+    tag2,
+  };
+
+  struct taggedunion { // warning: Tagged union has more data members than tags! Data members: 3 Tags: 2 [bugprone-tagged-union-member-count]
+    enum tags2 kind;
+    union {
+      int i;
+      float f;
+      char *str;
+    } data;
+  };
+
+  enum tags4 {
+    tag1,
+    tag2,
+    tag3,
+    tag4,
+  };
+  
+  struct taggedunion { // warning: Tagged union has more fewer members than tags! Data members: 3 Tags: 4 [bugprone-tagged-union-member-count]
+    enum tags4 kind;
+    union {
+      int i;
+      float f;
+      char *str;
+    } data;
+  };
+
+Counting enum constant heuristic
+--------------------------------
+
+Some programmers use enums in such a way, where the value of the last enum 
+constant is used to keep track of how many enum constants have been declared.
+
+.. code-block:: c++
+
+  enum tags_with_counter {
+    tag1, // is 0
+    tag2, // is 1
+    tag3, // is 2
+    tags_count, // is 3
+  };
+
+The checker detects this usage pattern heuristically and does not include
+the counter enum constant in the final tag count, since the counter is not
+meant to indicate the valid variant member.
+
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index 79e81dd174e4f..3058bee1357fc 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -142,6 +142,7 @@ Clang-Tidy Checks
    :doc:`bugprone-suspicious-stringview-data-usage <bugprone/suspicious-stringview-data-usage>`,
    :doc:`bugprone-swapped-arguments <bugprone/swapped-arguments>`, "Yes"
    :doc:`bugprone-switch-missing-default-case <bugprone/switch-missing-default-case>`,
+   :doc:`bugprone-tagged-union-member-count <bugprone/tagged-union-member-count>`, "Yes"
    :doc:`bugprone-terminating-continue <bugprone/terminating-continue>`, "Yes"
    :doc:`bugprone-throw-keyword-missing <bugprone/throw-keyword-missing>`,
    :doc:`bugprone-too-small-loop-variable <bugprone/too-small-loop-variable>`,
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.cpp
new file mode 100644
index 0000000000000..91165f1bd64e0
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.cpp
@@ -0,0 +1,181 @@
+// RUN: %check_clang_tidy %s bugprone-tagged-union-member-count %t
+
+enum tags3 {
+	tags3_1,
+	tags3_2,
+	tags3_3,
+};
+
+enum tags4 {
+	tags4_1,
+	tags4_2,
+	tags4_3,
+	tags4_4,
+};
+
+enum tags5 {
+	tags5_1,
+	tags5_2,
+	tags5_3,
+	tags5_4,
+	tags5_5,
+};
+
+union union3 {
+	short *shorts;
+	int *ints;
+	float *floats;
+};
+
+union union4 {
+	short *shorts;
+	double *doubles;
+	int *ints;
+	float *floats;
+};
+
+// It is not obvious which enum is the tag for the union.
+struct taggedunion2 { // No warnings expected.
+	enum tags3 tagA;
+	enum tags4 tagB;
+	union union4 data;
+};
+
+// It is not obvious which union does the tag belong to.
+struct taggedunion4 { // No warnings expected.
+	enum tags3 tag;
+	union union3 dataB;
+	union union3 dataA;
+};
+
+struct taggedunion1 { // CHECK-MESSAGES: :[[@LINE]]:8: warning: Tagged union has more data members than tags! Data members: 4 Tags: 3 [bugprone-tagged-union-member-count]
+	enum tags3 tag;
+    union union4 data;
+};
+
+struct taggedunion5 { // CHECK-MESSAGES: :[[@LINE]]:8: warning: Tagged union has more data members than tags! Data members: 4 Tags: 3 [bugprone-tagged-union-member-count]
+	enum tags3 tag;
+    union {
+		int *ints;
+		char characters[13];
+		struct {
+			double re;
+			double im;
+		} complex;
+		long l;
+    } data;
+};
+
+struct taggedunion7 { // CHECK-MESSAGES: :[[@LINE]]:8: warning: Tagged union has more data members than tags! Data members: 4 Tags: 3 [bugprone-tagged-union-member-count]
+	enum {
+		tag1,
+		tag2,
+		tag3,
+	} tag;
+	union union4 data;
+};
+
+struct taggedunion8 { // CHECK-MESSAGES: :[[@LINE]]:8: warning: Tagged union has more data members than tags! Data members: 4 Tags: 3 [bugprone-tagged-union-member-count]
+	enum {
+		tag1,
+		tag2,
+		tag3,
+	} tag;
+	union {
+		int *ints;
+		char characters[13];
+		struct {
+			double re;
+			double im;
+		} complex;
+		long l;
+	} data;
+};
+
+struct nested1 { // CHECK-MESSAGES: :[[@LINE]]:8: warning: Tagged union has more data members than tags! Data members: 4 Tags: 3 [bugprone-tagged-union-member-count]
+	enum tags3 tag;
+	union {
+		char c;
+		short s;
+		int i;
+		struct { // CHECK-MESSAGES: :[[@LINE]]:3: warning: Tagged union has fewer data members than tags! Data members: 4 Tags: 5 [bugprone-tagged-union-member-count]
+			enum tags5 tag;
+			union union4 data;
+		} inner;
+	} data;
+};
+
+struct nested2 {
+	enum tags3 tag;
+	union {
+		float f;
+		int i;
+		struct { // CHECK-MESSAGES: :[[@LINE]]:3: warning: Tagged union has more data members than tags! Data members: 4 Tags: 3 [bugprone-tagged-union-member-count]
+			enum tags3 tag;
+			union union4 data;
+		} inner;
+	} data;
+};
+
+struct nested3 { // CHECK-MESSAGES: :[[@LINE]]:8: warning: Tagged union has fewer data members than tags! Data members: 2 Tags: 3 [bugprone-tagged-union-member-count]
+	enum tags3 tag;
+	union {
+		float f;
+		int i;
+		struct innerdecl { // CHECK-MESSAGES: :[[@LINE]]:10: warning: Tagged union has more data members than tags! Data members: 4 Tags: 3 [bugprone-tagged-union-member-count]
+			enum tags3 tag;
+			union union4 data;
+		}; 
+	} data;
+};
+
+enum tag_with_counter_lowercase {
+	node_type_loop,
+	node_type_branch,
+	node_type_function,
+	node_type_count,
+};
+
+enum tag_with_counter_uppercase {
+	NODE_TYPE_LOOP,
+	NODE_TYPE_BRANCH,
+	NODE_TYPE_FUNCTION,
+	NODE_TYPE_COUNT,
+};
+
+struct taggedunion10 { // CHECK-MESSAGES: :[[@LINE]]:8: warning: Tagged union has more data members than tags! Data members: 4 Tags: 3 [bugprone-tagged-union-member-count]
+	enum tag_with_counter_lowercase tag;
+	union union4 data;
+};
+
+struct taggedunion11 { // CHECK-MESSAGES: :[[@LINE]]:8: warning: Tagged union has more data members than tags! Data members: 4 Tags: 3 [bugprone-tagged-union-member-count]
+	enum tag_with_counter_uppercase tag;
+	union union4 data;
+};
+
+// Without the counter enum constant the number of tags
+// and the number data members are equal.
+struct taggedunion12 { // No warnings expected.
+	enum tag_with_counter_uppercase tag;
+	union union3 data;
+};
+
+// Technically this means that every enum value is defined from 0-256 and therefore a warning is given.
+enum mycolor {
+	mycolor_black = 0x00,
+	mycolor_gray  = 0xcc,
+	mycolor_white = 0xff,
+};
+
+struct taggedunion9 { // CHECK-MESSAGES: :[[@LINE]]:8: warning: Tagged union has fewer data members than tags! Data members: 3 Tags: 256 [bugprone-tagged-union-member-count]
+	enum mycolor tag;
+	union {
+		int a;
+		float b;
+		struct {
+			double re;
+			double im;
+		} complex;
+	} data;
+};
+

>From a8eadb10876dded0deb3fbbbb6ad5b13ce9861fa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A1bor=20T=C3=B3thv=C3=A1ri?= <tigbrcode at protonmail.com>
Date: Tue, 21 May 2024 13:51:28 +0200
Subject: [PATCH 2/3] pull request fixes

---
 .../bugprone/TaggedUnionMemberCountCheck.cpp  |  80 +++++---
 .../bugprone/TaggedUnionMemberCountCheck.h    |  10 +-
 clang-tools-extra/docs/ReleaseNotes.rst       |  12 +-
 .../bugprone/tagged-union-member-count.rst    | 190 ++++++++++++++++--
 .../docs/clang-tidy/checks/list.rst           |   2 +-
 ...er-count-enumcounterheuristicisenabled.cpp |  38 ++++
 ...d-union-member-count-enumcountersuffix.cpp |  47 +++++
 .../tagged-union-member-count-strictmode.cpp  | 150 ++++++++++++++
 .../bugprone/tagged-union-member-count.cpp    | 176 ++++++++++------
 9 files changed, 589 insertions(+), 116 deletions(-)
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-enumcounterheuristicisenabled.cpp
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-enumcountersuffix.cpp
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-strictmode.cpp

diff --git a/clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.cpp
index 6a7b5a92902c8..786198b011f30 100644
--- a/clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.cpp
@@ -10,44 +10,53 @@
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/Expr.h"
 #include "clang/ASTMatchers/ASTMatchFinder.h"
-
 #include "clang/AST/PrettyPrinter.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/Support/Casting.h"
 #include <limits>
+#include <iostream>
 
 using namespace clang::ast_matchers;
 
 namespace clang::tidy::bugprone {
 
+TaggedUnionMemberCountCheck::TaggedUnionMemberCountCheck(StringRef Name, ClangTidyContext *Context)
+      : ClangTidyCheck(Name, Context),
+        EnumCounterHeuristicIsEnabled(Options.get("EnumCounterHeuristicIsEnabled", true)),
+        EnumCounterSuffix(Options.get("EnumCounterSuffix", "count")),
+        StrictMode(Options.get("StrictMode", true)) { }
+
+void TaggedUnionMemberCountCheck::storeOptions(
+    ClangTidyOptions::OptionMap &Opts) {
+  Options.store(Opts, "StrictMode", StrictMode);
+  Options.store(Opts, "EnumCounterHeuristicIsEnabled", EnumCounterHeuristicIsEnabled);
+  Options.store(Opts, "EnumCounterSuffix", EnumCounterSuffix);
+}
+
 void TaggedUnionMemberCountCheck::registerMatchers(MatchFinder *Finder) {
-  Finder->addMatcher(
+Finder->addMatcher(
       recordDecl(
           allOf(isStruct(),
-                has(fieldDecl(hasType(recordDecl(isUnion()).bind("union")))),
-                has(fieldDecl(hasType(enumDecl().bind("tags"))))))
+				has(fieldDecl(hasType(qualType(hasCanonicalType(recordType())))).bind("union")),
+				has(fieldDecl(hasType(qualType(hasCanonicalType(enumType())))).bind("tags"))))
           .bind("root"),
       this);
 }
 
+static bool isUnion(const FieldDecl *R) {
+	return R->getType().getCanonicalType().getTypePtr()->isUnionType();
+}
+
+static bool isEnum(const FieldDecl *R) {
+	return R->getType().getCanonicalType().getTypePtr()->isEnumeralType();
+}
+
 static bool hasMultipleUnionsOrEnums(const RecordDecl *rec) {
-  int tags = 0;
-  int unions = 0;
-  for (const FieldDecl *r : rec->fields()) {
-    TypeSourceInfo *info = r->getTypeSourceInfo();
-    QualType qualtype = info->getType();
-    const Type *type = qualtype.getTypePtr();
-    if (type->isUnionType())
-      unions += 1;
-    else if (type->isEnumeralType())
-      tags += 1;
-    if (tags > 1 || unions > 1)
-      return true;
-  }
-  return false;
+  return llvm::count_if(rec->fields(), isUnion) > 1 ||
+         llvm::count_if(rec->fields(), isEnum) > 1;
 }
 
-static int64_t getNumberOfValidEnumValues(const EnumDecl *ed) {
+static size_t getNumberOfValidEnumValues(const EnumDecl *ed, bool EnumCounterHeuristicIsEnabled, StringRef EnumCounterSuffix) {
   int64_t maxTagValue = std::numeric_limits<int64_t>::min();
   int64_t minTagValue = std::numeric_limits<int64_t>::max();
 
@@ -80,7 +89,7 @@ static int64_t getNumberOfValidEnumValues(const EnumDecl *ed) {
     if (enumValue < minTagValue)
       minTagValue = enumValue;
 
-    if (enumName.ends_with_insensitive("count")) {
+    if (enumName.ends_with_insensitive(EnumCounterSuffix)) {
       if (ceCount == 0) {
         ceFirstIndex = En.index();
       }
@@ -90,7 +99,8 @@ static int64_t getNumberOfValidEnumValues(const EnumDecl *ed) {
   }
 
   int64_t validValuesCount = maxTagValue - minTagValue + 1;
-  if (ceCount == 1 &&
+  if (EnumCounterHeuristicIsEnabled &&
+      ceCount == 1 &&
       ceFirstIndex == enumConstantsCount - 1 &&
       ceValue == maxTagValue) {
     validValuesCount -= 1;
@@ -98,26 +108,36 @@ static int64_t getNumberOfValidEnumValues(const EnumDecl *ed) {
   return validValuesCount;
 }
 
+// Feladatok:
+// - typedef tesztelés
+// - template tesztelés
+// - névtelen union tesztelés
+// - "count" paraméterezése
 void TaggedUnionMemberCountCheck::check(
     const MatchFinder::MatchResult &Result) {
   const auto *root = Result.Nodes.getNodeAs<RecordDecl>("root");
-  const auto *unionMatch = Result.Nodes.getNodeAs<RecordDecl>("union");
-  const auto *tagMatch = Result.Nodes.getNodeAs<EnumDecl>("tags");
+  const auto *unionField = Result.Nodes.getNodeAs<FieldDecl>("union");
+  const auto *tagField = Result.Nodes.getNodeAs<FieldDecl>("tags");
+
+  // The matcher can only narrow down the type to recordType()
+  if (!isUnion(unionField))
+    return;
 
   if (hasMultipleUnionsOrEnums(root))
     return;
 
-  int64_t unionMemberCount = llvm::range_size(unionMatch->fields());
-  int64_t tagCount = getNumberOfValidEnumValues(tagMatch);
+  const auto *unionDef = unionField->getType().getCanonicalType().getTypePtr()->getAsRecordDecl();
+  const auto *enumDef = static_cast<EnumDecl*>(tagField->getType().getCanonicalType().getTypePtr()->getAsTagDecl());
+
+  size_t unionMemberCount = llvm::range_size(unionDef->fields());
+  size_t tagCount = getNumberOfValidEnumValues(enumDef, EnumCounterHeuristicIsEnabled, EnumCounterSuffix);
 
   // FIXME: Maybe a emit a note when a counter enum constant was found.
   if (unionMemberCount > tagCount) {
-    diag(root->getLocation(), "Tagged union has more data members than tags! "
-                              "Data members: %0 Tags: %1")
+    diag(root->getLocation(), "Tagged union has more data members (%0) than tags (%1)!")
         << unionMemberCount << tagCount;
-  } else if (unionMemberCount < tagCount) {
-    diag(root->getLocation(), "Tagged union has fewer data members than tags! "
-                              "Data members: %0 Tags: %1")
+  } else if (StrictMode && unionMemberCount < tagCount) {
+    diag(root->getLocation(), "Tagged union has fewer data members (%0) than tags (%1)!")
         << unionMemberCount << tagCount;
   }
 }
diff --git a/clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.h b/clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.h
index bde42da23ff38..2c6ec3b05358a 100644
--- a/clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.h
+++ b/clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.h
@@ -10,6 +10,7 @@
 #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_TAGGEDUNIONMEMBERCOUNTCHECK_H
 
 #include "../ClangTidyCheck.h"
+#include "llvm/ADT/StringRef.h"
 
 namespace clang::tidy::bugprone {
 
@@ -20,10 +21,15 @@ namespace clang::tidy::bugprone {
 // http://clang.llvm.org/extra/clang-tidy/checks/bugprone/tagged-union-member-count.html
 class TaggedUnionMemberCountCheck : public ClangTidyCheck {
 public:
-  TaggedUnionMemberCountCheck(StringRef Name, ClangTidyContext *Context)
-      : ClangTidyCheck(Name, Context) {}
+  TaggedUnionMemberCountCheck(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;
+
+private:
+  bool EnumCounterHeuristicIsEnabled;
+  StringRef EnumCounterSuffix;
+  bool StrictMode;
 };
 
 } // namespace clang::tidy::bugprone
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 9f9069a8d0892..b287b99da5a01 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -117,6 +117,12 @@ New checks
   to reading out-of-bounds data due to inadequate or incorrect string null
   termination.
 
+- New :doc:`bugprone-tagged-union-member-count
+  <clang-tidy/checks/bugprone/tagged-union-member-count>` check.
+
+  Gives warnings for tagged unions, where the number of tags is
+  different from the number of data members inside the union.
+
 - New :doc:`modernize-use-designated-initializers
   <clang-tidy/checks/modernize/use-designated-initializers>` check.
 
@@ -129,12 +135,6 @@ New checks
   Replaces certain conditional statements with equivalent calls to
   ``std::min`` or ``std::max``.
 
-- New :doc:`bugprone-tagged-union-member-count
-  <clang-tidy/checks/bugprone/tagged-union-member-count>` check.
-
-  Warns about suspicious looking tagged unions where the number of enum
-  constants and the number of union data members are not equal.
-
 New check aliases
 ^^^^^^^^^^^^^^^^^
 
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/tagged-union-member-count.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/tagged-union-member-count.rst
index 7eac93c6c9602..cd7de571041e4 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/tagged-union-member-count.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/tagged-union-member-count.rst
@@ -15,29 +15,13 @@ Example
 
 .. code-block:: c++
 
-  enum tags2 {
+  enum tags {
     tag1,
     tag2,
   };
 
-  struct taggedunion { // warning: Tagged union has more data members than tags! Data members: 3 Tags: 2 [bugprone-tagged-union-member-count]
-    enum tags2 kind;
-    union {
-      int i;
-      float f;
-      char *str;
-    } data;
-  };
-
-  enum tags4 {
-    tag1,
-    tag2,
-    tag3,
-    tag4,
-  };
-  
-  struct taggedunion { // warning: Tagged union has more fewer members than tags! Data members: 3 Tags: 4 [bugprone-tagged-union-member-count]
-    enum tags4 kind;
+  struct taggedUnion { // warning: Tagged union has more data members (3) than tags (2)! [bugprone-tagged-union-member-count]
+    enum tags kind;
     union {
       int i;
       float f;
@@ -60,7 +44,173 @@ constant is used to keep track of how many enum constants have been declared.
     tags_count, // is 3
   };
 
-The checker detects this usage pattern heuristically and does not include
+The check detects this usage pattern heuristically and does not include
 the counter enum constant in the final tag count, since the counter is not
 meant to indicate the valid variant member.
 
+Options
+-------
+
+.. option:: EnumCounterHeuristicIsEnabled
+
+    This option enables or disables the counting enum heuristic.
+    To find possible counting enum constants this option uses the value of
+    the string :option:`EnumCounterSuffix`.
+
+    This option is enabled by default.
+
+Example of :option:`EnumCounterHeuristicIsEnabled`:
+
+When :option:`EnumCounterHeuristicIsEnabled` is false:
+
+.. code-block:: c++
+
+  enum tags_with_counter {
+    tag1,
+    tag2,
+    tag3,
+    tags_count,
+  };
+
+  struct taggedUnion {
+    tags_with_counter tag;
+    union data {
+      int a;
+      int b;
+      char *str;
+      float f;
+    };
+  };
+ 
+When :option:`EnumCounterHeuristicIsEnabled` is true:
+
+.. code-block:: c++
+
+  enum tags_with_counter {
+    tag1,
+    tag2,
+    tag3,
+    tags_count,
+  };
+
+  struct taggedUnion { // warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
+    tags_with_counter tag;
+    union data {
+      int a;
+      int b;
+      char *str;
+      float f;
+    };
+  };
+
+.. option:: EnumCounterSuffix
+
+    When defined, the check will use the given string to search for counting
+    enum constants. This option does not alter the check's behavior when
+    :option:`EnumCounterHeuristicIsEnabled` is disabled.
+
+    The default value is "count".
+
+Example of :option:`EnumCounterSuffix`:
+
+When :option:`EnumCounterHeuristicIsEnabled` is true and
+:option:`EnumCounterSuffix` is "size":
+
+.. code-block:: c++
+
+  enum tags_with_counter_count {
+    tag1,
+    tag2,
+    tag3,
+    tags_count,
+  };
+
+  enum tags_with_counter_size {
+    tag4,
+    tag5,
+    tag6,
+    tags_size,
+  };
+
+  struct taggedUnion1 {
+    tags_with_counter_count tag;
+    union data {
+      int a;
+      int b;
+      char *str;
+      float f;
+    };
+  };
+
+  struct taggedUnion2 { // warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
+    tags_with_counter_size tag;
+    union data {
+      int a;
+      int b;
+      char *str;
+      float f;
+    };
+  };
+ 
+When :option:`EnumCounterSuffix` is true:
+
+.. code-block:: c++
+
+  enum tags_with_counter {
+    tag1,
+    tag2,
+    tag3,
+    tags_count,
+  };
+
+  struct taggedUnion { // warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
+    tags_with_counter tag;
+    union data {
+      int a;
+      int b;
+      char *str;
+      float f;
+    };
+  };
+
+.. option:: StrictMode
+
+    When enabled, the check will also give a warning, when the number of tags
+    is greater than the number of union data members.
+
+    This option is not enabled by default.
+
+Example of :option:`StrictMode`:
+
+When :option:`StrictMode` is false:
+
+.. code-block:: c++
+
+    struct taggedUnion {
+      enum {
+        tags1,
+        tags2,
+        tags3,
+      } tags;
+      union {
+        int i;
+        float f;
+      };
+    };
+
+When :option:`StrictMode` is true:
+
+.. code-block:: c++
+
+    struct taggedunion1 { // warning: Tagged union has fewer data members (2) than tags (3)! [bugprone-tagged-union-member-count]
+      enum {
+        tags1,
+        tags2,
+        tags3,
+      } tags;
+      union {
+        int i;
+        float f;
+      };
+    };
+
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index 3058bee1357fc..5b2fb2bfbfc30 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -142,7 +142,7 @@ Clang-Tidy Checks
    :doc:`bugprone-suspicious-stringview-data-usage <bugprone/suspicious-stringview-data-usage>`,
    :doc:`bugprone-swapped-arguments <bugprone/swapped-arguments>`, "Yes"
    :doc:`bugprone-switch-missing-default-case <bugprone/switch-missing-default-case>`,
-   :doc:`bugprone-tagged-union-member-count <bugprone/tagged-union-member-count>`, "Yes"
+   :doc:`bugprone-tagged-union-member-count <bugprone/tagged-union-member-count>`,
    :doc:`bugprone-terminating-continue <bugprone/terminating-continue>`, "Yes"
    :doc:`bugprone-throw-keyword-missing <bugprone/throw-keyword-missing>`,
    :doc:`bugprone-too-small-loop-variable <bugprone/too-small-loop-variable>`,
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-enumcounterheuristicisenabled.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-enumcounterheuristicisenabled.cpp
new file mode 100644
index 0000000000000..69b44c10b7c31
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-enumcounterheuristicisenabled.cpp
@@ -0,0 +1,38 @@
+// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
+// RUN:   -config='{CheckOptions: { \
+// RUN:     bugprone-tagged-union-member-count.StrictMode: 1, \
+// RUN:     bugprone-tagged-union-member-count.EnumCounterHeuristicIsEnabled: 0, \
+// RUN:     bugprone-tagged-union-member-count.EnumCounterSuffix: "count", \
+// RUN:  }}' --
+
+// Without the heuristic the tags and the data members match
+struct taggedUnion1 { // No warnings expected
+	enum {
+		tags1,
+		tags2,
+		tags3,
+		tags_count,
+	} tags;
+	union {
+		int a;
+		int b;
+		int c;
+		int d;
+	} data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has fewer data members (3) than tags (4)! [bugprone-tagged-union-member-count]
+struct taggedUnion2 {
+	enum {
+		tags11,
+		tags22,
+		tags33,
+		tags_count,
+	} tags;
+	union {
+		int a;
+		int b;
+		int c;
+	} data;
+};
+
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-enumcountersuffix.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-enumcountersuffix.cpp
new file mode 100644
index 0000000000000..cddbb003a01f9
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-enumcountersuffix.cpp
@@ -0,0 +1,47 @@
+// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
+// RUN:   -config='{CheckOptions: { \
+// RUN:     bugprone-tagged-union-member-count.StrictMode: 0, \
+// RUN:     bugprone-tagged-union-member-count.EnumCounterHeuristicIsEnabled: 1, \
+// RUN:     bugprone-tagged-union-member-count.EnumCounterSuffix: "size", \
+// RUN:  }}' --
+
+typedef union union3 {
+	short *shorts;
+	int *ints;
+	float *floats;
+} union3;
+
+typedef union union4 {
+	short *shorts;
+	double *doubles;
+	int *ints;
+	float *floats;
+} union4;
+
+enum tag_with_counter_count {
+	twc1,
+	twc2,
+	twc3,
+	twc_count,
+};
+
+enum tag_with_counter_size {
+	twc11,
+	twc22,
+	twc33,
+	twc_size,
+};
+
+// The heuristic is configured with the "size" suffix so
+// twc_count will not be considered a counting enum constant
+struct taggedUnion1 { // No warning expected
+	enum tag_with_counter_count tag;
+	union union4 data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
+struct taggedUnion2 { 
+	enum tag_with_counter_size tag;
+	union union4 data;
+};
+
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-strictmode.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-strictmode.cpp
new file mode 100644
index 0000000000000..7d6c5f385c8e1
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-strictmode.cpp
@@ -0,0 +1,150 @@
+// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
+// RUN:   -config='{CheckOptions: { \
+// RUN:     bugprone-tagged-union-member-count.StrictMode: 1, \
+// RUN:     bugprone-tagged-union-member-count.EnumCounterHeuristicIsEnabled: 0, \
+// RUN:     bugprone-tagged-union-member-count.EnumCounterSuffix: "count", \
+// RUN:  }}' --
+
+typedef enum tags3 {
+	tags3_1,
+	tags3_2,
+	tags3_3,
+} tags3;
+
+typedef enum tags4 {
+	tags4_1,
+	tags4_2,
+	tags4_3,
+	tags4_4,
+} tags4;
+
+typedef enum tags5 {
+	tags5_1,
+	tags5_2,
+	tags5_3,
+	tags5_4,
+	tags5_5,
+} tags5;
+
+enum class classtags3 {
+	classtags3_1,
+	classtags3_2,
+	classtags3_3,
+};
+
+enum class classtags4 {
+	classtags4_1,
+	classtags4_2,
+	classtags4_3,
+	classtags4_4,
+};
+
+enum class classtags5 {
+	classtags5_1,
+	classtags5_2,
+	classtags5_3,
+	classtags5_4,
+	classtags5_5,
+};
+
+enum class typedtags3 : unsigned int {
+	typedtags3_1,
+	typedtags3_2,
+	typedtags3_3,
+};
+
+enum class typedtags4 : long {
+	typedtags4_1,
+	typedtags4_2,
+	typedtags4_3,
+	typedtags4_4,
+};
+
+enum class typedtags5 {
+	typedtags5_1,
+	typedtags5_2,
+	typedtags5_3,
+	typedtags5_4,
+	typedtags5_5,
+};
+
+typedef union union3 {
+	short *shorts;
+	int *ints;
+	float *floats;
+} union3;
+
+typedef union union4 {
+	short *shorts;
+	double *doubles;
+	int *ints;
+	float *floats;
+} union4;
+
+// Technically this means that every enum value is defined from 0-256 and therefore a warning is given.
+enum mycolor {
+	mycolor_black = 0x00,
+	mycolor_gray  = 0xcc,
+	mycolor_white = 0xff,
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has fewer data members (3) than tags (256)! [bugprone-tagged-union-member-count]
+struct taggedunion9 { 
+	enum mycolor tag;
+	union {
+		int a;
+		float b;
+		struct {
+			double re;
+			double im;
+		} complex;
+	} data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has fewer data members (3) than tags (4)! [bugprone-tagged-union-member-count]
+struct withanonymous { 
+	enum tags4 tag;
+	union {
+		int a;
+		float b;
+		char *c;
+	};
+}; 
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has fewer data members (3) than tags (4)! [bugprone-tagged-union-member-count]
+struct withTypedef1 { 
+	tags4 tag;
+	union3 data;
+}; 
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has fewer data members (3) than tags (4)! [bugprone-tagged-union-member-count]
+struct withEnumClass1 {
+	enum classtags4 tag;
+	union3 data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has fewer data members (3) than tags (4)! [bugprone-tagged-union-member-count]
+struct withTypedEnum2 {
+	typedtags4 tag;
+	union3 data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has fewer data members (2) than tags (3)! [bugprone-tagged-union-member-count]
+struct anonymous2 {
+	tags3 tag;
+	union {
+		int a;
+		float f;
+	};
+};
+
+// CHECK-MESSAGES: :[[@LINE+2]]:8: warning: Tagged union has fewer data members (3) than tags (5)! [bugprone-tagged-union-member-count]
+template <typename Union, typename Tag>
+struct templated {
+	Tag tag;
+	Union data;
+};
+
+templated<union3, tags3> t1; // No warning expected
+templated<union3, tags5> t2;
+
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.cpp
index 91165f1bd64e0..05ae457f57410 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.cpp
@@ -1,38 +1,81 @@
-// RUN: %check_clang_tidy %s bugprone-tagged-union-member-count %t
+// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t
+// Test cases for the default configuration
 
-enum tags3 {
+typedef enum tags3 {
 	tags3_1,
 	tags3_2,
 	tags3_3,
-};
+} tags3;
 
-enum tags4 {
+typedef enum tags4 {
 	tags4_1,
 	tags4_2,
 	tags4_3,
 	tags4_4,
-};
+} tags4;
 
-enum tags5 {
+typedef enum tags5 {
 	tags5_1,
 	tags5_2,
 	tags5_3,
 	tags5_4,
 	tags5_5,
+} tags5;
+
+enum class classtags3 {
+	classtags3_1,
+	classtags3_2,
+	classtags3_3,
+};
+
+enum class classtags4 {
+	classtags4_1,
+	classtags4_2,
+	classtags4_3,
+	classtags4_4,
+};
+
+enum class classtags5 {
+	classtags5_1,
+	classtags5_2,
+	classtags5_3,
+	classtags5_4,
+	classtags5_5,
+};
+
+enum class typedtags3 : unsigned int {
+	typedtags3_1,
+	typedtags3_2,
+	typedtags3_3,
+};
+
+enum class typedtags4 : long {
+	typedtags4_1,
+	typedtags4_2,
+	typedtags4_3,
+	typedtags4_4,
+};
+
+enum class typedtags5 {
+	typedtags5_1,
+	typedtags5_2,
+	typedtags5_3,
+	typedtags5_4,
+	typedtags5_5,
 };
 
-union union3 {
+typedef union union3 {
 	short *shorts;
 	int *ints;
 	float *floats;
-};
+} union3;
 
-union union4 {
+typedef union union4 {
 	short *shorts;
 	double *doubles;
 	int *ints;
 	float *floats;
-};
+} union4;
 
 // It is not obvious which enum is the tag for the union.
 struct taggedunion2 { // No warnings expected.
@@ -48,12 +91,29 @@ struct taggedunion4 { // No warnings expected.
 	union union3 dataA;
 };
 
-struct taggedunion1 { // CHECK-MESSAGES: :[[@LINE]]:8: warning: Tagged union has more data members than tags! Data members: 4 Tags: 3 [bugprone-tagged-union-member-count]
+// It is not obvious which union does the tag belong to.
+struct taggedunion6 { // No warnings expected.
+	enum tags3 tag;
+	union {
+		int i1;
+		int i2;
+		int i3;
+	};
+	union {
+		float f1;
+		float f2;
+		float f3;
+	};
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
+struct taggedunion1 {
 	enum tags3 tag;
     union union4 data;
 };
 
-struct taggedunion5 { // CHECK-MESSAGES: :[[@LINE]]:8: warning: Tagged union has more data members than tags! Data members: 4 Tags: 3 [bugprone-tagged-union-member-count]
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
+struct taggedunion5 {
 	enum tags3 tag;
     union {
 		int *ints;
@@ -66,7 +126,8 @@ struct taggedunion5 { // CHECK-MESSAGES: :[[@LINE]]:8: warning: Tagged union has
     } data;
 };
 
-struct taggedunion7 { // CHECK-MESSAGES: :[[@LINE]]:8: warning: Tagged union has more data members than tags! Data members: 4 Tags: 3 [bugprone-tagged-union-member-count]
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
+struct taggedunion7 { 
 	enum {
 		tag1,
 		tag2,
@@ -75,7 +136,8 @@ struct taggedunion7 { // CHECK-MESSAGES: :[[@LINE]]:8: warning: Tagged union has
 	union union4 data;
 };
 
-struct taggedunion8 { // CHECK-MESSAGES: :[[@LINE]]:8: warning: Tagged union has more data members than tags! Data members: 4 Tags: 3 [bugprone-tagged-union-member-count]
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
+struct taggedunion8 { 
 	enum {
 		tag1,
 		tag2,
@@ -92,40 +154,18 @@ struct taggedunion8 { // CHECK-MESSAGES: :[[@LINE]]:8: warning: Tagged union has
 	} data;
 };
 
-struct nested1 { // CHECK-MESSAGES: :[[@LINE]]:8: warning: Tagged union has more data members than tags! Data members: 4 Tags: 3 [bugprone-tagged-union-member-count]
-	enum tags3 tag;
-	union {
-		char c;
-		short s;
-		int i;
-		struct { // CHECK-MESSAGES: :[[@LINE]]:3: warning: Tagged union has fewer data members than tags! Data members: 4 Tags: 5 [bugprone-tagged-union-member-count]
-			enum tags5 tag;
-			union union4 data;
-		} inner;
-	} data;
-};
-
-struct nested2 {
-	enum tags3 tag;
-	union {
-		float f;
-		int i;
-		struct { // CHECK-MESSAGES: :[[@LINE]]:3: warning: Tagged union has more data members than tags! Data members: 4 Tags: 3 [bugprone-tagged-union-member-count]
-			enum tags3 tag;
-			union union4 data;
-		} inner;
-	} data;
-};
-
-struct nested3 { // CHECK-MESSAGES: :[[@LINE]]:8: warning: Tagged union has fewer data members than tags! Data members: 2 Tags: 3 [bugprone-tagged-union-member-count]
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
+struct nested4 { 
 	enum tags3 tag;
 	union {
 		float f;
 		int i;
-		struct innerdecl { // CHECK-MESSAGES: :[[@LINE]]:10: warning: Tagged union has more data members than tags! Data members: 4 Tags: 3 [bugprone-tagged-union-member-count]
+		long l;
+		// CHECK-MESSAGES: :[[@LINE+1]]:10: warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
+		struct innerdecl { 
 			enum tags3 tag;
 			union union4 data;
-		}; 
+		} inner; 
 	} data;
 };
 
@@ -143,12 +183,14 @@ enum tag_with_counter_uppercase {
 	NODE_TYPE_COUNT,
 };
 
-struct taggedunion10 { // CHECK-MESSAGES: :[[@LINE]]:8: warning: Tagged union has more data members than tags! Data members: 4 Tags: 3 [bugprone-tagged-union-member-count]
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
+struct taggedunion10 { 
 	enum tag_with_counter_lowercase tag;
 	union union4 data;
 };
 
-struct taggedunion11 { // CHECK-MESSAGES: :[[@LINE]]:8: warning: Tagged union has more data members than tags! Data members: 4 Tags: 3 [bugprone-tagged-union-member-count]
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
+struct taggedunion11 { 
 	enum tag_with_counter_uppercase tag;
 	union union4 data;
 };
@@ -160,22 +202,42 @@ struct taggedunion12 { // No warnings expected.
 	union union3 data;
 };
 
-// Technically this means that every enum value is defined from 0-256 and therefore a warning is given.
-enum mycolor {
-	mycolor_black = 0x00,
-	mycolor_gray  = 0xcc,
-	mycolor_white = 0xff,
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
+struct withTypedef2 { 
+	tags3 tag;
+	union4 data;
+}; 
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
+struct withEnumClass2 { 
+	enum classtags3 tag;
+	union4 data;
+}; 
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
+struct withTypedEnum1 {
+	typedtags3 tag;
+	union4 data;
 };
 
-struct taggedunion9 { // CHECK-MESSAGES: :[[@LINE]]:8: warning: Tagged union has fewer data members than tags! Data members: 3 Tags: 256 [bugprone-tagged-union-member-count]
-	enum mycolor tag;
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
+struct anonymous1 {
+	tags3 tag;
 	union {
 		int a;
-		float b;
-		struct {
-			double re;
-			double im;
-		} complex;
-	} data;
+		int b;
+		int c;
+		int d;
+	};
 };
 
+// CHECK-MESSAGES: :[[@LINE+2]]:8: warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
+template <typename Union, typename Tag>
+struct templated {
+	Tag tag;
+	Union data;
+};
+
+templated<union3, tags3> t1; // No warning expected
+templated<union4, tags3> t3;
+

>From be3ec1a4e9ac3309da8f2037334de68bd478883f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=A1bor=20T=C3=B3thv=C3=A1ri?= <tigbrcode at protonmail.com>
Date: Fri, 19 Jul 2024 17:37:49 +0200
Subject: [PATCH 3/3] Major iteration based on review comments

Feature changes:

* Added C++ classes to matcher
* Added multiple prefix/suffix option support to the counting enum heuristic

Implementation changes:

* Distinct enum values are counted with a set instead of the previous max - min + 1 approach
* Use APSInt to avoid truncation or overflow

Testing changes:

* Tests use more descriptive names
* Removed redundant suffix from test case messages
* Separated test files for C, C++, Objective-C and Objective-C++
* C++ tests are ran with the -std=c98-or-later flag
* C++ tests are extended with more language feature test cases
* Created tests for the Prefix/suffix feature of the counting enum heuristic
* StrictMode and heuristic enabling/disabling is more thoroughly tested

Also made plenty of small improvements as well.
---
 .../bugprone/TaggedUnionMemberCountCheck.cpp  | 189 +++++---
 .../bugprone/TaggedUnionMemberCountCheck.h    |  31 +-
 .../bugprone/tagged-union-member-count.rst    | 239 +++++-----
 ...nt-counting-enum-heuristic-is-disabled.cpp |  68 +++
 ...unt-counting-enum-heuristic-is-enabled.cpp |  63 +++
 ...nt-counting-enum-prefixes-and-suffixes.cpp |  53 +++
 ...on-member-count-counting-enum-prefixes.cpp |  36 ++
 ...on-member-count-counting-enum-suffixes.cpp |  36 ++
 ...er-count-enumcounterheuristicisenabled.cpp |  38 --
 ...d-union-member-count-enumcountersuffix.cpp |  47 --
 ...on-member-count-strictmode-is-disabled.cpp |  28 ++
 ...ion-member-count-strictmode-is-enabled.cpp |  31 ++
 .../tagged-union-member-count-strictmode.cpp  | 150 ------
 .../bugprone/tagged-union-member-count.c      | 148 ++++++
 .../bugprone/tagged-union-member-count.cpp    | 434 +++++++++---------
 .../bugprone/tagged-union-member-count.m      | 147 ++++++
 .../bugprone/tagged-union-member-count.mm     | 262 +++++++++++
 17 files changed, 1354 insertions(+), 646 deletions(-)
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-heuristic-is-disabled.cpp
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-heuristic-is-enabled.cpp
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-prefixes-and-suffixes.cpp
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-prefixes.cpp
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-suffixes.cpp
 delete mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-enumcounterheuristicisenabled.cpp
 delete mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-enumcountersuffix.cpp
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-strictmode-is-disabled.cpp
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-strictmode-is-enabled.cpp
 delete mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-strictmode.cpp
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.c
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.m
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.mm

diff --git a/clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.cpp
index 786198b011f30..756ecb586fdf4 100644
--- a/clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.cpp
@@ -7,58 +7,94 @@
 //===----------------------------------------------------------------------===//
 
 #include "TaggedUnionMemberCountCheck.h"
-#include "clang/AST/ASTContext.h"
-#include "clang/AST/Expr.h"
+#include "../utils/OptionsUtils.h"
 #include "clang/ASTMatchers/ASTMatchFinder.h"
-#include "clang/AST/PrettyPrinter.h"
 #include "llvm/ADT/STLExtras.h"
-#include "llvm/Support/Casting.h"
-#include <limits>
-#include <iostream>
+#include "llvm/ADT/SmallSet.h"
 
 using namespace clang::ast_matchers;
 
 namespace clang::tidy::bugprone {
 
-TaggedUnionMemberCountCheck::TaggedUnionMemberCountCheck(StringRef Name, ClangTidyContext *Context)
-      : ClangTidyCheck(Name, Context),
-        EnumCounterHeuristicIsEnabled(Options.get("EnumCounterHeuristicIsEnabled", true)),
-        EnumCounterSuffix(Options.get("EnumCounterSuffix", "count")),
-        StrictMode(Options.get("StrictMode", true)) { }
+TaggedUnionMemberCountCheck::TaggedUnionMemberCountCheck(
+    StringRef Name, ClangTidyContext *Context)
+    : ClangTidyCheck(Name, Context),
+      StrictModeIsEnabled(Options.get(StrictModeIsEnabledOptionName, true)),
+      CountingEnumHeuristicIsEnabled(
+          Options.get(CountingEnumHeuristicIsEnabledOptionName, true)),
+      RawCountingEnumPrefixes(Options.get(CountingEnumPrefixesOptionName, "")),
+      RawCountingEnumSuffixes(
+          Options.get(CountingEnumSuffixesOptionName, "count")),
+      ParsedCountingEnumPrefixes(
+          utils::options::parseStringList(RawCountingEnumPrefixes)),
+      ParsedCountingEnumSuffixes(
+          utils::options::parseStringList(RawCountingEnumSuffixes)) {}
 
 void TaggedUnionMemberCountCheck::storeOptions(
     ClangTidyOptions::OptionMap &Opts) {
-  Options.store(Opts, "StrictMode", StrictMode);
-  Options.store(Opts, "EnumCounterHeuristicIsEnabled", EnumCounterHeuristicIsEnabled);
-  Options.store(Opts, "EnumCounterSuffix", EnumCounterSuffix);
+  Options.store(Opts, StrictModeIsEnabledOptionName, StrictModeIsEnabled);
+  Options.store(Opts, CountingEnumHeuristicIsEnabledOptionName,
+                CountingEnumHeuristicIsEnabled);
+  Options.store(Opts, CountingEnumPrefixesOptionName, RawCountingEnumPrefixes);
+  Options.store(Opts, CountingEnumSuffixesOptionName, RawCountingEnumSuffixes);
 }
 
 void TaggedUnionMemberCountCheck::registerMatchers(MatchFinder *Finder) {
-Finder->addMatcher(
+  Finder->addMatcher(
       recordDecl(
-          allOf(isStruct(),
-				has(fieldDecl(hasType(qualType(hasCanonicalType(recordType())))).bind("union")),
-				has(fieldDecl(hasType(qualType(hasCanonicalType(enumType())))).bind("tags"))))
+          allOf(anyOf(isStruct(), isClass()),
+                has(fieldDecl(hasType(qualType(hasCanonicalType(recordType()))))
+                        .bind("union")),
+                has(fieldDecl(hasType(qualType(hasCanonicalType(enumType()))))
+                        .bind("tags"))))
           .bind("root"),
       this);
 }
 
 static bool isUnion(const FieldDecl *R) {
-	return R->getType().getCanonicalType().getTypePtr()->isUnionType();
+  return R->getType().getCanonicalType().getTypePtr()->isUnionType();
 }
 
 static bool isEnum(const FieldDecl *R) {
-	return R->getType().getCanonicalType().getTypePtr()->isEnumeralType();
+  return R->getType().getCanonicalType().getTypePtr()->isEnumeralType();
 }
 
-static bool hasMultipleUnionsOrEnums(const RecordDecl *rec) {
-  return llvm::count_if(rec->fields(), isUnion) > 1 ||
-         llvm::count_if(rec->fields(), isEnum) > 1;
+static bool hasMultipleUnionsOrEnums(const RecordDecl *Rec) {
+  return llvm::count_if(Rec->fields(), isUnion) > 1 ||
+         llvm::count_if(Rec->fields(), isEnum) > 1;
 }
 
-static size_t getNumberOfValidEnumValues(const EnumDecl *ed, bool EnumCounterHeuristicIsEnabled, StringRef EnumCounterSuffix) {
-  int64_t maxTagValue = std::numeric_limits<int64_t>::min();
-  int64_t minTagValue = std::numeric_limits<int64_t>::max();
+static bool signEquals(const llvm::APSInt &A, const llvm::APSInt &B) {
+  return (A.isNegative() && B.isNegative()) ||
+         (A.isStrictlyPositive() && B.isStrictlyPositive()) ||
+         (A.isZero() && B.isZero());
+}
+
+static bool greaterBySign(const llvm::APSInt &A, const llvm::APSInt &B) {
+  return (A.isNonNegative() && B.isNegative()) ||
+         (A.isStrictlyPositive() && B.isNonPositive());
+}
+
+bool TaggedUnionMemberCountCheck::isCountingEnumLikeName(
+    StringRef Name) const noexcept {
+  if (llvm::any_of(ParsedCountingEnumPrefixes,
+                   [&Name](const StringRef &prefix) -> bool {
+                     return Name.starts_with_insensitive(prefix);
+                   }))
+    return true;
+  if (llvm::any_of(ParsedCountingEnumSuffixes,
+                   [&Name](const StringRef &suffix) -> bool {
+                     return Name.ends_with_insensitive(suffix);
+                   }))
+    return true;
+  return false;
+}
+
+size_t TaggedUnionMemberCountCheck::getNumberOfValidEnumValues(
+    const EnumDecl *Ed) const noexcept {
+  bool FoundMax = false;
+  llvm::APSInt MaxTagValue;
+  llvm::SmallSet<llvm::APSInt, 32> EnumValues;
 
   // Heuristic for counter enum constants.
   //
@@ -68,77 +104,78 @@ static size_t getNumberOfValidEnumValues(const EnumDecl *ed, bool EnumCounterHeu
   //     tag_count, <-- Searching for these enum constants
   //   };
   //
-  // The 'ce' prefix is used to abbreviate counterEnum.
   // The final tag count is decreased by 1 if and only if:
-  // 1. The number of counting enum constants = 1,
-  int ceCount = 0;
+  // 1. There is only one counting enum constant,
   // 2. The counting enum constant is the last enum constant that is defined,
-  int ceFirstIndex = 0;
-  // 3. The value of the counting enum constant is the largest out of every enum constant.
-  int64_t ceValue = 0;
-
-  int64_t enumConstantsCount = 0;
-  for (auto En : llvm::enumerate(ed->enumerators())) {
-    enumConstantsCount += 1;
-
-    int64_t enumValue = En.value()->getInitVal().getExtValue();
-    StringRef enumName = En.value()->getName();
-
-    if (enumValue > maxTagValue)
-      maxTagValue = enumValue;
-    if (enumValue < minTagValue)
-      minTagValue = enumValue;
+  // 3. The value of the counting enum constant is the largest out of every enum
+  //    constant.
+  // The 'ce' prefix is a shorthand for 'counting enum'.
+  size_t CeCount = 0;
+  bool IsLast = false;
+  llvm::APSInt CeValue = llvm::APSInt::get(0);
+
+  for (const auto &&[index, enumerator] : llvm::enumerate(Ed->enumerators())) {
+    const llvm::APSInt Val = enumerator->getInitVal();
+    EnumValues.insert(Val);
+    if (FoundMax) {
+      if (greaterBySign(Val, MaxTagValue) ||
+          (signEquals(Val, MaxTagValue) && Val > MaxTagValue)) {
+        MaxTagValue = Val;
+      }
+    } else {
+      MaxTagValue = Val;
+      FoundMax = true;
+    }
 
-    if (enumName.ends_with_insensitive(EnumCounterSuffix)) {
-      if (ceCount == 0) {
-        ceFirstIndex = En.index();
+    if (CountingEnumHeuristicIsEnabled) {
+      if (isCountingEnumLikeName(enumerator->getName())) {
+        IsLast = true;
+        CeValue = Val;
+        CeCount += 1;
+      } else {
+        IsLast = false;
       }
-      ceValue = enumValue;
-      ceCount += 1;
     }
   }
 
-  int64_t validValuesCount = maxTagValue - minTagValue + 1;
-  if (EnumCounterHeuristicIsEnabled &&
-      ceCount == 1 &&
-      ceFirstIndex == enumConstantsCount - 1 &&
-      ceValue == maxTagValue) {
-    validValuesCount -= 1;
+  size_t ValidValuesCount = EnumValues.size();
+  if (CeCount == 1 && IsLast && CeValue == MaxTagValue) {
+    ValidValuesCount -= 1;
   }
-  return validValuesCount;
+
+  return ValidValuesCount;
 }
 
-// Feladatok:
-// - typedef tesztelés
-// - template tesztelés
-// - névtelen union tesztelés
-// - "count" paraméterezése
 void TaggedUnionMemberCountCheck::check(
     const MatchFinder::MatchResult &Result) {
-  const auto *root = Result.Nodes.getNodeAs<RecordDecl>("root");
-  const auto *unionField = Result.Nodes.getNodeAs<FieldDecl>("union");
-  const auto *tagField = Result.Nodes.getNodeAs<FieldDecl>("tags");
+  const auto *Root = Result.Nodes.getNodeAs<RecordDecl>("root");
+  const auto *UnionField = Result.Nodes.getNodeAs<FieldDecl>("union");
+  const auto *TagField = Result.Nodes.getNodeAs<FieldDecl>("tags");
 
   // The matcher can only narrow down the type to recordType()
-  if (!isUnion(unionField))
+  if (!isUnion(UnionField))
     return;
 
-  if (hasMultipleUnionsOrEnums(root))
+  if (hasMultipleUnionsOrEnums(Root))
     return;
 
-  const auto *unionDef = unionField->getType().getCanonicalType().getTypePtr()->getAsRecordDecl();
-  const auto *enumDef = static_cast<EnumDecl*>(tagField->getType().getCanonicalType().getTypePtr()->getAsTagDecl());
+  const auto *UnionDef =
+      UnionField->getType().getCanonicalType().getTypePtr()->getAsRecordDecl();
+  const auto *EnumDef = static_cast<EnumDecl *>(
+      TagField->getType().getCanonicalType().getTypePtr()->getAsTagDecl());
 
-  size_t unionMemberCount = llvm::range_size(unionDef->fields());
-  size_t tagCount = getNumberOfValidEnumValues(enumDef, EnumCounterHeuristicIsEnabled, EnumCounterSuffix);
+  const size_t UnionMemberCount = llvm::range_size(UnionDef->fields());
+  const size_t TagCount = getNumberOfValidEnumValues(EnumDef);
 
   // FIXME: Maybe a emit a note when a counter enum constant was found.
-  if (unionMemberCount > tagCount) {
-    diag(root->getLocation(), "Tagged union has more data members (%0) than tags (%1)!")
-        << unionMemberCount << tagCount;
-  } else if (StrictMode && unionMemberCount < tagCount) {
-    diag(root->getLocation(), "Tagged union has fewer data members (%0) than tags (%1)!")
-        << unionMemberCount << tagCount;
+  if (UnionMemberCount > TagCount) {
+    diag(Root->getLocation(),
+         "Tagged union has more data members (%0) than tags (%1)!")
+        << UnionMemberCount << TagCount;
+  } else if (StrictModeIsEnabled && UnionMemberCount < TagCount) {
+    diag(Root->getLocation(),
+         "Tagged union has fewer data members (%0) than tags (%1)!")
+        << UnionMemberCount << TagCount;
   }
 }
 
diff --git a/clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.h b/clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.h
index 2c6ec3b05358a..0a13ead0102db 100644
--- a/clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.h
+++ b/clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.h
@@ -10,15 +10,14 @@
 #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_TAGGEDUNIONMEMBERCOUNTCHECK_H
 
 #include "../ClangTidyCheck.h"
-#include "llvm/ADT/StringRef.h"
 
 namespace clang::tidy::bugprone {
 
-// Gives warnings for tagged unions, where the number of tags is
-// different from the number of data members inside the union.
-//
-// For the user-facing documentation see:
-// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/tagged-union-member-count.html
+/// Gives warnings for tagged unions, where the number of tags is
+/// different from the number of data members inside the union.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/tagged-union-member-count.html
 class TaggedUnionMemberCountCheck : public ClangTidyCheck {
 public:
   TaggedUnionMemberCountCheck(StringRef Name, ClangTidyContext *Context);
@@ -27,9 +26,23 @@ class TaggedUnionMemberCountCheck : public ClangTidyCheck {
   void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
 
 private:
-  bool EnumCounterHeuristicIsEnabled;
-  StringRef EnumCounterSuffix;
-  bool StrictMode;
+  static constexpr char StrictModeIsEnabledOptionName[] = "StrictModeIsEnabled";
+  static constexpr char CountingEnumHeuristicIsEnabledOptionName[] =
+      "CountingEnumHeuristicIsEnabled";
+  static constexpr char CountingEnumPrefixesOptionName[] =
+      "CountingEnumPrefixes";
+  static constexpr char CountingEnumSuffixesOptionName[] =
+      "CountingEnumSuffixes";
+
+  const bool StrictModeIsEnabled;
+  const bool CountingEnumHeuristicIsEnabled;
+  const StringRef RawCountingEnumPrefixes;
+  const StringRef RawCountingEnumSuffixes;
+  std::vector<StringRef> ParsedCountingEnumPrefixes;
+  std::vector<StringRef> ParsedCountingEnumSuffixes;
+
+  size_t getNumberOfValidEnumValues(const EnumDecl *ed) const noexcept;
+  bool isCountingEnumLikeName(StringRef name) const noexcept;
 };
 
 } // namespace clang::tidy::bugprone
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/tagged-union-member-count.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/tagged-union-member-count.rst
index cd7de571041e4..369fc8bdc19c1 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/tagged-union-member-count.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/tagged-union-member-count.rst
@@ -1,5 +1,6 @@
 .. title:: clang-tidy - bugprone-tagged-union-member-count
 
+==================================
 bugprone-tagged-union-member-count
 ==================================
 
@@ -11,190 +12,190 @@ exactly one union data member and exactly one enum data member and
 any number of other data members that are neither unions or enums.
 
 Example
--------
+=======
 
 .. code-block:: c++
 
-  enum tags {
-    tag1,
-    tag2,
+  enum Tags {
+    Tag1,
+    Tag2,
   };
 
-  struct taggedUnion { // warning: Tagged union has more data members (3) than tags (2)! [bugprone-tagged-union-member-count]
-    enum tags kind;
+  struct TaggedUnion { // warning: Tagged union has more data members (3) than tags (2)
+    enum Tags Kind;
     union {
-      int i;
-      float f;
-      char *str;
-    } data;
+      int I;
+      float F;
+      char *Str;
+    } Data;
   };
 
 Counting enum constant heuristic
---------------------------------
+================================
 
 Some programmers use enums in such a way, where the value of the last enum 
 constant is used to keep track of how many enum constants have been declared.
 
 .. code-block:: c++
 
-  enum tags_with_counter {
-    tag1, // is 0
-    tag2, // is 1
-    tag3, // is 2
-    tags_count, // is 3
+  enum TagWithCounter {
+    Tag1, // is 0
+    Tag2, // is 1
+    Tag3, // is 2
+    TagCount, // is 3
   };
 
-The check detects this usage pattern heuristically and does not include
+This usage pattern is detected heuristically and the check does not include
 the counter enum constant in the final tag count, since the counter is not
-meant to indicate the valid variant member.
+meant to indicate the valid union data member.
 
-Options
--------
+When the check finds multiple possible counting enums, then it does not change the enum count.
 
-.. option:: EnumCounterHeuristicIsEnabled
+This heuristic can be disabled entirely (:option:`CountingEnumHeuristicIsEnabled`) or
+configured to follow your naming convention (:option:`CountingEnumPrefixes/Suffixes`).
+String matching is done case insensitively.
 
-    This option enables or disables the counting enum heuristic.
-    To find possible counting enum constants this option uses the value of
-    the string :option:`EnumCounterSuffix`.
+Options
+=======
 
-    This option is enabled by default.
+.. option:: CountingEnumHeuristicIsEnabled
 
-Example of :option:`EnumCounterHeuristicIsEnabled`:
+This option enables or disables the counting enum heuristic.
+To find possible counting enum constants this option uses the prefixes
+and suffixes specified 
+the string :option:`CountingEnumSuffixes`.
 
-When :option:`EnumCounterHeuristicIsEnabled` is false:
+This option is enabled by default.
+
+When :option:`CountingEnumHeuristicIsEnabled` is false:
 
 .. code-block:: c++
 
-  enum tags_with_counter {
-    tag1,
-    tag2,
-    tag3,
-    tags_count,
+  enum TagWithCounter {
+    Tag1,
+    Tag2,
+    Tag3,
+    TagCount,
   };
 
-  struct taggedUnion {
-    tags_with_counter tag;
-    union data {
-      int a;
-      int b;
-      char *str;
-      float f;
+  struct TaggedUnion {
+    TagWithCounter Kind;
+    union Data {
+      int A;
+      long B;
+      char *Str;
+      float F;
     };
   };
  
-When :option:`EnumCounterHeuristicIsEnabled` is true:
+When :option:`CountingEnumHeuristicIsEnabled` is true:
 
 .. code-block:: c++
 
-  enum tags_with_counter {
-    tag1,
-    tag2,
-    tag3,
-    tags_count,
+  enum TagWithCounter {
+    Tag1,
+    Tag2,
+    Tag3,
+    TagCount,
   };
 
-  struct taggedUnion { // warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
-    tags_with_counter tag;
-    union data {
-      int a;
-      int b;
-      char *str;
-      float f;
+  struct TaggedUnion { // warning: Tagged union has more data members (4) than tags (3)
+    TagWithCounter Kind;
+    union Data {
+      int A;
+      long B;
+      char *Str;
+      float F;
     };
   };
 
-.. option:: EnumCounterSuffix
-
-    When defined, the check will use the given string to search for counting
-    enum constants. This option does not alter the check's behavior when
-    :option:`EnumCounterHeuristicIsEnabled` is disabled.
+.. option:: CountingEnumPrefixes/Suffixes
 
-    The default value is "count".
+When defined, the check will use the list of the semicolon separated strings
+in CountingEnumPrefixes or CountingEnumSuffixes for the identification of possible counting enum constants.
+These options do not alter the check's behavior when :option:`CountingEnumHeuristicIsEnabled` is set to false.
 
-Example of :option:`EnumCounterSuffix`:
+The default value for CountingEnumSuffixes is "count" and for CountingEnumPrefixes is "" (empty string).
 
-When :option:`EnumCounterHeuristicIsEnabled` is true and
-:option:`EnumCounterSuffix` is "size":
+When :option:`CountingEnumHeuristicIsEnabled` is true and CountingEnumSuffixes is "count;size":
 
 .. code-block:: c++
 
-  enum tags_with_counter_count {
-    tag1,
-    tag2,
-    tag3,
-    tags_count,
+  enum TagWithCounterCount {
+    Tag1,
+    Tag2,
+    Tag3,
+    TagCount,
   };
 
-  enum tags_with_counter_size {
-    tag4,
-    tag5,
-    tag6,
-    tags_size,
+  struct TaggedUnionCount { // warning: Tagged union has more data members (4) than tags (3)
+    TagWithCounterCount Kind;
+    union Data {
+      int A;
+      long B;
+      char *Str;
+      float F;
+    };
   };
 
-  struct taggedUnion1 {
-    tags_with_counter_count tag;
-    union data {
-      int a;
-      int b;
-      char *str;
-      float f;
-    };
+  enum TagWithCounterSize {
+    Tag11,
+    Tag22,
+    Tag33,
+    TagSize,
   };
 
-  struct taggedUnion2 { // warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
-    tags_with_counter_size tag;
-    union data {
-      int a;
-      int b;
-      char *str;
-      float f;
+  struct TaggedUnionSize { // warning: Tagged union has more data members (4) than tags (3)
+    TagWithCounterSize Kind;
+    union Data {
+      int A;
+      long B;
+      char *Str;
+      float F;
     };
   };
- 
-When :option:`EnumCounterSuffix` is true:
+
+When :option:`CountingEnumHeuristicIsEnabled` is true and CountingEnumPrefixes is "maxsize;last_"
 
 .. code-block:: c++
 
-  enum tags_with_counter {
-    tag1,
-    tag2,
-    tag3,
-    tags_count,
+  enum TagWithCounter {
+    Tag1,
+    Tag2,
+    Tag3,
+    TagCount,
   };
 
-  struct taggedUnion { // warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
-    tags_with_counter tag;
-    union data {
-      int a;
-      int b;
-      char *str;
-      float f;
+  struct TaggedUnion { // warning: Tagged union has more data members (4) than tags (3)
+    TagWithCounter tag;
+    union Data {
+      int I;
+      short S;
+      char *C;
+      float F;
     };
   };
 
 .. option:: StrictMode
 
-    When enabled, the check will also give a warning, when the number of tags
-    is greater than the number of union data members.
-
-    This option is not enabled by default.
+When enabled, the check will also give a warning, when the number of tags
+is greater than the number of union data members.
 
-Example of :option:`StrictMode`:
+This option is disabled by default.
 
 When :option:`StrictMode` is false:
 
 .. code-block:: c++
 
-    struct taggedUnion {
+    struct TaggedUnion {
       enum {
-        tags1,
-        tags2,
-        tags3,
-      } tags;
+        Tag1,
+        Tag2,
+        Tag3,
+      } Tags;
       union {
-        int i;
-        float f;
+        int I;
+        float F;
       };
     };
 
@@ -202,15 +203,15 @@ When :option:`StrictMode` is true:
 
 .. code-block:: c++
 
-    struct taggedunion1 { // warning: Tagged union has fewer data members (2) than tags (3)! [bugprone-tagged-union-member-count]
+    struct TaggedUnion { // warning: Tagged union has fewer data members (2) than tags (3)
       enum {
-        tags1,
-        tags2,
-        tags3,
-      } tags;
+        Tag1,
+        Tag2,
+        Tag3,
+      } Tags;
       union {
-        int i;
-        float f;
+        int I;
+        float F;
       };
     };
 
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-heuristic-is-disabled.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-heuristic-is-disabled.cpp
new file mode 100644
index 0000000000000..f02aa94959e21
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-heuristic-is-disabled.cpp
@@ -0,0 +1,68 @@
+// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
+// RUN:   -config='{CheckOptions: { \
+// RUN:       bugprone-tagged-union-member-count.StrictModeIsEnabled: 1, \
+// RUN:       bugprone-tagged-union-member-count.CountingEnumHeuristicIsEnabled: 0, \
+// RUN:       bugprone-tagged-union-member-count.CountingEnumSuffixes: "count", \
+// RUN:       bugprone-tagged-union-member-count.CountingEnumPrefixes: "last", \
+// RUN:   }}' --
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has fewer data members (3) than tags (4)
+struct IncorrectBecauseHeuristicIsDisabledPrefixCase {
+  enum {
+    tags11,
+    tags22,
+    tags33,
+    lasttag,
+  } Tags;
+  union {
+    char A;
+    short B;
+    int C;
+  } Data;
+};
+
+struct CorrectBecauseHeuristicIsDisabledPrefixCase { // No warnings expected
+  enum {
+    tags1,
+    tags2,
+    tags3,
+    lasttags,
+  } Tags;
+  union {
+    char A;
+    short B;
+    int C;
+    long D;
+  } Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has fewer data members (3) than tags (4)
+struct IncorrectBecauseHeuristicIsDisabledSuffixCase {
+  enum {
+    tags11,
+    tags22,
+    tags33,
+    tags_count,
+  } Tags;
+  union {
+    char A;
+    short B;
+    int C;
+  } Data;
+};
+
+struct CorrectBecauseHeuristicIsDisabledSuffixCase { // No warnings expected
+  enum {
+    tags1,
+    tags2,
+    tags3,
+    tags_count,
+  } Tags;
+  union {
+    char A;
+    short B;
+    int C;
+    long D;
+  } Data;
+};
+
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-heuristic-is-enabled.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-heuristic-is-enabled.cpp
new file mode 100644
index 0000000000000..672787869b53a
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-heuristic-is-enabled.cpp
@@ -0,0 +1,63 @@
+// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
+// RUN:   -config='{CheckOptions: { \
+// RUN:     bugprone-tagged-union-member-count.StrictModeIsEnabled: 0, \
+// RUN:     bugprone-tagged-union-member-count.CountingEnumHeuristicIsEnabled: 1, \
+// RUN:     bugprone-tagged-union-member-count.CountingEnumSuffixes: "count", \
+// RUN:     bugprone-tagged-union-member-count.CountingEnumPrefixes: "last", \
+// RUN:  }}' --
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (3) than tags (2)
+struct IncorrectBecauseHeuristicIsEnabledPrefixCase {
+  enum {
+    tags1,
+    tags2,
+    lasttag,
+  } Tags;
+  union {
+    char A;
+    short B;
+    int C;
+  } Data;
+};
+
+struct CorrectBecauseHeuristicIsEnabledPrefixCase { // No warnings expected
+  enum {
+    tags1,
+    tags2,
+    tags3,
+    lasttag,
+  } Tags;
+  union {
+    int A;
+    int B;
+    int C;
+  } Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (3) than tags (2)
+struct IncorrectBecauseHeuristicIsEnabledSuffixCase {
+  enum {
+    tags1,
+    tags2,
+    tags_count,
+  } Tags;
+  union {
+    char A;
+    short B;
+    int C;
+  } Data;
+};
+
+struct CorrectBecauseHeuristicIsEnabledSuffixCase { // No warnings expected
+  enum {
+    tags1,
+    tags2,
+    tags3,
+    tags_count,
+  } Tags;
+  union {
+    int A;
+    int B;
+    int C;
+  } Data;
+};
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-prefixes-and-suffixes.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-prefixes-and-suffixes.cpp
new file mode 100644
index 0000000000000..af655e4f4c848
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-prefixes-and-suffixes.cpp
@@ -0,0 +1,53 @@
+// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
+// RUN:   -config='{CheckOptions: { \
+// RUN:     bugprone-tagged-union-member-count.StrictModeIsEnabled: 0, \
+// RUN:     bugprone-tagged-union-member-count.CountingEnumHeuristicIsEnabled: 1, \
+// RUN:     bugprone-tagged-union-member-count.CountingEnumSuffixes: "count", \
+// RUN:     bugprone-tagged-union-member-count.CountingEnumPrefixes: "last", \
+// RUN:  }}' --
+
+union Union3 {
+  short *Shorts;
+  int *Ints;
+  float *Floats;
+};
+
+union Union4 {
+  short *Shorts;
+  double *Doubles;
+  int *Ints;
+  float *Floats;
+};
+
+// No warning expected, because Kind has multiple counting enum candidates,
+// therefore the enum count is left unchanged.
+struct TaggedUnionPrefixAndSuffixMatch {
+  enum {
+    tags1,
+    tags2,
+    tagscount,
+    lasttags
+  } Kind;
+  Union4 Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (3) than tags (2)
+struct TaggedUnionOnlyPrefixMatch {
+  enum { 
+    prefixtag1,
+    prefixtag2,
+    lastprefixtag
+  } Kind;
+  Union3 Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (3) than tags (2)
+struct TaggedUnionOnlySuffixMatch {
+  enum {
+    suffixtag1,
+    suffixtag2,
+    suffixtagcount
+  } Kind;
+  Union3 Data;
+};
+
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-prefixes.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-prefixes.cpp
new file mode 100644
index 0000000000000..f61a48f111a30
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-prefixes.cpp
@@ -0,0 +1,36 @@
+// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
+// RUN:   -config='{CheckOptions: { \
+// RUN:     bugprone-tagged-union-member-count.StrictModeIsEnabled: 0, \
+// RUN:     bugprone-tagged-union-member-count.CountingEnumHeuristicIsEnabled: 1, \
+// RUN:     bugprone-tagged-union-member-count.CountingEnumPrefixes: "maxsize;last", \
+// RUN:  }}' --
+
+union Union4 {
+  short *Shorts;
+  double *Doubles;
+  int *Ints;
+  float *Floats;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)
+struct TaggedUnionWithMaxsizeAsCounterPrefix {
+  enum {
+    twc1,
+    twc2,
+    twc3,
+    maxsizetwc,  
+  } Kind;
+  Union4 Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)
+struct TaggedUnionWithLastAsCounterPrefix { 
+  enum {
+    twc11,
+    twc22,
+    twc33,
+    lasttwc,
+  } Kind;
+  Union4 Data;
+};
+
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-suffixes.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-suffixes.cpp
new file mode 100644
index 0000000000000..4815a7361b819
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-counting-enum-suffixes.cpp
@@ -0,0 +1,36 @@
+// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
+// RUN:   -config='{CheckOptions: { \
+// RUN:     bugprone-tagged-union-member-count.StrictModeIsEnabled: 0, \
+// RUN:     bugprone-tagged-union-member-count.CountingEnumHeuristicIsEnabled: 1, \
+// RUN:     bugprone-tagged-union-member-count.CountingEnumSuffixes: "count;size", \
+// RUN:  }}' --
+
+typedef union Union4 {
+  short *Shorts;
+  double *Doubles;
+  int *Ints;
+  float *Floats;
+} union4;
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)
+struct TaggedUnionWithCounterCountSuffix {
+  enum {
+    twc1,
+    twc2,
+    twc3,
+    twc_count,
+  } Kind;
+  union Union4 Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)
+struct TaggedUnionWithCounterSizeSuffix { 
+  enum {
+    twc11,
+    twc22,
+    twc33,
+    twc_size,
+  } Kind;
+  union Union4 Data;
+};
+
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-enumcounterheuristicisenabled.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-enumcounterheuristicisenabled.cpp
deleted file mode 100644
index 69b44c10b7c31..0000000000000
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-enumcounterheuristicisenabled.cpp
+++ /dev/null
@@ -1,38 +0,0 @@
-// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
-// RUN:   -config='{CheckOptions: { \
-// RUN:     bugprone-tagged-union-member-count.StrictMode: 1, \
-// RUN:     bugprone-tagged-union-member-count.EnumCounterHeuristicIsEnabled: 0, \
-// RUN:     bugprone-tagged-union-member-count.EnumCounterSuffix: "count", \
-// RUN:  }}' --
-
-// Without the heuristic the tags and the data members match
-struct taggedUnion1 { // No warnings expected
-	enum {
-		tags1,
-		tags2,
-		tags3,
-		tags_count,
-	} tags;
-	union {
-		int a;
-		int b;
-		int c;
-		int d;
-	} data;
-};
-
-// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has fewer data members (3) than tags (4)! [bugprone-tagged-union-member-count]
-struct taggedUnion2 {
-	enum {
-		tags11,
-		tags22,
-		tags33,
-		tags_count,
-	} tags;
-	union {
-		int a;
-		int b;
-		int c;
-	} data;
-};
-
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-enumcountersuffix.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-enumcountersuffix.cpp
deleted file mode 100644
index cddbb003a01f9..0000000000000
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-enumcountersuffix.cpp
+++ /dev/null
@@ -1,47 +0,0 @@
-// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
-// RUN:   -config='{CheckOptions: { \
-// RUN:     bugprone-tagged-union-member-count.StrictMode: 0, \
-// RUN:     bugprone-tagged-union-member-count.EnumCounterHeuristicIsEnabled: 1, \
-// RUN:     bugprone-tagged-union-member-count.EnumCounterSuffix: "size", \
-// RUN:  }}' --
-
-typedef union union3 {
-	short *shorts;
-	int *ints;
-	float *floats;
-} union3;
-
-typedef union union4 {
-	short *shorts;
-	double *doubles;
-	int *ints;
-	float *floats;
-} union4;
-
-enum tag_with_counter_count {
-	twc1,
-	twc2,
-	twc3,
-	twc_count,
-};
-
-enum tag_with_counter_size {
-	twc11,
-	twc22,
-	twc33,
-	twc_size,
-};
-
-// The heuristic is configured with the "size" suffix so
-// twc_count will not be considered a counting enum constant
-struct taggedUnion1 { // No warning expected
-	enum tag_with_counter_count tag;
-	union union4 data;
-};
-
-// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
-struct taggedUnion2 { 
-	enum tag_with_counter_size tag;
-	union union4 data;
-};
-
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-strictmode-is-disabled.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-strictmode-is-disabled.cpp
new file mode 100644
index 0000000000000..5db5965c56880
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-strictmode-is-disabled.cpp
@@ -0,0 +1,28 @@
+// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
+// RUN:   -config='{CheckOptions: { \
+// RUN:     bugprone-tagged-union-member-count.StrictModeIsEnabled: 0, \
+// RUN:  }}' --
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (2) than tags (1)
+struct Incorrect {
+  enum {
+    tags1,
+  } Tags;
+  union {
+    char A;
+    short B;
+  } Data;
+};
+
+struct CorrectBecauseStrictModeIsDisabled { // No warnings expected
+  enum {
+    tags1,
+    tags2,
+    tags3,
+  } Tags;
+  union {
+    char A;
+    short B;
+  } Data;
+};
+
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-strictmode-is-enabled.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-strictmode-is-enabled.cpp
new file mode 100644
index 0000000000000..eed1bc1bdc35c
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-strictmode-is-enabled.cpp
@@ -0,0 +1,31 @@
+// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
+// RUN:   -config='{CheckOptions: { \
+// RUN:     bugprone-tagged-union-member-count.StrictModeIsEnabled: 1, \
+// RUN:  }}' --
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has fewer data members (2) than tags (3)
+struct IncorrectBecauseStrictmodeIsEnabled {
+  enum {
+    tags1,
+    tags2,
+    tags3,
+  } Tags;
+  union {
+    char A;
+    short B;
+  } Data;
+};
+
+struct Correct { // No warnings expected
+  enum {
+    tags1,
+    tags2,
+    tags3,
+  } Tags;
+  union {
+    char A;
+    short B;
+    int C;
+  } Data;
+};
+
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-strictmode.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-strictmode.cpp
deleted file mode 100644
index 7d6c5f385c8e1..0000000000000
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count-strictmode.cpp
+++ /dev/null
@@ -1,150 +0,0 @@
-// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
-// RUN:   -config='{CheckOptions: { \
-// RUN:     bugprone-tagged-union-member-count.StrictMode: 1, \
-// RUN:     bugprone-tagged-union-member-count.EnumCounterHeuristicIsEnabled: 0, \
-// RUN:     bugprone-tagged-union-member-count.EnumCounterSuffix: "count", \
-// RUN:  }}' --
-
-typedef enum tags3 {
-	tags3_1,
-	tags3_2,
-	tags3_3,
-} tags3;
-
-typedef enum tags4 {
-	tags4_1,
-	tags4_2,
-	tags4_3,
-	tags4_4,
-} tags4;
-
-typedef enum tags5 {
-	tags5_1,
-	tags5_2,
-	tags5_3,
-	tags5_4,
-	tags5_5,
-} tags5;
-
-enum class classtags3 {
-	classtags3_1,
-	classtags3_2,
-	classtags3_3,
-};
-
-enum class classtags4 {
-	classtags4_1,
-	classtags4_2,
-	classtags4_3,
-	classtags4_4,
-};
-
-enum class classtags5 {
-	classtags5_1,
-	classtags5_2,
-	classtags5_3,
-	classtags5_4,
-	classtags5_5,
-};
-
-enum class typedtags3 : unsigned int {
-	typedtags3_1,
-	typedtags3_2,
-	typedtags3_3,
-};
-
-enum class typedtags4 : long {
-	typedtags4_1,
-	typedtags4_2,
-	typedtags4_3,
-	typedtags4_4,
-};
-
-enum class typedtags5 {
-	typedtags5_1,
-	typedtags5_2,
-	typedtags5_3,
-	typedtags5_4,
-	typedtags5_5,
-};
-
-typedef union union3 {
-	short *shorts;
-	int *ints;
-	float *floats;
-} union3;
-
-typedef union union4 {
-	short *shorts;
-	double *doubles;
-	int *ints;
-	float *floats;
-} union4;
-
-// Technically this means that every enum value is defined from 0-256 and therefore a warning is given.
-enum mycolor {
-	mycolor_black = 0x00,
-	mycolor_gray  = 0xcc,
-	mycolor_white = 0xff,
-};
-
-// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has fewer data members (3) than tags (256)! [bugprone-tagged-union-member-count]
-struct taggedunion9 { 
-	enum mycolor tag;
-	union {
-		int a;
-		float b;
-		struct {
-			double re;
-			double im;
-		} complex;
-	} data;
-};
-
-// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has fewer data members (3) than tags (4)! [bugprone-tagged-union-member-count]
-struct withanonymous { 
-	enum tags4 tag;
-	union {
-		int a;
-		float b;
-		char *c;
-	};
-}; 
-
-// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has fewer data members (3) than tags (4)! [bugprone-tagged-union-member-count]
-struct withTypedef1 { 
-	tags4 tag;
-	union3 data;
-}; 
-
-// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has fewer data members (3) than tags (4)! [bugprone-tagged-union-member-count]
-struct withEnumClass1 {
-	enum classtags4 tag;
-	union3 data;
-};
-
-// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has fewer data members (3) than tags (4)! [bugprone-tagged-union-member-count]
-struct withTypedEnum2 {
-	typedtags4 tag;
-	union3 data;
-};
-
-// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has fewer data members (2) than tags (3)! [bugprone-tagged-union-member-count]
-struct anonymous2 {
-	tags3 tag;
-	union {
-		int a;
-		float f;
-	};
-};
-
-// CHECK-MESSAGES: :[[@LINE+2]]:8: warning: Tagged union has fewer data members (3) than tags (5)! [bugprone-tagged-union-member-count]
-template <typename Union, typename Tag>
-struct templated {
-	Tag tag;
-	Union data;
-};
-
-templated<union3, tags3> t1; // No warning expected
-templated<union3, tags5> t2;
-
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.c b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.c
new file mode 100644
index 0000000000000..7e19e9753e660
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.c
@@ -0,0 +1,148 @@
+// RUN: %check_clang_tidy -std=c89-or-later %s bugprone-tagged-union-member-count %t
+
+typedef enum Tags3 {
+  tags3_1,
+  tags3_2,
+  tags3_3,
+} tags3;
+
+typedef enum Tags4 {
+  tags4_1,
+  tags4_2,
+  tags4_3,
+  tags4_4,
+} tags4;
+
+typedef union union3 {
+  short *Shorts;
+  int *Ints;
+  float *Floats;
+} union3;
+
+typedef union union4 {
+  short *Shorts;
+  double *Doubles;
+  int *Ints;
+  float *Floats;
+} union4;
+
+// It is not obvious which enum is the tag for the union.
+struct maybeTaggedUnion1 { // No warnings expected.
+  enum Tags3 TagA;
+  enum Tags4 TagB;
+  union union4 Data;
+};
+
+// It is not obvious which union does the tag belong to.
+struct maybeTaggedUnion2 { // No warnings expected.
+  enum Tags3 Tag;
+  union union3 DataB;
+  union union3 DataA;
+};
+
+// It is not obvious which union does the tag belong to.
+struct maybeTaggedUnion3 { // No warnings expected.
+  enum Tags3 Tag;
+  union {
+    int I1;
+    int I2;
+    int I3;
+  };
+  union {
+    float F1;
+    float F2;
+    float F3;
+  };
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)
+struct TaggedUnionStructWithPredefinedTagAndPredefinedUnion {
+  enum Tags3 Tag;
+  union union4 Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)
+struct TaggedUnionStructWithPredefinedTagAndInlineUnion {
+  enum Tags3 Tag;
+  union {
+    int *Ints;
+    char Characters[13];
+    struct {
+      double Re;
+      double Im;
+    } Complex;
+    long L;
+  } Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)
+struct TaggedUnionStructWithInlineTagAndPredefinedUnion { 
+  enum {
+    TaggedUnion7tag1,
+    TaggedUnion7tag2,
+    TaggedUnion7tag3,
+  } Tag;
+  union union4 Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)
+struct TaggedUnionStructWithInlineTagAndInlineUnion { 
+  enum {
+    TaggedUnion8tag1,
+    TaggedUnion8tag2,
+    TaggedUnion8tag3,
+  } Tag;
+  union {
+    int *Ints;
+    char Characters[13];
+    struct {
+      double Re;
+      double Im;
+    } Complex;
+    long L;
+  } Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)
+struct TaggedUnionStructNesting { 
+  enum Tags3 Tag;
+  union {
+    float F;
+    int I;
+    long L;
+    // CHECK-MESSAGES: :[[@LINE+1]]:12: warning: Tagged union has more data members (4) than tags (3)
+    struct innerdecl { 
+      enum Tags3 Tag;
+      union union4 Data;
+    } Inner; 
+  } Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)
+struct CountingEnumCaseInsensitivityTest1 { 
+  enum {
+    node_type_loop,
+    node_type_branch,
+    node_type_function,
+    node_type_count,
+  } Kind;
+  union union4 Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)
+struct CountingEnumCaseInsensitivityTest2 { 
+  enum {
+    NODE_TYPE_LOOP,
+    NODE_TYPE_BRANCH,
+    NODE_TYPE_FUNCTION,
+    NODE_TYPE_COUNT,
+  } Kind;
+  union union4 Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)
+struct TaggedUnionStructWithTypedefedTagAndTypedefedUnion { 
+  tags3 Tag;
+  union4 Data;
+}; 
+
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.cpp
index 05ae457f57410..286e69070cc15 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.cpp
@@ -1,243 +1,263 @@
 // RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t
-// Test cases for the default configuration
+// Test check with C++ features
 
-typedef enum tags3 {
-	tags3_1,
-	tags3_2,
-	tags3_3,
+typedef enum Tags3 {
+  tags3_1,
+  tags3_2,
+  tags3_3,
 } tags3;
 
-typedef enum tags4 {
-	tags4_1,
-	tags4_2,
-	tags4_3,
-	tags4_4,
+typedef enum Tags4 {
+  tags4_1,
+  tags4_2,
+  tags4_3,
+  tags4_4,
 } tags4;
 
-typedef enum tags5 {
-	tags5_1,
-	tags5_2,
-	tags5_3,
-	tags5_4,
-	tags5_5,
-} tags5;
-
-enum class classtags3 {
-	classtags3_1,
-	classtags3_2,
-	classtags3_3,
+enum class Classtags3 {
+  classtags3_1,
+  classtags3_2,
+  classtags3_3,
 };
 
-enum class classtags4 {
-	classtags4_1,
-	classtags4_2,
-	classtags4_3,
-	classtags4_4,
+enum class Typedtags3 : unsigned int {
+  typedtags3_1,
+  typedtags3_2,
+  typedtags3_3,
 };
 
-enum class classtags5 {
-	classtags5_1,
-	classtags5_2,
-	classtags5_3,
-	classtags5_4,
-	classtags5_5,
+typedef union Union3 {
+  short *Shorts;
+  int *Ints;
+  float *Floats;
+} union3;
+
+typedef union Union4 {
+  short *Shorts;
+  double *Doubles;
+  int *Ints;
+  float *Floats;
+} union4;
+
+// It is not obvious which enum is the tag for the union.
+class MaybeTaggedUnion1 { // No warnings expected.
+  enum Tags3 TagA;
+  enum Tags4 TagB;
+  union Union4 Data;
 };
 
-enum class typedtags3 : unsigned int {
-	typedtags3_1,
-	typedtags3_2,
-	typedtags3_3,
+// It is not obvious which union does the tag belong to.
+class MaybeTaggedUnion2 { // No warnings expected.
+  enum Tags3 Tag;
+  union Union3 DataB;
+  union Union3 DataA;
 };
 
-enum class typedtags4 : long {
-	typedtags4_1,
-	typedtags4_2,
-	typedtags4_3,
-	typedtags4_4,
+// It is not obvious which union does the tag belong to.
+class MaybeTaggedUnion3 { // No warnings expected.
+  enum Tags3 Tag;
+  union {
+    int I1;
+    int I2;
+    int I3;
+  };
+  union {
+    float F1;
+    float F2;
+    float F3;
+  };
 };
 
-enum class typedtags5 {
-	typedtags5_1,
-	typedtags5_2,
-	typedtags5_3,
-	typedtags5_4,
-	typedtags5_5,
+// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: Tagged union has more data members (4) than tags (3)
+class TaggedUnionClassPredefinedTagAndPredefinedUnion {
+  enum Tags3 Tag;
+    union Union4 Data;
 };
 
-typedef union union3 {
-	short *shorts;
-	int *ints;
-	float *floats;
-} union3;
+// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: Tagged union has more data members (4) than tags (3)
+class TaggedUnionClassPredefinedTagAndInlineUnion {
+  enum Tags3 Tag;
+    union {
+    int *Ints;
+    char Characters[13];
+    class {
+      double Re;
+      double Im;
+    } Complex;
+    long L;
+    } Data;
+};
 
-typedef union union4 {
-	short *shorts;
-	double *doubles;
-	int *ints;
-	float *floats;
-} union4;
+// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: Tagged union has more data members (4) than tags (3)
+class TaggedUnionClassInlineTagAndPredefinedUnion { 
+  enum {
+    tag1,
+    tag2,
+    tag3,
+  } Tag;
+  union Union4 Data;
+};
 
-// It is not obvious which enum is the tag for the union.
-struct taggedunion2 { // No warnings expected.
-	enum tags3 tagA;
-	enum tags4 tagB;
-	union union4 data;
+// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: Tagged union has more data members (4) than tags (3)
+class TaggedUnionClassInlineTagAndInlineUnion { 
+  enum {
+    tag1,
+    tag2,
+    tag3,
+  } Tag;
+  union {
+    int *Ints;
+    char Characters[13];
+    class {
+      double Re;
+      double Im;
+    } Complex;
+    long L;
+  } Data;
 };
 
-// It is not obvious which union does the tag belong to.
-struct taggedunion4 { // No warnings expected.
-	enum tags3 tag;
-	union union3 dataB;
-	union union3 dataA;
+// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: Tagged union has more data members (4) than tags (3)
+class TaggedUnionClassWithNestedTaggedUnionClass { 
+  enum Tags3 Tag;
+  union {
+    float F;
+    int I;
+    long L;
+    // CHECK-MESSAGES: :[[@LINE+1]]:11: warning: Tagged union has more data members (4) than tags (3)
+    class Innerdecl { 
+      enum Tags3 Tag;
+      union Union4 Data;
+    } Inner; 
+  } Data;
 };
 
-// It is not obvious which union does the tag belong to.
-struct taggedunion6 { // No warnings expected.
-	enum tags3 tag;
-	union {
-		int i1;
-		int i2;
-		int i3;
-	};
-	union {
-		float f1;
-		float f2;
-		float f3;
-	};
-};
-
-// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
-struct taggedunion1 {
-	enum tags3 tag;
-    union union4 data;
-};
-
-// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
-struct taggedunion5 {
-	enum tags3 tag;
-    union {
-		int *ints;
-		char characters[13];
-		struct {
-			double re;
-			double im;
-		} complex;
-		long l;
-    } data;
-};
-
-// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
-struct taggedunion7 { 
-	enum {
-		tag1,
-		tag2,
-		tag3,
-	} tag;
-	union union4 data;
-};
-
-// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
-struct taggedunion8 { 
-	enum {
-		tag1,
-		tag2,
-		tag3,
-	} tag;
-	union {
-		int *ints;
-		char characters[13];
-		struct {
-			double re;
-			double im;
-		} complex;
-		long l;
-	} data;
-};
-
-// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
-struct nested4 { 
-	enum tags3 tag;
-	union {
-		float f;
-		int i;
-		long l;
-		// CHECK-MESSAGES: :[[@LINE+1]]:10: warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
-		struct innerdecl { 
-			enum tags3 tag;
-			union union4 data;
-		} inner; 
-	} data;
-};
-
-enum tag_with_counter_lowercase {
-	node_type_loop,
-	node_type_branch,
-	node_type_function,
-	node_type_count,
-};
-
-enum tag_with_counter_uppercase {
-	NODE_TYPE_LOOP,
-	NODE_TYPE_BRANCH,
-	NODE_TYPE_FUNCTION,
-	NODE_TYPE_COUNT,
-};
-
-// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
-struct taggedunion10 { 
-	enum tag_with_counter_lowercase tag;
-	union union4 data;
-};
-
-// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
-struct taggedunion11 { 
-	enum tag_with_counter_uppercase tag;
-	union union4 data;
-};
-
-// Without the counter enum constant the number of tags
-// and the number data members are equal.
-struct taggedunion12 { // No warnings expected.
-	enum tag_with_counter_uppercase tag;
-	union union3 data;
-};
-
-// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
-struct withTypedef2 { 
-	tags3 tag;
-	union4 data;
+// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: Tagged union has more data members (4) than tags (3)
+class TaggedUnionClassWithTypedefedTag { 
+  tags3 Tag;
+  union4 Data;
+}; 
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)
+struct TaggedUnionStructWithEnumClass { 
+  enum Classtags3 Tag;
+  union4 Data;
 }; 
 
-// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
-struct withEnumClass2 { 
-	enum classtags3 tag;
-	union4 data;
+// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: Tagged union has more data members (4) than tags (3)
+class TaggedUnionClasswithEnumClass { 
+  enum Classtags3 Tag;
+  union4 Data;
 }; 
 
-// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
-struct withTypedEnum1 {
-	typedtags3 tag;
-	union4 data;
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)
+struct TaggedUnionStructWithTypedEnum {
+  Typedtags3 Tag;
+  union4 Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: Tagged union has more data members (4) than tags (3)
+class TaggedUnionClassWithTypedEnum {
+  Typedtags3 Tag;
+  union4 Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)
+struct AnonymousTaggedUnionStruct {
+  tags3 Tag;
+  union {
+    char A;
+    short B;
+    int C;
+    long D;
+  };
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: Tagged union has more data members (4) than tags (3)
+class TaggedUnionClassWithAnonymousUnion {
+  tags3 Tag;
+  union {
+    char A;
+    short B;
+    int C;
+    long D;
+  };
+};
+
+namespace testnamespace {
+
+enum Tags3 {
+  tags3_1,
+  tags3_2,
+  tags3_3,
+};
+
+union Union4 {
+  short *Shorts;
+  double *Doubles;
+  int *Ints;
+  float *Floats;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)
+struct TaggedUnionStructInNamespace {
+  Tags3 Tags;
+  Union4 Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: Tagged union has more data members (4) than tags (3)
+class TaggedUnionClassInNamespace {
+  Tags3 Tags;
+  Union4 Data;
+};
+
+} // namespace testnamespace
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)
+struct TaggedUnionStructWithNamespacedTagAndUnion {
+  testnamespace::Tags3 Tags;
+  testnamespace::Union4 Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: Tagged union has more data members (4) than tags (3)
+class TaggedUnionClassWithNamespacedTagAndUnion {
+  testnamespace::Tags3 Tags;
+  testnamespace::Union4 Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+2]]:8: warning: Tagged union has more data members (4) than tags (3)
+template <typename Union, typename Tag>
+struct TemplatedStructWithNamespacedTagAndUnion {
+  Tag Kind;
+  Union Data;
 };
 
-// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
-struct anonymous1 {
-	tags3 tag;
-	union {
-		int a;
-		int b;
-		int c;
-		int d;
-	};
+TemplatedStructWithNamespacedTagAndUnion<testnamespace::Union4, testnamespace::Tags3> TemplatedStruct3;
+
+// CHECK-MESSAGES: :[[@LINE+2]]:7: warning: Tagged union has more data members (4) than tags (3)
+template <typename Union, typename Tag>
+class TemplatedClassWithNamespacedTagAndUnion {
+  Tag Kind;
+  Union Data;
 };
 
-// CHECK-MESSAGES: :[[@LINE+2]]:8: warning: Tagged union has more data members (4) than tags (3)! [bugprone-tagged-union-member-count]
+TemplatedClassWithNamespacedTagAndUnion<testnamespace::Union4, testnamespace::Tags3> TemplatedClass3;
+
+// CHECK-MESSAGES: :[[@LINE+2]]:8: warning: Tagged union has more data members (4) than tags (3)
 template <typename Union, typename Tag>
-struct templated {
-	Tag tag;
-	Union data;
+struct TemplatedStruct {
+  Tag Kind;
+  Union Data;
 };
 
-templated<union3, tags3> t1; // No warning expected
-templated<union4, tags3> t3;
+TemplatedStruct<union3, tags3> TemplatedStruct1; // No warning expected
+TemplatedStruct<union4, tags3> TemplatedStruct2;
+
+// CHECK-MESSAGES: :[[@LINE+2]]:7: warning: Tagged union has more data members (4) than tags (3)
+template <typename Union, typename Tag>
+class TemplatedClass {
+  Tag Kind;
+  Union Data;
+};
 
+TemplatedClass<union3, tags3> TemplatedClass1; // No warning expected
+TemplatedClass<union4, tags3> TemplatedClass2;
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.m b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.m
new file mode 100644
index 0000000000000..a84e9b2db30ea
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.m
@@ -0,0 +1,147 @@
+// RUN: %check_clang_tidy %s bugprone-tagged-union-member-count %t
+
+typedef enum Tags3 {
+  tags3_1,
+  tags3_2,
+  tags3_3,
+} tags3;
+
+typedef enum Tags4 {
+  tags4_1,
+  tags4_2,
+  tags4_3,
+  tags4_4,
+} tags4;
+
+typedef union union3 {
+  short *Shorts;
+  int *Ints;
+  float *Floats;
+} union3;
+
+typedef union union4 {
+  short *Shorts;
+  double *Doubles;
+  int *Ints;
+  float *Floats;
+} union4;
+
+// It is not obvious which enum is the tag for the union.
+struct maybeTaggedUnion1 { // No warnings expected.
+  enum Tags3 TagA;
+  enum Tags4 TagB;
+  union union4 Data;
+};
+
+// It is not obvious which union does the tag belong to.
+struct maybeTaggedUnion2 { // No warnings expected.
+  enum Tags3 Tag;
+  union union3 DataB;
+  union union3 DataA;
+};
+
+// It is not obvious which union does the tag belong to.
+struct maybeTaggedUnion3 { // No warnings expected.
+  enum Tags3 Tag;
+  union {
+    int I1;
+    int I2;
+    int I3;
+  };
+  union {
+    float F1;
+    float F2;
+    float F3;
+  };
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)
+struct TaggedUnionStructWithPredefinedTagAndPredefinedUnion {
+  enum Tags3 Tag;
+    union union4 Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)
+struct TaggedUnionStructWithPredefinedTagAndInlineUnion {
+  enum Tags3 Tag;
+    union {
+    int *Ints;
+    char Characters[13];
+    struct {
+      double Re;
+      double Im;
+    } Complex;
+    long L;
+    } Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)
+struct TaggedUnionStructWithInlineTagAndPredefinedUnion { 
+  enum {
+    TaggedUnion7tag1,
+    TaggedUnion7tag2,
+    TaggedUnion7tag3,
+  } Tag;
+  union union4 Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)
+struct TaggedUnionStructWithInlineTagAndInlineUnion { 
+  enum {
+    TaggedUnion8tag1,
+    TaggedUnion8tag2,
+    TaggedUnion8tag3,
+  } Tag;
+  union {
+    int *Ints;
+    char Characters[13];
+    struct {
+      double Re;
+      double Im;
+    } Complex;
+    long L;
+  } Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)
+struct TaggedUnionStructNesting { 
+  enum Tags3 Tag;
+  union {
+    float F;
+    int I;
+    long L;
+    // CHECK-MESSAGES: :[[@LINE+1]]:12: warning: Tagged union has more data members (4) than tags (3)
+    struct innerdecl { 
+      enum Tags3 Tag;
+      union union4 Data;
+    } Inner; 
+  } Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)
+struct CountingEnumCaseInsensitivityTest1 { 
+  enum {
+    node_type_loop,
+    node_type_branch,
+    node_type_function,
+    node_type_count,
+  } Kind;
+  union union4 Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)
+struct CountingEnumCaseInsensitivityTest2 { 
+  enum {
+    NODE_TYPE_LOOP,
+    NODE_TYPE_BRANCH,
+    NODE_TYPE_FUNCTION,
+    NODE_TYPE_COUNT,
+  } Kind;
+  union union4 Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)
+struct TaggedUnionStructWithTypedefedTagAndTypedefedUnion { 
+  tags3 Tag;
+  union4 Data;
+};
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.mm b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.mm
new file mode 100644
index 0000000000000..f8cdf92d78437
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/tagged-union-member-count.mm
@@ -0,0 +1,262 @@
+// RUN: %check_clang_tidy %s bugprone-tagged-union-member-count %t
+
+typedef enum Tags3 {
+  tags3_1,
+  tags3_2,
+  tags3_3,
+} tags3;
+
+typedef enum Tags4 {
+  tags4_1,
+  tags4_2,
+  tags4_3,
+  tags4_4,
+} tags4;
+
+enum class Classtags3 {
+  classtags3_1,
+  classtags3_2,
+  classtags3_3,
+};
+
+enum class Typedtags3 : unsigned int {
+  typedtags3_1,
+  typedtags3_2,
+  typedtags3_3,
+};
+
+typedef union Union3 {
+  short *Shorts;
+  int *Ints;
+  float *Floats;
+} union3;
+
+typedef union Union4 {
+  short *Shorts;
+  double *Doubles;
+  int *Ints;
+  float *Floats;
+} union4;
+
+// It is not obvious which enum is the tag for the union.
+class MaybeTaggedUnion1 { // No warnings expected.
+  enum Tags3 TagA;
+  enum Tags4 TagB;
+  union Union4 Data;
+};
+
+// It is not obvious which union does the tag belong to.
+class MaybeTaggedUnion2 { // No warnings expected.
+  enum Tags3 Tag;
+  union Union3 DataB;
+  union Union3 DataA;
+};
+
+// It is not obvious which union does the tag belong to.
+class MaybeTaggedUnion3 { // No warnings expected.
+  enum Tags3 Tag;
+  union {
+    int I1;
+    int I2;
+    int I3;
+  };
+  union {
+    float F1;
+    float F2;
+    float F3;
+  };
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: Tagged union has more data members (4) than tags (3)
+class TaggedUnionClassPredefinedTagAndPredefinedUnion {
+  enum Tags3 Tag;
+    union Union4 Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: Tagged union has more data members (4) than tags (3)
+class TaggedUnionClassPredefinedTagAndInlineUnion {
+  enum Tags3 Tag;
+    union {
+    int *Ints;
+    char Characters[13];
+    class {
+      double Re;
+      double Im;
+    } Complex;
+    long L;
+    } Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: Tagged union has more data members (4) than tags (3)
+class TaggedUnionClassInlineTagAndPredefinedUnion { 
+  enum {
+    tag1,
+    tag2,
+    tag3,
+  } Tag;
+  union Union4 Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: Tagged union has more data members (4) than tags (3)
+class TaggedUnionClassInlineTagAndInlineUnion { 
+  enum {
+    tag1,
+    tag2,
+    tag3,
+  } Tag;
+  union {
+    int *Ints;
+    char Characters[13];
+    class {
+      double Re;
+      double Im;
+    } Complex;
+    long L;
+  } Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: Tagged union has more data members (4) than tags (3)
+class TaggedUnionClassWithNestedTaggedUnionClass { 
+  enum Tags3 Tag;
+  union {
+    float F;
+    int I;
+    long L;
+    // CHECK-MESSAGES: :[[@LINE+1]]:11: warning: Tagged union has more data members (4) than tags (3)
+    class Innerdecl { 
+      enum Tags3 Tag;
+      union Union4 Data;
+    } Inner; 
+  } Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: Tagged union has more data members (4) than tags (3)
+class TaggedUnionClassWithTypedefedTag { 
+  tags3 Tag;
+  union4 Data;
+}; 
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)
+struct TaggedUnionStructWithEnumClass { 
+  enum Classtags3 Tag;
+  union4 Data;
+}; 
+
+// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: Tagged union has more data members (4) than tags (3)
+class TaggedUnionClasswithEnumClass { 
+  enum Classtags3 Tag;
+  union4 Data;
+}; 
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)
+struct TaggedUnionStructWithTypedEnum {
+  Typedtags3 Tag;
+  union4 Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: Tagged union has more data members (4) than tags (3)
+class TaggedUnionClassWithTypedEnum {
+  Typedtags3 Tag;
+  union4 Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)
+struct AnonymousTaggedUnionStruct {
+  tags3 Tag;
+  union {
+    char A;
+    short B;
+    int C;
+    long D;
+  };
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: Tagged union has more data members (4) than tags (3)
+class TaggedUnionClassWithAnonymousUnion {
+  tags3 Tag;
+  union {
+    char A;
+    short B;
+    int C;
+    long D;
+  };
+};
+
+namespace testnamespace {
+
+enum Tags3 {
+  tags3_1,
+  tags3_2,
+  tags3_3,
+};
+
+union Union4 {
+  short *Shorts;
+  double *Doubles;
+  int *Ints;
+  float *Floats;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)
+struct TaggedUnionStructInNamespace {
+  Tags3 Tags;
+  Union4 Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: Tagged union has more data members (4) than tags (3)
+class TaggedUnionClassInNamespace {
+  Tags3 Tags;
+  Union4 Data;
+};
+
+} // namespace testnamespace
+
+// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: Tagged union has more data members (4) than tags (3)
+struct TaggedUnionStructWithNamespacedTagAndUnion {
+  testnamespace::Tags3 Tags;
+  testnamespace::Union4 Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: Tagged union has more data members (4) than tags (3)
+class TaggedUnionClassWithNamespacedTagAndUnion {
+  testnamespace::Tags3 Tags;
+  testnamespace::Union4 Data;
+};
+
+// CHECK-MESSAGES: :[[@LINE+2]]:8: warning: Tagged union has more data members (4) than tags (3)
+template <typename Union, typename Tag>
+struct TemplatedStructWithNamespacedTagAndUnion {
+  Tag Kind;
+  Union Data;
+};
+
+TemplatedStructWithNamespacedTagAndUnion<testnamespace::Union4, testnamespace::Tags3> TemplatedStruct3;
+
+// CHECK-MESSAGES: :[[@LINE+2]]:7: warning: Tagged union has more data members (4) than tags (3)
+template <typename Union, typename Tag>
+class TemplatedClassWithNamespacedTagAndUnion {
+  Tag Kind;
+  Union Data;
+};
+
+TemplatedClassWithNamespacedTagAndUnion<testnamespace::Union4, testnamespace::Tags3> TemplatedClass3;
+
+// CHECK-MESSAGES: :[[@LINE+2]]:8: warning: Tagged union has more data members (4) than tags (3)
+template <typename Union, typename Tag>
+struct TemplatedStruct {
+  Tag Kind;
+  Union Data;
+};
+
+TemplatedStruct<union3, tags3> TemplatedStruct1; // No warning expected
+TemplatedStruct<union4, tags3> TemplatedStruct2;
+
+// CHECK-MESSAGES: :[[@LINE+2]]:7: warning: Tagged union has more data members (4) than tags (3)
+template <typename Union, typename Tag>
+class TemplatedClass {
+  Tag Kind;
+  Union Data;
+};
+
+TemplatedClass<union3, tags3> TemplatedClass1; // No warning expected
+TemplatedClass<union4, tags3> TemplatedClass2;



More information about the cfe-commits mailing list