russellmcc created this revision.
russellmcc added a reviewer: djasper.
Herald added a subscriber: cfe-commits.

When clang-format is set to always use tabs (with tab width 4), when asked to 
format line 3 (using the `-lines=3:3` flag):

  int f() {
      int a;
      foobar();
      int b;
  }

We would expect clang-format to not modify the leading whitespace on line 4.  
However, it does - see the new test.  This is because the whitespace manager is 
called to replace the whitespace before the first token of line 4.  This is 
necessary to edit the number of new lines before line 4, and to edit the 
trailing whitespace on line 3.  I've added a flag to `replaceWhitespace` that 
allows it to not edit the leading whitespace, and only edit the whitespace up 
to the last newline.

We ran into this when trying to integrate clang-format into our CI to ensure no 
new formatting diffs were introduced in a patch.  With the behavior without 
this patch, the number of affected lines grew each time clang-format was run, 
so running clang-format locally was not enough to ensure that CI would pass.


Repository:
  rC Clang

https://reviews.llvm.org/D54881

Files:
  lib/Format/UnwrappedLineFormatter.cpp
  lib/Format/UnwrappedLineFormatter.h
  lib/Format/WhitespaceManager.cpp
  lib/Format/WhitespaceManager.h
  unittests/Format/FormatTest.cpp

Index: unittests/Format/FormatTest.cpp
===================================================================
--- unittests/Format/FormatTest.cpp
+++ unittests/Format/FormatTest.cpp
@@ -36,12 +36,12 @@
     SC_DoNotCheck
   };
 
-  std::string format(llvm::StringRef Code,
-                     const FormatStyle &Style = getLLVMStyle(),
-                     StatusCheck CheckComplete = SC_ExpectComplete) {
+  std::string formatRange(llvm::StringRef Code, tooling::Range Range,
+                          const FormatStyle &Style = getLLVMStyle(),
+                          StatusCheck CheckComplete = SC_ExpectComplete) {
     LLVM_DEBUG(llvm::errs() << "---\n");
     LLVM_DEBUG(llvm::errs() << Code << "\n\n");
-    std::vector<tooling::Range> Ranges(1, tooling::Range(0, Code.size()));
+    std::vector<tooling::Range> Ranges(1, std::move(Range));
     FormattingAttemptStatus Status;
     tooling::Replacements Replaces =
         reformat(Style, Code, Ranges, "<stdin>", &Status);
@@ -57,6 +57,13 @@
     return *Result;
   }
 
+  std::string format(llvm::StringRef Code,
+                     const FormatStyle &Style = getLLVMStyle(),
+                     StatusCheck CheckComplete = SC_ExpectComplete) {
+    return formatRange(Code, tooling::Range(0, Code.size()), Style,
+                       CheckComplete);
+  }
+
   FormatStyle getStyleWithColumns(FormatStyle Style, unsigned ColumnLimit) {
     Style.ColumnLimit = ColumnLimit;
     return Style;
@@ -110,6 +117,14 @@
     format(Code, Style, SC_DoNotCheck);
   }
 
+  void verifyWithPrefixAndSuffix(llvm::StringRef Expected, llvm::StringRef Code,
+                                 llvm::StringRef prefix, llvm::StringRef suffix,
+                                 const FormatStyle &Style = getLLVMStyle()) {
+    EXPECT_EQ(llvm::Twine(prefix + Expected + suffix).str(),
+              formatRange(llvm::Twine(prefix + Code + suffix).str(),
+                          tooling::Range(prefix.size(), Code.size()), Style));
+  }
+
   int ReplacementCount;
 };
 
@@ -9141,6 +9156,7 @@
                    "\t */\n"
                    "\t int i;\n"
                    "}"));
+
   Tab.AlignConsecutiveAssignments = true;
   Tab.AlignConsecutiveDeclarations = true;
   Tab.TabWidth = 4;
@@ -9156,6 +9172,18 @@
                Tab);
 }
 
