-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Tagged templates ES3 & 5 #1589
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Tagged templates ES3 & 5 #1589
Changes from all commits
6469375
c2d0bf8
69d724f
8f28c95
a13af6b
349841e
28b90a2
ed7ae27
d339a52
161545d
434c908
cbec9a3
39027d9
9fc0144
30c10fb
04dd08d
8e16e1d
f77bedd
eedcb09
c4008c3
f883259
35c815e
63e1ddb
c291d12
acdc177
964ed7f
904b520
ac8e395
80ff139
2b10d39
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2072,7 +2072,7 @@ module ts { | |
} | ||
} | ||
|
||
function emitParenthesized(node: Node, parenthesized: boolean) { | ||
function emitParenthesizedIf(node: Node, parenthesized: boolean) { | ||
if (parenthesized) { | ||
write("("); | ||
} | ||
|
@@ -2205,6 +2205,72 @@ module ts { | |
function getTemplateLiteralAsStringLiteral(node: LiteralExpression): string { | ||
return '"' + escapeString(node.text) + '"'; | ||
} | ||
|
||
function emitDownlevelRawTemplateLiteral(node: LiteralExpression) { | ||
// Find original source text, since we need to emit the raw strings of the tagged template. | ||
// The raw strings contain the (escaped) strings of what the user wrote. | ||
// Examples: `\n` is converted to "\\n", a template string with a newline to "\n". | ||
var text = getSourceTextOfNodeFromSourceFile(currentSourceFile, node); | ||
|
||
// text contains the original source, it will also contain quotes ("`"), dolar signs and braces ("${" and "}"), | ||
// thus we need to remove those characters. | ||
// First template piece starts with "`", others with "}" | ||
// Last template piece ends with "`", others with "${" | ||
var isLast = node.kind === SyntaxKind.NoSubstitutionTemplateLiteral || node.kind === SyntaxKind.TemplateTail; | ||
text = text.substring(1, text.length - (isLast ? 1 : 2)); | ||
|
||
// Newline normalization: | ||
// ES6 Spec 11.8.6.1 - Static Semantics of TV's and TRV's | ||
// <CR><LF> and <CR> LineTerminatorSequences are normalized to <LF> for both TV and TRV. | ||
text = text.replace(/\r\n?/g, "\n"); | ||
text = escapeString(text); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aren't these steps for the cooked strings? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh maybe these are the steps that uncook the string |
||
|
||
write('"' + text + '"'); | ||
} | ||
|
||
function emitDownlevelTaggedTemplateArray(node: TaggedTemplateExpression, literalEmitter: (literal: LiteralExpression) => void) { | ||
write("["); | ||
if (node.template.kind === SyntaxKind.NoSubstitutionTemplateLiteral) { | ||
literalEmitter(<LiteralExpression>node.template); | ||
} | ||
else { | ||
literalEmitter((<TemplateExpression>node.template).head); | ||
forEach((<TemplateExpression>node.template).templateSpans, (child) => { | ||
write(", "); | ||
literalEmitter(child.literal); | ||
}); | ||
} | ||
write("]"); | ||
} | ||
|
||
function emitDownlevelTaggedTemplate(node: TaggedTemplateExpression) { | ||
var tempVariable = createAndRecordTempVariable(node); | ||
write("("); | ||
emit(tempVariable); | ||
write(" = "); | ||
emitDownlevelTaggedTemplateArray(node, emit); | ||
write(", "); | ||
|
||
emit(tempVariable); | ||
write(".raw = "); | ||
emitDownlevelTaggedTemplateArray(node, emitDownlevelRawTemplateLiteral); | ||
write(", "); | ||
|
||
emitParenthesizedIf(node.tag, needsParenthesisForPropertyAccessOrInvocation(node.tag)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do you need parentheses? I don't think you need to add them. The reason is that you want something that can be the left hand side of a call. But MemberExpression and CallExpression are the two nonterminals that can be the LHS of a tagged template, and those are precisely the things you need on the LHS of a call. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unless there is some subtlety with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As nonsensical as it is, it's because of #1589 (comment) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah looking at the grammar, you can't have a NewExpression as the tag, so you don't need to parenthesize the tag. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh gosh, you're right! The template literal gets demoted to an AdditiveExpression! Disregard what I said. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But do we do the right thing in this case? `hello${"goodbye"}hello`[0] There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My point is that rather than each parent node needing to know that its child could be a template, and therefore needs to parenthesize that child, the template should parenthesize itself accordingly, because it is the one that lowers its precedence when it gets emitted. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To clarify, when I say template in this context, I mean an untagged template. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I was agreeing with you that node.tag should include the parens, instead of requiring the code that uses it to add them. For your example, the current PR also emits it correctly There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I think that's a better approach. Although I do think this PR can go in as is, and we make that change separately. |
||
write("("); | ||
emit(tempVariable); | ||
|
||
// Now we emit the expressions | ||
if (node.template.kind === SyntaxKind.TemplateExpression) { | ||
forEach((<TemplateExpression>node.template).templateSpans, templateSpan => { | ||
write(", "); | ||
var needsParens = templateSpan.expression.kind === SyntaxKind.BinaryExpression | ||
&& (<BinaryExpression>templateSpan.expression).operatorToken.kind === SyntaxKind.CommaToken; | ||
emitParenthesizedIf(templateSpan.expression, needsParens); | ||
}); | ||
} | ||
write("))"); | ||
} | ||
|
||
function emitTemplateExpression(node: TemplateExpression): void { | ||
// In ES6 mode and above, we can simply emit each portion of a template in order, but in | ||
|
@@ -2249,7 +2315,8 @@ module ts { | |
write(" + "); | ||
} | ||
|
||
emitParenthesized(templateSpan.expression, needsParens); | ||
emitParenthesizedIf(templateSpan.expression, needsParens); | ||
|
||
// Only emit if the literal is non-empty. | ||
// The binary '+' operator is left-associative, so the first string concatenation | ||
// with the head will force the result up to this point to be a string. | ||
|
@@ -2479,7 +2546,7 @@ module ts { | |
emit((<SpreadElementExpression>node).expression); | ||
} | ||
|
||
function needsParenthesisForPropertyAccess(node: Expression) { | ||
function needsParenthesisForPropertyAccessOrInvocation(node: Expression) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Leave as is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nvm, this change is fine. |
||
switch (node.kind) { | ||
case SyntaxKind.Identifier: | ||
case SyntaxKind.ArrayLiteralExpression: | ||
|
@@ -2509,7 +2576,7 @@ module ts { | |
var e = elements[pos]; | ||
if (e.kind === SyntaxKind.SpreadElementExpression) { | ||
e = (<SpreadElementExpression>e).expression; | ||
emitParenthesized(e, /*parenthesized*/ group === 0 && needsParenthesisForPropertyAccess(e)); | ||
emitParenthesizedIf(e, /*parenthesized*/ group === 0 && needsParenthesisForPropertyAccessOrInvocation(e)); | ||
pos++; | ||
} | ||
else { | ||
|
@@ -2985,9 +3052,14 @@ module ts { | |
} | ||
|
||
function emitTaggedTemplateExpression(node: TaggedTemplateExpression): void { | ||
emit(node.tag); | ||
write(" "); | ||
emit(node.template); | ||
if (compilerOptions.target >= ScriptTarget.ES6) { | ||
emit(node.tag); | ||
write(" "); | ||
emit(node.template); | ||
} | ||
else { | ||
emitDownlevelTaggedTemplate(node); | ||
} | ||
} | ||
|
||
function emitParenExpression(node: ParenthesizedExpression) { | ||
|
@@ -3157,7 +3229,7 @@ module ts { | |
} | ||
|
||
function emitExpressionStatement(node: ExpressionStatement) { | ||
emitParenthesized(node.expression, /*parenthesized*/ node.expression.kind === SyntaxKind.ArrowFunction); | ||
emitParenthesizedIf(node.expression, /*parenthesized*/ node.expression.kind === SyntaxKind.ArrowFunction); | ||
write(";"); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
//// [taggedTemplateStringsHexadecimalEscapes.ts] | ||
function f(...args: any[]) { | ||
} | ||
|
||
f `\x0D${ "Interrupted CRLF" }\x0A`; | ||
|
||
//// [taggedTemplateStringsHexadecimalEscapes.js] | ||
function f() { | ||
var args = []; | ||
for (var _i = 0; _i < arguments.length; _i++) { | ||
args[_i - 0] = arguments[_i]; | ||
} | ||
} | ||
(_a = ["\r", "\n"], _a.raw = ["\\x0D", "\\x0A"], f(_a, "Interrupted CRLF")); | ||
var _a; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
=== tests/cases/compiler/taggedTemplateStringsHexadecimalEscapes.ts === | ||
function f(...args: any[]) { | ||
>f : (...args: any[]) => void | ||
>args : any[] | ||
} | ||
|
||
f `\x0D${ "Interrupted CRLF" }\x0A`; | ||
>f : (...args: any[]) => void | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
//// [taggedTemplateStringsHexadecimalEscapesES6.ts] | ||
function f(...args: any[]) { | ||
} | ||
|
||
f `\x0D${ "Interrupted CRLF" }\x0A`; | ||
|
||
//// [taggedTemplateStringsHexadecimalEscapesES6.js] | ||
function f(...args) { | ||
} | ||
f `\x0D${"Interrupted CRLF"}\x0A`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
=== tests/cases/compiler/taggedTemplateStringsHexadecimalEscapesES6.ts === | ||
function f(...args: any[]) { | ||
>f : (...args: any[]) => void | ||
>args : any[] | ||
} | ||
|
||
f `\x0D${ "Interrupted CRLF" }\x0A`; | ||
>f : (...args: any[]) => void | ||
|
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
=== tests/cases/conformance/es6/templates/taggedTemplateStringsPlainCharactersThatArePartsOfEscapes01.ts === | ||
|
||
|
||
function f(...x: any[]) { | ||
>f : (...x: any[]) => void | ||
>x : any[] | ||
|
||
} | ||
|
||
f `0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 2028 2029 0085 t v f b r n` | ||
>f : (...x: any[]) => void | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm torn. On one hand, this brings us closer to what the user already wrote. ON the other, it means you need to do a lot more processing. Why not just use node.text instead? It will already have stripped off the } and ${ bits, and will already have changes newlines appropriately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh. Is this because it's supposed to be 'raw'? If so, can you comment here the difference and how this shoudl behave on different inputs? Thanks!