Skip to content

Commit a34fdb2

Browse files
IllusionMHsandersn
authored andcommitted
Better template literals support in checker (#32064)
* Support template literals in enum declarations * Support template literals in const enum access * Support template literals in swith with typeof narrowing * Support template literals in element access discriminant * Support template literals in ambient module declaration * Unify symbols for template literals in computed properties * Unify expression position checks for template literals * Support template literals in rename and find all references * Mark computed properties with template literals as write access * Inline startsWithQuote
1 parent c0573c5 commit a34fdb2

File tree

60 files changed

+2445
-213
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+2445
-213
lines changed

src/compiler/binder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -850,7 +850,7 @@ namespace ts {
850850
return expr.kind === SyntaxKind.Identifier || expr.kind === SyntaxKind.ThisKeyword || expr.kind === SyntaxKind.SuperKeyword ||
851851
(isPropertyAccessExpression(expr) || isNonNullExpression(expr) || isParenthesizedExpression(expr)) && isNarrowableReference(expr.expression) ||
852852
isElementAccessExpression(expr) &&
853-
(isStringLiteral(expr.argumentExpression) || isNumericLiteral(expr.argumentExpression)) &&
853+
isStringOrNumericLiteralLike(expr.argumentExpression) &&
854854
isNarrowableReference(expr.expression);
855855
}
856856

src/compiler/checker.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7930,7 +7930,7 @@ namespace ts {
79307930
}
79317931

79327932
function isStringConcatExpression(expr: Node): boolean {
7933-
if (expr.kind === SyntaxKind.StringLiteral) {
7933+
if (isStringLiteralLike(expr)) {
79347934
return true;
79357935
}
79367936
else if (expr.kind === SyntaxKind.BinaryExpression) {
@@ -7947,6 +7947,7 @@ namespace ts {
79477947
switch (expr.kind) {
79487948
case SyntaxKind.StringLiteral:
79497949
case SyntaxKind.NumericLiteral:
7950+
case SyntaxKind.NoSubstitutionTemplateLiteral:
79507951
return true;
79517952
case SyntaxKind.PrefixUnaryExpression:
79527953
return (<PrefixUnaryExpression>expr).operator === SyntaxKind.MinusToken &&
@@ -7969,7 +7970,7 @@ namespace ts {
79697970
for (const declaration of symbol.declarations) {
79707971
if (declaration.kind === SyntaxKind.EnumDeclaration) {
79717972
for (const member of (<EnumDeclaration>declaration).members) {
7972-
if (member.initializer && member.initializer.kind === SyntaxKind.StringLiteral) {
7973+
if (member.initializer && isStringLiteralLike(member.initializer)) {
79737974
return links.enumKind = EnumKind.Literal;
79747975
}
79757976
if (!isLiteralEnumMember(member)) {
@@ -17921,7 +17922,7 @@ namespace ts {
1792117922

1792217923
function getAccessedPropertyName(access: AccessExpression): __String | undefined {
1792317924
return access.kind === SyntaxKind.PropertyAccessExpression ? access.name.escapedText :
17924-
isStringLiteral(access.argumentExpression) || isNumericLiteral(access.argumentExpression) ? escapeLeadingUnderscores(access.argumentExpression.text) :
17925+
isStringOrNumericLiteralLike(access.argumentExpression) ? escapeLeadingUnderscores(access.argumentExpression.text) :
1792517926
undefined;
1792617927
}
1792717928

@@ -18319,8 +18320,8 @@ namespace ts {
1831918320
const witnesses: (string | undefined)[] = [];
1832018321
for (const clause of switchStatement.caseBlock.clauses) {
1832118322
if (clause.kind === SyntaxKind.CaseClause) {
18322-
if (clause.expression.kind === SyntaxKind.StringLiteral) {
18323-
witnesses.push((clause.expression as StringLiteral).text);
18323+
if (isStringLiteralLike(clause.expression)) {
18324+
witnesses.push(clause.expression.text);
1832418325
continue;
1832518326
}
1832618327
return emptyArray;
@@ -22887,7 +22888,7 @@ namespace ts {
2288722888
return objectType;
2288822889
}
2288922890

22890-
if (isConstEnumObjectType(objectType) && indexExpression.kind !== SyntaxKind.StringLiteral) {
22891+
if (isConstEnumObjectType(objectType) && !isStringLiteralLike(indexExpression)) {
2289122892
error(indexExpression, Diagnostics.A_const_enum_member_can_only_be_accessed_using_a_string_literal);
2289222893
return errorType;
2289322894
}
@@ -31603,7 +31604,8 @@ namespace ts {
3160331604
}
3160431605
break;
3160531606
case SyntaxKind.StringLiteral:
31606-
return (<StringLiteral>expr).text;
31607+
case SyntaxKind.NoSubstitutionTemplateLiteral:
31608+
return (<StringLiteralLike>expr).text;
3160731609
case SyntaxKind.NumericLiteral:
3160831610
checkGrammarNumericLiteral(<NumericLiteral>expr);
3160931611
return +(<NumericLiteral>expr).text;
@@ -31656,7 +31658,7 @@ namespace ts {
3165631658
return node.kind === SyntaxKind.Identifier ||
3165731659
node.kind === SyntaxKind.PropertyAccessExpression && isConstantMemberAccess((<PropertyAccessExpression>node).expression) ||
3165831660
node.kind === SyntaxKind.ElementAccessExpression && isConstantMemberAccess((<ElementAccessExpression>node).expression) &&
31659-
(<ElementAccessExpression>node).argumentExpression.kind === SyntaxKind.StringLiteral;
31661+
isStringLiteralLike((<ElementAccessExpression>node).argumentExpression);
3166031662
}
3166131663

3166231664
function checkEnumDeclaration(node: EnumDeclaration) {
@@ -35239,7 +35241,7 @@ namespace ts {
3523935241
}
3524035242

3524135243
function isStringOrNumberLiteralExpression(expr: Expression) {
35242-
return expr.kind === SyntaxKind.StringLiteral || expr.kind === SyntaxKind.NumericLiteral ||
35244+
return isStringOrNumericLiteralLike(expr) ||
3524335245
expr.kind === SyntaxKind.PrefixUnaryExpression && (<PrefixUnaryExpression>expr).operator === SyntaxKind.MinusToken &&
3524435246
(<PrefixUnaryExpression>expr).operand.kind === SyntaxKind.NumericLiteral;
3524535247
}

src/compiler/transformers/ts.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3249,9 +3249,10 @@ namespace ts {
32493249

32503250
const substitute = createLiteral(constantValue);
32513251
if (!compilerOptions.removeComments) {
3252-
const propertyName = isPropertyAccessExpression(node)
3253-
? declarationNameToString(node.name)
3254-
: getTextOfNode(node.argumentExpression);
3252+
const originalNode = getOriginalNode(node, isAccessExpression);
3253+
const propertyName = isPropertyAccessExpression(originalNode)
3254+
? declarationNameToString(originalNode.name)
3255+
: getTextOfNode(originalNode.argumentExpression);
32553256

32563257
addSyntheticTrailingComment(substitute, SyntaxKind.MultiLineCommentTrivia, ` ${propertyName} `);
32573258
}

src/compiler/utilities.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1692,7 +1692,6 @@ namespace ts {
16921692
case SyntaxKind.ConditionalExpression:
16931693
case SyntaxKind.SpreadElement:
16941694
case SyntaxKind.TemplateExpression:
1695-
case SyntaxKind.NoSubstitutionTemplateLiteral:
16961695
case SyntaxKind.OmittedExpression:
16971696
case SyntaxKind.JsxElement:
16981697
case SyntaxKind.JsxSelfClosingElement:
@@ -1715,6 +1714,7 @@ namespace ts {
17151714
case SyntaxKind.NumericLiteral:
17161715
case SyntaxKind.BigIntLiteral:
17171716
case SyntaxKind.StringLiteral:
1717+
case SyntaxKind.NoSubstitutionTemplateLiteral:
17181718
case SyntaxKind.ThisKeyword:
17191719
return isInExpressionContext(node);
17201720
default:
@@ -2529,6 +2529,7 @@ namespace ts {
25292529
const parent = name.parent;
25302530
switch (name.kind) {
25312531
case SyntaxKind.StringLiteral:
2532+
case SyntaxKind.NoSubstitutionTemplateLiteral:
25322533
case SyntaxKind.NumericLiteral:
25332534
if (isComputedPropertyName(parent)) return parent.parent;
25342535
// falls through
@@ -2556,7 +2557,7 @@ namespace ts {
25562557
}
25572558

25582559
export function isLiteralComputedPropertyDeclarationName(node: Node) {
2559-
return (node.kind === SyntaxKind.StringLiteral || node.kind === SyntaxKind.NumericLiteral) &&
2560+
return isStringOrNumericLiteralLike(node) &&
25602561
node.parent.kind === SyntaxKind.ComputedPropertyName &&
25612562
isDeclaration(node.parent.parent);
25622563
}
@@ -3243,20 +3244,22 @@ namespace ts {
32433244
}
32443245

32453246
/**
3246-
* Strip off existed single quotes or double quotes from a given string
3247+
* Strip off existed surrounding single quotes, double quotes, or backticks from a given string
32473248
*
32483249
* @return non-quoted string
32493250
*/
32503251
export function stripQuotes(name: string) {
32513252
const length = name.length;
3252-
if (length >= 2 && name.charCodeAt(0) === name.charCodeAt(length - 1) && startsWithQuote(name)) {
3253+
if (length >= 2 && name.charCodeAt(0) === name.charCodeAt(length - 1) && isQuoteOrBacktick(name.charCodeAt(0))) {
32533254
return name.substring(1, length - 1);
32543255
}
32553256
return name;
32563257
}
32573258

3258-
export function startsWithQuote(name: string): boolean {
3259-
return isSingleOrDoubleQuote(name.charCodeAt(0));
3259+
function isQuoteOrBacktick(charCode: number) {
3260+
return charCode === CharacterCodes.singleQuote ||
3261+
charCode === CharacterCodes.doubleQuote ||
3262+
charCode === CharacterCodes.backtick;
32603263
}
32613264

32623265
function getReplacement(c: string, offset: number, input: string) {

src/services/completions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2086,7 +2086,7 @@ namespace ts.Completions {
20862086
if (name === undefined
20872087
// If the symbol is external module, don't show it in the completion list
20882088
// (i.e declare module "http" { const x; } | // <= request completion here, "http" should not be there)
2089-
|| symbol.flags & SymbolFlags.Module && startsWithQuote(name)
2089+
|| symbol.flags & SymbolFlags.Module && isSingleOrDoubleQuote(name.charCodeAt(0))
20902090
// If the symbol is the internal name of an ES symbol, it is not a valid entry. Internal names for ES symbols start with "__@"
20912091
|| isKnownSymbol(symbol)) {
20922092
return undefined;

src/services/findAllReferences.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ namespace ts.FindAllReferences {
448448
function getTextSpan(node: Node, sourceFile: SourceFile, endNode?: Node): TextSpan {
449449
let start = node.getStart(sourceFile);
450450
let end = (endNode || node).getEnd();
451-
if (node.kind === SyntaxKind.StringLiteral) {
451+
if (isStringLiteralLike(node)) {
452452
Debug.assert(endNode === undefined);
453453
start += 1;
454454
end -= 1;
@@ -1234,8 +1234,9 @@ namespace ts.FindAllReferences.Core {
12341234
case SyntaxKind.Identifier:
12351235
return (node as Identifier).text.length === searchSymbolName.length;
12361236

1237+
case SyntaxKind.NoSubstitutionTemplateLiteral:
12371238
case SyntaxKind.StringLiteral: {
1238-
const str = node as StringLiteral;
1239+
const str = node as StringLiteralLike;
12391240
return (isLiteralNameOfPropertyDeclarationOrIndexAccess(str) || isNameOfModuleDeclaration(node) || isExpressionOfExternalModuleImportEqualsDeclaration(node) || (isCallExpression(node.parent) && isBindableObjectDefinePropertyCall(node.parent) && node.parent.arguments[1] === node)) &&
12401241
str.text.length === searchSymbolName.length;
12411242
}

src/services/rename.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ namespace ts.Rename {
8181
function createTriggerSpanForNode(node: Node, sourceFile: SourceFile) {
8282
let start = node.getStart(sourceFile);
8383
let width = node.getWidth(sourceFile);
84-
if (node.kind === SyntaxKind.StringLiteral) {
84+
if (isStringLiteralLike(node)) {
8585
// Exclude the quotes
8686
start += 1;
8787
width -= 2;
@@ -93,6 +93,7 @@ namespace ts.Rename {
9393
switch (node.kind) {
9494
case SyntaxKind.Identifier:
9595
case SyntaxKind.StringLiteral:
96+
case SyntaxKind.NoSubstitutionTemplateLiteral:
9697
case SyntaxKind.ThisKeyword:
9798
return true;
9899
case SyntaxKind.NumericLiteral:

src/services/services.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2231,6 +2231,7 @@ namespace ts {
22312231
function getContainingObjectLiteralElementWorker(node: Node): ObjectLiteralElement | undefined {
22322232
switch (node.kind) {
22332233
case SyntaxKind.StringLiteral:
2234+
case SyntaxKind.NoSubstitutionTemplateLiteral:
22342235
case SyntaxKind.NumericLiteral:
22352236
if (node.parent.kind === SyntaxKind.ComputedPropertyName) {
22362237
return isObjectLiteralElement(node.parent.parent) ? node.parent.parent : undefined;

src/services/utilities.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ namespace ts {
267267
isFunctionLike(node.parent) && (<FunctionLikeDeclaration>node.parent).name === node;
268268
}
269269

270-
export function isLiteralNameOfPropertyDeclarationOrIndexAccess(node: StringLiteral | NumericLiteral): boolean {
270+
export function isLiteralNameOfPropertyDeclarationOrIndexAccess(node: StringLiteral | NumericLiteral | NoSubstitutionTemplateLiteral): boolean {
271271
switch (node.parent.kind) {
272272
case SyntaxKind.PropertyDeclaration:
273273
case SyntaxKind.PropertySignature:
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//// [ambientModuleWithTemplateLiterals.ts]
2+
declare module Foo {
3+
enum Bar {
4+
a = `1`,
5+
b = '2',
6+
c = '3'
7+
}
8+
9+
export const a = 'string';
10+
export const b = `template`;
11+
12+
export const c = Bar.a;
13+
export const d = Bar['b'];
14+
export const e = Bar[`c`];
15+
}
16+
17+
Foo.a;
18+
Foo.b;
19+
Foo.c;
20+
Foo.d;
21+
Foo.e;
22+
23+
//// [ambientModuleWithTemplateLiterals.js]
24+
Foo.a;
25+
Foo.b;
26+
Foo.c;
27+
Foo.d;
28+
Foo.e;
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
=== tests/cases/compiler/ambientModuleWithTemplateLiterals.ts ===
2+
declare module Foo {
3+
>Foo : Symbol(Foo, Decl(ambientModuleWithTemplateLiterals.ts, 0, 0))
4+
5+
enum Bar {
6+
>Bar : Symbol(Bar, Decl(ambientModuleWithTemplateLiterals.ts, 0, 20))
7+
8+
a = `1`,
9+
>a : Symbol(Bar.a, Decl(ambientModuleWithTemplateLiterals.ts, 1, 14))
10+
11+
b = '2',
12+
>b : Symbol(Bar.b, Decl(ambientModuleWithTemplateLiterals.ts, 2, 16))
13+
14+
c = '3'
15+
>c : Symbol(Bar.c, Decl(ambientModuleWithTemplateLiterals.ts, 3, 16))
16+
}
17+
18+
export const a = 'string';
19+
>a : Symbol(a, Decl(ambientModuleWithTemplateLiterals.ts, 7, 16))
20+
21+
export const b = `template`;
22+
>b : Symbol(b, Decl(ambientModuleWithTemplateLiterals.ts, 8, 16))
23+
24+
export const c = Bar.a;
25+
>c : Symbol(c, Decl(ambientModuleWithTemplateLiterals.ts, 10, 16))
26+
>Bar.a : Symbol(Bar.a, Decl(ambientModuleWithTemplateLiterals.ts, 1, 14))
27+
>Bar : Symbol(Bar, Decl(ambientModuleWithTemplateLiterals.ts, 0, 20))
28+
>a : Symbol(Bar.a, Decl(ambientModuleWithTemplateLiterals.ts, 1, 14))
29+
30+
export const d = Bar['b'];
31+
>d : Symbol(d, Decl(ambientModuleWithTemplateLiterals.ts, 11, 16))
32+
>Bar : Symbol(Bar, Decl(ambientModuleWithTemplateLiterals.ts, 0, 20))
33+
>'b' : Symbol(Bar.b, Decl(ambientModuleWithTemplateLiterals.ts, 2, 16))
34+
35+
export const e = Bar[`c`];
36+
>e : Symbol(e, Decl(ambientModuleWithTemplateLiterals.ts, 12, 16))
37+
>Bar : Symbol(Bar, Decl(ambientModuleWithTemplateLiterals.ts, 0, 20))
38+
>`c` : Symbol(Bar.c, Decl(ambientModuleWithTemplateLiterals.ts, 3, 16))
39+
}
40+
41+
Foo.a;
42+
>Foo.a : Symbol(Foo.a, Decl(ambientModuleWithTemplateLiterals.ts, 7, 16))
43+
>Foo : Symbol(Foo, Decl(ambientModuleWithTemplateLiterals.ts, 0, 0))
44+
>a : Symbol(Foo.a, Decl(ambientModuleWithTemplateLiterals.ts, 7, 16))
45+
46+
Foo.b;
47+
>Foo.b : Symbol(Foo.b, Decl(ambientModuleWithTemplateLiterals.ts, 8, 16))
48+
>Foo : Symbol(Foo, Decl(ambientModuleWithTemplateLiterals.ts, 0, 0))
49+
>b : Symbol(Foo.b, Decl(ambientModuleWithTemplateLiterals.ts, 8, 16))
50+
51+
Foo.c;
52+
>Foo.c : Symbol(Foo.c, Decl(ambientModuleWithTemplateLiterals.ts, 10, 16))
53+
>Foo : Symbol(Foo, Decl(ambientModuleWithTemplateLiterals.ts, 0, 0))
54+
>c : Symbol(Foo.c, Decl(ambientModuleWithTemplateLiterals.ts, 10, 16))
55+
56+
Foo.d;
57+
>Foo.d : Symbol(Foo.d, Decl(ambientModuleWithTemplateLiterals.ts, 11, 16))
58+
>Foo : Symbol(Foo, Decl(ambientModuleWithTemplateLiterals.ts, 0, 0))
59+
>d : Symbol(Foo.d, Decl(ambientModuleWithTemplateLiterals.ts, 11, 16))
60+
61+
Foo.e;
62+
>Foo.e : Symbol(Foo.e, Decl(ambientModuleWithTemplateLiterals.ts, 12, 16))
63+
>Foo : Symbol(Foo, Decl(ambientModuleWithTemplateLiterals.ts, 0, 0))
64+
>e : Symbol(Foo.e, Decl(ambientModuleWithTemplateLiterals.ts, 12, 16))
65+
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
=== tests/cases/compiler/ambientModuleWithTemplateLiterals.ts ===
2+
declare module Foo {
3+
>Foo : typeof Foo
4+
5+
enum Bar {
6+
>Bar : Bar
7+
8+
a = `1`,
9+
>a : Bar.a
10+
>`1` : "1"
11+
12+
b = '2',
13+
>b : Bar.b
14+
>'2' : "2"
15+
16+
c = '3'
17+
>c : Bar.c
18+
>'3' : "3"
19+
}
20+
21+
export const a = 'string';
22+
>a : "string"
23+
>'string' : "string"
24+
25+
export const b = `template`;
26+
>b : "template"
27+
>`template` : "template"
28+
29+
export const c = Bar.a;
30+
>c : Bar.a
31+
>Bar.a : Bar.a
32+
>Bar : typeof Bar
33+
>a : Bar.a
34+
35+
export const d = Bar['b'];
36+
>d : Bar.b
37+
>Bar['b'] : Bar.b
38+
>Bar : typeof Bar
39+
>'b' : "b"
40+
41+
export const e = Bar[`c`];
42+
>e : Bar.c
43+
>Bar[`c`] : Bar.c
44+
>Bar : typeof Bar
45+
>`c` : "c"
46+
}
47+
48+
Foo.a;
49+
>Foo.a : "string"
50+
>Foo : typeof Foo
51+
>a : "string"
52+
53+
Foo.b;
54+
>Foo.b : "template"
55+
>Foo : typeof Foo
56+
>b : "template"
57+
58+
Foo.c;
59+
>Foo.c : Foo.Bar.a
60+
>Foo : typeof Foo
61+
>c : Foo.Bar.a
62+
63+
Foo.d;
64+
>Foo.d : Foo.Bar.b
65+
>Foo : typeof Foo
66+
>d : Foo.Bar.b
67+
68+
Foo.e;
69+
>Foo.e : Foo.Bar.c
70+
>Foo : typeof Foo
71+
>e : Foo.Bar.c
72+

0 commit comments

Comments
 (0)