diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 83d89420a7..c6ff38888a 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 @@ -3197,7 +3197,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); @@ -3858,7 +3858,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; } @@ -4527,8 +4527,7 @@ namespace ts { return links.resolvedType; } - function getStringLiteralType(node: StringLiteral): StringLiteralType { - const text = node.text; + function getStringLiteralType(text: string): StringLiteralType { if (hasProperty(stringLiteralTypes, text)) { return stringLiteralTypes[text]; } @@ -4538,10 +4537,10 @@ 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); + links.resolvedType = getStringLiteralType(node.text); } return links.resolvedType; } @@ -4583,8 +4582,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: @@ -8791,7 +8790,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); } @@ -8986,7 +8985,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); @@ -10608,7 +10607,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; @@ -11442,7 +11442,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 2747a3c647..0250f97ba1 100644 --- a/src/compiler/declarationEmitter.ts +++ b/src/compiler/declarationEmitter.ts @@ -357,7 +357,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 ca44be8bf7..db3d63050f 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1276,7 +1276,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: @@ -1290,7 +1290,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))) { @@ -1305,7 +1305,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)) { @@ -1364,7 +1364,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); @@ -5954,7 +5954,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"); @@ -5980,7 +5979,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 212594f7b7..cb96731dbc 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1874,7 +1874,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 = >[]; @@ -1895,22 +1895,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; @@ -2397,7 +2409,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 9edd7654b6..8af2746eb3 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 1884cee151..abef7706aa 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -466,9 +466,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 8d66b5c7b8..7c98e650a1 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -3372,6 +3372,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(); @@ -6369,6 +6370,7 @@ namespace ts { case SyntaxKind.PropertyAccessExpression: case SyntaxKind.QualifiedName: case SyntaxKind.StringLiteral: + case SyntaxKind.StringLiteralType: case SyntaxKind.FalseKeyword: case SyntaxKind.TrueKeyword: case SyntaxKind.NullKeyword: @@ -6830,7 +6832,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) { @@ -7749,7 +7751,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()) { @@ -7899,6 +7901,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 a7198b4cc6..b3b4ec31a0 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;