]> granicus.if.org Git - clang/commitdiff
Fixes use of unescaped newlines when formatting preprocessor directives.
authorManuel Klimek <klimek@google.com>
Wed, 2 Jan 2013 16:30:12 +0000 (16:30 +0000)
committerManuel Klimek <klimek@google.com>
Wed, 2 Jan 2013 16:30:12 +0000 (16:30 +0000)
This is the first step towards handling preprocessor directives. This
patch only fixes the most pressing issue, namely correctly escaping
newlines for tokens within a sequence of a preprocessor directive.

The next step will be to fix incorrect format decisions on #define
directives.

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@171393 91177308-0d34-0410-b5e6-96231b3b80d8

lib/Format/Format.cpp
lib/Format/UnwrappedLineParser.cpp
lib/Format/UnwrappedLineParser.h
unittests/Format/FormatTest.cpp

index 60b2f56745f9c0898d14b6fa786835c7c1e5cba8..8ea95c48621c4ca17f11cd04655e240eb069f264 100644 (file)
@@ -434,9 +434,21 @@ private:
   /// each \c FormatToken.
   void replaceWhitespace(const FormatToken &Tok, unsigned NewLines,
                          unsigned Spaces) {
+    std::string NewLineText;
+    if (!Line.InPPDirective) {
+      NewLineText = std::string(NewLines, '\n');
+    } else if (NewLines > 0) {
+      unsigned Offset =
+          SourceMgr.getSpellingColumnNumber(Tok.WhiteSpaceStart) - 1;
+      for (unsigned i = 0; i < NewLines; ++i) {
+        NewLineText += std::string(Style.ColumnLimit - Offset - 1, ' ');
+        NewLineText += "\\\n";
+        Offset = 0;
+      }
+    }
     Replaces.insert(tooling::Replacement(
         SourceMgr, Tok.WhiteSpaceStart, Tok.WhiteSpaceLength,
-        std::string(NewLines, '\n') + std::string(Spaces, ' ')));
+        NewLineText + std::string(Spaces, ' ')));
   }
 
   /// \brief Add a new line and the required indent before the first Token
@@ -634,6 +646,9 @@ public:
       next();
       if (Index >= Tokens.size())
         return;
