mprobst updated this revision to Diff 49562.
mprobst marked 6 inline comments as done.
mprobst added a comment.

- Move code closer together by piping Replacements into the FormatTokenLexer.
- remove sstream import
- * Treat JavaScript single quoted string literals as tok::string_literal, not
- Address review comments, mostly renaming things.


http://reviews.llvm.org/D17385

Files:
  include/clang/Format/Format.h
  lib/Format/Format.cpp
  unittests/Format/FormatTest.cpp
  unittests/Format/FormatTestJS.cpp

Index: unittests/Format/FormatTestJS.cpp
===================================================================
--- unittests/Format/FormatTestJS.cpp
+++ unittests/Format/FormatTestJS.cpp
@@ -250,7 +250,7 @@
   verifyFormat("f({'a': [{}]});");
 }
 
-TEST_F(FormatTestJS, SingleQuoteStrings) {
+TEST_F(FormatTestJS, SingleQuotedStrings) {
   verifyFormat("this.function('', true);");
 }
 
@@ -636,7 +636,8 @@
 
 TEST_F(FormatTestJS, StringLiteralConcatenation) {
   verifyFormat("var literal = 'hello ' +\n"
-               "    'world';");
+               "    'worldworldworldworld';",
+               getGoogleJSStyleWithColumns(30));
 }
 
 TEST_F(FormatTestJS, RegexLiteralClassification) {
@@ -874,7 +875,7 @@
   verifyFormat("import {\n"
                "  X,\n"
                "  Y,\n"
-               "} from 'some/long/module.js';",
+               "} from\n    'some/long/module.js';",
                getGoogleJSStyleWithColumns(20));
   verifyFormat("import {X as myLocalX, Y as myLocalY} from 'some/module.js';");
   verifyFormat("import * as lib from 'some/module.js';");
@@ -1085,5 +1086,36 @@
                    getGoogleJSStyleWithColumns(20)));
 }
 
+TEST_F(FormatTestJS, RequoteStringsSingle) {
+  EXPECT_EQ("var x = 'foo';", format("var x = \"foo\";"));
+  EXPECT_EQ("var x = 'fo\\'o\\'';", format("var x = \"fo'o'\";"));
+  EXPECT_EQ("var x = 'fo\\'o\\'';", format("var x = \"fo\\'o'\";"));
+  EXPECT_EQ("var x =\n"
+            "    'foo\\'';",
+            // Code below is 15 chars wide, doesn't fit into the line with the
+            // \ escape added.
+            format("var x = \"foo'\";", getGoogleJSStyleWithColumns(15)));
+  // Removes no-longer needed \ escape from ".
+  EXPECT_EQ("var x = 'fo\"o';", format("var x = \"fo\\\"o\";"));
+  // Code below fits into 15 chars *after* removing the \ escape.
+  EXPECT_EQ("var x = 'fo\"o';",
+            format("var x = \"fo\\\"o\";", getGoogleJSStyleWithColumns(15)));
+}
+
+TEST_F(FormatTestJS, RequoteStringsDouble) {
+  FormatStyle DoubleQuotes = getGoogleStyle(FormatStyle::LK_JavaScript);
+  DoubleQuotes.JavaScriptQuotes = FormatStyle::JSQS_Double;
+  verifyFormat("var x = \"foo\";", DoubleQuotes);
+  EXPECT_EQ("var x = \"foo\";", format("var x = 'foo';", DoubleQuotes));
+  EXPECT_EQ("var x = \"fo'o\";", format("var x = 'fo\\'o';", DoubleQuotes));
+}
+
+TEST_F(FormatTestJS, RequoteStringsLeave) {
+  FormatStyle LeaveQuotes = getGoogleStyle(FormatStyle::LK_JavaScript);
+  LeaveQuotes.JavaScriptQuotes = FormatStyle::JSQS_Leave;
+  verifyFormat("var x = \"foo\";", LeaveQuotes);
+  verifyFormat("var x = 'foo';", LeaveQuotes);
+}
+
 } // end namespace tooling
 } // end namespace clang
Index: unittests/Format/FormatTest.cpp
===================================================================
--- unittests/Format/FormatTest.cpp
+++ unittests/Format/FormatTest.cpp
@@ -6642,7 +6642,7 @@
                "                   bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb};");
 
   verifyNoCrash("a<,");
-  
+
   // No braced initializer here.
   verifyFormat("void f() {\n"
                "  struct Dummy {};\n"
Index: lib/Format/Format.cpp
===================================================================
--- lib/Format/Format.cpp
+++ lib/Format/Format.cpp
@@ -71,6 +71,14 @@
   }
 };
 
