-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Support for ES6 Templates #960
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
Changes from 8 commits
518a5d3
c0893e1
b704f19
e709628
0d1a46d
a5b77c6
799609c
c03dc10
7fad769
4aafe1d
8786d30
d45fb77
64097a3
aabfebd
b8535d3
d522c88
ead3c1b
35cf95c
76c0381
63340a0
3e8978f
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 |
---|---|---|
|
@@ -4785,7 +4785,7 @@ module ts { | |
} | ||
return createArrayType(getUnionType(elementTypes)); | ||
} | ||
|
||
function isNumericName(name: string) { | ||
// The intent of numeric names is that | ||
// - they are names with text in a numeric form, and that | ||
|
@@ -4810,7 +4810,7 @@ module ts { | |
// with the strings '"Infinity"', '"-Infinity"', and '"NaN"' respectively. | ||
return (+name).toString() === name; | ||
} | ||
|
||
function checkObjectLiteral(node: ObjectLiteral, contextualMapper?: TypeMapper): Type { | ||
var members = node.symbol.members; | ||
var properties: SymbolTable = {}; | ||
|
@@ -5422,6 +5422,13 @@ module ts { | |
return getReturnTypeOfSignature(signature); | ||
} | ||
|
||
function checkTaggedTemplateExpression(node: TaggedTemplateExpression): Type { | ||
// TODO (drosen): Make sure substitutions are assignable to the tag's arguments. | ||
checkExpression(node.tag); | ||
checkExpression(node.template); | ||
return anyType; | ||
} | ||
|
||
function checkTypeAssertion(node: TypeAssertion): Type { | ||
var exprType = checkExpression(node.operand); | ||
var targetType = getTypeFromTypeNode(node.type); | ||
|
@@ -5932,6 +5939,12 @@ module ts { | |
return getUnionType([type1, type2]); | ||
} | ||
|
||
function checkTemplateExpression(node: TemplateExpression): void { | ||
forEach((<TemplateExpression>node).templateSpans, templateSpan => { | ||
checkExpression(templateSpan.expression); | ||
}); | ||
} | ||
|
||
function checkExpressionWithContextualType(node: Expression, contextualType: Type, contextualMapper?: TypeMapper): Type { | ||
var saveContextualType = node.contextualType; | ||
node.contextualType = contextualType; | ||
|
@@ -5985,7 +5998,11 @@ module ts { | |
return booleanType; | ||
case SyntaxKind.NumericLiteral: | ||
return numberType; | ||
case SyntaxKind.TemplateExpression: | ||
checkTemplateExpression(<TemplateExpression>node); | ||
// fall through | ||
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. 👍 for the "falls through" comment! Should be done on more places in the source code! :) 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. Thanks @DickvdBrink - I try to keep up that pattern in general. 😄 Although, I'm turning this into a straight |
||
case SyntaxKind.StringLiteral: | ||
case SyntaxKind.NoSubstitutionTemplateLiteral: | ||
return stringType; | ||
case SyntaxKind.RegularExpressionLiteral: | ||
return globalRegExpType; | ||
|
@@ -6002,6 +6019,8 @@ module ts { | |
case SyntaxKind.CallExpression: | ||
case SyntaxKind.NewExpression: | ||
return checkCallExpression(<CallExpression>node); | ||
case SyntaxKind.TaggedTemplateExpression: | ||
return checkTaggedTemplateExpression(<TaggedTemplateExpression>node); | ||
case SyntaxKind.TypeAssertion: | ||
return checkTypeAssertion(<TypeAssertion>node); | ||
case SyntaxKind.ParenExpression: | ||
|
@@ -7734,6 +7753,7 @@ module ts { | |
case SyntaxKind.IndexedAccess: | ||
case SyntaxKind.CallExpression: | ||
case SyntaxKind.NewExpression: | ||
case SyntaxKind.TaggedTemplateExpression: | ||
case SyntaxKind.TypeAssertion: | ||
case SyntaxKind.ParenExpression: | ||
case SyntaxKind.PrefixOperator: | ||
|
@@ -7944,77 +7964,6 @@ module ts { | |
return node.parent && node.parent.kind === SyntaxKind.TypeReference; | ||
} | ||
|
||
function isExpression(node: Node): boolean { | ||
switch (node.kind) { | ||
case SyntaxKind.ThisKeyword: | ||
case SyntaxKind.SuperKeyword: | ||
case SyntaxKind.NullKeyword: | ||
case SyntaxKind.TrueKeyword: | ||
case SyntaxKind.FalseKeyword: | ||
case SyntaxKind.RegularExpressionLiteral: | ||
case SyntaxKind.ArrayLiteral: | ||
case SyntaxKind.ObjectLiteral: | ||
case SyntaxKind.PropertyAccess: | ||
case SyntaxKind.IndexedAccess: | ||
case SyntaxKind.CallExpression: | ||
case SyntaxKind.NewExpression: | ||
case SyntaxKind.TypeAssertion: | ||
case SyntaxKind.ParenExpression: | ||
case SyntaxKind.FunctionExpression: | ||
case SyntaxKind.ArrowFunction: | ||
case SyntaxKind.PrefixOperator: | ||
case SyntaxKind.PostfixOperator: | ||
case SyntaxKind.BinaryExpression: | ||
case SyntaxKind.ConditionalExpression: | ||
case SyntaxKind.OmittedExpression: | ||
return true; | ||
case SyntaxKind.QualifiedName: | ||
while (node.parent.kind === SyntaxKind.QualifiedName) node = node.parent; | ||
return node.parent.kind === SyntaxKind.TypeQuery; | ||
case SyntaxKind.Identifier: | ||
if (node.parent.kind === SyntaxKind.TypeQuery) { | ||
return true; | ||
} | ||
// Fall through | ||
case SyntaxKind.NumericLiteral: | ||
case SyntaxKind.StringLiteral: | ||
var parent = node.parent; | ||
switch (parent.kind) { | ||
case SyntaxKind.VariableDeclaration: | ||
case SyntaxKind.Parameter: | ||
case SyntaxKind.Property: | ||
case SyntaxKind.EnumMember: | ||
case SyntaxKind.PropertyAssignment: | ||
return (<VariableDeclaration>parent).initializer === node; | ||
case SyntaxKind.ExpressionStatement: | ||
case SyntaxKind.IfStatement: | ||
case SyntaxKind.DoStatement: | ||
case SyntaxKind.WhileStatement: | ||
case SyntaxKind.ReturnStatement: | ||
case SyntaxKind.WithStatement: | ||
case SyntaxKind.SwitchStatement: | ||
case SyntaxKind.CaseClause: | ||
case SyntaxKind.ThrowStatement: | ||
case SyntaxKind.SwitchStatement: | ||
return (<ExpressionStatement>parent).expression === node; | ||
case SyntaxKind.ForStatement: | ||
return (<ForStatement>parent).initializer === node || | ||
(<ForStatement>parent).condition === node || | ||
(<ForStatement>parent).iterator === node; | ||
case SyntaxKind.ForInStatement: | ||
return (<ForInStatement>parent).variable === node || | ||
(<ForInStatement>parent).expression === node; | ||
case SyntaxKind.TypeAssertion: | ||
return node === (<TypeAssertion>parent).operand; | ||
default: | ||
if (isExpression(parent)) { | ||
return true; | ||
} | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
function isTypeNode(node: Node): boolean { | ||
if (SyntaxKind.FirstTypeNode <= node.kind && node.kind <= SyntaxKind.LastTypeNode) { | ||
return true; | ||
|
@@ -8081,6 +8030,9 @@ module ts { | |
case SyntaxKind.CallExpression: | ||
case SyntaxKind.NewExpression: | ||
return (<CallExpression>parent).typeArguments && (<CallExpression>parent).typeArguments.indexOf(node) >= 0; | ||
case SyntaxKind.TaggedTemplateExpression: | ||
// TODO (drosen): TaggedTemplateExpressions may eventually support type arguments. | ||
return false; | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -786,14 +786,101 @@ module ts { | |
} | ||
} | ||
|
||
function emitLiteral(node: LiteralExpression) { | ||
var text = getSourceTextOfLocalNode(node); | ||
if (node.kind === SyntaxKind.StringLiteral && compilerOptions.sourceMap) { | ||
function emitLiteral(node: LiteralExpression): void { | ||
var text = getLiteralText(); | ||
|
||
if (compilerOptions.sourceMap && (node.kind === SyntaxKind.StringLiteral || isTemplateLiteralKind(node.kind))) { | ||
writer.writeLiteral(text); | ||
} | ||
else { | ||
write(text); | ||
} | ||
|
||
function getLiteralText() { | ||
if (compilerOptions.target < ScriptTarget.ES6 && isTemplateLiteralKind(node.kind)) { | ||
return getTemplateLiteralAsStringLiteral(node) | ||
} | ||
|
||
return getSourceTextOfLocalNode(node); | ||
} | ||
} | ||
|
||
function getTemplateLiteralAsStringLiteral(node: LiteralExpression): string { | ||
return "\"" + escapeString(node.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. '"' + |
||
} | ||
|
||
function emitTemplateExpression(node: TemplateExpression): void { | ||
if (compilerOptions.target >= ScriptTarget.ES6) { | ||
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. Explain that in ES6 we don't need to do anything. But that in ES5 we'll convert things according. Explain the type of conversion you're trying to do. |
||
forEachChild(node, emitNode); | ||
return; | ||
} | ||
|
||
var templateNeedsParens = isExpression(node.parent) && | ||
comparePrecedenceToBinaryPlus(node.parent) !== Comparison.LessThan; | ||
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. explain this bit. (i understand it. but we should make the code clear). |
||
|
||
if (templateNeedsParens) { | ||
write("("); | ||
} | ||
|
||
emitLiteral(node.head); | ||
|
||
forEach(node.templateSpans, templateSpan => { | ||
var needsParens = comparePrecedenceToBinaryPlus(templateSpan.expression) !== Comparison.GreaterThan; | ||
|
||
write(" + "); | ||
|
||
if (needsParens) { | ||
write("("); | ||
} | ||
emit(templateSpan.expression); | ||
if (needsParens) { | ||
write(")"); | ||
} | ||
|
||
write(" + ") | ||
emitLiteral(templateSpan.literal); | ||
}); | ||
|
||
if (templateNeedsParens) { | ||
write(")"); | ||
} | ||
|
||
/** | ||
* Returns whether the expression has lesser, greater, | ||
* or equal precedence to the binary '+' operator | ||
*/ | ||
function comparePrecedenceToBinaryPlus(expression: Expression): Comparison { | ||
// All binary expressions have lower precedence than '+' apart from '*', '/', and '%'. | ||
// All unary operators have a higher precedence apart from yield. | ||
// Arrow functions and conditionals have a lower precedence, | ||
// although we convert the former into regular function expressions in ES5 mode, | ||
// and in ES6 mode this function won't get called anyway. | ||
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. add assert that you're in ES5 mode or lower. |
||
// | ||
// TODO (drosen): Note that we need to account for the upcoming 'yield' and | ||
// spread ('...') unary operators that are anticipated for ES6. | ||
switch (expression.kind) { | ||
case SyntaxKind.BinaryExpression: | ||
switch ((<BinaryExpression>expression).operator) { | ||
case SyntaxKind.AsteriskToken: | ||
case SyntaxKind.SlashToken: | ||
case SyntaxKind.PercentToken: | ||
return Comparison.GreaterThan; | ||
case SyntaxKind.PlusToken: | ||
return Comparison.EqualTo; | ||
default: | ||
return Comparison.LessThan; | ||
} | ||
case SyntaxKind.ConditionalExpression: | ||
return Comparison.LessThan; | ||
default: | ||
return Comparison.GreaterThan; | ||
} | ||
} | ||
|
||
} | ||
|
||
function emitTemplateSpan(span: TemplateSpan) { | ||
forEachChild(span, emitNode); | ||
} | ||
|
||
// This function specifically handles numeric/string literals for enum and accessor 'identifiers'. | ||
|
@@ -977,6 +1064,13 @@ module ts { | |
} | ||
} | ||
|
||
function emitTaggedTemplateExpression(node: TaggedTemplateExpression): void { | ||
Debug.assert(compilerOptions.target >= ScriptTarget.ES6, "Trying to emit a tagged template in pre-ES6 mode."); | ||
emit(node.tag); | ||
write(" "); | ||
emit(node.template); | ||
} | ||
|
||
function emitParenExpression(node: ParenExpression) { | ||
if (node.expression.kind === SyntaxKind.TypeAssertion) { | ||
var operand = (<TypeAssertion>node.expression).operand; | ||
|
@@ -2085,7 +2179,15 @@ module ts { | |
case SyntaxKind.NumericLiteral: | ||
case SyntaxKind.StringLiteral: | ||
case SyntaxKind.RegularExpressionLiteral: | ||
case SyntaxKind.NoSubstitutionTemplateLiteral: | ||
case SyntaxKind.TemplateHead: | ||
case SyntaxKind.TemplateMiddle: | ||
case SyntaxKind.TemplateTail: | ||
return emitLiteral(<LiteralExpression>node); | ||
case SyntaxKind.TemplateExpression: | ||
return emitTemplateExpression(<TemplateExpression>node); | ||
case SyntaxKind.TemplateSpan: | ||
return emitTemplateSpan(<TemplateSpan>node); | ||
case SyntaxKind.QualifiedName: | ||
return emitPropertyAccess(<QualifiedName>node); | ||
case SyntaxKind.ArrayLiteral: | ||
|
@@ -2102,6 +2204,8 @@ module ts { | |
return emitCallExpression(<CallExpression>node); | ||
case SyntaxKind.NewExpression: | ||
return emitNewExpression(<NewExpression>node); | ||
case SyntaxKind.TaggedTemplateExpression: | ||
return emitTaggedTemplateExpression(<TaggedTemplateExpression>node); | ||
case SyntaxKind.TypeAssertion: | ||
return emit((<TypeAssertion>node).operand); | ||
case SyntaxKind.ParenExpression: | ||
|
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.
Explain why this is correct. I believe it's because we rewrite this to string addition, and string addition allows the other side to be of any type. So we don't need to check for things like void types, etc. But it would be good to comment this bit.