-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Disambiguate string literal types from other string literals. #5725
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 1 commit
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 |
---|---|---|
|
@@ -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((<ArrayTypeNode>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 { | ||
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. Can you add a I need to see how this affects things, but I don't want it to affect your work. 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. I'll put a comment in checkStringLiteralExpression instead. The big difference is that getTypeFromStringLiteral caches the type on the resolvedType of the node's NodeLinks. |
||
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(<StringLiteral>node); | ||
case SyntaxKind.StringLiteralType: | ||
return getTypeFromStringLiteral(<StringLiteralTypeNode>node); | ||
case SyntaxKind.TypeReference: | ||
return getTypeFromTypeReference(<TypeReferenceNode>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); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 { | ||
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. Nice! |
||
_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) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
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. This |
||
|| kind === SyntaxKind.RegularExpressionLiteral | ||
|| isTemplateLiteralKind(kind)) { | ||
return true; | ||
|
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.
Maybe this should just take the
string
and call itgetStringLiteralTypeForText
; what are your thoughts?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.
No need to change the name, I can just change the argument to be a string.