Index: lib/Format/Format.cpp =================================================================== --- lib/Format/Format.cpp +++ lib/Format/Format.cpp @@ -617,7 +617,7 @@ do { Tokens.push_back(getNextToken()); tryMergePreviousTokens(); - if (Tokens.back()->NewlinesBefore > 0) + if (Tokens.back()->NewlinesBefore > 0 || Tokens.back()->IsMultiline) FirstInLineIndex = Tokens.size() - 1; } while (Tokens.back()->Tok.isNot(tok::eof)); return Tokens; @@ -639,6 +639,8 @@ return; if (tryMergeEscapeSequence()) return; + if (tryMergeTemplateString()) + return; static tok::TokenKind JSIdentity[] = {tok::equalequal, tok::equal}; static tok::TokenKind JSNotIdentity[] = {tok::exclaimequal, tok::equal}; @@ -779,6 +781,42 @@ return false; } + bool tryMergeTemplateString() { + if (Tokens.size() < 2) + return false; + FormatToken *End = Tokens.back(); + if (!(End->is(tok::unknown) && End->TokenText == "`")) + return false; + unsigned TokenCount = 0; + unsigned LastColumn = End->OriginalColumn; + bool IsMultiline = false; + const char *EndOffset = End->TokenText.data() + 1; + for (auto I = Tokens.rbegin() + 1, E = Tokens.rend(); I != E; I++) { + ++TokenCount; + IsMultiline = + IsMultiline || I[0]->NewlinesBefore > 0 || I[0]->IsMultiline; + if (I[0]->isNot(tok::unknown) || I[0]->TokenText != "`") + continue; + + Tokens.resize(Tokens.size() - TokenCount); + Tokens.back()->Type = TT_TemplateString; + Tokens.back()->TokenText = + StringRef(Tokens.back()->TokenText.data(), + EndOffset - Tokens.back()->TokenText.data()); + if (!IsMultiline) { + auto StartCol = + SourceMgr.getSpellingColumnNumber(Tokens.back()->Tok.getLocation()); + auto EndCol = + SourceMgr.getSpellingColumnNumber(End->Tok.getLocation()) + 1; + Tokens.back()->ColumnWidth = EndCol - StartCol; + } + Tokens.back()->LastLineColumnWidth = LastColumn; + Tokens.back()->IsMultiline = IsMultiline; + return true; + } + return false; + } + bool tryMerge_TMacro() { if (Tokens.size() < 4) return false; Index: lib/Format/FormatToken.h =================================================================== --- lib/Format/FormatToken.h +++ lib/Format/FormatToken.h @@ -70,6 +70,7 @@ TT_StartOfName, TT_TemplateCloser, TT_TemplateOpener, + TT_TemplateString, TT_TrailingAnnotation, TT_TrailingReturnArrow, TT_TrailingUnaryOperator, Index: unittests/Format/FormatTestJS.cpp =================================================================== --- unittests/Format/FormatTestJS.cpp +++ unittests/Format/FormatTestJS.cpp @@ -573,5 +573,33 @@ "};"); } +TEST_F(FormatTestJS, TemplateStrings) { + // Keeps any whitespace/indentation within the template string. + EXPECT_EQ("var x = `hello\n" + " ${ name }\n" + " !`;", + format("var x = `hello\n" + " ${ name }\n" + " !`;")); + verifyFormat("var x =\n" + " `hello ${world}` >= some();", + getGoogleJSStyleWithColumns(34)); // Barely doesn't fit. + verifyFormat("var x = `hello ${world}` >= some();", + getGoogleJSStyleWithColumns(35)); // Barely fits. + EXPECT_EQ("var x = `hello\n" + " ${world}` >=\n" + " some();", + format("var x =\n" + " `hello\n" + " ${world}` >= some();", + getGoogleJSStyleWithColumns(21))); // Barely doesn't fit. + EXPECT_EQ("var x = `hello\n" + " ${world}` >= some();", + format("var x =\n" + " `hello\n" + " ${world}` >= some();", + getGoogleJSStyleWithColumns(22))); // Barely fits. +} + } // end namespace tooling } // end namespace clang