+TEST_F(FormatTest, FormattingIndentationDoesNotAffectOtherLines) {
+  FormatStyle Tab = getLLVMStyleWithColumns(42);
+  Tab.TabWidth = 4;
+  Tab.UseTab = FormatStyle::UT_Always;
+  verifyWithPrefixAndSuffix("\tfoobar();", "    foobar();",
+                            "int f() {\n    int a;\n", "\n    int b;\n}\n",
+                            Tab);
+  Tab.UseTab = FormatStyle::UT_Never;
+  verifyWithPrefixAndSuffix("    foobar();", "\tfoobar();",
+                            "int f() {\n\tint a;\n", "\n\tint b;\n}\n", Tab);
+}
+
 TEST_F(FormatTest, CalculatesOriginalColumn) {
   EXPECT_EQ("\"qqqqqqqqqqqqqqqqqqqqqqqqqq\\\n"
             "q\"; /* some\n"
Index: lib/Format/WhitespaceManager.h
===================================================================
--- lib/Format/WhitespaceManager.h
+++ lib/Format/WhitespaceManager.h
@@ -49,7 +49,8 @@
   /// into tabs and spaces for some format styles.
   void replaceWhitespace(FormatToken &Tok, unsigned Newlines, unsigned Spaces,
                          unsigned StartOfTokenColumn,
-                         bool InPPDirective = false);
+                         bool InPPDirective = false,
+                         bool OnlyUntilLastNewline = false);
 
   /// Adds information about an unchangeable token's whitespace.
   ///
Index: lib/Format/WhitespaceManager.cpp
===================================================================
--- lib/Format/WhitespaceManager.cpp
+++ lib/Format/WhitespaceManager.cpp
@@ -46,13 +46,23 @@
 void WhitespaceManager::replaceWhitespace(FormatToken &Tok, unsigned Newlines,
                                           unsigned Spaces,
                                           unsigned StartOfTokenColumn,
-                                          bool InPPDirective) {
+                                          bool InPPDirective,
+                                          bool OnlyUntilLastNewline) {
   if (Tok.Finalized)
     return;
   Tok.Decision = (Newlines > 0) ? FD_Break : FD_Continue;
-  Changes.push_back(Change(Tok, /*CreateReplacement=*/true, Tok.WhitespaceRange,
-                           Spaces, StartOfTokenColumn, Newlines, "", "",
-                           InPPDirective && !Tok.IsFirst,
+  auto OriginalWhitespaceRange = Tok.WhitespaceRange;
+
+  if (OnlyUntilLastNewline) {
+    OriginalWhitespaceRange.setEnd(
+        OriginalWhitespaceRange.getBegin().getLocWithOffset(
+            Tok.LastNewlineOffset));
+    Spaces = 0;
+    StartOfTokenColumn = 0;
+  }
+  Changes.push_back(Change(Tok, /*CreateReplacement=*/true,
+                           OriginalWhitespaceRange, Spaces, StartOfTokenColumn,
+                           Newlines, "", "", InPPDirective && !Tok.IsFirst,
                            /*IsInsideToken=*/false));
 }
 
Index: lib/Format/UnwrappedLineFormatter.h
===================================================================
--- lib/Format/UnwrappedLineFormatter.h
+++ lib/Format/UnwrappedLineFormatter.h
@@ -51,7 +51,8 @@
   void formatFirstToken(const AnnotatedLine &Line,
                         const AnnotatedLine *PreviousLine,
                         const SmallVectorImpl<AnnotatedLine *> &Lines,
-                        unsigned Indent, unsigned NewlineIndent);
+                        unsigned Indent, unsigned NewlineIndent,
+                        bool OnlyUntilLastNewline = false);
 
   /// Returns the column limit for a line, taking into account whether we
   /// need an escaped newline due to a continued preprocessor directive.
Index: lib/Format/UnwrappedLineFormatter.cpp
===================================================================
--- lib/Format/UnwrappedLineFormatter.cpp
+++ lib/Format/UnwrappedLineFormatter.cpp
@@ -1113,7 +1113,7 @@
         if (ReformatLeadingWhitespace)
           formatFirstToken(TheLine, PreviousLine, Lines,
                            TheLine.First->OriginalColumn,
-                           TheLine.First->OriginalColumn);
+                           TheLine.First->OriginalColumn, true);
         else
           Whitespaces->addUntouchableToken(*TheLine.First,
                                            TheLine.InPPDirective);
@@ -1136,7 +1136,7 @@
 void UnwrappedLineFormatter::formatFirstToken(
     const AnnotatedLine &Line, const AnnotatedLine *PreviousLine,
     const SmallVectorImpl<AnnotatedLine *> &Lines, unsigned Indent,
-    unsigned NewlineIndent) {
+    unsigned NewlineIndent, bool OnlyUntilLastNewline) {
   FormatToken &RootToken = *Line.First;
   if (RootToken.is(tok::eof)) {
     unsigned Newlines = std::min(RootToken.NewlinesBefore, 1u);
@@ -1188,7 +1188,8 @@
 
   Whitespaces->replaceWhitespace(RootToken, Newlines, Indent, Indent,
                                  Line.InPPDirective &&
-                                     !RootToken.HasUnescapedNewline);
+                                     !RootToken.HasUnescapedNewline,
+                                 OnlyUntilLastNewline);
 }
 
 unsigned
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to