From f9ef9439bd02e1895991d71d2277d3214a7871f3 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 13 Mar 2024 13:11:02 -0700 Subject: [PATCH] Ensure enum members syntactically determinable to be strings do not get reverse mappings (#57686) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: frigus02 <3579251+frigus02@users.noreply.github.com> Co-authored-by: Jan Kühle --- src/compiler/transformers/ts.ts | 3 +- src/compiler/utilities.ts | 19 +++++++++++++ .../unittests/services/transpile.ts | 9 ++++++ ...lyString(isolatedmodules=false).errors.txt | 28 +++++++++++++++++++ ...tacticallyString(isolatedmodules=false).js | 24 ++++++++++++++++ ...llyString(isolatedmodules=true).errors.txt | 28 +++++++++++++++++++ ...ntacticallyString(isolatedmodules=true).js | 24 ++++++++++++++++ ...acticallyString2(isolatedmodules=false).js | 17 +++++++++++ ...tacticallyString2(isolatedmodules=true).js | 17 +++++++++++ ...enum members do not get reverse mapping.js | 6 ++++ ...do not get reverse mapping.oldTranspile.js | 6 ++++ .../computedEnumMemberSyntacticallyString.ts | 13 +++++++++ .../computedEnumMemberSyntacticallyString2.ts | 10 +++++++ 13 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=false).errors.txt create mode 100644 tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=false).js create mode 100644 tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=true).errors.txt create mode 100644 tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=true).js create mode 100644 tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=false).js create mode 100644 tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=true).js create mode 100644 tests/baselines/reference/transpile/Syntactically string but non-evaluatable enum members do not get reverse mapping.js create mode 100644 tests/baselines/reference/transpile/Syntactically string but non-evaluatable enum members do not get reverse mapping.oldTranspile.js create mode 100644 tests/cases/compiler/computedEnumMemberSyntacticallyString.ts create mode 100644 tests/cases/compiler/computedEnumMemberSyntacticallyString2.ts diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 6cc1db763806b..7680adf8ada1e 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -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( diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 68870769a8fa4..e6d6ac12125a3 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -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; +} diff --git a/src/testRunner/unittests/services/transpile.ts b/src/testRunner/unittests/services/transpile.ts index f308c20a1bb0f..9896c80db1d92 100644 --- a/src/testRunner/unittests/services/transpile.ts +++ b/src/testRunner/unittests/services/transpile.ts @@ -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 } }, + }, + ); }); diff --git a/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=false).errors.txt b/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=false).errors.txt new file mode 100644 index 0000000000000..94d81a5a16ac2 --- /dev/null +++ b/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=false).errors.txt @@ -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. + } + \ No newline at end of file diff --git a/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=false).js b/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=false).js new file mode 100644 index 0000000000000..a8cc14cca3ad4 --- /dev/null +++ b/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=false).js @@ -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 = {})); diff --git a/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=true).errors.txt b/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=true).errors.txt new file mode 100644 index 0000000000000..94d81a5a16ac2 --- /dev/null +++ b/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=true).errors.txt @@ -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. + } + \ No newline at end of file diff --git a/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=true).js b/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=true).js new file mode 100644 index 0000000000000..a8cc14cca3ad4 --- /dev/null +++ b/tests/baselines/reference/computedEnumMemberSyntacticallyString(isolatedmodules=true).js @@ -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 = {})); diff --git a/tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=false).js b/tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=false).js new file mode 100644 index 0000000000000..0a0bf9e0a8a24 --- /dev/null +++ b/tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=false).js @@ -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 = {})); diff --git a/tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=true).js b/tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=true).js new file mode 100644 index 0000000000000..0a0bf9e0a8a24 --- /dev/null +++ b/tests/baselines/reference/computedEnumMemberSyntacticallyString2(isolatedmodules=true).js @@ -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 = {})); diff --git a/tests/baselines/reference/transpile/Syntactically string but non-evaluatable enum members do not get reverse mapping.js b/tests/baselines/reference/transpile/Syntactically string but non-evaluatable enum members do not get reverse mapping.js new file mode 100644 index 0000000000000..32dd7ae8ee2d7 --- /dev/null +++ b/tests/baselines/reference/transpile/Syntactically string but non-evaluatable enum members do not get reverse mapping.js @@ -0,0 +1,6 @@ +import { BAR } from './bar'; +var Foo; +(function (Foo) { + Foo["A"] = `${BAR}`; +})(Foo || (Foo = {})); +//# sourceMappingURL=file.js.map \ No newline at end of file diff --git a/tests/baselines/reference/transpile/Syntactically string but non-evaluatable enum members do not get reverse mapping.oldTranspile.js b/tests/baselines/reference/transpile/Syntactically string but non-evaluatable enum members do not get reverse mapping.oldTranspile.js new file mode 100644 index 0000000000000..32dd7ae8ee2d7 --- /dev/null +++ b/tests/baselines/reference/transpile/Syntactically string but non-evaluatable enum members do not get reverse mapping.oldTranspile.js @@ -0,0 +1,6 @@ +import { BAR } from './bar'; +var Foo; +(function (Foo) { + Foo["A"] = `${BAR}`; +})(Foo || (Foo = {})); +//# sourceMappingURL=file.js.map \ No newline at end of file diff --git a/tests/cases/compiler/computedEnumMemberSyntacticallyString.ts b/tests/cases/compiler/computedEnumMemberSyntacticallyString.ts new file mode 100644 index 0000000000000..15af31ebc6fb8 --- /dev/null +++ b/tests/cases/compiler/computedEnumMemberSyntacticallyString.ts @@ -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}`!, +} diff --git a/tests/cases/compiler/computedEnumMemberSyntacticallyString2.ts b/tests/cases/compiler/computedEnumMemberSyntacticallyString2.ts new file mode 100644 index 0000000000000..e163858b13c4a --- /dev/null +++ b/tests/cases/compiler/computedEnumMemberSyntacticallyString2.ts @@ -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'; \ No newline at end of file