+template <> struct ScalarEnumerationTraits<FormatStyle::JavaScriptQuoteStyle> {
+  static void enumeration(IO &IO, FormatStyle::JavaScriptQuoteStyle &Value) {
+    IO.enumCase(Value, "Leave", FormatStyle::JSQS_Leave);
+    IO.enumCase(Value, "Single", FormatStyle::JSQS_Single);
+    IO.enumCase(Value, "Double", FormatStyle::JSQS_Double);
+  }
+};
+
 template <> struct ScalarEnumerationTraits<FormatStyle::ShortFunctionStyle> {
   static void enumeration(IO &IO, FormatStyle::ShortFunctionStyle &Value) {
     IO.enumCase(Value, "None", FormatStyle::SFS_None);
@@ -335,6 +343,7 @@
     IO.mapOptional("Standard", Style.Standard);
     IO.mapOptional("TabWidth", Style.TabWidth);
     IO.mapOptional("UseTab", Style.UseTab);
+    IO.mapOptional("JavaScriptQuotes", Style.JavaScriptQuotes);
   }
 };
 
@@ -522,6 +531,7 @@
   LLVMStyle.SpacesBeforeTrailingComments = 1;
   LLVMStyle.Standard = FormatStyle::LS_Cpp11;
   LLVMStyle.UseTab = FormatStyle::UT_Never;
+  LLVMStyle.JavaScriptQuotes = FormatStyle::JSQS_Leave;
   LLVMStyle.ReflowComments = true;
   LLVMStyle.SpacesInParentheses = false;
   LLVMStyle.SpacesInSquareBrackets = false;
@@ -590,6 +600,7 @@
     GoogleStyle.CommentPragmas = "@(export|see|visibility) ";
     GoogleStyle.MaxEmptyLinesToKeep = 3;
     GoogleStyle.SpacesInContainerLiterals = false;
+    GoogleStyle.JavaScriptQuotes = FormatStyle::JSQS_Single;
   } else if (Language == FormatStyle::LK_Proto) {
     GoogleStyle.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_None;
     GoogleStyle.SpacesInContainerLiterals = false;
@@ -766,13 +777,13 @@
 class FormatTokenLexer {
 public:
   FormatTokenLexer(SourceManager &SourceMgr, FileID ID, FormatStyle &Style,
-                   encoding::Encoding Encoding)
+                   encoding::Encoding Encoding, tooling::Replacements &Replaces)
       : FormatTok(nullptr), IsFirstToken(true), GreaterStashed(false),
         LessStashed(false), Column(0), TrailingWhitespace(0),
         SourceMgr(SourceMgr), ID(ID), Style(Style),
         IdentTable(getFormattingLangOpts(Style)), Keywords(IdentTable),
-        Encoding(Encoding), FirstInLineIndex(0), FormattingDisabled(false),
-        MacroBlockBeginRegex(Style.MacroBlockBegin),
+        Encoding(Encoding), Replaces(Replaces), FirstInLineIndex(0),
+        FormattingDisabled(false), MacroBlockBeginRegex(Style.MacroBlockBegin),
         MacroBlockEndRegex(Style.MacroBlockEnd) {
     Lex.reset(new Lexer(ID, SourceMgr.getBuffer(ID), SourceMgr,
                         getFormattingLangOpts(Style)));
@@ -791,6 +802,8 @@
       if (Style.Language == FormatStyle::LK_JavaScript)
         tryParseJSRegexLiteral();
       tryMergePreviousTokens();
+      if (Style.Language == FormatStyle::LK_JavaScript)
+        tryRequoteJSStringLiteral();
       if (Tokens.back()->NewlinesBefore > 0 || Tokens.back()->IsMultiline)
         FirstInLineIndex = Tokens.size() - 1;
     } while (Tokens.back()->Tok.isNot(tok::eof));
@@ -1061,6 +1074,67 @@
     return false;
   }
 
