Search Terms
template enum, template discriminated, template literals
Suggestion
Following #31042 (comment)
I've reviewed functions that has checks for StringLiterals, but checks for NoSubstitutionTemplateLiteral are missing or logic differs.
I've prepared code for all places below (except "Out of scope" sections) and most of the tests cases are ready for PR.
Could you please check cases below and approve proposed changes?
Please let me know if you have other examples where template literals behaves differently.
1. Support template literals in enum declaration
Original issue: #30962
Original PR: #31042
Example
Result:
const expr = '1';
enum Example {
a = 'a',
b = `b`, // Works
c = `c` + `c`, // Works
d = `a${expr}b` // Still error
}
Related functions:
isStringConcatExpression (in checker.ts)
isLiteralEnumMember (in checker.ts)
getEnumKind (in checker.ts)
evaluate (inside of computeConstanValue, in checker.ts)
2. Support template literals in enum properties access
Example
Result:
const enum Test {
a = 1,
b = 2,
c = 3
}
const enum Test {
d = Test.a, // OK, Test.d = 1
e = Test['b'], // OK, Test.e = 2
f = Test[`c`] // OK, Test.f = 3
}
const a = Test.a; // OK, typeof a == Test
const b = Test['b']; // OK, typeof b == Test
const c = Test[`c`]; // OK, typeof c == Test
enum Example {
a = 10,
b = Example['a'], // OK, Example.b = 10
c = Example[`a`] // OK, Example.c = 10 (correct value now)
}
Related functions:
isConstantMemberAccess (in checker.ts)
checkIndexedAccess (in checker.ts)
3. Support template literals in switch statements with typeof condition
Example
Result:
function errorOnValue(val: never): never {
throw new Error(`Unexpected value: ${val}`);
}
function test(value: number | string) {
switch (typeof value) {
case "number":
return 'num';
case `string`:
return 'str';
default:
return errorOnValue(value); // value will have type `never` and no error here
}
}
Related functions:
getSwitchClauseTypeOfWitnesses (in checker.ts)
4. Support template literals in const initializers inside an ambient declarations
Example
Result:
declare module Example {
enum Test {
a = '1',
b = '2',
c = '3'
}
export const a = 'string';
export const b = `template`; // OK
export const c = Test.a;
export const d = Test['b'];
export const e = Test[`c`]; // OK
}
Related functions:
isStringOrNumberLiteralExpression (in checker.ts)
5. Support template literals in control flow checks
Example
Result:
type T1 = { kind: 'A', a: number };
type T2 = { kind: 'B', b: string };
type TUnion = T1 | T2;
function verifyNever(val: never): never {
throw new Error(`Unexpected value: ${val}`);
}
function Example1(val: TUnion) {
switch (val[`kind`]) {
case 'A':
return val.a; // OK, val: T1
case 'B':
return val.b; // OK, val: T2
default:
return verifyNever(val); // OK, val: never
}
}
function Example2(val: TUnion) {
if (val[`kind`] === 'B') {
return val.b; // OK, val: T2
} else {
return val.a; // OK, val: T1
}
}
Related functions:
getAccessedPropertyName (in checker.ts)
isNarrowableReference (in binder.ts)
6. Unify symbols for computed properties
I've noticed difference only in generated symbols for computed properties, that will produce similar list of symbols as regular string literals.
Result:
class C {
>C : Symbol(C, Decl(computedPropertyNames13_ES6.ts, 2, 11))
static [""]() { }
>[""] : Symbol(C[""], Decl(computedPropertyNames13_ES6.ts, 8, 14))
>"" : Symbol(C[""], Decl(computedPropertyNames13_ES6.ts, 8, 14))
[`hello bye`]() { }
>[`hello bye`] : Symbol(C[`hello bye`], Decl(computedPropertyNames13_ES6.ts, 12, 28))
+>`hello bye` : Symbol(C[`hello bye`], Decl(computedPropertyNames13_ES6.ts, 12, 28))
}
Related functions:
isLiteralComputedPropertyDeclarationName (in utilities.ts)
7. Unify isExpressionNode checks
While both StringLiteral and NoSubstitutionTemplateLiteral kinds are checked inside this function - later one is always considered as expression.
Unified checks will remove additional type(?) line and make output similar to regular strings.
Result:
const x: `foo` = "foo";
>x : "foo"
->`foo` : "foo"
>"foo" : "foo"
Which will be similar to
const y: 'bar' = "bar";
>y : "bar"
>"bar" : "bar"
Related functions:
isExpressionNode (in utilities.ts)
8. Optional suggestion: Support template literals in let a: import(`test`) and import b = require(`test`)
These are two cases when TS correctly builds AST and can support template literals without updates to parser (see "Out of scope" section with examples of unexpected AST).
Example
Result:
let a: import(`test`); // OK
import b = require(`test`); // OK
Related functions:
isLiteralImportTypeNode (in utilities.ts)
tryGetImportFromModuleSpecifier (in utilities.ts)
checkExternalImportOrExportDeclaration (in checker.ts)
getExternalModuleFileFromDeclaration (in checker.ts)
Out of scope
Supporting template literals in import statements. Related issue: #29318
TS has unexpected (at least for me) AST built for cases like
declare module `*.tpl` {}
let a: Promise<`test`>;
Related issue: #29331
I would like to leave these cases out of scope for new PR because these changes are not in checker level.
Few implementation questions
- What would be preferred way to check kind of node.
isStringLiteralLike or (node.kind === SyntaxKind.StringLiteral || node.kind === SyntaxKind.NoSubstitutionTemplateLiteral)? Later won't look great in long && or || expressions.
- Is it possible to update text of existing error message (if it is used only in one place that will be updated) or I should create new message and existing one will be abandoned?
Checklist
My suggestion meets these guidelines:
Search Terms
template enum, template discriminated, template literals
Suggestion
Following #31042 (comment)
I've reviewed functions that has checks for
StringLiterals, but checks forNoSubstitutionTemplateLiteralare missing or logic differs.I've prepared code for all places below (except "Out of scope" sections) and most of the tests cases are ready for PR.
Could you please check cases below and approve proposed changes?
Please let me know if you have other examples where template literals behaves differently.
1. Support template literals in enum declaration
Original issue: #30962
Original PR: #31042
Example
Result:
Related functions:
isStringConcatExpression(inchecker.ts)isLiteralEnumMember(inchecker.ts)getEnumKind(inchecker.ts)evaluate(inside ofcomputeConstanValue, inchecker.ts)2. Support template literals in enum properties access
Example
Result:
Related functions:
isConstantMemberAccess(inchecker.ts)checkIndexedAccess(inchecker.ts)3. Support template literals in switch statements with
typeofconditionExample
Result:
Related functions:
getSwitchClauseTypeOfWitnesses(inchecker.ts)4. Support template literals in
constinitializers inside an ambient declarationsExample
Result:
Related functions:
isStringOrNumberLiteralExpression(inchecker.ts)5. Support template literals in control flow checks
Example
Result:
Related functions:
getAccessedPropertyName(inchecker.ts)isNarrowableReference(inbinder.ts)6. Unify symbols for computed properties
I've noticed difference only in generated symbols for computed properties, that will produce similar list of symbols as regular string literals.
Result:
class C { >C : Symbol(C, Decl(computedPropertyNames13_ES6.ts, 2, 11)) static [""]() { } >[""] : Symbol(C[""], Decl(computedPropertyNames13_ES6.ts, 8, 14)) >"" : Symbol(C[""], Decl(computedPropertyNames13_ES6.ts, 8, 14)) [`hello bye`]() { } >[`hello bye`] : Symbol(C[`hello bye`], Decl(computedPropertyNames13_ES6.ts, 12, 28)) +>`hello bye` : Symbol(C[`hello bye`], Decl(computedPropertyNames13_ES6.ts, 12, 28)) }Related functions:
isLiteralComputedPropertyDeclarationName(inutilities.ts)7. Unify
isExpressionNodechecksWhile both
StringLiteralandNoSubstitutionTemplateLiteralkinds are checked inside this function - later one is always considered as expression.Unified checks will remove additional type(?) line and make output similar to regular strings.
Result:
const x: `foo` = "foo"; >x : "foo" ->`foo` : "foo" >"foo" : "foo"Which will be similar to
Related functions:
isExpressionNode(inutilities.ts)8. Optional suggestion: Support template literals in
let a: import(`test`)andimport b = require(`test`)These are two cases when TS correctly builds AST and can support template literals without updates to parser (see "Out of scope" section with examples of unexpected AST).
Example
Result:
Related functions:
isLiteralImportTypeNode(inutilities.ts)tryGetImportFromModuleSpecifier(inutilities.ts)checkExternalImportOrExportDeclaration(inchecker.ts)getExternalModuleFileFromDeclaration(inchecker.ts)Out of scope
Supporting template literals in import statements. Related issue: #29318
TS has unexpected (at least for me) AST built for cases like
Related issue: #29331
I would like to leave these cases out of scope for new PR because these changes are not in checker level.
Few implementation questions
isStringLiteralLikeor(node.kind === SyntaxKind.StringLiteral || node.kind === SyntaxKind.NoSubstitutionTemplateLiteral)? Later won't look great in long&&or||expressions.Checklist
My suggestion meets these guidelines: