Index: lib/Format/Format.cpp =================================================================== --- lib/Format/Format.cpp +++ lib/Format/Format.cpp @@ -792,47 +792,25 @@ namespace { -class Formatter : public TokenAnalyzer { +class JavaScriptRequoter : public TokenAnalyzer { public: - Formatter(const Environment &Env, const FormatStyle &Style, - bool *IncompleteFormat) - : TokenAnalyzer(Env, Style), IncompleteFormat(IncompleteFormat) {} + JavaScriptRequoter(const Environment &Env, const FormatStyle &Style) + : TokenAnalyzer(Env, Style) {} tooling::Replacements analyze(TokenAnnotator &Annotator, SmallVectorImpl &AnnotatedLines, FormatTokenLexer &Tokens) override { - tooling::Replacements RequoteReplaces; - deriveLocalStyle(AnnotatedLines); AffectedRangeMgr.computeAffectedLines(AnnotatedLines.begin(), AnnotatedLines.end()); - - if (Style.Language == FormatStyle::LK_JavaScript && - Style.JavaScriptQuotes != FormatStyle::JSQS_Leave) - requoteJSStringLiteral(AnnotatedLines, RequoteReplaces); - - for (unsigned i = 0, e = AnnotatedLines.size(); i != e; ++i) { - Annotator.calculateFormattingInformation(*AnnotatedLines[i]); - } - - Annotator.setCommentLineLevels(AnnotatedLines); - - WhitespaceManager Whitespaces( - Env.getSourceManager(), Style, - inputUsesCRLF(Env.getSourceManager().getBufferData(Env.getFileID()))); - ContinuationIndenter Indenter(Style, Tokens.getKeywords(), - Env.getSourceManager(), Whitespaces, Encoding, - BinPackInconclusiveFunctions); - UnwrappedLineFormatter(&Indenter, &Whitespaces, Style, Tokens.getKeywords(), - IncompleteFormat) - .format(AnnotatedLines); - return RequoteReplaces.merge(Whitespaces.generateReplacements()); + tooling::Replacements Result; + requoteJSStringLiteral(AnnotatedLines, Result); + return Result; } private: - // 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. + // Replaces double/single-quoted string literal as appropriate, re-escaping + // the contents in the process. void requoteJSStringLiteral(SmallVectorImpl &Lines, tooling::Replacements &Result) { for (AnnotatedLine *Line : Lines) { @@ -844,8 +822,7 @@ StringRef Input = FormatTok->TokenText; if (FormatTok->Finalized || !FormatTok->isStringLiteral() || // NB: testing for not starting with a double quote to avoid - // breaking - // `template strings`. + // breaking `template strings`. (Style.JavaScriptQuotes == FormatStyle::JSQS_Single && !Input.startswith("\"")) || (Style.JavaScriptQuotes == FormatStyle::JSQS_Double && @@ -870,7 +847,6 @@ IsSingle ? "'" : "\""); // Escape internal quotes. - size_t ColumnWidth = FormatTok->TokenText.size(); bool Escaped = false; for (size_t i = 1; i < Input.size() - 1; i++) { switch (Input[i]) { @@ -880,7 +856,6 @@ (!IsSingle && Input[i + 1] == '\''))) { // Remove this \, it's escaping a " or ' that no longer needs // escaping - ColumnWidth--; Replace(Start.getLocWithOffset(i), 1, ""); continue; } @@ -891,7 +866,6 @@ if (!Escaped && IsSingle == (Input[i] == '\'')) { // Escape the quote. Replace(Start.getLocWithOffset(i), 0, "\\"); - ColumnWidth++; } Escaped = false; break; @@ -900,16 +874,46 @@ break; } } - - // For formatting, count the number of non-escaped single quotes in them - // and adjust ColumnWidth to take the added escapes into account. - // FIXME(martinprobst): this might conflict with code breaking a long - // string literal (which clang-format doesn't do, yet). For that to - // work, this code would have to modify TokenText directly. - FormatTok->ColumnWidth = ColumnWidth; } } } +}; + +class Formatter : public TokenAnalyzer { +public: + Formatter(const Environment &Env, const FormatStyle &Style, + bool *IncompleteFormat) + : TokenAnalyzer(Env, Style), IncompleteFormat(IncompleteFormat) {} + + tooling::Replacements + analyze(TokenAnnotator &Annotator, + SmallVectorImpl &AnnotatedLines, + FormatTokenLexer &Tokens) override { + tooling::Replacements Result; + deriveLocalStyle(AnnotatedLines); + AffectedRangeMgr.computeAffectedLines(AnnotatedLines.begin(), + AnnotatedLines.end()); + for (unsigned i = 0, e = AnnotatedLines.size(); i != e; ++i) { + Annotator.calculateFormattingInformation(*AnnotatedLines[i]); + } + Annotator.setCommentLineLevels(AnnotatedLines); + + WhitespaceManager Whitespaces( + Env.getSourceManager(), Style, + inputUsesCRLF(Env.getSourceManager().getBufferData(Env.getFileID()))); + ContinuationIndenter Indenter(Style, Tokens.getKeywords(), + Env.getSourceManager(), Whitespaces, Encoding, + BinPackInconclusiveFunctions); + UnwrappedLineFormatter(&Indenter, &Whitespaces, Style, Tokens.getKeywords(), + IncompleteFormat) + .format(AnnotatedLines); + for (const auto &R : Whitespaces.generateReplacements()) + if (Result.add(R)) + return Result; + return Result; + } + +private: static bool inputUsesCRLF(StringRef Text) { return Text.count('\r') * 2 > Text.count('\n'); @@ -1691,8 +1695,24 @@ if (Expanded.DisableFormat) return tooling::Replacements(); - std::unique_ptr Env = - Environment::CreateVirtualEnvironment(Code, FileName, Ranges); + auto Env = Environment::CreateVirtualEnvironment(Code, FileName, Ranges); + + if (Style.Language == FormatStyle::LK_JavaScript && + Style.JavaScriptQuotes != FormatStyle::JSQS_Leave) { + JavaScriptRequoter Requoter(*Env, Expanded); + tooling::Replacements Requotes = Requoter.process(); + if (!Requotes.empty()) { + auto NewCode = applyAllReplacements(Code, Requotes); + if (NewCode) { + auto NewEnv = Environment::CreateVirtualEnvironment( + *NewCode, FileName, + tooling::calculateRangesAfterReplacements(Requotes, Ranges)); + Formatter Format(*NewEnv, Expanded, IncompleteFormat); + return Requotes.merge(Format.process()); + } + } + } + Formatter Format(*Env, Expanded, IncompleteFormat); return Format.process(); } Index: unittests/Format/FormatTestJS.cpp =================================================================== --- unittests/Format/FormatTestJS.cpp +++ unittests/Format/FormatTestJS.cpp @@ -1342,6 +1342,14 @@ " 'double quoted string that needs wrapping');", "let x = someVeryLongFunctionThatGoesOnAndOn(" "\"double quoted string that needs wrapping\");"); + + verifyFormat("let x =\n" + " 'foo\\'oo';\n" + "let x =\n" + " 'foo\\'oo';", + "let x=\"foo'oo\";\n" + "let x=\"foo'oo\";", + getGoogleJSStyleWithColumns(15)); } TEST_F(FormatTestJS, RequoteStringsDouble) {