Index: lib/Format/Format.cpp =================================================================== --- lib/Format/Format.cpp +++ lib/Format/Format.cpp @@ -30,6 +30,7 @@ #include "llvm/Support/Regex.h" #include "llvm/Support/YAMLTraits.h" #include +#include #include #define DEBUG_TYPE "format-formatter" @@ -1328,6 +1329,35 @@ Column = FormatTok->LastLineColumnWidth; } + if (Style.Language == FormatStyle::LK_JavaScript && + FormatTok->isStringLiteral() && FormatTok->TokenText.startswith("\"")) { + // Double quoted JavaScript strings get requoted as single quoted below. + // For formatting, count the number of non-escaped single quotes in them + // and adjust ColumnWidth to take the later added escapes into account. + + StringRef Input = FormatTok->TokenText; + bool Escaped = false; + size_t ColumnWidth = FormatTok->TokenText.size(); + for (size_t i = 0; i != Input.size(); ++i) { + switch (Input[i]) { + case '\\': + Escaped = !Escaped; + break; + case '\'': + if (!Escaped) { + // Will later need to insert a \ to escape the double quote. + ColumnWidth++; + } + Escaped = false; + break; + default: + Escaped = false; + break; + } + } + FormatTok->ColumnWidth = ColumnWidth; + } + if (Style.Language == FormatStyle::LK_Cpp) { if (!(Tokens.size() > 0 && Tokens.back()->Tok.getIdentifierInfo() && Tokens.back()->Tok.getIdentifierInfo()->getPPKeywordID() == @@ -1495,7 +1525,12 @@ UnwrappedLineFormatter(&Indenter, &Whitespaces, Style, Tokens.getKeywords(), IncompleteFormat) .format(AnnotatedLines); - return Whitespaces.generateReplacements(); + tooling::Replacements Replaces = Whitespaces.generateReplacements(); + if (Style.Language == FormatStyle::LK_JavaScript) { + FormatToken *Tok = (*AnnotatedLines.begin())->First; + requoteJSStringLiterals(Replaces, Tok); + } + return Replaces; } private: @@ -1538,6 +1573,42 @@ return SomeLineAffected; } + // Finds all double-quoted string literals, and replaces them with single + // quoted string literals, escaping the contents in the process. + // The width of the extra \ escapes is taken into account in getNextToken. + void requoteJSStringLiterals(tooling::Replacements &Replaces, + FormatToken *Current) { + for (; Current != nullptr; Current = Current->Next) { + if (!Current->isStringLiteral() || !Current->TokenText.startswith("\"")) + continue; + StringRef Input = Current->TokenText; + std::stringstream Res; + Res << '\''; + bool escaped = false; + for (size_t i = 1; i < Input.size() - 1; i++) { + switch (Input[i]) { + case '\\': + escaped = !escaped; + break; + case '\'': + if (!escaped) { + Res << '\\'; + } + escaped = false; + break; + default: + escaped = false; + break; + } + Res << Input[i]; + } + Res << '\''; + SourceRange Range(Current->Tok.getLocation(), Current->Tok.getEndLoc()); + Replaces.insert(tooling::Replacement( + SourceMgr, CharSourceRange::getCharRange(Range), Res.str())); + } + } + // Determines whether 'Line' is affected by the SourceRanges given as input. // Returns \c true if line or one if its children is affected. bool nonPPLineAffected(AnnotatedLine *Line, 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);"); } @@ -1085,5 +1085,16 @@ getGoogleJSStyleWithColumns(20))); } +TEST_F(FormatTestJS, RequoteDoubleQuotedStrings) { + 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 + // escaped added. + format("var x = \"foo'\";", getGoogleJSStyleWithColumns(15))); +} + } // end namespace tooling } // end namespace clang