+      // It is the responsibility of the UnwrappedLineParser to make sure
+      // this sequence is not produced inside an unwrapped line.
+      assert(Tokens[Index].Tok.getIdentifierInfo() != NULL);
       switch (Tokens[Index].Tok.getIdentifierInfo()->getPPKeywordID()) {
       case tok::pp_include:
       case tok::pp_import:
@@ -969,7 +984,10 @@ public:
 
     // Consume and record whitespace until we find a significant token.
     while (FormatTok.Tok.is(tok::unknown)) {
-      FormatTok.NewlinesBefore += tokenText(FormatTok.Tok).count('\n');
+      StringRef Text = tokenText(FormatTok.Tok);
+      FormatTok.NewlinesBefore += Text.count('\n');
+      FormatTok.HasUnescapedNewline =
+          Text.count("\\\n") != FormatTok.NewlinesBefore;
       FormatTok.WhiteSpaceLength += FormatTok.Tok.getLength();
 
       if (FormatTok.Tok.is(tok::eof))
index 78a1abdcf8df5d4a027e3fdcfff08aa7f8e46e83..614125b9437c6d5231e65f62c23752668571f526 100644 (file)
@@ -80,13 +80,25 @@ bool UnwrappedLineParser::parseBlock(unsigned AddLevels) {
 }
 
 void UnwrappedLineParser::parsePPDirective() {
-  while (!eof()) {
-    nextToken();
-    if (FormatTok.NewlinesBefore > 0) {
-      addUnwrappedLine();
-      return;
-    }
+  assert(FormatTok.Tok.is(tok::hash) && "'#' expected");
+  nextToken();
+
+  Line.InPPDirective = true;
+  if (FormatTok.Tok.getIdentifierInfo() == NULL) {
+    addUnwrappedLine();
+    Line.InPPDirective = false;
+    return;
   }
+
+  do {
+    if (FormatTok.NewlinesBefore > 0 &&
+        FormatTok.HasUnescapedNewline) {
+      break;
+    }
+    nextToken();
+  } while (!eof());
+  addUnwrappedLine();
+  Line.InPPDirective = false;
 }
 
 void UnwrappedLineParser::parseComments() {
index ccf437f50744b1b5ae245f75019c6b52eab1743e..9ed796d45b4297103b5a8058c54e46b2f2490af8 100644 (file)
@@ -30,7 +30,8 @@ namespace format {
 /// \brief A wrapper around a \c Token storing information about the
 /// whitespace characters preceeding it.
 struct FormatToken {
-  FormatToken() : NewlinesBefore(0), WhiteSpaceLength(0) {
+  FormatToken()
+      : NewlinesBefore(0), HasUnescapedNewline(false), WhiteSpaceLength(0) {
   }
 
   /// \brief The \c Token.
@@ -42,6 +43,10 @@ struct FormatToken {
   /// and thereby e.g. leave an empty line between two function definitions.
   unsigned NewlinesBefore;
 
+  /// \brief Whether there is at least one unescaped newline before the \c
+  /// Token.
+  bool HasUnescapedNewline;
+
   /// \brief The location of the start of the whitespace immediately preceeding
   /// the \c Token.
   ///
@@ -60,7 +65,7 @@ struct FormatToken {
 /// \c UnwrappedLineFormatter. The key property is that changing the formatting
 /// within an unwrapped line does not affect any other unwrapped lines.
 struct UnwrappedLine {
-  UnwrappedLine() : Level(0) {
+  UnwrappedLine() : Level(0), InPPDirective(false) {
   }
 
   /// \brief The \c Token comprising this \c UnwrappedLine.
@@ -68,6 +73,9 @@ struct UnwrappedLine {
 
   /// \brief The indent level of the \c UnwrappedLine.
   unsigned Level;
+
+  /// \brief Whether this \c UnwrappedLine is part of a preprocessor directive.
+  bool InPPDirective;
 };
 
 class UnwrappedLineConsumer {
index b52a9fd7c372e698c96be440595a08a1a35a80e4..daae94bb13edc30cb85b43a73f35063331c6bfaf 100644 (file)
@@ -373,6 +373,37 @@ TEST_F(FormatTest, StaticInitializers) {
       "    \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\" };");
 }
 
+TEST_F(FormatTest, FormatsSmallMacroDefinitionsInSingleLine) {
+  verifyFormat("#define ALooooooooooooooooooooooooooooooooooooooongMacro("
+               "                      \\\n"
+               "    aLoooooooooooooooooooooooongFuuuuuuuuuuuuuunctiooooooooo)");
+}
+
+TEST_F(FormatTest, BreaksOnHashWhenDirectiveIsInvalid) {
+  EXPECT_EQ("#\n;", format("#;"));
+}
+
+TEST_F(FormatTest, UnescapedEndOfLineEndsPPDirective) {
+  EXPECT_EQ("#line 42 \"test\"\n",
+            format("#  \\\n  line  \\\n  42  \\\n  \"test\"\n"));
+  EXPECT_EQ("#define A B\n", format("#  \\\n define  \\\n    A  \\\n    B\n"));
+}
+
+TEST_F(FormatTest, EndOfFileEndsPPDirective) {
+  EXPECT_EQ("#line 42 \"test\"",
+            format("#  \\\n  line  \\\n  42  \\\n  \"test\""));
+  EXPECT_EQ("#define A B", format("#  \\\n define  \\\n    A  \\\n    B"));
+}
+
+TEST_F(FormatTest, MixingPreprocessorDirectivesAndNormalCode) {
+  verifyFormat("#define ALooooooooooooooooooooooooooooooooooooooongMacro("
+               "                      \\\n"
+               "    aLoooooooooooooooooooooooongFuuuuuuuuuuuuuunctiooooooooo)\n"
+               "\n"
+               "AlooooooooooooooooooooooooooooooooooooooongCaaaaaaaaaal(\n"
+               "    aLooooooooooooooooooooooonPaaaaaaaaaaaaaaaaaaaaarmmmm);\n");
+}
+
 //===----------------------------------------------------------------------===//
 // Line break tests.
 //===----------------------------------------------------------------------===//