Merge pull request #5725 from Microsoft/stringTypesCleanup

Disambiguate string literal types from other string literals.
This commit is contained in:
Ron Buckton 2015-11-24 15:23:25 -08:00
commit 8909c3a2dd
8 changed files with 72 additions and 48 deletions

View file

@ -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((<ArrayTypeNode>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(<StringLiteral>node);
case SyntaxKind.StringLiteralType:
return getTypeFromStringLiteral(<StringLiteralTypeNode>node);
case SyntaxKind.TypeReference:
return getTypeFromTypeReference(<TypeReferenceNode>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(<StringLiteral>arg)
? getStringLiteralType((<StringLiteral>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(<StringLiteral>element.name);
return getStringLiteralType((<Identifier | LiteralExpression>element.name).text);
case SyntaxKind.ComputedPropertyName:
const nameType = checkComputedPropertyName(<ComputedPropertyName>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);

View file

@ -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(<ExpressionWithTypeArguments>type);

View file

@ -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(<LiteralExpression>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;

View file

@ -1874,7 +1874,7 @@ namespace ts {
function parseTemplateExpression(): TemplateExpression {
const template = <TemplateExpression>createNode(SyntaxKind.TemplateExpression);
template.head = parseLiteralNode();
template.head = parseTemplateLiteralFragment();
Debug.assert(template.head.kind === SyntaxKind.TemplateHead, "Template head has wrong token kind");
const templateSpans = <NodeArray<TemplateSpan>>[];
@ -1895,22 +1895,34 @@ namespace ts {
const span = <TemplateSpan>createNode(SyntaxKind.TemplateSpan);
span.expression = allowInAnd(parseExpression);
let literal: LiteralExpression;
let literal: TemplateLiteralFragment;
if (token === SyntaxKind.CloseBraceToken) {
reScanTemplateToken();
literal = parseLiteralNode();
literal = parseTemplateLiteralFragment();
}
else {
literal = <LiteralExpression>parseExpectedToken(SyntaxKind.TemplateTail, /*reportAtCurrentPosition*/ false, Diagnostics._0_expected, tokenToString(SyntaxKind.CloseBraceToken));
literal = <TemplateLiteralFragment>parseExpectedToken(SyntaxKind.TemplateTail, /*reportAtCurrentPosition*/ false, Diagnostics._0_expected, tokenToString(SyntaxKind.CloseBraceToken));
}
span.literal = literal;
return finishNode(span);
}
function parseStringLiteralTypeNode(): StringLiteralTypeNode {
return <StringLiteralTypeNode>parseLiteralLikeNode(SyntaxKind.StringLiteralType, /*internName*/ true);
}
function parseLiteralNode(internName?: boolean): LiteralExpression {
const node = <LiteralExpression>createNode(token);
return <LiteralExpression>parseLiteralLikeNode(token, internName);
}
function parseTemplateLiteralFragment(): TemplateLiteralFragment {
return <TemplateLiteralFragment>parseLiteralLikeNode(token, /*internName*/ false);
}
function parseLiteralLikeNode(kind: SyntaxKind, internName: boolean): LiteralLikeNode {
const node = <LiteralExpression>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 <StringLiteral>parseLiteralNode(/*internName*/ true);
return parseStringLiteralTypeNode();
case SyntaxKind.VoidKeyword:
return parseTokenNode<TypeNode>();
case SyntaxKind.ThisKeyword:

View file

@ -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<TemplateSpan>;
}
@ -937,7 +949,7 @@ namespace ts {
// @kind(SyntaxKind.TemplateSpan)
export interface TemplateSpan extends Node {
expression: Expression;
literal: LiteralExpression;
literal: TemplateLiteralFragment;
}
// @kind(SyntaxKind.ParenthesizedExpression)

View file

@ -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);

View file

@ -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;

View file

@ -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;