[clang-tools-extra] [clang-tidy] Update google todo checker with style guide changes. (PR #165565)

Quentin Khan via cfe-commits cfe-commits at lists.llvm.org
Fri Oct 31 08:46:13 PDT 2025


https://github.com/qukhan updated https://github.com/llvm/llvm-project/pull/165565

>From 5de2f88c0bf82f10e6e56a2284ed9b97a82df737 Mon Sep 17 00:00:00 2001
From: Quentin Khan <qkhan at google.com>
Date: Thu, 23 Oct 2025 15:10:55 +0200
Subject: [PATCH] [clang-tidy] Update google todo checker with style guide
 changes.

The [Google style guide] now allows (and recommends) writing TODOs with
the following format:

```cpp
// TODO: bug reference - details about what needs to be done.
```

With this change the checker accepts the new style and suggests in in the
fix-it hint. The previous style is still accepted.

A new configuration option, `google-readability-todo.Style` is available
to switch between the `Parentheses` (legacy) style and the `Hyphen`
style.

[Google style guide]: https://google.github.io/styleguide/cppguide.html#TODO_Comments
---
 .../clang-tidy/google/TodoCommentCheck.cpp    | 83 ++++++++++++++++---
 .../clang-tidy/google/TodoCommentCheck.h      |  2 +
 clang-tools-extra/docs/ReleaseNotes.rst       |  4 +
 .../checks/google/readability-todo.rst        | 11 +++
 .../google/readability-todo-hyphen.cpp        | 40 +++++++++
 ...o.cpp => readability-todo-parentheses.cpp} | 16 +++-
 6 files changed, 145 insertions(+), 11 deletions(-)
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/google/readability-todo-hyphen.cpp
 rename clang-tools-extra/test/clang-tidy/checkers/google/{readability-todo.cpp => readability-todo-parentheses.cpp} (54%)

diff --git a/clang-tools-extra/clang-tidy/google/TodoCommentCheck.cpp b/clang-tools-extra/clang-tidy/google/TodoCommentCheck.cpp
index 8554870287c81..dd80fe31047c4 100644
--- a/clang-tools-extra/clang-tidy/google/TodoCommentCheck.cpp
+++ b/clang-tools-extra/clang-tidy/google/TodoCommentCheck.cpp
@@ -11,41 +11,100 @@
 #include "clang/Lex/Preprocessor.h"
 #include <optional>
 
