Skip to content

Commit f9ef943

Browse files
Ensure enum members syntactically determinable to be strings do not get reverse mappings (#57686)
Co-authored-by: frigus02 <[email protected]> Co-authored-by: Jan Kühle <[email protected]>
1 parent ac8eb2c commit f9ef943

13 files changed

+203
-1
lines changed

src/compiler/transformers/ts.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ import {
122122
isSimpleInlineableExpression,
123123
isSourceFile,
124124
isStatement,
125+
isSyntacticallyString,
125126
isTemplateLiteral,
126127
isTryStatement,
127128
JsxOpeningElement,
@@ -1922,7 +1923,7 @@ export function transformTypeScript(context: TransformationContext) {
19221923
),
19231924
valueExpression,
19241925
);
1925-
const outerAssignment = valueExpression.kind === SyntaxKind.StringLiteral ?
1926+
const outerAssignment = isSyntacticallyString(valueExpression) ?
19261927
innerAssignment :
19271928
factory.createAssignment(
19281929
factory.createElementAccessExpression(

src/compiler/utilities.ts

+19
Original file line numberDiff line numberDiff line change
@@ -10638,3 +10638,22 @@ export function replaceFirstStar(s: string, replacement: string): string {
1063810638
export function getNameFromImportAttribute(node: ImportAttribute) {
1063910639
return isIdentifier(node.name) ? node.name.escapedText : escapeLeadingUnderscores(node.name.text);
1064010640
}
10641+
10642+
/** @internal */
10643+
export function isSyntacticallyString(expr: Expression): boolean {
10644+
expr = skipOuterExpressions(expr);
10645+
switch (expr.kind) {
10646+
case SyntaxKind.BinaryExpression:
10647+
const left = (expr as BinaryExpression).left;
10648+
const right = (expr as BinaryExpression).right;
10649+
return (
10650+
(expr as BinaryExpression).operatorToken.kind === SyntaxKind.PlusToken &&
10651+
(isSyntacticallyString(left) || isSyntacticallyString(right))
10652+
);
10653+
case SyntaxKind.TemplateExpression:
10654+
case SyntaxKind.StringLiteral:
10655+
case SyntaxKind.NoSubstitutionTemplateLiteral:
10656+
return true;
10657+
}
10658+
return false;
10659+
}

src/testRunner/unittests/services/transpile.ts

+9
Original file line numberDiff line numberDiff line change
@@ -673,4 +673,13 @@ export * as alias from './file';`,
673673
options: { compilerOptions: { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ESNext } },
674674
},
675675
);
676+
677+
transpilesCorrectly(
678+
"Syntactically string but non-evaluatable enum members do not get reverse mapping",
679+
// eslint-disable-next-line no-template-curly-in-string
680+
"import { BAR } from './bar'; enum Foo { A = `${BAR}` }",
681+
{
682+
options: { compilerOptions: { target: ts.ScriptTarget.ESNext } },
683+
},
684+
);
676685
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
computedEnumMemberSyntacticallyString.ts(4,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
2+
computedEnumMemberSyntacticallyString.ts(5,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
3+
computedEnumMemberSyntacticallyString.ts(6,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
4+
computedEnumMemberSyntacticallyString.ts(7,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
5+
computedEnumMemberSyntacticallyString.ts(8,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
6+
7+
8+
==== computedEnumMemberSyntacticallyString.ts (5 errors) ====
9+
const BAR = 2..toFixed(0);
10+
11+
enum Foo {
12+
A = `${BAR}`,
13+
~~~~~~~~
14+
!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
15+
B = "2" + BAR,
16+
~~~~~~~~~
17+
!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
18+
C = (`${BAR}`),
19+
~~~~~~~~~~
20+
!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
21+
D = (`${BAR}}`) as string,
22+
~~~~~~~~~~~~~~~~~~~~~
23+
!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
24+
E = `${BAR}`!,
25+
~~~~~~~~~
26+
!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
27+
}
28+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//// [tests/cases/compiler/computedEnumMemberSyntacticallyString.ts] ////
2+
3+
//// [computedEnumMemberSyntacticallyString.ts]
4+
const BAR = 2..toFixed(0);
5+
6+
enum Foo {
7+
A = `${BAR}`,
8+
B = "2" + BAR,
9+
C = (`${BAR}`),
10+
D = (`${BAR}}`) as string,
11+
E = `${BAR}`!,
12+
}
13+
14+
15+
//// [computedEnumMemberSyntacticallyString.js]
16+
const BAR = 2..toFixed(0);
17+
var Foo;
18+
(function (Foo) {
19+
Foo["A"] = `${BAR}`;
20+
Foo["B"] = "2" + BAR;
21+
Foo["C"] = (`${BAR}`);
22+
Foo["D"] = (`${BAR}}`);
23+
Foo["E"] = `${BAR}`;
24+
})(Foo || (Foo = {}));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
computedEnumMemberSyntacticallyString.ts(4,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
2+
computedEnumMemberSyntacticallyString.ts(5,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
3+
computedEnumMemberSyntacticallyString.ts(6,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
4+
computedEnumMemberSyntacticallyString.ts(7,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
5+
computedEnumMemberSyntacticallyString.ts(8,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
6+
7+
8+
==== computedEnumMemberSyntacticallyString.ts (5 errors) ====
9+
const BAR = 2..toFixed(0);
10+
11+
enum Foo {
12+
A = `${BAR}`,
13+
~~~~~~~~
14+
!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
15+
B = "2" + BAR,
16+
~~~~~~~~~
17+
!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
18+
C = (`${BAR}`),
19+
~~~~~~~~~~
20+
!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
21+
D = (`${BAR}}`) as string,
22+
~~~~~~~~~~~~~~~~~~~~~
23+
!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
24+
E = `${BAR}`!,
25+
~~~~~~~~~
26+
!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
27+
}
28+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//// [tests/cases/compiler/computedEnumMemberSyntacticallyString.ts] ////
2+
3+
//// [computedEnumMemberSyntacticallyString.ts]
4+
const BAR = 2..toFixed(0);
5+
6+
enum Foo {
7+
A = `${BAR}`,
8+
B = "2" + BAR,
9+
C = (`${BAR}`),
10+
D = (`${BAR}}`) as string,
11+
E = `${BAR}`!,
12+
}
13+
14+
15+
//// [computedEnumMemberSyntacticallyString.js]
16+
const BAR = 2..toFixed(0);
17+
var Foo;
18+
(function (Foo) {
19+
Foo["A"] = `${BAR}`;
20+
Foo["B"] = "2" + BAR;
21+
Foo["C"] = (`${BAR}`);
22+
Foo["D"] = (`${BAR}}`);
23+
Foo["E"] = `${BAR}`;
24+
})(Foo || (Foo = {}));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//// [tests/cases/compiler/computedEnumMemberSyntacticallyString2.ts] ////
2+
3+
//// [foo.ts]
4+
import { BAR } from './bar';
5+
enum Foo { A = `${BAR}` }
6+
7+
//// [bar.ts]
8+
export const BAR = 'bar';
9+
10+
//// [bar.js]
11+
export const BAR = 'bar';
12+
//// [foo.js]
13+
import { BAR } from './bar';
14+
var Foo;
15+
(function (Foo) {
16+
Foo["A"] = "bar";
17+
})(Foo || (Foo = {}));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//// [tests/cases/compiler/computedEnumMemberSyntacticallyString2.ts] ////
2+
3+
//// [foo.ts]
4+
import { BAR } from './bar';
5+
enum Foo { A = `${BAR}` }
6+
7+
//// [bar.ts]
8+
export const BAR = 'bar';
9+
10+
//// [bar.js]
11+
export const BAR = 'bar';
12+
//// [foo.js]
13+
import { BAR } from './bar';
14+
var Foo;
15+
(function (Foo) {
16+
Foo["A"] = "bar";
17+
})(Foo || (Foo = {}));

tests/baselines/reference/transpile/Syntactically string but non-evaluatable enum members do not get reverse mapping.js

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/baselines/reference/transpile/Syntactically string but non-evaluatable enum members do not get reverse mapping.oldTranspile.js

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// @isolatedModules: true,false
2+
// @noTypesAndSymbols: true
3+
// @target: esnext
4+
5+
const BAR = 2..toFixed(0);
6+
7+
enum Foo {
8+
A = `${BAR}`,
9+
B = "2" + BAR,
10+
C = (`${BAR}`),
11+
D = (`${BAR}}`) as string,
12+
E = `${BAR}`!,
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// @isolatedModules: true,false
2+
// @noTypesAndSymbols: true
3+
// @target: esnext
4+
5+
// @filename: ./foo.ts
6+
import { BAR } from './bar';
7+
enum Foo { A = `${BAR}` }
8+
9+
// @filename: ./bar.ts
10+
export const BAR = 'bar';

0 commit comments

Comments
 (0)