Rudimentary template support, excluding tagging.

This commit is contained in:
Daniel Rosenwasser 2014-10-15 17:12:13 -07:00
parent 61549239d8
commit 518a5d3a48
8 changed files with 445 additions and 153 deletions

View File

@ -4103,11 +4103,11 @@ module ts {
}
return createArrayType(elementType);
}
function isNumericName(name: string) {
return (name !== "") && !isNaN(<number><any>name);
}
function checkObjectLiteral(node: ObjectLiteral, contextualMapper?: TypeMapper): Type {
var members = node.symbol.members;
var properties: SymbolTable = {};
@ -5227,6 +5227,12 @@ module ts {
return resultType;
}
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;
@ -5280,7 +5286,11 @@ module ts {
return booleanType;
case SyntaxKind.NumericLiteral:
return numberType;
case SyntaxKind.TemplateExpression:
checkTemplateExpression(<TemplateExpression>node);
// fall through
case SyntaxKind.StringLiteral:
case SyntaxKind.NoSubstitutionTemplateLiteral:
return stringType;
case SyntaxKind.RegularExpressionLiteral:
return globalRegExpType;
@ -7229,77 +7239,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;

View File

@ -5,6 +5,12 @@ module ts {
[index: string]: T;
}
export enum Comparison {
LessThan = -1,
EqualTo = 0,
GreaterThan = 1
}
export interface StringSet extends Map<any> { }
export function forEach<T, U>(array: T[], callback: (element: T) => U): U {
@ -79,6 +85,7 @@ module ts {
export function concatenate<T>(array1: T[], array2: T[]): T[] {
if (!array2 || !array2.length) return array1;
if (!array1 || !array1.length) return array2;
return array1.concat(array2);
}
@ -304,11 +311,11 @@ module ts {
};
}
export function compareValues<T>(a: T, b: T): number {
if (a === b) return 0;
if (a === undefined) return -1;
if (b === undefined) return 1;
return a < b ? -1 : 1;
export function compareValues<T>(a: T, b: T): Comparison {
if (a === b) return Comparison.EqualTo;
if (a === undefined) return Comparison.LessThan;
if (b === undefined) return Comparison.GreaterThan;
return a < b ? Comparison.LessThan : Comparison.GreaterThan;
}
function getDiagnosticFilename(diagnostic: Diagnostic): string {
@ -333,7 +340,7 @@ module ts {
var previousDiagnostic = diagnostics[0];
for (var i = 1; i < diagnostics.length; i++) {
var currentDiagnostic = diagnostics[i];
var isDupe = compareDiagnostics(currentDiagnostic, previousDiagnostic) === 0;
var isDupe = compareDiagnostics(currentDiagnostic, previousDiagnostic) === Comparison.EqualTo;
if (!isDupe) {
newDiagnostics.push(currentDiagnostic);
previousDiagnostic = currentDiagnostic;

View File

@ -122,6 +122,7 @@ module ts {
const_must_be_declared_inside_a_block: { code: 1156, category: DiagnosticCategory.Error, key: "const must be declared inside a block." },
let_must_be_declared_inside_a_block: { code: 1157, category: DiagnosticCategory.Error, key: "let must be declared inside a block." },
Only_var_declarations_can_be_exported: { code: 1158, category: DiagnosticCategory.Error, key: "Only var declarations can be exported." },
Invalid_template_literal_expected: { code: 1159, category: DiagnosticCategory.Error, key: "Invalid template literal; expected '}'" },
Duplicate_identifier_0: { code: 2300, category: DiagnosticCategory.Error, key: "Duplicate identifier '{0}'." },
Initializer_of_instance_member_variable_0_cannot_reference_identifier_1_declared_in_the_constructor: { code: 2301, category: DiagnosticCategory.Error, key: "Initializer of instance member variable '{0}' cannot reference identifier '{1}' declared in the constructor." },
Static_members_cannot_reference_class_type_parameters: { code: 2302, category: DiagnosticCategory.Error, key: "Static members cannot reference class type parameters." },

View File

@ -449,7 +449,7 @@
},
"An enum member cannot have a numeric name.": {
"category": "Error",
"code": 1151
"code": 1151
},
"'var', 'let' or 'const' expected.": {
"category": "Error",
@ -479,6 +479,10 @@
"category": "Error",
"code": 1158
},
"Invalid template literal; expected '}'": {
"category": "Error",
"code": 1159
},
"Duplicate identifier '{0}'.": {
"category": "Error",

View File

@ -792,14 +792,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) + "\"";
}
function emitTemplateExpression(node: TemplateExpression): void {
if (compilerOptions.target >= ScriptTarget.ES6) {
forEachChild(node, emitNode);
return;
}
var templateNeedsParens = isExpression(node.parent) &&
comparePrecedenceToBinaryPlus(node.parent) !== Comparison.LessThan;
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.
//
// 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'.
@ -2091,7 +2178,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:

View File

@ -344,6 +344,10 @@ module ts {
child((<ImportDeclaration>node).externalModuleName);
case SyntaxKind.ExportAssignment:
return child((<ExportAssignment>node).exportName);
case SyntaxKind.TemplateExpression:
return child((<TemplateExpression>node).head) || children((<TemplateExpression>node).templateSpans);
case SyntaxKind.TemplateSpan:
return child((<TemplateSpan>node).expression) || child((<TemplateSpan>node).literal);
}
}
@ -448,10 +452,95 @@ module ts {
}
}
export 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.TemplateExpression:
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:
case SyntaxKind.NoSubstitutionTemplateLiteral:
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;
}
export function hasRestParameters(s: SignatureDeclaration): boolean {
return s.parameters.length > 0 && (s.parameters[s.parameters.length - 1].flags & NodeFlags.Rest) !== 0;
}
export function isLiteralKind(kind: SyntaxKind): boolean {
return SyntaxKind.FirstLiteralToken <= kind && kind <= SyntaxKind.LastLiteralToken;
}
export function isTextualLiteralKind(kind: SyntaxKind): boolean {
return kind === SyntaxKind.StringLiteral || kind === SyntaxKind.NoSubstitutionTemplateLiteral;
}
export function isTemplateLiteralKind(kind: SyntaxKind): boolean {
return SyntaxKind.FirstTemplateToken <= kind && kind <= SyntaxKind.LastTemplateToken;
}
export function isInAmbientContext(node: Node): boolean {
while (node) {
if (node.flags & (NodeFlags.Ambient | NodeFlags.DeclarationFile)) return true;
@ -460,6 +549,7 @@ module ts {
return false;
}
export function isDeclaration(node: Node): boolean {
switch (node.kind) {
case SyntaxKind.TypeParameter:
@ -875,6 +965,10 @@ module ts {
return token = scanner.reScanSlashToken();
}
function reScanTemplateToken(): SyntaxKind {
return token = scanner.reScanTemplateToken();
}
function lookAheadHelper<T>(callback: () => T, alwaysResetState: boolean): T {
// Keep track of the state we'll need to rollback to if lookahead fails (or if the
// caller asked us to always reset our state).
@ -1021,7 +1115,9 @@ module ts {
}
function isPropertyName(): boolean {
return token >= SyntaxKind.Identifier || token === SyntaxKind.StringLiteral || token === SyntaxKind.NumericLiteral;
return token >= SyntaxKind.Identifier ||
token === SyntaxKind.StringLiteral ||
token === SyntaxKind.NumericLiteral;
}
function parsePropertyName(): Identifier {
@ -1296,7 +1392,44 @@ module ts {
return finishNode(node);
}
function parseLiteralNode(internName?:boolean): LiteralExpression {
function parseTemplateExpression() {
var template = <TemplateExpression>createNode(SyntaxKind.TemplateExpression);
template.head = parseLiteralNode();
Debug.assert(template.head.kind === SyntaxKind.TemplateHead, "Template head has wrong token kind");
var templateSpans: TemplateSpan[] = [];
do {
templateSpans.push(parseTemplateSpan());
}
while (templateSpans[templateSpans.length - 1].literal.kind === SyntaxKind.TemplateMiddle)
template.templateSpans = templateSpans;
return finishNode(template);
}
function parseTemplateSpan(): TemplateSpan {
var span = <TemplateSpan>createNode(SyntaxKind.TemplateSpan);
span.expression = parseExpression(/*noIn*/ false);
var literal: LiteralExpression;
if (token === SyntaxKind.CloseBraceToken) {
reScanTemplateToken()
literal = parseLiteralNode();
}
else {
error(Diagnostics.Invalid_template_literal_expected);
literal = <LiteralExpression>createMissingNode();
literal.text = "";
}
span.literal = literal;
return finishNode(span);
}
function parseLiteralNode(internName?: boolean): LiteralExpression {
var node = <LiteralExpression>createNode(token);
var text = scanner.getTokenValue();
node.text = internName ? internIdentifier(text) : text;
@ -1308,7 +1441,7 @@ module ts {
// Octal literals are not allowed in strict mode or ES5
// Note that theoretically the following condition would hold true literals like 009,
// which is not octal.But because of how the scanner separates the tokens, we would
// never get a token like this.Instead, we would get 00 and 9 as two separate tokens.
// never get a token like this. Instead, we would get 00 and 9 as two separate tokens.
// We also do not need to check for negatives because any prefix operator would be part of a
// parent unary expression.
if (node.kind === SyntaxKind.NumericLiteral
@ -1327,7 +1460,9 @@ module ts {
}
function parseStringLiteral(): LiteralExpression {
if (token === SyntaxKind.StringLiteral) return parseLiteralNode(/*internName:*/ true);
if (token === SyntaxKind.StringLiteral) {
return parseLiteralNode(/*internName:*/ true);
}
error(Diagnostics.String_literal_expected);
return <LiteralExpression>createMissingNode();
}
@ -1755,6 +1890,8 @@ module ts {
case SyntaxKind.FalseKeyword:
case SyntaxKind.NumericLiteral:
case SyntaxKind.StringLiteral:
case SyntaxKind.NoSubstitutionTemplateLiteral:
case SyntaxKind.TemplateHead:
case SyntaxKind.OpenParenToken:
case SyntaxKind.OpenBracketToken:
case SyntaxKind.OpenBraceToken:
@ -1846,8 +1983,8 @@ module ts {
}
// Now see if we might be in cases '2' or '3'.
// If the expression was a LHS expression, and we have an assignment operator, then
// we're in '2' or '3'. Consume the assignment and return.
// If the expression was a LHS expression, and we have an assignment operator, then
// we're in '2' or '3'. Consume the assignment and return.
if (isLeftHandSideExpression(expr) && isAssignmentOperator()) {
if (isInStrictMode && isEvalOrArgumentsIdentifier(expr)) {
// ECMA 262 (Annex C) The identifier eval or arguments may not appear as the LeftHandSideExpression of an
@ -1879,6 +2016,8 @@ module ts {
case SyntaxKind.RegularExpressionLiteral:
case SyntaxKind.NumericLiteral:
case SyntaxKind.StringLiteral:
case SyntaxKind.NoSubstitutionTemplateLiteral:
case SyntaxKind.TemplateExpression:
case SyntaxKind.FalseKeyword:
case SyntaxKind.NullKeyword:
case SyntaxKind.ThisKeyword:
@ -2343,6 +2482,7 @@ module ts {
return parseTokenNode();
case SyntaxKind.NumericLiteral:
case SyntaxKind.StringLiteral:
case SyntaxKind.NoSubstitutionTemplateLiteral:
return parseLiteralNode();
case SyntaxKind.OpenParenToken:
return parseParenExpression();
@ -2360,6 +2500,9 @@ module ts {
return parseLiteralNode();
}
break;
case SyntaxKind.TemplateHead:
return parseTemplateExpression();
default:
if (isIdentifier()) {
return parseIdentifier();

View File

@ -24,6 +24,7 @@ module ts {
isReservedWord(): boolean;
reScanGreaterToken(): SyntaxKind;
reScanSlashToken(): SyntaxKind;
reScanTemplateToken(): SyntaxKind;
scan(): SyntaxKind;
setText(text: string): void;
setTextPos(textPos: number): void;
@ -465,7 +466,7 @@ module ts {
var len: number; // Length of text
var startPos: number; // Start position of whitespace before current token
var tokenPos: number; // Start position of text of current token
var token: number;
var token: SyntaxKind;
var tokenValue: string;
var precedingLineBreak: boolean;
@ -518,10 +519,10 @@ module ts {
return +(text.substring(start, pos));
}
function scanHexDigits(count: number, exact?: boolean): number {
function scanHexDigits(count: number, useExactCount?: boolean): number {
var digits = 0;
var value = 0;
while (digits < count || !exact) {
while (digits < count || !useExactCount) {
var ch = text.charCodeAt(pos);
if (ch >= CharacterCodes._0 && ch <= CharacterCodes._9) {
value = value * 16 + ch - CharacterCodes._0;
@ -562,60 +563,7 @@ module ts {
}
if (ch === CharacterCodes.backslash) {
result += text.substring(start, pos);
pos++;
if (pos >= len) {
error(Diagnostics.Unexpected_end_of_text);
break;
}
ch = text.charCodeAt(pos++);
switch (ch) {
case CharacterCodes._0:
result += "\0";
break;
case CharacterCodes.b:
result += "\b";
break;
case CharacterCodes.t:
result += "\t";
break;
case CharacterCodes.n:
result += "\n";
break;
case CharacterCodes.v:
result += "\v";
break;
case CharacterCodes.f:
result += "\f";
break;
case CharacterCodes.r:
result += "\r";
break;
case CharacterCodes.singleQuote:
result += "\'";
break;
case CharacterCodes.doubleQuote:
result += "\"";
break;
case CharacterCodes.x:
case CharacterCodes.u:
var ch = scanHexDigits(ch === CharacterCodes.x ? 2 : 4, true);
if (ch >= 0) {
result += String.fromCharCode(ch);
}
else {
error(Diagnostics.Hexadecimal_digit_expected);
}
break;
case CharacterCodes.carriageReturn:
if (pos < len && text.charCodeAt(pos) === CharacterCodes.lineFeed) pos++;
break;
case CharacterCodes.lineFeed:
case CharacterCodes.lineSeparator:
case CharacterCodes.paragraphSeparator:
break;
default:
result += String.fromCharCode(ch);
}
result += scanEscapeSequence();
start = pos;
continue;
}
@ -629,13 +577,134 @@ module ts {
return result;
}
/**
* Sets the current 'tokenValue' and returns a NoSubstitutionTemplateLiteral or
* a literal component of a TemplateExpression.
*/
function scanTemplateAndSetTokenValue(): SyntaxKind {
var isStartOfTemplate = text.charCodeAt(pos) === CharacterCodes.backtick;
pos++;
var start = pos;
var contents = ""
var resultingToken = SyntaxKind.Unknown;
while (true) {
if (pos >= len) {
contents += text.substring(start, pos);
error(Diagnostics.Unexpected_end_of_text);
resultingToken = isStartOfTemplate ? SyntaxKind.NoSubstitutionTemplateLiteral : SyntaxKind.TemplateTail;
break;
}
var currChar = text.charCodeAt(pos);
// '`'
if (currChar === CharacterCodes.backtick) {
contents += text.substring(start, pos);
pos++;
resultingToken = isStartOfTemplate ? SyntaxKind.NoSubstitutionTemplateLiteral : SyntaxKind.TemplateTail;
break;
}
// '${'
if (currChar === CharacterCodes.$ && pos + 1 < len && text.charCodeAt(pos + 1) === CharacterCodes.openBrace) {
contents += text.substring(start, pos);
pos += 2;
resultingToken = isStartOfTemplate ? SyntaxKind.TemplateHead : SyntaxKind.TemplateMiddle;
break;
}
// Escape character
if (currChar === CharacterCodes.backslash) {
contents += text.substring(start, pos);
contents += scanEscapeSequence();
start = pos;
continue;
}
// Speculated ECMAScript 6 Spec 11.8.6.1:
// <CR><LF> and <CR> LineTerminatorSequences are normalized to <LF> for Template Values
// An explicit EscapeSequence is needed to include a <CR> or <CR><LF> sequence.
if (currChar === CharacterCodes.carriageReturn) {
contents += text.substring(start, pos);
if (pos + 1 < len && text.charCodeAt(pos + 1) === CharacterCodes.lineFeed) {
pos++;
}
pos++;
contents += "\n";
start = pos;
continue;
}
pos++;
}
tokenValue = contents;
return resultingToken;
}
function scanEscapeSequence(): string {
pos++;
if (pos >= len) {
error(Diagnostics.Unexpected_end_of_text);
return "";
}
var ch = text.charCodeAt(pos++);
switch (ch) {
case CharacterCodes._0:
return "\0";
case CharacterCodes.b:
return "\b";
case CharacterCodes.t:
return "\t";
case CharacterCodes.n:
return "\n";
case CharacterCodes.v:
return "\v";
case CharacterCodes.f:
return "\f";
case CharacterCodes.r:
return "\r";
case CharacterCodes.singleQuote:
return "\'";
case CharacterCodes.doubleQuote:
return "\"";
case CharacterCodes.x:
case CharacterCodes.u:
var ch = scanHexDigits(ch === CharacterCodes.x ? 2 : 4, /*useExactCount*/ true);
if (ch >= 0) {
return String.fromCharCode(ch);
}
else {
error(Diagnostics.Hexadecimal_digit_expected);
return ""
}
// when encountering a LineContinuation (i.e. a backslash and a line terminator sequence),
// the line terminator is interpreted to be "the empty code unit sequence".
case CharacterCodes.carriageReturn:
if (pos < len && text.charCodeAt(pos) === CharacterCodes.lineFeed) {
pos++;
}
// fall through
case CharacterCodes.lineFeed:
case CharacterCodes.lineSeparator:
case CharacterCodes.paragraphSeparator:
return ""
default:
return String.fromCharCode(ch);
}
}
// Current character is known to be a backslash. Check for Unicode escape of the form '\uXXXX'
// and return code point value if valid Unicode escape is found. Otherwise return -1.
function peekUnicodeEscape(): number {
if (pos + 5 < len && text.charCodeAt(pos + 1) === CharacterCodes.u) {
var start = pos;
pos += 2;
var value = scanHexDigits(4, true);
var value = scanHexDigits(4, /*useExactCount*/ true);
pos = start;
return value;
}
@ -734,6 +803,8 @@ module ts {
case CharacterCodes.singleQuote:
tokenValue = scanString();
return token = SyntaxKind.StringLiteral;
case CharacterCodes.backtick:
return token = scanTemplateAndSetTokenValue()
case CharacterCodes.percent:
if (text.charCodeAt(pos + 1) === CharacterCodes.equals) {
return pos += 2, token = SyntaxKind.PercentEqualsToken;
@ -851,7 +922,7 @@ module ts {
case CharacterCodes._0:
if (pos + 2 < len && (text.charCodeAt(pos + 1) === CharacterCodes.X || text.charCodeAt(pos + 1) === CharacterCodes.x)) {
pos += 2;
var value = scanHexDigits(1, false);
var value = scanHexDigits(1, /*useExactCount*/ false);
if (value < 0) {
error(Diagnostics.Hexadecimal_digit_expected);
value = 0;
@ -1037,6 +1108,15 @@ module ts {
return token;
}
/**
* Unconditionally back up and scan a template expression portion.
*/
function reScanTemplateToken(): SyntaxKind {
Debug.assert("'reScanTemplateToken' should only be called on a '}'");
pos = tokenPos;
return token = scanTemplateAndSetTokenValue();
}
function tryScan<T>(callback: () => T): T {
var savePos = pos;
var saveStartPos = startPos;
@ -1085,10 +1165,11 @@ module ts {
isReservedWord: () => token >= SyntaxKind.FirstReservedWord && token <= SyntaxKind.LastReservedWord,
reScanGreaterToken: reScanGreaterToken,
reScanSlashToken: reScanSlashToken,
reScanTemplateToken: reScanTemplateToken,
scan: scan,
setText: setText,
setTextPos: setTextPos,
tryScan: tryScan
tryScan: tryScan,
};
}
}

View File

@ -20,6 +20,11 @@ module ts {
NumericLiteral,
StringLiteral,
RegularExpressionLiteral,
NoSubstitutionTemplateLiteral,
// Pseudo-literals
TemplateHead,
TemplateMiddle,
TemplateTail,
// Punctuation
OpenBraceToken,
CloseBraceToken,
@ -170,6 +175,8 @@ module ts {
PostfixOperator,
BinaryExpression,
ConditionalExpression,
TemplateExpression,
TemplateSpan,
OmittedExpression,
// Element
Block,
@ -230,7 +237,11 @@ module ts {
FirstToken = EndOfFileToken,
LastToken = StringKeyword,
FirstTriviaToken = SingleLineCommentTrivia,
LastTriviaToken = WhitespaceTrivia
LastTriviaToken = WhitespaceTrivia,
FirstLiteralToken = NumericLiteral,
LastLiteralToken = NoSubstitutionTemplateLiteral,
FirstTemplateToken = NoSubstitutionTemplateLiteral,
LastTemplateToken = TemplateTail
}
export enum NodeFlags {
@ -369,13 +380,23 @@ module ts {
body: Node; // Required, whereas the member inherited from FunctionDeclaration is optional
}
// The text property of a LiteralExpression stores the interpreted value of the literal in text form. For a StringLiteral
// this means quotes have been removed and escapes have been converted to actual characters. For a NumericLiteral, the
// stored value is the toString() representation of the number. For example 1, 1.00, and 1e0 are all stored as just "1".
// The text property of a LiteralExpression stores the interpreted value of the literal in text form. For a StringLiteral,
// or any literal of a template, this means quotes have been removed and escapes have been converted to actual characters.
// For a NumericLiteral, the stored value is the toString() representation of the number. For example 1, 1.00, and 1e0 are all stored as just "1".
export interface LiteralExpression extends Expression {
text: string;
}
export interface TemplateExpression extends Expression {
head: LiteralExpression;
templateSpans: TemplateSpan[]
}
export interface TemplateSpan extends Node {
expression: Expression;
literal: LiteralExpression;
}
export interface ParenExpression extends Expression {
expression: Expression;
}
@ -1189,6 +1210,7 @@ module ts {
asterisk = 0x2A, // *
at = 0x40, // @
backslash = 0x5C, // \
backtick = 0x60, // `
bar = 0x7C, // |
caret = 0x5E, // ^
closeBrace = 0x7D, // }