-namespace clang::tidy::google::readability {
+namespace clang::tidy {
+
+namespace google::readability {
+
+enum class StyleKind { Parentheses, Hyphen };
+
+} // namespace google::readability
+
+template <> struct OptionEnumMapping<google::readability::StyleKind> {
+  static ArrayRef<std::pair<google::readability::StyleKind, StringRef>>
+  getEnumMapping() {
+    static constexpr std::pair<google::readability::StyleKind, StringRef>
+        Mapping[] = {
+            {google::readability::StyleKind::Hyphen, "Hyphen"},
+            {google::readability::StyleKind::Parentheses, "Parentheses"},
+        };
+    return {Mapping};
+  }
+};
+
+} // namespace clang::tidy
 
+namespace clang::tidy::google::readability {
 class TodoCommentCheck::TodoCommentHandler : public CommentHandler {
 public:
   TodoCommentHandler(TodoCommentCheck &Check, std::optional<std::string> User)
       : Check(Check), User(User ? *User : "unknown"),
-        TodoMatch("^// *TODO *(\\(.*\\))?:?( )?(.*)$") {}
+        TodoMatch(R"(^// *TODO *((\((.*)\))?:?( )?|: *(.*) *- *)?(.*)$)") {
+    llvm::StringRef TodoStyleString = Check.Options.get("Style", "Hyphen");
+    for (auto [Value, Name] : OptionEnumMapping<StyleKind>::getEnumMapping()) {
+      if (Name == TodoStyleString) {
+        TodoStyle = Value;
+        return;
+      }
+    }
+    Check.configurationDiag(
+        "invalid value '%0' for "
+        "google-readability-todo.Style; valid values are "
+        "'Parentheses' and 'Hyphen'. Defaulting to 'Hyphen'")
+        << TodoStyleString;
+  }
 
   bool HandleComment(Preprocessor &PP, SourceRange Range) override {
     StringRef Text =
         Lexer::getSourceText(CharSourceRange::getCharRange(Range),
                              PP.getSourceManager(), PP.getLangOpts());
 
-    SmallVector<StringRef, 4> Matches;
+    SmallVector<StringRef, 7> Matches;
     if (!TodoMatch.match(Text, &Matches))
       return false;
 
-    StringRef Username = Matches[1];
-    StringRef Comment = Matches[3];
+    const StyleKind ParsedStyle =
+        !Matches[3].empty() ? StyleKind::Parentheses : StyleKind::Hyphen;
+    const StringRef Username =
+        ParsedStyle == StyleKind::Parentheses ? Matches[3] : Matches[5];
+    const StringRef Comment = Matches[6];
 
-    if (!Username.empty())
+    if (!Username.empty() &&
+        (ParsedStyle == StyleKind::Parentheses || !Comment.empty())) {
       return false;
+    }
 
-    std::string NewText = ("// TODO(" + Twine(User) + "): " + Comment).str();
+    if (Username.empty()) {
+      Check.diag(Range.getBegin(), "missing username/bug in TODO")
+          << FixItHint::CreateReplacement(
+                 CharSourceRange::getCharRange(Range),
+                 createReplacementString(Username, Comment));
+    }
+
+    if (Comment.empty())
+      Check.diag(Range.getBegin(), "missing details in TODO");
 
-    Check.diag(Range.getBegin(), "missing username/bug in TODO")
-        << FixItHint::CreateReplacement(CharSourceRange::getCharRange(Range),
-                                        NewText);
     return false;
   }
 
+  std::string createReplacementString(const StringRef Username,
+                                      const StringRef Comment) const {
+    if (TodoStyle == StyleKind::Parentheses) {
+      return ("// TODO(" + Twine(User) +
+              "): " + (Comment.empty() ? "some details" : Comment))
+          .str();
+    }
+    return ("// TODO: " + Twine(User) + " - " +
+            (Comment.empty() ? "some details" : Comment))
+        .str();
+  }
+
+  StyleKind getTodoStyle() const { return TodoStyle; }
+
 private:
   TodoCommentCheck &Check;
   std::string User;
   llvm::Regex TodoMatch;
+  StyleKind TodoStyle = StyleKind::Hyphen;
 };
 
 TodoCommentCheck::TodoCommentCheck(StringRef Name, ClangTidyContext *Context)
@@ -61,4 +120,8 @@ void TodoCommentCheck::registerPPCallbacks(const SourceManager &SM,
   PP->addCommentHandler(Handler.get());
 }
 
+void TodoCommentCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+  Options.store(Opts, "Style", Handler->getTodoStyle());
+}
+
 } // namespace clang::tidy::google::readability
diff --git a/clang-tools-extra/clang-tidy/google/TodoCommentCheck.h b/clang-tools-extra/clang-tidy/google/TodoCommentCheck.h
index 05f9cc6618eb1..854b9cea673c2 100644
--- a/clang-tools-extra/clang-tidy/google/TodoCommentCheck.h
+++ b/clang-tools-extra/clang-tidy/google/TodoCommentCheck.h
@@ -27,6 +27,8 @@ class TodoCommentCheck : public ClangTidyCheck {
   void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
                            Preprocessor *ModuleExpanderPP) override;
 
+  void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
+
 private:
   class TodoCommentHandler;
   std::unique_ptr<TodoCommentHandler> Handler;
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 8a0151f567c24..68e2cf3a4656f 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -329,6 +329,10 @@ Changes in existing checks
   adding an option to allow pointer arithmetic via prefix/postfix increment or
   decrement operators.
 
+- Improved :doc:`google-readability-todo
+  <clang-tidy/checks/google/readability-todo>` check to accept the new todo
+  format from the Google Style Guide.
+
 - Improved :doc:`llvm-prefer-isa-or-dyn-cast-in-conditionals
   <clang-tidy/checks/llvm/prefer-isa-or-dyn-cast-in-conditionals>` check:
 
diff --git a/clang-tools-extra/docs/clang-tidy/checks/google/readability-todo.rst b/clang-tools-extra/docs/clang-tidy/checks/google/readability-todo.rst
index 159d2b4adccac..fccfa81166436 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/google/readability-todo.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/google/readability-todo.rst
@@ -9,3 +9,14 @@ The relevant style guide section is
 https://google.github.io/styleguide/cppguide.html#TODO_Comments.
 
 Corresponding cpplint.py check: `readability/todo`
