Skip to content

[clang-format] Add IndentPPDirectives Leave option #139750

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

gedare
Copy link
Contributor

@gedare gedare commented May 13, 2025

Allow an option to leave preprocessor directive indenting as-is. This simplifies handling mixed styles of CPP directive indentation.

Fixes #38511

gedare added 3 commits May 13, 2025 08:52
Allow an option to leave preprocessor directive indenting as-is.
This simplifies handling mixed styles of CPP directive indentation.

Fixes llvm#38511.
@llvmbot llvmbot added clang Clang issues not falling into any other category clang-format labels May 13, 2025
@llvmbot
Copy link
Member

llvmbot commented May 13, 2025

@llvm/pr-subscribers-clang-format

@llvm/pr-subscribers-clang

Author: Gedare Bloom (gedare)

Changes

Allow an option to leave preprocessor directive indenting as-is. This simplifies handling mixed styles of CPP directive indentation.

Fixes #38511


Full diff: https://github.com/llvm/llvm-project/pull/139750.diff

9 Files Affected:

  • (modified) clang/docs/ClangFormatStyleOptions.rst (+15)
  • (modified) clang/docs/ReleaseNotes.rst (+1)
  • (modified) clang/include/clang/Format/Format.h (+13-1)
  • (modified) clang/lib/Format/ContinuationIndenter.cpp (+6)
  • (modified) clang/lib/Format/Format.cpp (+1)
  • (modified) clang/lib/Format/TokenAnnotator.cpp (+2-1)
  • (modified) clang/lib/Format/UnwrappedLineFormatter.cpp (+12-5)
  • (modified) clang/lib/Format/UnwrappedLineParser.cpp (+6-3)
  • (modified) clang/unittests/Format/FormatTest.cpp (+61-15)
diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst
index a4c381bf583b6..99058bece63a0 100644
--- a/clang/docs/ClangFormatStyleOptions.rst
+++ b/clang/docs/ClangFormatStyleOptions.rst
@@ -4423,6 +4423,21 @@ the configuration (without a prefix: ``Auto``).
          #endif
        #endif
 
+  * ``PPDIS_Leave`` (in configuration: ``Leave``)
+    Leaves indentation of directives as-is.
+
+    .. note::
+
+     Ignores ``PPIndentWidth``.
+
+    .. code-block:: c++
+
+      #if FOO
+        #if BAR
+      #include <foo>
+        #endif
+      #endif
+
 
 
 .. _IndentRequiresClause:
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index bc13d02e2d20b..7baca95c8aea5 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -844,6 +844,7 @@ clang-format
   ``enum`` enumerator lists.
 - Add ``OneLineFormatOffRegex`` option for turning formatting off for one line.
 - Add ``SpaceAfterOperatorKeyword`` option.
+- Add ``Leave`` suboption to ``IndentPPDirectives``.
 
 libclang
 --------
diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h
index b86c4bd00eb91..07859bb7de68d 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -2976,7 +2976,19 @@ struct FormatStyle {
     ///      #endif
     ///    #endif
     /// \endcode
-    PPDIS_BeforeHash
+    PPDIS_BeforeHash,
+    /// Leaves indentation of directives as-is.
+    /// \note
+    ///  Ignores ``PPIndentWidth``.
+    /// \endnote
+    /// \code
+    ///   #if FOO
+    ///     #if BAR
+    ///   #include <foo>
+    ///     #endif
+    ///   #endif
+    /// \endcode
+    PPDIS_Leave
   };
 
   /// The preprocessor directive indenting style to use.
