Skip to content

Commit

Permalink
Ensure enum members syntactically determinable to be strings do not g…
Browse files Browse the repository at this point in the history
…et reverse mappings (microsoft#57686)

Co-authored-by: frigus02 <[email protected]>
Co-authored-by: Jan Kühle <[email protected]>
  • Loading branch information
3 people authored Mar 13, 2024
1 parent ac8eb2c commit f9ef943
Show file tree
Hide file tree
Showing 13 changed files with 203 additions and 1 deletion.
3 changes: 2 additions & 1 deletion src/compiler/transformers/ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ import {
isSimpleInlineableExpression,
isSourceFile,
isStatement,
isSyntacticallyString,
isTemplateLiteral,
isTryStatement,
JsxOpeningElement,
Expand Down Expand Up @@ -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(
Expand Down
19 changes: 19 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
9 changes: 9 additions & 0 deletions src/testRunner/unittests/services/transpile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 } },
},
);
});
Original file line number Diff line number Diff line change
@@ -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.
}

Original file line number Diff line number Diff line change
@@ -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 = {}));
Original file line number Diff line number Diff line change
@@ -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.
}

Original file line number Diff line number Diff line change
@@ -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 = {}));
Original file line number Diff line number Diff line change
@@ -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 = {}));
Original file line number Diff line number Diff line change
@@ -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 = {}));

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions tests/cases/compiler/computedEnumMemberSyntacticallyString.ts
Original file line number Diff line number Diff line change
@@ -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}`!,
}
10 changes: 10 additions & 0 deletions tests/cases/compiler/computedEnumMemberSyntacticallyString2.ts
Original file line number Diff line number Diff line change
@@ -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';

0 comments on commit f9ef943

Please sign in to comment.