Ensure enum members syntactically determinable to be strings do not get reverse mappings (#57686)

Co-authored-by: frigus02 <3579251+frigus02@users.noreply.github.com>
Co-authored-by: Jan Kühle <jkuehle90@gmail.com>
This commit is contained in:
Andrew Branch 2024-03-13 13:11:02 -07:00 committed by GitHub
parent ac8eb2c993
commit f9ef9439bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 203 additions and 1 deletions

View File

@ -122,6 +122,7 @@ import {
isSimpleInlineableExpression,
isSourceFile,
isStatement,
isSyntacticallyString,
isTemplateLiteral,
isTryStatement,
JsxOpeningElement,
@ -1922,7 +1923,7 @@ export function transformTypeScript(context: TransformationContext) {
),
valueExpression,
);
const outerAssignment = valueExpression.kind === SyntaxKind.StringLiteral ?
const outerAssignment = isSyntacticallyString(valueExpression) ?
innerAssignment :
factory.createAssignment(
factory.createElementAccessExpression(

View File

@ -10638,3 +10638,22 @@ export function replaceFirstStar(s: string, replacement: string): string {
export function getNameFromImportAttribute(node: ImportAttribute) {
return isIdentifier(node.name) ? node.name.escapedText : escapeLeadingUnderscores(node.name.text);
}
/** @internal */
export function isSyntacticallyString(expr: Expression): boolean {
expr = skipOuterExpressions(expr);
switch (expr.kind) {
case SyntaxKind.BinaryExpression:
const left = (expr as BinaryExpression).left;
const right = (expr as BinaryExpression).right;
return (
(expr as BinaryExpression).operatorToken.kind === SyntaxKind.PlusToken &&
(isSyntacticallyString(left) || isSyntacticallyString(right))
);
case SyntaxKind.TemplateExpression:
case SyntaxKind.StringLiteral:
case SyntaxKind.NoSubstitutionTemplateLiteral:
return true;
}
return false;
}

View File

@ -673,4 +673,13 @@ export * as alias from './file';`,
options: { compilerOptions: { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ESNext } },
},
);
transpilesCorrectly(
"Syntactically string but non-evaluatable enum members do not get reverse mapping",
// eslint-disable-next-line no-template-curly-in-string
"import { BAR } from './bar'; enum Foo { A = `${BAR}` }",
{
options: { compilerOptions: { target: ts.ScriptTarget.ESNext } },
},
);
});

View File

@ -0,0 +1,28 @@
computedEnumMemberSyntacticallyString.ts(4,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
computedEnumMemberSyntacticallyString.ts(5,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
computedEnumMemberSyntacticallyString.ts(6,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
computedEnumMemberSyntacticallyString.ts(7,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
computedEnumMemberSyntacticallyString.ts(8,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
==== computedEnumMemberSyntacticallyString.ts (5 errors) ====
const BAR = 2..toFixed(0);
enum Foo {
A = `${BAR}`,
~~~~~~~~
!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
B = "2" + BAR,
~~~~~~~~~
!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
C = (`${BAR}`),
~~~~~~~~~~
!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
D = (`${BAR}}`) as string,
~~~~~~~~~~~~~~~~~~~~~
!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
E = `${BAR}`!,
~~~~~~~~~
!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
}

View File

@ -0,0 +1,24 @@
//// [tests/cases/compiler/computedEnumMemberSyntacticallyString.ts] ////
//// [computedEnumMemberSyntacticallyString.ts]
const BAR = 2..toFixed(0);
enum Foo {
A = `${BAR}`,
B = "2" + BAR,
C = (`${BAR}`),
D = (`${BAR}}`) as string,
E = `${BAR}`!,
}
//// [computedEnumMemberSyntacticallyString.js]
const BAR = 2..toFixed(0);
var Foo;
(function (Foo) {
Foo["A"] = `${BAR}`;
Foo["B"] = "2" + BAR;
Foo["C"] = (`${BAR}`);
Foo["D"] = (`${BAR}}`);
Foo["E"] = `${BAR}`;
})(Foo || (Foo = {}));

View File

@ -0,0 +1,28 @@
computedEnumMemberSyntacticallyString.ts(4,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
computedEnumMemberSyntacticallyString.ts(5,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
computedEnumMemberSyntacticallyString.ts(6,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
computedEnumMemberSyntacticallyString.ts(7,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
computedEnumMemberSyntacticallyString.ts(8,9): error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
==== computedEnumMemberSyntacticallyString.ts (5 errors) ====
const BAR = 2..toFixed(0);
enum Foo {
A = `${BAR}`,
~~~~~~~~
!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
B = "2" + BAR,
~~~~~~~~~
!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
C = (`${BAR}`),
~~~~~~~~~~
!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
D = (`${BAR}}`) as string,
~~~~~~~~~~~~~~~~~~~~~
!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
E = `${BAR}`!,
~~~~~~~~~
!!! error TS18033: Type 'string' is not assignable to type 'number' as required for computed enum member values.
}

View File

@ -0,0 +1,24 @@
//// [tests/cases/compiler/computedEnumMemberSyntacticallyString.ts] ////
//// [computedEnumMemberSyntacticallyString.ts]
const BAR = 2..toFixed(0);
enum Foo {
A = `${BAR}`,
B = "2" + BAR,
C = (`${BAR}`),
D = (`${BAR}}`) as string,
E = `${BAR}`!,
}
//// [computedEnumMemberSyntacticallyString.js]
const BAR = 2..toFixed(0);
var Foo;
(function (Foo) {
Foo["A"] = `${BAR}`;
Foo["B"] = "2" + BAR;
Foo["C"] = (`${BAR}`);
Foo["D"] = (`${BAR}}`);
Foo["E"] = `${BAR}`;
})(Foo || (Foo = {}));

View File

@ -0,0 +1,17 @@
//// [tests/cases/compiler/computedEnumMemberSyntacticallyString2.ts] ////
//// [foo.ts]
import { BAR } from './bar';
enum Foo { A = `${BAR}` }
//// [bar.ts]
export const BAR = 'bar';
//// [bar.js]
export const BAR = 'bar';
//// [foo.js]
import { BAR } from './bar';
var Foo;
(function (Foo) {
Foo["A"] = "bar";
})(Foo || (Foo = {}));

View File

@ -0,0 +1,17 @@
//// [tests/cases/compiler/computedEnumMemberSyntacticallyString2.ts] ////
//// [foo.ts]
import { BAR } from './bar';
enum Foo { A = `${BAR}` }
//// [bar.ts]
export const BAR = 'bar';
//// [bar.js]
export const BAR = 'bar';
//// [foo.js]
import { BAR } from './bar';
var Foo;
(function (Foo) {
Foo["A"] = "bar";
})(Foo || (Foo = {}));

View File

@ -0,0 +1,6 @@
import { BAR } from './bar';
var Foo;
(function (Foo) {
Foo["A"] = `${BAR}`;
})(Foo || (Foo = {}));
//# sourceMappingURL=file.js.map

View File

@ -0,0 +1,6 @@
import { BAR } from './bar';
var Foo;
(function (Foo) {
Foo["A"] = `${BAR}`;
})(Foo || (Foo = {}));
//# sourceMappingURL=file.js.map

View File

@ -0,0 +1,13 @@
// @isolatedModules: true,false
// @noTypesAndSymbols: true
// @target: esnext
const BAR = 2..toFixed(0);
enum Foo {
A = `${BAR}`,
B = "2" + BAR,
C = (`${BAR}`),
D = (`${BAR}}`) as string,
E = `${BAR}`!,
}

View File

@ -0,0 +1,10 @@
// @isolatedModules: true,false
// @noTypesAndSymbols: true
// @target: esnext
// @filename: ./foo.ts
import { BAR } from './bar';
enum Foo { A = `${BAR}` }
// @filename: ./bar.ts
export const BAR = 'bar';