diff --git a/clang/lib/Format/ContinuationIndenter.cpp b/clang/lib/Format/ContinuationIndenter.cpp
index 55e1d1ceb55b7..06e3dda7a9c74 100644
--- a/clang/lib/Format/ContinuationIndenter.cpp
+++ b/clang/lib/Format/ContinuationIndenter.cpp
@@ -764,6 +764,12 @@ void ContinuationIndenter::addTokenOnCurrentLine(LineState &State, bool DryRun,
 
   unsigned Spaces = Current.SpacesRequiredBefore + ExtraSpaces;
 
+  if (Style.IndentPPDirectives == FormatStyle::PPDIS_Leave &&
+      State.Line->InPPDirective && Previous.is(tok::hash) &&
+      &Previous == State.Line->First) {
+    Spaces += Current.OriginalColumn - Previous.OriginalColumn - 1;
+  }
+
   // Indent preprocessor directives after the hash if required.
   int PPColumnCorrection = 0;
   if (Style.IndentPPDirectives == FormatStyle::PPDIS_AfterHash &&
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index 20b5352b83a9e..c8ec30c33fdfd 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -514,6 +514,7 @@ struct ScalarEnumerationTraits<FormatStyle::PPDirectiveIndentStyle> {
     IO.enumCase(Value, "None", FormatStyle::PPDIS_None);
     IO.enumCase(Value, "AfterHash", FormatStyle::PPDIS_AfterHash);
     IO.enumCase(Value, "BeforeHash", FormatStyle::PPDIS_BeforeHash);
+    IO.enumCase(Value, "Leave", FormatStyle::PPDIS_Leave);
   }
 };
 
diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp
index 542c362ccacae..520cec52465c9 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -3622,7 +3622,8 @@ void TokenAnnotator::setCommentLineLevels(
       // Align comments for preprocessor lines with the # in column 0 if
       // preprocessor lines are not indented. Otherwise, align with the next
       // line.
-      Line->Level = Style.IndentPPDirectives != FormatStyle::PPDIS_BeforeHash &&
+      Line->Level = (Style.IndentPPDirectives == FormatStyle::PPDIS_AfterHash ||
+                     Style.IndentPPDirectives == FormatStyle::PPDIS_None) &&
                             PPDirectiveOrImportStmt
                         ? 0
                         : NextNonCommentLine->Level;
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index 1298e3e7bab38..afd37cb71137b 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -62,10 +62,16 @@ class LevelIndentTracker {
     // having the right size in adjustToUnmodifiedline.
     if (Line.Level >= IndentForLevel.size())
       IndentForLevel.resize(Line.Level + 1, -1);
-    if (Style.IndentPPDirectives != FormatStyle::PPDIS_None &&
-        (Line.InPPDirective ||
-         (Style.IndentPPDirectives == FormatStyle::PPDIS_BeforeHash &&
-          Line.Type == LT_CommentAbovePPDirective))) {
+    if (Style.IndentPPDirectives == FormatStyle::PPDIS_Leave &&
+        Line.InPPDirective) {
+      Indent = Line.InMacroBody
+                   ? (Line.Level - Line.PPLevel) * Style.IndentWidth +
+                         AdditionalIndent
+                   : Line.First->OriginalColumn;
+    } else if (Style.IndentPPDirectives != FormatStyle::PPDIS_None &&
+               (Line.InPPDirective ||
+                (Style.IndentPPDirectives == FormatStyle::PPDIS_BeforeHash &&
+                 Line.Type == LT_CommentAbovePPDirective))) {
       unsigned PPIndentWidth =
           (Style.PPIndentWidth >= 0) ? Style.PPIndentWidth : Style.IndentWidth;
       Indent = Line.InMacroBody
@@ -1651,7 +1657,8 @@ void UnwrappedLineFormatter::formatFirstToken(
   // Preprocessor directives get indented before the hash only if specified. In
   // Javascript import statements are indented like normal statements.
   if (!Style.isJavaScript() &&
-      Style.IndentPPDirectives != FormatStyle::PPDIS_BeforeHash &&
+      (Style.IndentPPDirectives == FormatStyle::PPDIS_None ||
+       Style.IndentPPDirectives == FormatStyle::PPDIS_AfterHash) &&
       (Line.Type == LT_PreprocessorDirective ||
        Line.Type == LT_ImportStatement)) {
     Indent = 0;
diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp
index 2e138ff7ef59c..dd8e8bde126d3 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -164,7 +164,8 @@ UnwrappedLineParser::UnwrappedLineParser(
       LangOpts(getFormattingLangOpts(Style)), Keywords(Keywords),
       CommentPragmasRegex(Style.CommentPragmas), Tokens(nullptr),
       Callback(Callback), AllTokens(Tokens), PPBranchLevel(-1),
-      IncludeGuard(Style.IndentPPDirectives == FormatStyle::PPDIS_None
+      IncludeGuard((Style.IndentPPDirectives == FormatStyle::PPDIS_None ||
+                    Style.IndentPPDirectives == FormatStyle::PPDIS_Leave)
                        ? IG_Rejected
                        : IG_Inited),
       IncludeGuardToken(nullptr), FirstStartColumn(FirstStartColumn),
@@ -172,7 +173,8 @@ UnwrappedLineParser::UnwrappedLineParser(
 
 void UnwrappedLineParser::reset() {
   PPBranchLevel = -1;
-  IncludeGuard = Style.IndentPPDirectives == FormatStyle::PPDIS_None
+  IncludeGuard = Style.IndentPPDirectives == FormatStyle::PPDIS_None ||
+                         Style.IndentPPDirectives == FormatStyle::PPDIS_Leave
                      ? IG_Rejected
                      : IG_Inited;
   IncludeGuardToken = nullptr;
@@ -1142,7 +1144,8 @@ void UnwrappedLineParser::parsePPEndIf() {
   // If the #endif of a potential include guard is the last thing in the file,
   // then we found an include guard.
   if (IncludeGuard == IG_Defined && PPBranchLevel == -1 && Tokens->isEOF() &&
-      Style.IndentPPDirectives != FormatStyle::PPDIS_None) {
+      !(Style.IndentPPDirectives == FormatStyle::PPDIS_None ||
+        Style.IndentPPDirectives == FormatStyle::PPDIS_Leave)) {
     IncludeGuard = IG_Found;
   }
 }
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index 436beaf68bd2a..3908dc7536813 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -5522,7 +5522,6 @@ TEST_F(FormatTest, IndentsPPDirectiveWithPPIndentWidth) {
                "        x; \\\n"
                "    }",
                style);
-
   style.PPIndentWidth = 2;
   verifyFormat("#ifdef foo\n"
                "#define bar() \\\n"
@@ -5542,22 +5541,55 @@ TEST_F(FormatTest, IndentsPPDirectiveWithPPIndentWidth) {
                "#endif",
                style);
 
+  style.IndentPPDirectives = FormatStyle::PPDIS_Leave;
+  style.IndentWidth = 4;
+  verifyNoChange("#ifndef foo\n"
+                 "#define foo\n"
+                 "if (emacs) {\n"
+                 "#ifdef is\n"
+                 "#define lit           \\\n"
+                 "    if (af) {         \\\n"
+                 "        return duh(); \\\n"
+                 "    }\n"
+                 "#endif\n"
+                 "}\n"
+                 "#endif",
+                 style);
+  verifyNoChange("#ifndef foo\n"
+                 "  #define foo\n"
+                 "if (emacs) {\n"
+                 "  #ifdef is\n"
+                 "#define lit           \\\n"
+                 "    if (af) {         \\\n"
+                 "        return duh(); \\\n"
+                 "    }\n"
+                 "  #endif\n"
+                 "}\n"
+                 "#endif",
+                 style);
+  verifyNoChange("  #ifndef foo\n"
+                 "#  define foo\n"
+                 "if (emacs) {\n"
+                 "#ifdef is\n"
+                 "  #  define lit       \\\n"
+                 "    if (af) {         \\\n"
+                 "        return duh(); \\\n"
+                 "    }\n"
+                 "#endif\n"
+                 "}\n"
+                 "  #endif",
+                 style);
+
   style.IndentWidth = 1;
   style.PPIndentWidth = 4;
-  verifyFormat("#if 1\n"
-               "#define X \\\n"
-               " {        \\\n"
-               "  x;      \\\n"
-               "  x;      \\\n"
-               " }\n"
-               "#endif",
-               style);
-  verifyFormat("#define X \\\n"
-               " {        \\\n"
-               "  x;      \\\n"
-               "  x;      \\\n"
-               " }",
-               style);
+  verifyNoChange("# if 1\n"
+                 "  #define X \\\n"
+                 " {          \\\n"
+                 "  x;        \\\n"
+                 "  x;        \\\n"
+                 " }\n"
+                 "# endif",
+                 style);
 
   style.IndentWidth = 4;
   style.PPIndentWidth = 1;
@@ -25843,6 +25875,20 @@ TEST_F(FormatTest, SkipMacroDefinitionBody) {
                  "a",
                  Style);
 
+  Style.IndentPPDirectives = FormatStyle::PPDIS_Leave;
+  verifyNoChange("#if A\n"
+                 "#define A a\n"
+                 "#endif",
+                 Style);
+  verifyNoChange("#if A\n"
+                 "  #define A a\n"
+                 "#endif",
+                 Style);
+  verifyNoChange("#if A\n"
+                 "#  define A a\n"
+                 "#endif",
+                 Style);
+
   // Adjust indendations but don't change the definition.
   Style.IndentPPDirectives = FormatStyle::PPDIS_None;
   verifyNoChange("#if A\n"

@gedare
Copy link
Contributor Author

gedare commented May 13, 2025

This change can help support some of the goals stated in #35368 but doesn't exactly capture the intent of that Issue, which adds a requirement to make the PP indent level also advance the indent level. I am not currently interested in that particular feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang Clang issues not falling into any other category clang-format
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Extend IndentPPDirectives with a LeaveAsIs option
2 participants