From f5b8619199535d0ef593abbaff9e909d495499c7 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 19 Nov 2015 17:08:51 -0800 Subject: [PATCH 1/2] Adds StringLiteralType to SyntaxKind to disambiguate string literals in a type position. --- src/compiler/checker.ts | 16 +++++++-------- src/compiler/declarationEmitter.ts | 2 +- src/compiler/emitter.ts | 11 +++++----- src/compiler/parser.ts | 24 ++++++++++++++++------ src/compiler/types.ts | 32 ++++++++++++++++++++---------- src/compiler/utilities.ts | 3 --- src/services/services.ts | 7 +++++-- src/services/utilities.ts | 15 +++++++------- 8 files changed, 67 insertions(+), 43 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8f47b90cfb774..a155387d2ee12 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -532,7 +532,7 @@ namespace ts { } // Because of module/namespace merging, a module's exports are in scope, - // yet we never want to treat an export specifier as putting a member in scope. + // yet we never want to treat an export specifier as putting a member in scope. // Therefore, if the name we find is purely an export specifier, it is not actually considered in scope. // Two things to note about this: // 1. We have to check this without calling getSymbol. The problem with calling getSymbol @@ -3201,7 +3201,7 @@ namespace ts { case SyntaxKind.BooleanKeyword: case SyntaxKind.SymbolKeyword: case SyntaxKind.VoidKeyword: - case SyntaxKind.StringLiteral: + case SyntaxKind.StringLiteralType: return true; case SyntaxKind.ArrayType: return isIndependentType((node).elementType); @@ -3862,7 +3862,7 @@ namespace ts { paramSymbol = resolvedSymbol; } parameters.push(paramSymbol); - if (param.type && param.type.kind === SyntaxKind.StringLiteral) { + if (param.type && param.type.kind === SyntaxKind.StringLiteralType) { hasStringLiterals = true; } @@ -4531,7 +4531,7 @@ namespace ts { return links.resolvedType; } - function getStringLiteralType(node: StringLiteral): StringLiteralType { + function getStringLiteralType(node: StringLiteral | StringLiteralTypeNode): StringLiteralType { const text = node.text; if (hasProperty(stringLiteralTypes, text)) { return stringLiteralTypes[text]; @@ -4542,7 +4542,7 @@ namespace ts { return type; } - function getTypeFromStringLiteral(node: StringLiteral): Type { + function getTypeFromStringLiteral(node: StringLiteral | StringLiteralTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { links.resolvedType = getStringLiteralType(node); @@ -4587,8 +4587,8 @@ namespace ts { return voidType; case SyntaxKind.ThisType: return getTypeFromThisTypeNode(node); - case SyntaxKind.StringLiteral: - return getTypeFromStringLiteral(node); + case SyntaxKind.StringLiteralType: + return getTypeFromStringLiteral(node); case SyntaxKind.TypeReference: return getTypeFromTypeReference(node); case SyntaxKind.TypePredicate: @@ -11397,7 +11397,7 @@ namespace ts { // we can get here in two cases // 1. mixed static and instance class members // 2. something with the same name was defined before the set of overloads that prevents them from merging - // here we'll report error only for the first case since for second we should already report error in binder + // here we'll report error only for the first case since for second we should already report error in binder if (reportError) { const diagnostic = node.flags & NodeFlags.Static ? Diagnostics.Function_overload_must_be_static : Diagnostics.Function_overload_must_not_be_static; error(errorNode, diagnostic); diff --git a/src/compiler/declarationEmitter.ts b/src/compiler/declarationEmitter.ts index 59f89226d5fad..a9bcbd830f01f 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -379,7 +379,7 @@ namespace ts { case SyntaxKind.SymbolKeyword: case SyntaxKind.VoidKeyword: case SyntaxKind.ThisType: - case SyntaxKind.StringLiteral: + case SyntaxKind.StringLiteralType: return writeTextOfNode(currentText, type); case SyntaxKind.ExpressionWithTypeArguments: return emitExpressionWithTypeArguments(type); diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 2e07d7a634ee9..40b37e04191ab 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1310,7 +1310,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } } - function isBinaryOrOctalIntegerLiteral(node: LiteralExpression, text: string): boolean { + function isBinaryOrOctalIntegerLiteral(node: LiteralLikeNode, text: string): boolean { if (node.kind === SyntaxKind.NumericLiteral && text.length > 1) { switch (text.charCodeAt(1)) { case CharacterCodes.b: @@ -1324,7 +1324,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi return false; } - function emitLiteral(node: LiteralExpression) { + function emitLiteral(node: LiteralExpression | TemplateLiteralFragment) { const text = getLiteralText(node); if ((compilerOptions.sourceMap || compilerOptions.inlineSourceMap) && (node.kind === SyntaxKind.StringLiteral || isTemplateLiteralKind(node.kind))) { @@ -1339,7 +1339,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } } - function getLiteralText(node: LiteralExpression) { + function getLiteralText(node: LiteralExpression | TemplateLiteralFragment) { // Any template literal or string literal with an extended escape // (e.g. "\u{0067}") will need to be downleveled as a escaped string literal. if (languageVersion < ScriptTarget.ES6 && (isTemplateLiteralKind(node.kind) || node.hasExtendedUnicodeEscape)) { @@ -1398,7 +1398,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi write(`"${text}"`); } - function emitDownlevelTaggedTemplateArray(node: TaggedTemplateExpression, literalEmitter: (literal: LiteralExpression) => void) { + function emitDownlevelTaggedTemplateArray(node: TaggedTemplateExpression, literalEmitter: (literal: LiteralExpression | TemplateLiteralFragment) => void) { write("["); if (node.template.kind === SyntaxKind.NoSubstitutionTemplateLiteral) { literalEmitter(node.template); @@ -5964,7 +5964,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi function emitSerializedTypeNode(node: TypeNode) { if (node) { - switch (node.kind) { case SyntaxKind.VoidKeyword: write("void 0"); @@ -5990,7 +5989,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi return; case SyntaxKind.StringKeyword: - case SyntaxKind.StringLiteral: + case SyntaxKind.StringLiteralType: write("String"); return; diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 6dbadd074d450..b12db7b0fb15c 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1869,7 +1869,7 @@ namespace ts { function parseTemplateExpression(): TemplateExpression { const template = createNode(SyntaxKind.TemplateExpression); - template.head = parseLiteralNode(); + template.head = parseTemplateLiteralFragment(); Debug.assert(template.head.kind === SyntaxKind.TemplateHead, "Template head has wrong token kind"); const templateSpans = >[]; @@ -1890,22 +1890,34 @@ namespace ts { const span = createNode(SyntaxKind.TemplateSpan); span.expression = allowInAnd(parseExpression); - let literal: LiteralExpression; + let literal: TemplateLiteralFragment; if (token === SyntaxKind.CloseBraceToken) { reScanTemplateToken(); - literal = parseLiteralNode(); + literal = parseTemplateLiteralFragment(); } else { - literal = parseExpectedToken(SyntaxKind.TemplateTail, /*reportAtCurrentPosition*/ false, Diagnostics._0_expected, tokenToString(SyntaxKind.CloseBraceToken)); + literal = parseExpectedToken(SyntaxKind.TemplateTail, /*reportAtCurrentPosition*/ false, Diagnostics._0_expected, tokenToString(SyntaxKind.CloseBraceToken)); } span.literal = literal; return finishNode(span); } + function parseStringLiteralTypeNode(): StringLiteralTypeNode { + return parseLiteralLikeNode(SyntaxKind.StringLiteralType, /*internName*/ true); + } + function parseLiteralNode(internName?: boolean): LiteralExpression { - const node = createNode(token); + return parseLiteralLikeNode(token, internName); + } + + function parseTemplateLiteralFragment(): TemplateLiteralFragment { + return parseLiteralLikeNode(token, /*internName*/ false); + } + + function parseLiteralLikeNode(kind: SyntaxKind, internName: boolean): LiteralLikeNode { + const node = createNode(kind); const text = scanner.getTokenValue(); node.text = internName ? internIdentifier(text) : text; @@ -2392,7 +2404,7 @@ namespace ts { const node = tryParse(parseKeywordAndNoDot); return node || parseTypeReferenceOrTypePredicate(); case SyntaxKind.StringLiteral: - return parseLiteralNode(/*internName*/ true); + return parseStringLiteralTypeNode(); case SyntaxKind.VoidKeyword: return parseTokenNode(); case SyntaxKind.ThisKeyword: diff --git a/src/compiler/types.ts b/src/compiler/types.ts index cfa28c4616181..b4edfe2fa995d 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -205,6 +205,7 @@ namespace ts { IntersectionType, ParenthesizedType, ThisType, + StringLiteralType, // Binding patterns ObjectBindingPattern, ArrayBindingPattern, @@ -350,7 +351,7 @@ namespace ts { FirstFutureReservedWord = ImplementsKeyword, LastFutureReservedWord = YieldKeyword, FirstTypeNode = TypePredicate, - LastTypeNode = ThisType, + LastTypeNode = StringLiteralType, FirstPunctuation = OpenBraceToken, LastPunctuation = CaretEqualsToken, FirstToken = Unknown, @@ -790,10 +791,13 @@ namespace ts { type: TypeNode; } - // Note that a StringLiteral AST node is both an Expression and a TypeNode. The latter is - // because string literals can appear in type annotations as well. + // @kind(SyntaxKind.StringLiteralType) + export interface StringLiteralTypeNode extends LiteralLikeNode, TypeNode { + _stringLiteralTypeBrand: any; + } + // @kind(SyntaxKind.StringLiteral) - export interface StringLiteral extends LiteralExpression, TypeNode { + export interface StringLiteral extends LiteralExpression { _stringLiteralBrand: any; } @@ -911,24 +915,32 @@ namespace ts { body: ConciseBody; } + export interface LiteralLikeNode extends Node { + text: string; + isUnterminated?: boolean; + hasExtendedUnicodeEscape?: boolean; + } + // 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". // @kind(SyntaxKind.NumericLiteral) // @kind(SyntaxKind.RegularExpressionLiteral) // @kind(SyntaxKind.NoSubstitutionTemplateLiteral) + export interface LiteralExpression extends LiteralLikeNode, PrimaryExpression { + _literalExpressionBrand: any; + } + // @kind(SyntaxKind.TemplateHead) // @kind(SyntaxKind.TemplateMiddle) // @kind(SyntaxKind.TemplateTail) - export interface LiteralExpression extends PrimaryExpression { - text: string; - isUnterminated?: boolean; - hasExtendedUnicodeEscape?: boolean; + export interface TemplateLiteralFragment extends LiteralLikeNode { + _templateLiteralFragmentBrand: any; } // @kind(SyntaxKind.TemplateExpression) export interface TemplateExpression extends PrimaryExpression { - head: LiteralExpression; + head: TemplateLiteralFragment; templateSpans: NodeArray; } @@ -937,7 +949,7 @@ namespace ts { // @kind(SyntaxKind.TemplateSpan) export interface TemplateSpan extends Node { expression: Expression; - literal: LiteralExpression; + literal: TemplateLiteralFragment; } // @kind(SyntaxKind.ParenthesizedExpression) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 1e4ee1bed47d4..22228343cf34b 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -464,9 +464,6 @@ namespace ts { return true; case SyntaxKind.VoidKeyword: return node.parent.kind !== SyntaxKind.VoidExpression; - case SyntaxKind.StringLiteral: - // Specialized signatures can have string literals as their parameters' type names - return node.parent.kind === SyntaxKind.Parameter; case SyntaxKind.ExpressionWithTypeArguments: return !isExpressionWithTypeArgumentsInClassExtendsClause(node); diff --git a/src/services/services.ts b/src/services/services.ts index 95179ac0bc013..995966bdd5217 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -3535,6 +3535,7 @@ namespace ts { function isInStringOrRegularExpressionOrTemplateLiteral(contextToken: Node): boolean { if (contextToken.kind === SyntaxKind.StringLiteral + || contextToken.kind === SyntaxKind.StringLiteralType || contextToken.kind === SyntaxKind.RegularExpressionLiteral || isTemplateLiteralKind(contextToken.kind)) { let start = contextToken.getStart(); @@ -6532,6 +6533,7 @@ namespace ts { case SyntaxKind.PropertyAccessExpression: case SyntaxKind.QualifiedName: case SyntaxKind.StringLiteral: + case SyntaxKind.StringLiteralType: case SyntaxKind.FalseKeyword: case SyntaxKind.TrueKeyword: case SyntaxKind.NullKeyword: @@ -6993,7 +6995,7 @@ namespace ts { else if (tokenKind === SyntaxKind.NumericLiteral) { return ClassificationType.numericLiteral; } - else if (tokenKind === SyntaxKind.StringLiteral) { + else if (tokenKind === SyntaxKind.StringLiteral || tokenKind === SyntaxKind.StringLiteralType) { return ClassificationType.stringLiteral; } else if (tokenKind === SyntaxKind.RegularExpressionLiteral) { @@ -7912,7 +7914,7 @@ namespace ts { addResult(start, end, classFromKind(token)); if (end >= text.length) { - if (token === SyntaxKind.StringLiteral) { + if (token === SyntaxKind.StringLiteral || token === SyntaxKind.StringLiteralType) { // Check to see if we finished up on a multiline string literal. let tokenText = scanner.getTokenText(); if (scanner.isUnterminated()) { @@ -8062,6 +8064,7 @@ namespace ts { case SyntaxKind.NumericLiteral: return ClassificationType.numericLiteral; case SyntaxKind.StringLiteral: + case SyntaxKind.StringLiteralType: return ClassificationType.stringLiteral; case SyntaxKind.RegularExpressionLiteral: return ClassificationType.regularExpressionLiteral; diff --git a/src/services/utilities.ts b/src/services/utilities.ts index a7198b4cc6d9f..b3b4ec31a02f0 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -257,14 +257,14 @@ namespace ts { return syntaxList; } - /* Gets the token whose text has range [start, end) and + /* Gets the token whose text has range [start, end) and * position >= start and (position < end or (position === end && token is keyword or identifier)) */ export function getTouchingWord(sourceFile: SourceFile, position: number): Node { return getTouchingToken(sourceFile, position, n => isWord(n.kind)); } - /* Gets the token whose text has range [start, end) and position >= start + /* Gets the token whose text has range [start, end) and position >= start * and (position < end or (position === end && token is keyword or identifier or numeric\string litera)) */ export function getTouchingPropertyName(sourceFile: SourceFile, position: number): Node { @@ -391,8 +391,8 @@ namespace ts { const start = child.getStart(sourceFile); const lookInPreviousChild = (start >= position) || // cursor in the leading trivia - (child.kind === SyntaxKind.JsxText && start === child.end); // whitespace only JsxText - + (child.kind === SyntaxKind.JsxText && start === child.end); // whitespace only JsxText + if (lookInPreviousChild) { // actual start of the node is past the position - previous token should be at the end of previous child let candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ i); @@ -407,7 +407,7 @@ namespace ts { Debug.assert(startNode !== undefined || n.kind === SyntaxKind.SourceFile); - // Here we know that none of child token nodes embrace the position, + // Here we know that none of child token nodes embrace the position, // the only known case is when position is at the end of the file. // Try to find the rightmost token in the file without filtering. // Namely we are skipping the check: 'position < node.end' @@ -429,7 +429,7 @@ namespace ts { export function isInString(sourceFile: SourceFile, position: number) { let token = getTokenAtPosition(sourceFile, position); - return token && token.kind === SyntaxKind.StringLiteral && position > token.getStart(); + return token && (token.kind === SyntaxKind.StringLiteral || token.kind === SyntaxKind.StringLiteralType) && position > token.getStart(); } export function isInComment(sourceFile: SourceFile, position: number) { @@ -445,7 +445,7 @@ namespace ts { if (token && position <= token.getStart()) { let commentRanges = getLeadingCommentRanges(sourceFile.text, token.pos); - + // The end marker of a single-line comment does not include the newline character. // In the following case, we are inside a comment (^ denotes the cursor position): // @@ -565,6 +565,7 @@ namespace ts { export function isStringOrRegularExpressionOrTemplateLiteral(kind: SyntaxKind): boolean { if (kind === SyntaxKind.StringLiteral + || kind === SyntaxKind.StringLiteralType || kind === SyntaxKind.RegularExpressionLiteral || isTemplateLiteralKind(kind)) { return true; From 9b0231d9b897b44b75d7828afe61bb1048beff38 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 19 Nov 2015 17:50:28 -0800 Subject: [PATCH 2/2] Minor change to getStringLiteralType --- src/compiler/checker.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a155387d2ee12..61dabb6d50bb1 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4531,8 +4531,7 @@ namespace ts { return links.resolvedType; } - function getStringLiteralType(node: StringLiteral | StringLiteralTypeNode): StringLiteralType { - const text = node.text; + function getStringLiteralType(text: string): StringLiteralType { if (hasProperty(stringLiteralTypes, text)) { return stringLiteralTypes[text]; } @@ -4545,7 +4544,7 @@ namespace ts { function getTypeFromStringLiteral(node: StringLiteral | StringLiteralTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { - links.resolvedType = getStringLiteralType(node); + links.resolvedType = getStringLiteralType(node.text); } return links.resolvedType; } @@ -8747,7 +8746,7 @@ namespace ts { // for the argument. In that case, we should check the argument. if (argType === undefined) { argType = arg.kind === SyntaxKind.StringLiteral && !reportErrors - ? getStringLiteralType(arg) + ? getStringLiteralType((arg).text) : checkExpressionWithContextualType(arg, paramType, excludeArgument && excludeArgument[i] ? identityMapper : undefined); } @@ -8942,7 +8941,7 @@ namespace ts { case SyntaxKind.Identifier: case SyntaxKind.NumericLiteral: case SyntaxKind.StringLiteral: - return getStringLiteralType(element.name); + return getStringLiteralType((element.name).text); case SyntaxKind.ComputedPropertyName: const nameType = checkComputedPropertyName(element.name); @@ -10564,7 +10563,8 @@ namespace ts { function checkStringLiteralExpression(node: StringLiteral): Type { const contextualType = getContextualType(node); if (contextualType && contextualTypeIsStringLiteralType(contextualType)) { - return getStringLiteralType(node); + // TODO (drosen): Consider using getTypeFromStringLiteral instead + return getStringLiteralType(node.text); } return stringType;