+  // If the last token is a double/single-quoted string literal, generates a
+  // replacement with a single/double quoted string literal, re-escaping the
+  // contents in the process.
+  void tryRequoteJSStringLiteral() {
+    if (Style.JavaScriptQuotes == FormatStyle::JSQS_Leave)
+      return;
+    FormatToken *FormatTok = Tokens.back();
+
+    StringRef Input = FormatTok->TokenText;
+    if (!FormatTok->isStringLiteral() ||
+        // NB: testing for not starting with a double quote to avoid breaking
+        // `template strings`.
+        (Style.JavaScriptQuotes == FormatStyle::JSQS_Single &&
+         !Input.startswith("\"")) ||
+        (Style.JavaScriptQuotes == FormatStyle::JSQS_Double &&
+         !Input.startswith("\'")))
+      return;
+
+    char Quote = Style.JavaScriptQuotes == FormatStyle::JSQS_Single ? '\'' : '\"';
+
+    size_t ColumnWidth = FormatTok->TokenText.size();
+    std::string Res(1, Quote);
+    llvm::raw_string_ostream Out(Res);
+    bool Escaped = false;
+    for (size_t i = 1; i < Input.size() - 1; i++) {
+      switch (Input[i]) {
+      case '\\':
+        if (!Escaped && i + 1 < Input.size() &&
+            ((Quote == '\'' && Input[i + 1] == '"') ||
+             (Quote == '"' && Input[i + 1] == '\''))) {
+          // Skip this \, it's escaping a " or ' that no longer needs escaping.
+          ColumnWidth--;
+          continue;
+        }
+        Escaped = !Escaped;
+        break;
+      case '\"':
+      case '\'':
+        if (Input[i] == Quote && !Escaped) {
+          Out << '\\';
+          ColumnWidth++;
+        }
+        Escaped = false;
+        break;
+      default:
+        Escaped = false;
+        break;
+      }
+      Out << Input[i];
+    }
+    Out << Quote;
+
+    // For formatting, count the number of non-escaped single quotes in them
+    // and adjust ColumnWidth to take the added escapes into account.
+    FormatTok->ColumnWidth = ColumnWidth;
+
+    SourceRange Range(FormatTok->Tok.getLocation(), FormatTok->Tok.getEndLoc());
+    Replaces.insert(tooling::Replacement(
+        SourceMgr, CharSourceRange::getCharRange(Range), Out.str()));
+  }
+
   bool tryMerge_TMacro() {
     if (Tokens.size() < 4)
       return false;
@@ -1359,6 +1433,7 @@
   IdentifierTable IdentTable;
   AdditionalKeywords Keywords;
   encoding::Encoding Encoding;
+  tooling::Replacements &Replaces;
   llvm::SpecificBumpPtrAllocator<FormatToken> Allocator;
   // Index (in 'Tokens') of the last token that starts a new line.
   unsigned FirstInLineIndex;
@@ -1382,10 +1457,15 @@
         Tok.IsUnterminatedLiteral = true;
       } else if (Style.Language == FormatStyle::LK_JavaScript &&
                  Tok.TokenText == "''") {
-        Tok.Tok.setKind(tok::char_constant);
+        Tok.Tok.setKind(tok::string_literal);
       }
     }
 
+    if (Style.Language == FormatStyle::LK_JavaScript &&
+        Tok.is(tok::char_constant)) {
+      Tok.Tok.setKind(tok::string_literal);
+    }
+
     if (Tok.is(tok::comment) && (Tok.TokenText == "// clang-format on" ||
                                  Tok.TokenText == "/* clang-format on */")) {
       FormattingDisabled = false;
@@ -1443,7 +1523,7 @@
 
   tooling::Replacements format(bool *IncompleteFormat) {
     tooling::Replacements Result;
-    FormatTokenLexer Tokens(SourceMgr, ID, Style, Encoding);
+    FormatTokenLexer Tokens(SourceMgr, ID, Style, Encoding, Result);
 
     UnwrappedLineParser Parser(Style, Tokens.getKeywords(), Tokens.lex(),
                                *this);
Index: include/clang/Format/Format.h
===================================================================
--- include/clang/Format/Format.h
+++ include/clang/Format/Format.h
@@ -591,6 +591,20 @@
   /// \brief The way to use tab characters in the resulting file.
   UseTabStyle UseTab;
 
+  /// \brief Quotation styles for JavaScript strings. Does not affect template
+  /// strings.
+  enum JavaScriptQuoteStyle {
+    /// Leave string quotes as they are.
+    JSQS_Leave,
+    /// Always use single quotes.
+    JSQS_Single,
+    /// Always use double quotes.
+    JSQS_Double
+  };
+
+  /// \brief The JavaScriptQuoteStyle to use for JavaScript strings.
+  JavaScriptQuoteStyle JavaScriptQuotes;
+
   bool operator==(const FormatStyle &R) const {
     return AccessModifierOffset == R.AccessModifierOffset &&
            AlignAfterOpenBracket == R.AlignAfterOpenBracket &&
@@ -667,7 +681,8 @@
            SpacesInParentheses == R.SpacesInParentheses &&
            SpacesInSquareBrackets == R.SpacesInSquareBrackets &&
            Standard == R.Standard && TabWidth == R.TabWidth &&
-           UseTab == R.UseTab;
+           UseTab == R.UseTab &&
+           JavaScriptQuotes == R.JavaScriptQuotes;
   }
 };
 
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to