[clang-tools-extra] 975d562 - [clang-tidy] Add new check readability-trailing-comma (#173669)
via cfe-commits
cfe-commits at lists.llvm.org
Sat Jan 31 11:43:49 PST 2026
Author: Baranov Victor
Date: 2026-01-31T19:43:45Z
New Revision: 975d56272e353d6a9772de53de4270c0617b01a2
URL: https://github.com/llvm/llvm-project/commit/975d56272e353d6a9772de53de4270c0617b01a2
DIFF: https://github.com/llvm/llvm-project/commit/975d56272e353d6a9772de53de4270c0617b01a2.diff
LOG: [clang-tidy] Add new check readability-trailing-comma (#173669)
clang-format has a couple of similar options:
https://clang.llvm.org/docs/ClangFormatStyleOptions.html#enumtrailingcomma
- add trailing commas for enum
https://clang.llvm.org/docs/ClangFormatStyleOptions.html#inserttrailingcommas
- add trailing commas for C++
but generally they are marked with such warning:
> Warning
>
> Setting this option to any value other than Leave could lead to
incorrect code formatting due to clang-format’s lack of complete
semantic information. As such, extra care should be taken to review code
changes made by this option.
clang-tidy on the other hand has all semantic information, thus can
(hopefully) provide 0 false-positives.
Note that we have already overlapping checks in clang-format/clang-tidy
like:
https://clang.llvm.org/docs/ClangFormatStyleOptions.html#insertbraces vs
https://clang.llvm.org/extra/clang-tidy/checks/readability/braces-around-statements.html
Added:
clang-tools-extra/clang-tidy/readability/TrailingCommaCheck.cpp
clang-tools-extra/clang-tidy/readability/TrailingCommaCheck.h
clang-tools-extra/docs/clang-tidy/checks/readability/trailing-comma.rst
clang-tools-extra/test/clang-tidy/checkers/readability/trailing-comma-cxx11.cpp
clang-tools-extra/test/clang-tidy/checkers/readability/trailing-comma-cxx20-remove.cpp
clang-tools-extra/test/clang-tidy/checkers/readability/trailing-comma-cxx20.cpp
clang-tools-extra/test/clang-tidy/checkers/readability/trailing-comma-remove.cpp
clang-tools-extra/test/clang-tidy/checkers/readability/trailing-comma-wrong-config.cpp
clang-tools-extra/test/clang-tidy/checkers/readability/trailing-comma.c
clang-tools-extra/test/clang-tidy/checkers/readability/trailing-comma.cpp
Modified:
clang-tools-extra/clang-tidy/readability/CMakeLists.txt
clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp
clang-tools-extra/docs/ReleaseNotes.rst
clang-tools-extra/docs/clang-tidy/checks/list.rst
Removed:
################################################################################
diff --git a/clang-tools-extra/clang-tidy/readability/CMakeLists.txt b/clang-tools-extra/clang-tidy/readability/CMakeLists.txt
index a55f0ab78f97f..f1f3cde32feff 100644
--- a/clang-tools-extra/clang-tidy/readability/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/readability/CMakeLists.txt
@@ -58,6 +58,7 @@ add_clang_library(clangTidyReadabilityModule STATIC
StaticDefinitionInAnonymousNamespaceCheck.cpp
StringCompareCheck.cpp
SuspiciousCallArgumentCheck.cpp
+ TrailingCommaCheck.cpp
UniqueptrDeleteReleaseCheck.cpp
UppercaseLiteralSuffixCheck.cpp
UseAnyOfAllOfCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp b/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp
index d2da125a9d6da..c582dc98eac6b 100644
--- a/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp
@@ -60,6 +60,7 @@
#include "StaticDefinitionInAnonymousNamespaceCheck.h"
#include "StringCompareCheck.h"
#include "SuspiciousCallArgumentCheck.h"
+#include "TrailingCommaCheck.h"
#include "UniqueptrDeleteReleaseCheck.h"
#include "UppercaseLiteralSuffixCheck.h"
#include "UseAnyOfAllOfCheck.h"
@@ -177,6 +178,8 @@ class ReadabilityModule : public ClangTidyModule {
"readability-simplify-boolean-expr");
CheckFactories.registerCheck<SuspiciousCallArgumentCheck>(
"readability-suspicious-call-argument");
+ CheckFactories.registerCheck<TrailingCommaCheck>(
+ "readability-trailing-comma");
CheckFactories.registerCheck<UniqueptrDeleteReleaseCheck>(
"readability-uniqueptr-delete-release");
CheckFactories.registerCheck<UppercaseLiteralSuffixCheck>(
diff --git a/clang-tools-extra/clang-tidy/readability/TrailingCommaCheck.cpp b/clang-tools-extra/clang-tidy/readability/TrailingCommaCheck.cpp
new file mode 100644
index 0000000000000..4dd881cf37993
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/readability/TrailingCommaCheck.cpp
@@ -0,0 +1,179 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "TrailingCommaCheck.h"
+#include "../utils/LexerUtils.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Lex/Lexer.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy {
+
+template <>
+struct OptionEnumMapping<readability::TrailingCommaCheck::CommaPolicyKind> {
+ static llvm::ArrayRef<
+ std::pair<readability::TrailingCommaCheck::CommaPolicyKind, StringRef>>
+ getEnumMapping() {
+ static constexpr std::pair<readability::TrailingCommaCheck::CommaPolicyKind,
+ StringRef>
+ Mapping[] = {
+ {readability::TrailingCommaCheck::CommaPolicyKind::Append,
+ "Append"},
+ {readability::TrailingCommaCheck::CommaPolicyKind::Remove,
+ "Remove"},
+ {readability::TrailingCommaCheck::CommaPolicyKind::Ignore,
+ "Ignore"},
+ };
+ return {Mapping};
+ }
+};
+
+} // namespace clang::tidy
+
+namespace clang::tidy::readability {
+
+static bool isSingleLine(SourceRange Range, const SourceManager &SM) {
+ return SM.getExpansionLineNumber(Range.getBegin()) ==
+ SM.getExpansionLineNumber(Range.getEnd());
+}
+
+namespace {
+
+AST_POLYMORPHIC_MATCHER(isMacro,
+ AST_POLYMORPHIC_SUPPORTED_TYPES(EnumDecl,
+ InitListExpr)) {
+ return Node.getBeginLoc().isMacroID() || Node.getEndLoc().isMacroID();
+}
+
+AST_MATCHER(EnumDecl, isEmptyEnum) { return Node.enumerators().empty(); }
+
+AST_MATCHER(InitListExpr, isEmptyInitList) { return Node.getNumInits() == 0; }
+
+} // namespace
+
+TrailingCommaCheck::TrailingCommaCheck(StringRef Name,
+ ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context),
+ SingleLineCommaPolicy(
+ Options.get("SingleLineCommaPolicy", CommaPolicyKind::Remove)),
+ MultiLineCommaPolicy(
+ Options.get("MultiLineCommaPolicy", CommaPolicyKind::Append)) {
+ if (SingleLineCommaPolicy == CommaPolicyKind::Ignore &&
+ MultiLineCommaPolicy == CommaPolicyKind::Ignore)
+ configurationDiag("The check '%0' will not perform any analysis because "
+ "'SingleLineCommaPolicy' and 'MultiLineCommaPolicy' are "
+ "both set to 'Ignore'.")
+ << Name;
+}
+
+void TrailingCommaCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "SingleLineCommaPolicy", SingleLineCommaPolicy);
+ Options.store(Opts, "MultiLineCommaPolicy", MultiLineCommaPolicy);
+}
+
+void TrailingCommaCheck::registerMatchers(MatchFinder *Finder) {
+ Finder->addMatcher(
+ enumDecl(isDefinition(), unless(isEmptyEnum()), unless(isMacro()))
+ .bind("enum"),
+ this);
+
+ Finder->addMatcher(initListExpr(unless(isEmptyInitList()), unless(isMacro()))
+ .bind("initlist"),
+ this);
+}
+
+void TrailingCommaCheck::check(const MatchFinder::MatchResult &Result) {
+ if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("enum"))
+ checkEnumDecl(Enum, Result);
+ else if (const auto *InitList =
+ Result.Nodes.getNodeAs<InitListExpr>("initlist"))
+ checkInitListExpr(InitList, Result);
+ else
+ llvm_unreachable("No matches found");
+}
+
+void TrailingCommaCheck::checkEnumDecl(const EnumDecl *Enum,
+ const MatchFinder::MatchResult &Result) {
+ const bool IsSingleLine = isSingleLine(
+ {Enum->getBeginLoc(), Enum->getEndLoc()}, *Result.SourceManager);
+ const CommaPolicyKind Policy =
+ IsSingleLine ? SingleLineCommaPolicy : MultiLineCommaPolicy;
+
+ if (Policy == CommaPolicyKind::Ignore)
+ return;
+
+ const std::optional<Token> LastTok =
+ Lexer::findPreviousToken(Enum->getBraceRange().getEnd(),
+ *Result.SourceManager, getLangOpts(), false);
+ if (!LastTok)
+ return;
+
+ emitDiag(LastTok->getLocation(), LastTok, DiagKind::Enum, Result, Policy);
+}
+
+void TrailingCommaCheck::checkInitListExpr(
+ const InitListExpr *InitList, const MatchFinder::MatchResult &Result) {
+ // We need to use non-empty syntactic form for correct source locations.
+ if (const InitListExpr *SynInitInitList = InitList->getSyntacticForm();
+ SynInitInitList && SynInitInitList->getNumInits() > 0)
+ InitList = SynInitInitList;
+
+ const bool IsSingleLine = isSingleLine(
+ {InitList->getBeginLoc(), InitList->getEndLoc()}, *Result.SourceManager);
+ const CommaPolicyKind Policy =
+ IsSingleLine ? SingleLineCommaPolicy : MultiLineCommaPolicy;
+
+ if (Policy == CommaPolicyKind::Ignore)
+ return;
+
+ const Expr *LastInit = InitList->inits().back();
+ assert(LastInit);
+
+ // Skip pack expansions - they already have special syntax with '...'
+ if (isa<PackExpansionExpr>(LastInit))
+ return;
+
+ const std::optional<Token> NextTok =
+ utils::lexer::findNextTokenSkippingComments(
+ LastInit->getEndLoc(), *Result.SourceManager, getLangOpts());
+
+ // If the next token is neither a comma nor closing brace, there might be
+ // a macro (e.g., #define COMMA ,) that we can't safely analyze.
+ if (NextTok && !NextTok->isOneOf(tok::comma, tok::r_brace))
+ return;
+
+ emitDiag(LastInit->getEndLoc(), NextTok, DiagKind::InitList, Result, Policy);
+}
+
+void TrailingCommaCheck::emitDiag(
+ SourceLocation LastLoc, std::optional<Token> Token, DiagKind Kind,
+ const ast_matchers::MatchFinder::MatchResult &Result,
+ CommaPolicyKind Policy) {
+ if (LastLoc.isInvalid() || !Token)
+ return;
+
+ const bool HasTrailingComma = Token->is(tok::comma);
+ if (Policy == CommaPolicyKind::Append && !HasTrailingComma) {
+ const SourceLocation InsertLoc = Lexer::getLocForEndOfToken(
+ LastLoc, 0, *Result.SourceManager, getLangOpts());
+ diag(InsertLoc, "%select{initializer list|enum}0 should have "
+ "a trailing comma")
+ << Kind << FixItHint::CreateInsertion(InsertLoc, ",");
+ } else if (Policy == CommaPolicyKind::Remove && HasTrailingComma) {
+ const SourceLocation CommaLoc = Token->getLocation();
+ if (CommaLoc.isInvalid())
+ return;
+ diag(CommaLoc, "%select{initializer list|enum}0 should not have "
+ "a trailing comma")
+ << Kind << FixItHint::CreateRemoval(CommaLoc);
+ }
+}
+
+} // namespace clang::tidy::readability
diff --git a/clang-tools-extra/clang-tidy/readability/TrailingCommaCheck.h b/clang-tools-extra/clang-tidy/readability/TrailingCommaCheck.h
new file mode 100644
index 0000000000000..f10b3fbdfb142
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/readability/TrailingCommaCheck.h
@@ -0,0 +1,55 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_TRAILINGCOMMACHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_TRAILINGCOMMACHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::readability {
+
+/// Checks for presence or absence of trailing commas in enum definitions
+/// and initializer lists.
+///
+/// For the user-facing documentation see:
+/// https://clang.llvm.org/extra/clang-tidy/checks/readability/trailing-comma.html
+class TrailingCommaCheck : public ClangTidyCheck {
+public:
+ TrailingCommaCheck(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;
+ bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+ return LangOpts.CPlusPlus || LangOpts.C99;
+ }
+ std::optional<TraversalKind> getCheckTraversalKind() const override {
+ return TK_IgnoreUnlessSpelledInSource;
+ }
+
+ enum class CommaPolicyKind { Append, Remove, Ignore };
+
+private:
+ const CommaPolicyKind SingleLineCommaPolicy;
+ const CommaPolicyKind MultiLineCommaPolicy;
+
+ void checkEnumDecl(const EnumDecl *Enum,
+ const ast_matchers::MatchFinder::MatchResult &Result);
+ void checkInitListExpr(const InitListExpr *InitList,
+ const ast_matchers::MatchFinder::MatchResult &Result);
+
+ // Values correspond to %select{initializer list|enum}0 indices
+ enum DiagKind { InitList = 0, Enum = 1 };
+ void emitDiag(SourceLocation LastLoc, std::optional<Token> Token,
+ DiagKind Kind,
+ const ast_matchers::MatchFinder::MatchResult &Result,
+ CommaPolicyKind Policy);
+};
+
+} // namespace clang::tidy::readability
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_TRAILINGCOMMACHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 976223e5c8149..8cf2006172d3f 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -128,6 +128,12 @@ New checks
Finds and removes redundant conversions from ``std::[w|u8|u16|u32]string_view`` to
``std::[...]string`` in call expressions expecting ``std::[...]string_view``.
+- New :doc:`readability-trailing-comma
+ <clang-tidy/checks/readability/trailing-comma>` check.
+
+ Checks for presence or absence of trailing commas in enum definitions and
+ initializer lists.
+
New check aliases
^^^^^^^^^^^^^^^^^
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index 2c9d5face0bee..34d1c2ce0a174 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -429,6 +429,7 @@ Clang-Tidy Checks
:doc:`readability-static-definition-in-anonymous-namespace <readability/static-definition-in-anonymous-namespace>`, "Yes"
:doc:`readability-string-compare <readability/string-compare>`, "Yes"
:doc:`readability-suspicious-call-argument <readability/suspicious-call-argument>`,
+ :doc:`readability-trailing-comma <readability/trailing-comma>`, "Yes"
:doc:`readability-uniqueptr-delete-release <readability/uniqueptr-delete-release>`, "Yes"
:doc:`readability-uppercase-literal-suffix <readability/uppercase-literal-suffix>`, "Yes"
:doc:`readability-use-anyofallof <readability/use-anyofallof>`,
diff --git a/clang-tools-extra/docs/clang-tidy/checks/readability/trailing-comma.rst b/clang-tools-extra/docs/clang-tidy/checks/readability/trailing-comma.rst
new file mode 100644
index 0000000000000..83e05fb1bead6
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/readability/trailing-comma.rst
@@ -0,0 +1,68 @@
+.. title:: clang-tidy - readability-trailing-comma
+
+readability-trailing-comma
+==========================
+
+Checks for presence or absence of trailing commas in enum definitions and
+initializer lists.
+
+The check supports separate policies for single-line and multi-line constructs,
+allowing
diff erent styles for each. By default, the check enforces trailing
+commas in multi-line constructs and removes them from single-line constructs.
+
+Trailing commas in multi-line constructs offer several benefits:
+
+- Adding or removing elements at the end only changes a single line, making
+
diff s smaller and easier to read.
+- Formatters may change code to a more desired style.
+- Code generators avoid the need for special handling of the last element.
+
+.. code-block:: c++
+
+ // Without trailing commas - adding "Yellow" requires modifying the "Blue" line
+ enum Color {
+ Red,
+ Green,
+ Blue
+ };
+
+ // With trailing commas - adding "Yellow" is a clean, single-line change
+ enum Color {
+ Red,
+ Green,
+ Blue,
+ };
+
+
+Limitations
+-----------
+
+The check currently doesn't analyze code inside macros.
+
+
+Options
+-------
+
+.. option:: SingleLineCommaPolicy
+
+ Controls whether to add, remove, or ignore trailing commas in single-line
+ enum definitions and initializer lists.
+ Valid values are:
+
+ - `Append`: Add trailing commas where missing.
+ - `Remove`: Remove trailing commas where present.
+ - `Ignore`: Do not check single-line constructs.
+
+ Default is `Remove`.
+
+.. option:: MultiLineCommaPolicy
+
+ Controls whether to add, remove, or ignore trailing commas in multi-line
+ enum definitions and initializer lists.
+ Valid values are:
+
+ - `Append`: Add trailing commas where missing.
+ - `Remove`: Remove trailing commas where present.
+ - `Ignore`: Do not check multi-line constructs.
+
+ Default is `Append`.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/trailing-comma-cxx11.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/trailing-comma-cxx11.cpp
new file mode 100644
index 0000000000000..9f37db2c837c3
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability/trailing-comma-cxx11.cpp
@@ -0,0 +1,56 @@
+// RUN: %check_clang_tidy -std=c++11-or-later %s readability-trailing-comma %t
+
+enum class SingleLine2 { X1, Y1 = 1, };
+// CHECK-MESSAGES: :[[@LINE-1]]:36: warning: enum should not have a trailing comma
+// CHECK-FIXES: enum class SingleLine2 { X1, Y1 = 1 };
+
+enum EnumWithAttrs {
+ E1 [[deprecated]] = 1,
+ E2 [[deprecated]]
+};
+// CHECK-MESSAGES: :[[@LINE-2]]:20: warning: enum should have a trailing comma
+// CHECK-FIXES: enum EnumWithAttrs {
+// CHECK-FIXES-NEXT: E1 {{\[\[}}deprecated{{\]\]}} = 1,
+// CHECK-FIXES-NEXT: E2 {{\[\[}}deprecated{{\]\]}},
+// CHECK-FIXES-NEXT: };
+
+enum EnumWithAttrs2 {
+ E3 [[deprecated]] = 1,
+ E4 [[deprecated]] = 2
+};
+// CHECK-MESSAGES: :[[@LINE-2]]:24: warning: enum should have a trailing comma
+// CHECK-FIXES: enum EnumWithAttrs2 {
+// CHECK-FIXES-NEXT: E3 {{\[\[}}deprecated{{\]\]}} = 1,
+// CHECK-FIXES-NEXT: E4 {{\[\[}}deprecated{{\]\]}} = 2,
+// CHECK-FIXES-NEXT: };
+
+enum EnumWithAttrsTrailing {
+ E5 [[deprecated]] = 1,
+ E6 [[deprecated]],
+};
+
+enum SingleLineAttrs { E7 [[deprecated]], E8 [[deprecated]] [[deprecated ]] , };
+// CHECK-MESSAGES: :[[@LINE-1]]:78: warning: enum should not have a trailing comma
+// CHECK-FIXES: enum SingleLineAttrs { E7 {{\[\[}}deprecated{{\]\]}}, E8 {{\[\[}}deprecated{{\]\]}} {{\[\[}}deprecated {{\]\]}} };
+
+enum SingleLineAttrs2 { E9 [[deprecated]], E10 [[deprecated]] = 1, };
+// CHECK-MESSAGES: :[[@LINE-1]]:66: warning: enum should not have a trailing comma
+// CHECK-FIXES: enum SingleLineAttrs2 { E9 {{\[\[}}deprecated{{\]\]}}, E10 {{\[\[}}deprecated{{\]\]}} = 1 };
+
+// Template pack expansions - no warnings
+template <typename T, typename... Ts>
+struct Pack {
+ int values[sizeof...(Ts) + 1] = {sizeof(T), sizeof(Ts)...};
+};
+
+Pack<int> one;
+Pack<int, double> two;
+Pack<int, double, char> three;
+
+template <typename... Ts>
+struct PackSingle {
+ int values[sizeof...(Ts)] = {sizeof(Ts)...};
+};
+
+PackSingle<int> p1;
+PackSingle<int, double, char> p3;
diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/trailing-comma-cxx20-remove.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/trailing-comma-cxx20-remove.cpp
new file mode 100644
index 0000000000000..6c18678f88fec
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability/trailing-comma-cxx20-remove.cpp
@@ -0,0 +1,64 @@
+// RUN: %check_clang_tidy -std=c++20-or-later %s readability-trailing-comma %t -- \
+// RUN: -config='{CheckOptions: {readability-trailing-comma.SingleLineCommaPolicy: Remove, readability-trailing-comma.MultiLineCommaPolicy: Remove}}'
+
+struct S { int x, y; };
+
+void f() {
+ S s1 = {
+ .x = 1,
+ .y = 2,
+ };
+ // CHECK-MESSAGES: :[[@LINE-2]]:11: warning: initializer list should not have a trailing comma
+ // CHECK-FIXES: S s1 = {
+ // CHECK-FIXES-NEXT: .x = 1,
+ // CHECK-FIXES-NEXT: .y = 2
+ // CHECK-FIXES-NEXT: };
+
+ int a[3] = {
+ [0] = 1,
+ };
+ // CHECK-MESSAGES: :[[@LINE-2]]:12: warning: initializer list should not have a trailing comma
+ // CHECK-FIXES: int a[3] = {
+ // CHECK-FIXES-NEXT: [0] = 1
+ // CHECK-FIXES-NEXT: };
+
+ S s2 = {.x = 1, .y = 2,};
+ // CHECK-MESSAGES: :[[@LINE-1]]:25: warning: initializer list should not have a trailing comma
+ // CHECK-FIXES: S s2 = {.x = 1, .y = 2};
+
+ S s3 = {
+ .x = 1,
+ .y = 2
+ };
+}
+
+struct N { S a, b; };
+
+void nested() {
+ N n1 = {.a = {.x = 1, .y = 2}, .b = {.x = 3, .y = 4,},};
+ // CHECK-MESSAGES: :[[@LINE-1]]:54: warning: initializer list should not have a trailing comma
+ // CHECK-MESSAGES: :[[@LINE-2]]:56: warning: initializer list should not have a trailing comma
+ // CHECK-FIXES: N n1 = {.a = {.x = 1, .y = 2}, .b = {.x = 3, .y = 4}};
+
+ N n2 = {
+ .a = {.x = 1, .y = 2},
+ .b = {.x = 3, .y = 4}
+ };
+}
+
+struct WithArray {
+ int values[3];
+ int count;
+};
+
+void with_array() {
+ WithArray w2 = {.values = {1, 2, 3,}, .count = 3,};
+ // CHECK-MESSAGES: :[[@LINE-1]]:37: warning: initializer list should not have a trailing comma
+ // CHECK-MESSAGES: :[[@LINE-2]]:51: warning: initializer list should not have a trailing comma
+ // CHECK-FIXES: WithArray w2 = {.values = {1, 2, 3}, .count = 3};
+
+ WithArray w3 = {
+ .values = {1, 2, 3},
+ .count = 3
+ };
+}
diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/trailing-comma-cxx20.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/trailing-comma-cxx20.cpp
new file mode 100644
index 0000000000000..b2f6ed072563f
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability/trailing-comma-cxx20.cpp
@@ -0,0 +1,89 @@
+// RUN: %check_clang_tidy -std=c++20-or-later %s readability-trailing-comma %t
+
+struct S { int x, y; };
+
+void f() {
+ S s1 = {
+ .x = 1,
+ .y = 2
+ };
+ // CHECK-MESSAGES: :[[@LINE-2]]:11: warning: initializer list should have a trailing comma
+ // CHECK-FIXES: S s1 = {
+ // CHECK-FIXES-NEXT: .x = 1,
+ // CHECK-FIXES-NEXT: .y = 2,
+ // CHECK-FIXES-NEXT: };
+
+ int a[3] = {
+ [0] = 1
+ };
+ // CHECK-MESSAGES: :[[@LINE-2]]:12: warning: initializer list should have a trailing comma
+ // CHECK-FIXES: int a[3] = {
+ // CHECK-FIXES-NEXT: [0] = 1,
+ // CHECK-FIXES-NEXT: };
+
+ S s2 = {.x = 1, .y = 2};
+ S s3 = {.x = 1};
+
+ S s4 = {
+ .x = 1,
+ };
+}
+
+struct N { S a, b; };
+
+void nested() {
+ N n = {
+ .a = {.x = 1, .y = 2},
+ .b = {
+ .x = 3,
+ .y = 4
+ }
+ };
+ // CHECK-MESSAGES: :[[@LINE-3]]:13: warning: initializer list should have a trailing comma
+ // CHECK-MESSAGES: :[[@LINE-3]]:6: warning: initializer list should have a trailing comma
+ // CHECK-FIXES: N n = {
+ // CHECK-FIXES-NEXT: .a = {.x = 1, .y = 2},
+ // CHECK-FIXES-NEXT: .b = {
+ // CHECK-FIXES-NEXT: .x = 3,
+ // CHECK-FIXES-NEXT: .y = 4,
+ // CHECK-FIXES-NEXT: },
+ // CHECK-FIXES-NEXT: };
+
+ N n2 = {.a = {.x = 1, .y = 2}, .b = {.x = 3, .y = 4}};
+
+ N n3 = {
+ .a = {.x = 1, .y = 2},
+ .b = {
+ .x = 3,
+ .y = 4,
+ },
+ };
+}
+
+struct WithArray {
+ int values[3];
+ int count;
+};
+
+void with_array() {
+ WithArray w1 = {
+ .values = {1, 2,
+ 3
+ },
+ .count = 3
+ };
+ // CHECK-MESSAGES: :[[@LINE-4]]:8: warning: initializer list should have a trailing comma
+ // CHECK-MESSAGES: :[[@LINE-3]]:15: warning: initializer list should have a trailing comma
+ // CHECK-FIXES: WithArray w1 = {
+ // CHECK-FIXES-NEXT: .values = {1, 2,
+ // CHECK-FIXES-NEXT: 3,
+ // CHECK-FIXES-NEXT: },
+ // CHECK-FIXES-NEXT: .count = 3,
+ // CHECK-FIXES-NEXT: };
+
+ WithArray w2 = {.values = {1, 2, 3}, .count = 3};
+ WithArray w3 = {
+ .values = {1, 2, 3},
+ .count = 3,
+ };
+}
diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/trailing-comma-remove.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/trailing-comma-remove.cpp
new file mode 100644
index 0000000000000..fadce1808876f
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability/trailing-comma-remove.cpp
@@ -0,0 +1,79 @@
+// RUN: %check_clang_tidy -std=c++98-or-later %s readability-trailing-comma %t -- \
+// RUN: -config='{CheckOptions: {readability-trailing-comma.SingleLineCommaPolicy: Remove, readability-trailing-comma.MultiLineCommaPolicy: Remove}}'
+
+struct S { int x, y; };
+
+enum E1 {
+ A,
+ B,
+ C,
+};
+// CHECK-MESSAGES: :[[@LINE-2]]:4: warning: enum should not have a trailing comma [readability-trailing-comma]
+// CHECK-FIXES: enum E1 {
+// CHECK-FIXES-NEXT: A,
+// CHECK-FIXES-NEXT: B,
+// CHECK-FIXES-NEXT: C
+// CHECK-FIXES-NEXT: };
+
+enum E2 {
+ V = 1,
+};
+// CHECK-MESSAGES: :[[@LINE-2]]:8: warning: enum should not have a trailing comma
+// CHECK-FIXES: enum E2 {
+// CHECK-FIXES-NEXT: V = 1
+// CHECK-FIXES-NEXT: };
+
+enum SingleLine { A1, B1, C1, };
+// CHECK-MESSAGES: :[[@LINE-1]]:29: warning: enum should not have a trailing comma
+// CHECK-FIXES: enum SingleLine { A1, B1, C1 };
+
+enum E3 {
+ P,
+ Q
+};
+enum Empty {};
+
+void f() {
+ // Multi-line init lists with trailing commas - should warn to remove
+ int a[] = {
+ 1,
+ 2,
+ };
+ // CHECK-MESSAGES: :[[@LINE-2]]:6: warning: initializer list should not have a trailing comma
+ // CHECK-FIXES: int a[] = {
+ // CHECK-FIXES-NEXT: 1,
+ // CHECK-FIXES-NEXT: 2
+ // CHECK-FIXES-NEXT: };
+
+ S s = {
+ 1,
+ 2,
+ };
+ // CHECK-MESSAGES: :[[@LINE-2]]:6: warning: initializer list should not have a trailing comma
+ // CHECK-FIXES: S s = {
+ // CHECK-FIXES-NEXT: 1,
+ // CHECK-FIXES-NEXT: 2
+ // CHECK-FIXES-NEXT: };
+
+ int b[] = {1, 2, 3,};
+ // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: initializer list should not have a trailing comma
+ // CHECK-FIXES: int b[] = {1, 2, 3};
+ S s2 = {1, 2,};
+ // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: initializer list should not have a trailing comma
+ // CHECK-FIXES: S s2 = {1, 2};
+
+ int c[] = {
+ 1,
+ 2
+ };
+ int d[] = {};
+}
+
+struct N { S a, b; };
+void nested() {
+ N n = {{3, 4,},};
+ // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: initializer list should not have a trailing comma
+ // CHECK-MESSAGES: :[[@LINE-2]]:17: warning: initializer list should not have a trailing comma
+ // CHECK-FIXES: N n = {{[{][{]3, 4[}][}]}};
+ N n2 = {{3, 4}};
+}
diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/trailing-comma-wrong-config.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/trailing-comma-wrong-config.cpp
new file mode 100644
index 0000000000000..a3ce4e70d426a
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability/trailing-comma-wrong-config.cpp
@@ -0,0 +1,11 @@
+// RUN: %check_clang_tidy -std=c++98-or-later %s readability-trailing-comma %t -- \
+// RUN: -config='{CheckOptions: {readability-trailing-comma.SingleLineCommaPolicy: Ignore, readability-trailing-comma.MultiLineCommaPolicy: Ignore}}'
+
+
+// CHECK-MESSAGES: warning: The check 'readability-trailing-comma' will not perform any analysis because 'SingleLineCommaPolicy' and 'MultiLineCommaPolicy' are both set to 'Ignore'. [clang-tidy-config]
+
+enum E1 {
+ A
+};
+
+enum E2 { B, };
diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/trailing-comma.c b/clang-tools-extra/test/clang-tidy/checkers/readability/trailing-comma.c
new file mode 100644
index 0000000000000..bdf9912e54155
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability/trailing-comma.c
@@ -0,0 +1,117 @@
+// RUN: %check_clang_tidy %s readability-trailing-comma %t
+
+enum Color {
+ Red,
+ Green,
+ Blue
+};
+// CHECK-MESSAGES: :[[@LINE-2]]:7: warning: enum should have a trailing comma
+// CHECK-FIXES: enum Color {
+// CHECK-FIXES-NEXT: Red,
+// CHECK-FIXES-NEXT: Green,
+// CHECK-FIXES-NEXT: Blue,
+// CHECK-FIXES-NEXT: };
+
+enum SingleLine { A, B, C };
+enum SingleLine2 { X1, Y1, };
+// CHECK-MESSAGES: :[[@LINE-1]]:26: warning: enum should not have a trailing comma
+// CHECK-FIXES: enum SingleLine2 { X1, Y1 };
+enum SingleVal { VAL1 };
+enum SingleVal2 { VAL2, };
+// CHECK-MESSAGES: :[[@LINE-1]]:23: warning: enum should not have a trailing comma
+// CHECK-FIXES: enum SingleVal2 { VAL2 };
+enum SingleVal3 {
+ VAL3
+};
+// CHECK-MESSAGES: :[[@LINE-2]]:7: warning: enum should have a trailing comma
+// CHECK-FIXES: enum SingleVal3 {
+// CHECK-FIXES-NEXT: VAL3,
+// CHECK-FIXES-NEXT: };
+
+struct Point {
+ int x;
+ int y;
+};
+
+struct Point p = {
+ 10,
+ 20
+};
+// CHECK-MESSAGES: :[[@LINE-2]]:5: warning: initializer list should have a trailing comma
+// CHECK-FIXES: struct Point p = {
+// CHECK-FIXES-NEXT: 10,
+// CHECK-FIXES-NEXT: 20,
+// CHECK-FIXES-NEXT: };
+
+struct Point p2 = {
+ .x = 1,
+ .y = 2
+};
+// CHECK-MESSAGES: :[[@LINE-2]]:9: warning: initializer list should have a trailing comma
+// CHECK-FIXES: struct Point p2 = {
+// CHECK-FIXES-NEXT: .x = 1,
+// CHECK-FIXES-NEXT: .y = 2,
+// CHECK-FIXES-NEXT: };
+
+int arr2[5] = {
+ [0] = 1
+};
+// CHECK-MESSAGES: :[[@LINE-2]]:10: warning: initializer list should have a trailing comma
+// CHECK-FIXES: int arr2[5] = {
+// CHECK-FIXES-NEXT: [0] = 1,
+// CHECK-FIXES-NEXT: };
+
+int multiArr[] = {
+ 1
+};
+// CHECK-MESSAGES: :[[@LINE-2]]:4: warning: initializer list should have a trailing comma
+// CHECK-FIXES: int multiArr[] = {
+// CHECK-FIXES-NEXT: 1,
+// CHECK-FIXES-NEXT: };
+
+int arr[] = {1, 2, 3};
+struct Point p3 = {10, 20};
+struct Point p4 = {.x = 1, .y = 2};
+int matrix[2][2] = {{1, 2}, {3, 4}};
+int single1[1] = {42};
+int single2[1] = {42,};
+// CHECK-MESSAGES: :[[@LINE-1]]:21: warning: initializer list should not have a trailing comma
+// CHECK-FIXES: int single2[1] = {42};
+int single3[1] = {
+ 42
+};
+// CHECK-MESSAGES: :[[@LINE-2]]:5: warning: initializer list should have a trailing comma
+// CHECK-FIXES: int single3[1] = {
+// CHECK-FIXES-NEXT: 42,
+// CHECK-FIXES-NEXT: };
+int single4[1] = {
+ 42,
+};
+int empty1[] = {};
+struct Nested { int a[2]; int b; };
+struct Nested nest1 = {{1}, 2};
+struct Nested nest2 = {{1,}, 2};
+// CHECK-MESSAGES: :[[@LINE-1]]:26: warning: initializer list should not have a trailing comma
+// CHECK-FIXES: struct Nested nest2 = {{[{][{]}}1}, 2};
+struct Nested nest3 = {
+ {1},
+ 2
+};
+// CHECK-MESSAGES: :[[@LINE-2]]:4: warning: initializer list should have a trailing comma
+// CHECK-FIXES: struct Nested nest3 = {
+// CHECK-FIXES-NEXT: {1},
+// CHECK-FIXES-NEXT: 2,
+// CHECK-FIXES-NEXT: };
+
+struct Point singleDesig1 = {.x = 10};
+struct Point singleDesig2 = {.x = 10,};
+// CHECK-MESSAGES: :[[@LINE-1]]:37: warning: initializer list should not have a trailing comma
+// CHECK-FIXES: struct Point singleDesig2 = {.x = 10};
+struct Point singleDesig3 = {};
+struct Point singleDesig4 = {
+ .x = 10
+};
+// CHECK-MESSAGES: :[[@LINE-2]]:10: warning: initializer list should have a trailing comma
+// CHECK-FIXES: struct Point singleDesig4 = {
+// CHECK-FIXES-NEXT: .x = 10,
+// CHECK-FIXES-NEXT: };
diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/trailing-comma.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/trailing-comma.cpp
new file mode 100644
index 0000000000000..76fb4bbf0c37d
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability/trailing-comma.cpp
@@ -0,0 +1,161 @@
+// RUN: %check_clang_tidy -std=c++98-or-later %s readability-trailing-comma %t
+
+struct S { int x, y; };
+
+enum E1 {
+ A,
+ B,
+ C
+};
+// CHECK-MESSAGES: :[[@LINE-2]]:4: warning: enum should have a trailing comma [readability-trailing-comma]
+// CHECK-FIXES: enum E1 {
+// CHECK-FIXES-NEXT: A,
+// CHECK-FIXES-NEXT: B,
+// CHECK-FIXES-NEXT: C,
+// CHECK-FIXES-NEXT: };
+
+enum E2 {
+ V = 1
+};
+// CHECK-MESSAGES: :[[@LINE-2]]:8: warning: enum should have a trailing comma
+// CHECK-FIXES: enum E2 {
+// CHECK-FIXES-NEXT: V = 1,
+// CHECK-FIXES-NEXT: };
+
+enum SingleLine { A1, B1, C1 };
+
+enum E3 {
+ P,
+ Q,
+};
+
+enum SingleEnum1 { ONE };
+enum SingleEnum2 { TWO, };
+// CHECK-MESSAGES: :[[@LINE-1]]:23: warning: enum should not have a trailing comma
+// CHECK-FIXES: enum SingleEnum2 { TWO };
+enum SingleEnum3 {
+ THREE
+};
+// CHECK-MESSAGES: :[[@LINE-2]]:8: warning: enum should have a trailing comma
+// CHECK-FIXES: enum SingleEnum3 {
+// CHECK-FIXES-NEXT: THREE,
+// CHECK-FIXES-NEXT: };
+enum SingleEnum4 {
+ FOUR,
+};
+
+enum Empty {};
+
+void f() {
+ int a[] = {
+ 1
+ };
+ // CHECK-MESSAGES: :[[@LINE-2]]:6: warning: initializer list should have a trailing comma
+ // CHECK-FIXES: int a[] = {
+ // CHECK-FIXES-NEXT: 1,
+ // CHECK-FIXES-NEXT: };
+
+ S s = {
+ 1,
+ 2
+ };
+ // CHECK-MESSAGES: :[[@LINE-2]]:6: warning: initializer list should have a trailing comma
+ // CHECK-FIXES: S s = {
+ // CHECK-FIXES-NEXT: 1,
+ // CHECK-FIXES-NEXT: 2,
+ // CHECK-FIXES-NEXT: };
+
+ int b[] = {1, 2, 3};
+ S s2 = {1, 2};
+
+ int e[] = {1, 2, 3,};
+ // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: initializer list should not have a trailing comma
+ // CHECK-FIXES: int e[] = {1, 2, 3};
+
+ int c[] = {
+ 1,
+ 2,
+ };
+ int d[] = {};
+
+ int single1[] = {1};
+ int single2[] = {1,};
+ // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: initializer list should not have a trailing comma
+ // CHECK-FIXES: int single2[] = {1};
+ int single3[] = {
+ 1
+ };
+ // CHECK-MESSAGES: :[[@LINE-2]]:6: warning: initializer list should have a trailing comma
+ // CHECK-FIXES: int single3[] = {
+ // CHECK-FIXES-NEXT: 1,
+ // CHECK-FIXES-NEXT: };
+ int single4[] = {
+ 1,
+ };
+ S singleS1 = {42};
+ S singleS2 = {42,};
+ // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: initializer list should not have a trailing comma
+ // CHECK-FIXES: S singleS2 = {42};
+}
+
+struct N { S a, b; };
+void nested() {
+ N n = {{1, 2}, {3, 4}};
+ N n2 = {{3, 4,},};
+ // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: initializer list should not have a trailing comma
+ // CHECK-MESSAGES: :[[@LINE-2]]:18: warning: initializer list should not have a trailing comma
+ // CHECK-FIXES: N n2 = {{[{][{]3, 4[}][}]}};
+}
+
+void nestedMultiLine() {
+ N n = {
+ {1, 2},
+ {3, 4}
+ };
+ // CHECK-MESSAGES: :[[@LINE-2]]:11: warning: initializer list should have a trailing comma
+ // CHECK-FIXES: N n = {
+ // CHECK-FIXES-NEXT: {1, 2},
+ // CHECK-FIXES-NEXT: {3, 4},
+ // CHECK-FIXES-NEXT: };
+
+ N n2 = {
+ {1, 2},
+ {3, 4},
+ };
+
+ struct Container { int arr[3]; };
+ Container c1 = {{}};
+ Container c2 = {{},};
+ // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: initializer list should not have a trailing comma
+ // CHECK-FIXES: Container c2 = {{[{][{][}][}]}};
+
+ struct Wrapper { S s; };
+ Wrapper w1 = {{1}};
+ Wrapper w2 = {{1,}};
+ // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: initializer list should not have a trailing comma
+ // CHECK-FIXES: Wrapper w2 = {{[{][{]1[}][}]}};
+
+ Wrapper w3 = {
+ {1}
+ };
+ // CHECK-MESSAGES: :[[@LINE-2]]:8: warning: initializer list should have a trailing comma
+ // CHECK-FIXES: Wrapper w3 = {
+ // CHECK-FIXES-NEXT: {1},
+ // CHECK-FIXES-NEXT: };
+}
+
+// Macros are ignored
+#define ENUM(n, a, b) enum n { a, b }
+#define INIT {1, 2}
+#define ITEMS 1,2
+
+ENUM(E1M, Xm, Ym);
+int macroArr[] = INIT;
+int a[] = { ITEMS };
+
+// Comma from macro should not trigger false positive
+#define COMMA ,
+int comma_from_macro[] = {
+ 1
+ COMMA
+};
More information about the cfe-commits
mailing list