[clang] [clang-format] Add SpaceBeforeUnderscoreParens option for GNU gettext… (PR #159925)

via cfe-commits cfe-commits at lists.llvm.org
Mon Sep 22 03:36:16 PDT 2025


https://github.com/kgerlich updated https://github.com/llvm/llvm-project/pull/159925

>From 03b6ccc976d42847d06adaeb4174b2ca9b7cb393 Mon Sep 17 00:00:00 2001
From: Klaus Gerlicher <klaus at klausgerlicher.de>
Date: Sat, 20 Sep 2025 15:13:10 +0200
Subject: [PATCH] [clang-format] Add SpaceBetweenUnderscoreParens option for
 GNU gettext macro

This adds a new configuration option SpaceBetweenUnderscoreParens to control
spacing between underscore and opening parenthesis. This is specifically
designed for the gettext internationalization macro '_()' commonly used in
GNU projects like GDB.

The option:
- Defaults to true for LLVM style (preserving existing behavior)
- Defaults to false for GNU style (removes space before '_(')
- Only affects single underscore tokens '_', not other identifiers
- Leaves all other spacing rules unchanged

Examples:
  GNU style with SpaceBetweenUnderscoreParens=false:
    printf(_("Hello"));     // No space before '_('
    my_func (arg);            // Space before other functions preserved

  LLVM style with SpaceBetweenUnderscoreParens=true:
    printf(_ ("Hello"));     // Standard spacing rules apply

This addresses the common pattern in GNU software where gettext messages
use '_()' without spaces, improving consistency with GNU coding standards.
---
 clang/docs/ClangFormatStyleOptions.rst | 12 ++++++++++
 clang/include/clang/Format/Format.h    | 10 +++++++++
 clang/lib/Format/Format.cpp            |  4 ++++
 clang/lib/Format/TokenAnnotator.cpp    |  5 +++++
 clang/unittests/Format/FormatTest.cpp  | 31 ++++++++++++++++++++++++++
 5 files changed, 62 insertions(+)

diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst
index 9413b9a348b76..aa95eee801d21 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -6605,6 +6605,18 @@ the configuration (without a prefix: ``Auto``).
      int a [5];                    vs.      int a[5];
      int a [5][5];                 vs.      int a[5][5];
 
+.. _SpaceBetweenUnderscoreParens:
+
+**SpaceBetweenUnderscoreParens** (``Boolean``) :versionbadge:`clang-format 19` :ref:`¶ <SpaceBetweenUnderscoreParens>`
+  If ``false``, spaces will be removed between underscore and an opening
+  parenthesis. This is specifically for the gettext macro ``_()`` commonly
+  used in GNU projects.
+
+  .. code-block:: c++
+
+     true:                                  false:
+     _ (message);                   vs.     _(message);
+
 .. _SpaceInEmptyBlock:
 
 **SpaceInEmptyBlock** (``Boolean``) :versionbadge:`clang-format 10` :ref:`¶ <SpaceInEmptyBlock>`
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index 342fefcfc408c..9b9aa3c713798 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -4896,6 +4896,16 @@ struct FormatStyle {
   /// \version 10
   // bool SpaceInEmptyBlock;
 
+  /// If ``false``, spaces will be removed between underscore and an opening
+  /// parenthesis. This is specifically for the gettext macro ``_()`` commonly
+  /// used in GNU projects.
+  /// \code
+  ///    true:                                  false:
+  ///    _ (message);                   vs.     _(message);
+  /// \endcode
+  /// \version 19
+  bool SpaceBetweenUnderscoreParens;
+
   /// Style of when to insert a space in empty braces.
   enum SpaceInEmptyBracesStyle : int8_t {
     /// Always insert a space in empty braces.
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index 24a36d97a6fa9..99cdf8d2d3ff5 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -1219,6 +1219,8 @@ template <> struct MappingTraits<FormatStyle> {
                    Style.SpaceBeforeCtorInitializerColon);
     IO.mapOptional("SpaceBeforeInheritanceColon",
                    Style.SpaceBeforeInheritanceColon);
+    IO.mapOptional("SpaceBetweenUnderscoreParens",
+                   Style.SpaceBetweenUnderscoreParens);
     IO.mapOptional("SpaceBeforeJsonColon", Style.SpaceBeforeJsonColon);
     IO.mapOptional("SpaceBeforeParens", Style.SpaceBeforeParens);
     IO.mapOptional("SpaceBeforeParensOptions", Style.SpaceBeforeParensOptions);
@@ -1713,6 +1715,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
   LLVMStyle.SpaceBeforeCpp11BracedList = false;
   LLVMStyle.SpaceBeforeCtorInitializerColon = true;
   LLVMStyle.SpaceBeforeInheritanceColon = true;
+  LLVMStyle.SpaceBetweenUnderscoreParens = true;
   LLVMStyle.SpaceBeforeJsonColon = false;
   LLVMStyle.SpaceBeforeParens = FormatStyle::SBPO_ControlStatements;
   LLVMStyle.SpaceBeforeParensOptions = {};
@@ -2044,6 +2047,7 @@ FormatStyle getGNUStyle() {
   Style.FixNamespaceComments = false;
   Style.KeepFormFeed = true;
   Style.SpaceBeforeParens = FormatStyle::SBPO_Always;
+  Style.SpaceBetweenUnderscoreParens = false;
   return Style;
 }
 
diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp
index d97f56751ea69..142177fdbce94 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -4901,6 +4901,11 @@ bool TokenAnnotator::spaceRequiredBetween(const AnnotatedLine &Line,
     // Handle builtins like identifiers.
     if (Line.Type != LT_PreprocessorDirective &&
         (Left.Tok.getIdentifierInfo() || Left.is(tok::r_paren))) {
+      // Check for special case: single underscore token (gettext macro).
+      if (Left.Tok.getIdentifierInfo() && !Style.SpaceBetweenUnderscoreParens &&
+          Left.TokenText == "_") {
+        return false;
+      }
       return spaceRequiredBeforeParens(Right);
     }
     return false;
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index d9db06667d802..e21c3956e65cc 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -17852,6 +17852,37 @@ TEST_F(FormatTest, ConfigurableSpacesInParens) {
                Spaces);
 }
 
+TEST_F(FormatTest, SpaceBetweenUnderscoreParens) {
+  FormatStyle Style = getLLVMStyle();
+  Style.SpaceBeforeParens = FormatStyle::SBPO_Always;
+
+  EXPECT_TRUE(Style.SpaceBetweenUnderscoreParens);
+  verifyFormat("func (arg);", Style);
+  verifyFormat("my_func (arg);", Style);
+  verifyFormat("_ (message);", Style);
+  verifyFormat("underscore_ (param);", Style);
+
+  Style.SpaceBetweenUnderscoreParens = false;
+  verifyFormat("func (arg);", Style);
+  verifyFormat("my_func (arg);", Style);
+  verifyFormat("_(message);", Style);
+  verifyFormat("underscore_ (param);", Style);
+  verifyFormat("_private_func (data);", Style);
+
+  FormatStyle GNUStyle = getGNUStyle();
+  EXPECT_FALSE(GNUStyle.SpaceBetweenUnderscoreParens);
+  EXPECT_EQ(GNUStyle.SpaceBeforeParens, FormatStyle::SBPO_Always);
+  verifyFormat("func (arg);", GNUStyle);
+  verifyFormat("my_func (arg);", GNUStyle);
+  verifyFormat("_(message);", GNUStyle);
+  verifyFormat("_private_func (data);", GNUStyle);
+
+  verifyFormat("printf (_(\"Hello\"));\n"
+               "func (arg);\n"
+               "_(\"World\");",
+               GNUStyle);
+}
+
 TEST_F(FormatTest, ConfigurableSpacesInSquareBrackets) {
   verifyFormat("int a[5];");
   verifyFormat("a[3] += 42;");



More information about the cfe-commits mailing list