+
+Options
+-------
+
+.. option:: Style
+
+   A string specifying the TODO style for fix-it hints. Accepted values are
+   `Hyphen` and `Parentheses`. Default is `Hyphen`.
+
+   * `Hyphen` will format the fix-it as: ``// TODO: username - details``.
+   * `Parentheses` will format the fix-it as: ``// TODO(username): details``.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/google/readability-todo-hyphen.cpp b/clang-tools-extra/test/clang-tidy/checkers/google/readability-todo-hyphen.cpp
new file mode 100644
index 0000000000000..5701b30bef395
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/google/readability-todo-hyphen.cpp
@@ -0,0 +1,40 @@
+// RUN: %check_clang_tidy %s google-readability-todo %t -- -config="{User: 'some user'}" --
+
+//   TODOfix this1
+// CHECK-MESSAGES: [[@LINE-1]]:1: warning: missing username/bug in TODO
+// CHECK-FIXES: // TODO: some user - fix this1
+
+//   TODO fix this2
+// CHECK-MESSAGES: [[@LINE-1]]:1: warning: missing username/bug in TODO
+// CHECK-FIXES: // TODO: some user - fix this2
+
+// TODO fix this3
+// CHECK-MESSAGES: [[@LINE-1]]:1: warning: missing username/bug in TODO
+// CHECK-FIXES: // TODO: some user - fix this3
+
+// TODO: fix this4
+// CHECK-MESSAGES: [[@LINE-1]]:1: warning: missing username/bug in TODO
+// CHECK-FIXES: // TODO: some user - fix this4
+
+// TODO: bug 12345 -
+// CHECK-MESSAGES: [[@LINE-1]]:1: warning: missing details in TODO
+
+// TODO: a message without a reference
+// CHECK-MESSAGES: [[@LINE-1]]:1: warning: missing username/bug in TODO
+// CHECK-FIXES: // TODO: some user - a message without a reference
+
+//   TODO(clang)fix this5
+
+// TODO: foo - shave yaks
+// TODO:foo - no space bewteen semicolon and username
+// TODO: foo- no space bewteen username and dash
+// TODO:    foo - extra spaces between semicolon and username
+// TODO: foo   - extra spaces between username and dash
+// TODO: b/12345 - use a b/ prefix
+// TODO: bug 12345 - use a space in username/bug reference
+// TODO(foo):shave yaks
+// TODO(bar):
+// TODO(foo): paint bikeshed
+// TODO(b/12345): find the holy grail
+// TODO (b/12345): allow spaces before parentheses
+// TODO(asdf) allow missing semicolon
diff --git a/clang-tools-extra/test/clang-tidy/checkers/google/readability-todo.cpp b/clang-tools-extra/test/clang-tidy/checkers/google/readability-todo-parentheses.cpp
similarity index 54%
rename from clang-tools-extra/test/clang-tidy/checkers/google/readability-todo.cpp
rename to clang-tools-extra/test/clang-tidy/checkers/google/readability-todo-parentheses.cpp
index 6b900aa92150e..f97e395d8bf48 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/google/readability-todo.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/google/readability-todo-parentheses.cpp
@@ -1,4 +1,4 @@
-// RUN: %check_clang_tidy %s google-readability-todo %t -- -config="{User: 'some user'}" --
+// RUN: %check_clang_tidy %s google-readability-todo %t -- -config="{User: 'some user', CheckOptions: {google-readability-todo.Style: 'Parentheses'}}" --
 
 //   TODOfix this1
 // CHECK-MESSAGES: [[@LINE-1]]:1: warning: missing username/bug in TODO
@@ -16,8 +16,22 @@
 // CHECK-MESSAGES: [[@LINE-1]]:1: warning: missing username/bug in TODO
 // CHECK-FIXES: // TODO(some user): fix this4
 
+// TODO: bug 12345 -
+// CHECK-MESSAGES: [[@LINE-1]]:1: warning: missing details in TODO
+
+// TODO: a message without a reference
+// CHECK-MESSAGES: [[@LINE-1]]:1: warning: missing username/bug in TODO
+// CHECK-FIXES: // TODO(some user): a message without a reference
+
 //   TODO(clang)fix this5
 
+// TODO: foo - shave yaks
+// TODO:foo - no space bewteen semicolon and username
+// TODO: foo- no space bewteen username and dash
+// TODO:    foo - extra spaces between semicolon and username
+// TODO: foo   - extra spaces between username and dash
+// TODO: b/12345 - use a b/ prefix
+// TODO: bug 12345 - use a space in username/bug reference
 // TODO(foo):shave yaks
 // TODO(bar):
 // TODO(foo): paint bikeshed



More information about the cfe-commits mailing list