clang-format: [js] Support template strings.

Merge template strings (marked by backticks ``).
Do not format any contents of template strings.

Patch by Martin Probst. Thank you.

llvm-svn: 230011
This commit is contained in:
Daniel Jasper 2015-02-20 13:47:38 +00:00
parent ca1ba4b280
commit a0ef4f36c8
3 changed files with 107 additions and 1 deletions

View File

@ -617,7 +617,7 @@ public:
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 @@ private:
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,66 @@ private:
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;
@ -913,6 +975,8 @@ private:
// 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':

View File

@ -70,6 +70,7 @@ enum TokenType {
TT_StartOfName,
TT_TemplateCloser,
TT_TemplateOpener,
TT_TemplateString,
TT_TrailingAnnotation,
TT_TrailingReturnArrow,
TT_TrailingUnaryOperator,

View File

@ -572,5 +572,46 @@ TEST_F(FormatTestJS, Modules) {
"};");
}
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