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;
return;
if (tryMergeEscapeSequence())
return;
+ if (tryMergeTemplateString())
+ return;
static tok::TokenKind JSIdentity[] = {tok::equalequal, tok::equal};
static tok::TokenKind JSNotIdentity[] = {tok::exclaimequal, tok::equal};
return false;
}
+ bool tryMergeTemplateString() {
+ if (Tokens.size() < 2)
+ return false;
+
+ FormatToken *EndBacktick = Tokens.back();
+ if (!(EndBacktick->is(tok::unknown) && EndBacktick->TokenText == "`"))
+ return false;
+
+ unsigned TokenCount = 0;
+ bool IsMultiline = false;
+ unsigned EndColumnInFirstLine = 0;
+ for (auto I = Tokens.rbegin() + 1, E = Tokens.rend(); I != E; I++) {
+ ++TokenCount;
+ if (I[0]->NewlinesBefore > 0 || I[0]->IsMultiline)
+ IsMultiline = true;
+
+ // If there was a preceding template string, this must be the start of a
+ // template string, not the end.
+ if (I[0]->is(TT_TemplateString))
+ return false;
+
+ if (I[0]->isNot(tok::unknown) || I[0]->TokenText != "`") {
+ // Keep track of the rhs offset of the last token to wrap across lines -
+ // its the rhs offset of the first line of the template string, used to
+ // determine its width.
+ if (I[0]->IsMultiline)
+ EndColumnInFirstLine = I[0]->OriginalColumn + I[0]->ColumnWidth;
+ // If the token has newlines, the token before it (if it exists) is the
+ // rhs end of the previous line.
+ if (I[0]->NewlinesBefore > 0 && (I + 1 != E))
+ EndColumnInFirstLine = I[1]->OriginalColumn + I[1]->ColumnWidth;
+
+ continue;
+ }
+
+ Tokens.resize(Tokens.size() - TokenCount);
+ Tokens.back()->Type = TT_TemplateString;
+ const char *EndOffset = EndBacktick->TokenText.data() + 1;
+ Tokens.back()->TokenText =
+ StringRef(Tokens.back()->TokenText.data(),
+ EndOffset - Tokens.back()->TokenText.data());
+ if (IsMultiline) {
+ // ColumnWidth is from backtick to last token in line.
+ // LastLineColumnWidth is 0 to backtick.
+ // x = `some content
+ // until here`;
+ Tokens.back()->ColumnWidth =
+ EndColumnInFirstLine - Tokens.back()->OriginalColumn;
+ Tokens.back()->LastLineColumnWidth = EndBacktick->OriginalColumn;
+ Tokens.back()->IsMultiline = true;
+ } else {
+ // Token simply spans from start to end, +1 for the ` itself.
+ Tokens.back()->ColumnWidth =
+ EndBacktick->OriginalColumn - Tokens.back()->OriginalColumn + 1;
+ }
+ return true;
+ }
+ return false;
+ }
+
bool tryMerge_TMacro() {
if (Tokens.size() < 4)
return false;
// Consume and record whitespace until we find a significant token.
unsigned WhitespaceLength = TrailingWhitespace;
while (FormatTok->Tok.is(tok::unknown)) {
+ // FIXME: This miscounts tok:unknown tokens that are not just
+ // whitespace, e.g. a '`' character.
for (int i = 0, e = FormatTok->TokenText.size(); i != e; ++i) {
switch (FormatTok->TokenText[i]) {
case '\n':
"};");
}
+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"
+ " !`;"));
+
+ // FIXME: +1 / -1 offsets are to work around clang-format miscalculating
+ // widths for unknown tokens that are not whitespace (e.g. '`'). Remove when
+ // the code is corrected.
+
+ verifyFormat("var x =\n"
+ " `hello ${world}` >= some();",
+ getGoogleJSStyleWithColumns(34)); // Barely doesn't fit.
+ verifyFormat("var x = `hello ${world}` >= some();",
+ getGoogleJSStyleWithColumns(35 + 1)); // 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.
+
+ verifyFormat("var x =\n `h`;", getGoogleJSStyleWithColumns(13 - 1));
+ EXPECT_EQ(
+ "var x =\n `multi\n line`;",
+ format("var x = `multi\n line`;", getGoogleJSStyleWithColumns(14 - 1)));
+
+ // Two template strings.
+ verifyFormat("var x = `hello` == `hello`;");
+}
+
} // end namespace tooling
} // end namespace clang