From 6f2c029071d4d791adb5ac77cead34284bc446b8 Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Tue, 16 May 2023 18:27:08 -0400 Subject: [PATCH] require `"experimentalDecorators": true` (#104) --- CHANGELOG.md | 4 + internal/bundler_tests/bundler_ts_test.go | 66 +++++-- internal/config/config.go | 1 + internal/js_parser/js_parser.go | 5 + internal/js_parser/ts_parser_test.go | 199 +++++++++++++--------- internal/resolver/tsconfig_json.go | 11 ++ scripts/js-api-tests.js | 56 +++--- 7 files changed, 231 insertions(+), 111 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d8ff33c815..5300b5e4fd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ This release makes the following changes to esbuild's `tsconfig.json` support: + * Using experimental decorators now requires `"experimentalDecorators": true` ([#104](https://github.com/evanw/esbuild/issues/104)) + + Previously esbuild would always compile decorators in TypeScript code using TypeScript's experimental decorator transform. Now that standard JavaScript decorators are close to being finalized, esbuild will now require you to use `"experimentalDecorators": true` to do this. This new requirement makes it possible for esbuild to introduce a transform for standard JavaScript decorators in TypeScript code in the future. Such a transform has not been implemented yet, however. + * TypeScript's `target` no longer affects esbuild's `target` ([#2628](https://github.com/evanw/esbuild/issues/2628)) Some people requested that esbuild support TypeScript's `target` setting, so support for it was added (in [version 0.12.4](https://github.com/evanw/esbuild/releases/v0.12.4)). However, esbuild supports reading from multiple `tsconfig.json` files within a single build, which opens up the possibility that different files in the build have different language targets configured. There isn't really any reason to do this and it can lead to unexpected results. So with this release, the `target` setting in `tsconfig.json` will no longer affect esbuild's own `target` setting. You will have to use esbuild's own target setting instead (which is a single, global value). diff --git a/internal/bundler_tests/bundler_ts_test.go b/internal/bundler_tests/bundler_ts_test.go index 203857a4ed3..8588b22cd27 100644 --- a/internal/bundler_tests/bundler_ts_test.go +++ b/internal/bundler_tests/bundler_ts_test.go @@ -803,6 +803,30 @@ func TestTSMinifiedBundleCommonJS(t *testing.T) { }) } +func TestTSExperimentalDecoratorsNoConfig(t *testing.T) { + ts_suite.expectBundled(t, bundled{ + files: map[string]string{ + "/entry.ts": ` + @decorator + class Foo {} + `, + "/tsconfig.json": `{ + "compilerOptions": { + "experimentalDecorators": false + } + }`, + }, + entryPaths: []string{"/entry.ts"}, + options: config.Options{ + Mode: config.ModeBundle, + AbsOutputFile: "/out.js", + }, + expectedScanLog: "entry.ts: ERROR: Experimental decorators are not currently enabled\n" + + "NOTE: To use experimental decorators in TypeScript with esbuild, you need to enable them by adding \"experimentalDecorators\": true in your \"tsconfig.json\" file. " + + "TypeScript's experimental decorators are currently the only kind of decorators that esbuild supports.\n", + }) +} + func TestTSExperimentalDecorators(t *testing.T) { ts_suite.expectBundled(t, bundled{ files: map[string]string{ @@ -942,7 +966,8 @@ func TestTSExperimentalDecorators(t *testing.T) { `, "/tsconfig.json": `{ "compilerOptions": { - "useDefineForClassFields": false + "useDefineForClassFields": false, + "experimentalDecorators": true } }`, }, @@ -961,6 +986,11 @@ func TestTSExperimentalDecoratorsKeepNames(t *testing.T) { @decoratorMustComeAfterName class Foo {} `, + "/tsconfig.json": `{ + "compilerOptions": { + "experimentalDecorators": true + } + }`, }, entryPaths: []string{"/entry.ts"}, options: config.Options{ @@ -1001,7 +1031,8 @@ func TestTSExperimentalDecoratorScopeIssue2147(t *testing.T) { `, "/tsconfig.json": `{ "compilerOptions": { - "useDefineForClassFields": false + "useDefineForClassFields": false, + "experimentalDecorators": true } }`, }, @@ -1439,14 +1470,17 @@ func TestTSComputedClassFieldUseDefineFalse(t *testing.T) { } new Foo() `, + "/tsconfig.json": `{ + "compilerOptions": { + "useDefineForClassFields": false, + "experimentalDecorators": true + } + }`, }, entryPaths: []string{"/entry.ts"}, options: config.Options{ Mode: config.ModePassThrough, AbsOutputFile: "/out.js", - TS: config.TSOptions{Config: config.TSConfig{ - UseDefineForClassFields: config.False, - }}, }, }) } @@ -1465,14 +1499,17 @@ func TestTSComputedClassFieldUseDefineTrue(t *testing.T) { } new Foo() `, + "/tsconfig.json": `{ + "compilerOptions": { + "useDefineForClassFields": true, + "experimentalDecorators": true + } + }`, }, entryPaths: []string{"/entry.ts"}, options: config.Options{ Mode: config.ModePassThrough, AbsOutputFile: "/out.js", - TS: config.TSOptions{Config: config.TSConfig{ - UseDefineForClassFields: config.True, - }}, }, }) } @@ -1491,14 +1528,17 @@ func TestTSComputedClassFieldUseDefineTrueLower(t *testing.T) { } new Foo() `, + "/tsconfig.json": `{ + "compilerOptions": { + "useDefineForClassFields": true, + "experimentalDecorators": true + } + }`, }, entryPaths: []string{"/entry.ts"}, options: config.Options{ - Mode: config.ModePassThrough, - AbsOutputFile: "/out.js", - TS: config.TSOptions{Config: config.TSConfig{ - UseDefineForClassFields: config.True, - }}, + Mode: config.ModePassThrough, + AbsOutputFile: "/out.js", UnsupportedJSFeatures: compat.ClassField, }, }) diff --git a/internal/config/config.go b/internal/config/config.go index 805b45157fc..6ddc2c342de 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -83,6 +83,7 @@ type TSConfigJSX struct { // Note: This can currently only contain primitive values. It's compared // for equality using a structural equality comparison by the JS parser. type TSConfig struct { + ExperimentalDecorators MaybeBool ImportsNotUsedAsValues TSImportsNotUsedAsValues PreserveValueImports MaybeBool Target TSTarget diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index ea4cd2af2cf..5af404d8c52 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -6259,6 +6259,11 @@ func (p *parser) parseDecorators(decoratorScope *js_ast.Scope, classKeyword logg []logger.MsgData{p.tracker.MsgData(classKeyword, "This is a class expression, not a class declaration:")}) } else if (context & decoratorBeforeClassExpr) != 0 { p.log.AddError(&p.tracker, p.lexer.Range(), "Experimental decorators cannot be used in expression position in TypeScript") + } else if p.options.ts.Config.ExperimentalDecorators != config.True { + p.log.AddErrorWithNotes(&p.tracker, p.lexer.Range(), "Experimental decorators are not currently enabled", []logger.MsgData{{ + Text: "To use experimental decorators in TypeScript with esbuild, you need to enable them by adding \"experimentalDecorators\": true in your \"tsconfig.json\" file. " + + "TypeScript's experimental decorators are currently the only kind of decorators that esbuild supports.", + }}) } } else { if (context & decoratorInFnArgs) != 0 { diff --git a/internal/js_parser/ts_parser_test.go b/internal/js_parser/ts_parser_test.go index cfcb6d3249a..4bd52489f22 100644 --- a/internal/js_parser/ts_parser_test.go +++ b/internal/js_parser/ts_parser_test.go @@ -16,6 +16,18 @@ func expectParseErrorTS(t *testing.T, contents string, expected string) { }) } +func expectParseErrorExperimentalDecoratorTS(t *testing.T, contents string, expected string) { + t.Helper() + expectParseErrorCommon(t, contents, expected, config.Options{ + TS: config.TSOptions{ + Parse: true, + Config: config.TSConfig{ + ExperimentalDecorators: config.True, + }, + }, + }) +} + func expectParseErrorTargetTS(t *testing.T, esVersion int, contents string, expected string) { t.Helper() expectParseErrorCommon(t, contents, expected, config.Options{ @@ -49,6 +61,18 @@ func expectPrintedAssignSemanticsTS(t *testing.T, contents string, expected stri }) } +func expectPrintedExperimentalDecoratorTS(t *testing.T, contents string, expected string) { + t.Helper() + expectPrintedCommon(t, contents, expected, config.Options{ + TS: config.TSOptions{ + Parse: true, + Config: config.TSConfig{ + ExperimentalDecorators: config.True, + }, + }, + }) +} + func expectPrintedMangleTS(t *testing.T, contents string, expected string) { t.Helper() expectPrintedCommon(t, contents, expected, config.Options{ @@ -84,6 +108,21 @@ func expectPrintedTargetTS(t *testing.T, esVersion int, contents string, expecte }) } +func expectPrintedTargetExperimentalDecoratorTS(t *testing.T, esVersion int, contents string, expected string) { + t.Helper() + expectPrintedCommon(t, contents, expected, config.Options{ + TS: config.TSOptions{ + Parse: true, + Config: config.TSConfig{ + ExperimentalDecorators: config.True, + }, + }, + UnsupportedJSFeatures: compat.UnsupportedJSFeatures(map[compat.Engine][]int{ + compat.ES: {esVersion}, + }), + }) +} + func expectParseErrorTSNoAmbiguousLessThan(t *testing.T, contents string, expected string) { t.Helper() expectParseErrorCommon(t, contents, expected, config.Options{ @@ -749,24 +788,24 @@ func TestTSPrivateIdentifiers(t *testing.T) { expectPrintedTS(t, "class Foo { static set #foo(x) {} }", "class Foo {\n static set #foo(x) {\n }\n}\n") // Decorators are not valid on private members - expectParseErrorTS(t, "class Foo { @dec #foo }", ": ERROR: Expected identifier but found \"#foo\"\n") - expectParseErrorTS(t, "class Foo { @dec #foo = 1 }", ": ERROR: Expected identifier but found \"#foo\"\n") - expectParseErrorTS(t, "class Foo { @dec #foo() {} }", ": ERROR: Expected identifier but found \"#foo\"\n") - expectParseErrorTS(t, "class Foo { @dec get #foo() {} }", ": ERROR: Expected identifier but found \"#foo\"\n") - expectParseErrorTS(t, "class Foo { @dec set #foo() {x} }", ": ERROR: Expected identifier but found \"#foo\"\n") - expectParseErrorTS(t, "class Foo { @dec static #foo }", ": ERROR: Expected identifier but found \"#foo\"\n") - expectParseErrorTS(t, "class Foo { @dec static #foo = 1 }", ": ERROR: Expected identifier but found \"#foo\"\n") - expectParseErrorTS(t, "class Foo { @dec static #foo() {} }", ": ERROR: Expected identifier but found \"#foo\"\n") - expectParseErrorTS(t, "class Foo { @dec static get #foo() {} }", ": ERROR: Expected identifier but found \"#foo\"\n") - expectParseErrorTS(t, "class Foo { @dec static set #foo() {x} }", ": ERROR: Expected identifier but found \"#foo\"\n") + expectParseErrorExperimentalDecoratorTS(t, "class Foo { @dec #foo }", ": ERROR: Expected identifier but found \"#foo\"\n") + expectParseErrorExperimentalDecoratorTS(t, "class Foo { @dec #foo = 1 }", ": ERROR: Expected identifier but found \"#foo\"\n") + expectParseErrorExperimentalDecoratorTS(t, "class Foo { @dec #foo() {} }", ": ERROR: Expected identifier but found \"#foo\"\n") + expectParseErrorExperimentalDecoratorTS(t, "class Foo { @dec get #foo() {} }", ": ERROR: Expected identifier but found \"#foo\"\n") + expectParseErrorExperimentalDecoratorTS(t, "class Foo { @dec set #foo() {x} }", ": ERROR: Expected identifier but found \"#foo\"\n") + expectParseErrorExperimentalDecoratorTS(t, "class Foo { @dec static #foo }", ": ERROR: Expected identifier but found \"#foo\"\n") + expectParseErrorExperimentalDecoratorTS(t, "class Foo { @dec static #foo = 1 }", ": ERROR: Expected identifier but found \"#foo\"\n") + expectParseErrorExperimentalDecoratorTS(t, "class Foo { @dec static #foo() {} }", ": ERROR: Expected identifier but found \"#foo\"\n") + expectParseErrorExperimentalDecoratorTS(t, "class Foo { @dec static get #foo() {} }", ": ERROR: Expected identifier but found \"#foo\"\n") + expectParseErrorExperimentalDecoratorTS(t, "class Foo { @dec static set #foo() {x} }", ": ERROR: Expected identifier but found \"#foo\"\n") // Decorators are not able to access private names, since they use the scope // that encloses the class declaration. Note that the TypeScript compiler has // a bug where it doesn't handle this case and generates invalid code as a // result: https://github.com/microsoft/TypeScript/issues/48515. - expectParseErrorTS(t, "class Foo { static #foo; @dec(Foo.#foo) bar }", ": ERROR: Private name \"#foo\" must be declared in an enclosing class\n") - expectParseErrorTS(t, "class Foo { static #foo; @dec(Foo.#foo) bar() {} }", ": ERROR: Private name \"#foo\" must be declared in an enclosing class\n") - expectParseErrorTS(t, "class Foo { static #foo; bar(@dec(Foo.#foo) x) {} }", ": ERROR: Private name \"#foo\" must be declared in an enclosing class\n") + expectParseErrorExperimentalDecoratorTS(t, "class Foo { static #foo; @dec(Foo.#foo) bar }", ": ERROR: Private name \"#foo\" must be declared in an enclosing class\n") + expectParseErrorExperimentalDecoratorTS(t, "class Foo { static #foo; @dec(Foo.#foo) bar() {} }", ": ERROR: Private name \"#foo\" must be declared in an enclosing class\n") + expectParseErrorExperimentalDecoratorTS(t, "class Foo { static #foo; bar(@dec(Foo.#foo) x) {} }", ": ERROR: Private name \"#foo\" must be declared in an enclosing class\n") } func TestTSInterface(t *testing.T) { @@ -1688,80 +1727,84 @@ func TestTSDeclare(t *testing.T) { } func TestTSExperimentalDecorator(t *testing.T) { + expectParseErrorTS(t, "@dec class Foo {}", ": ERROR: Experimental decorators are not currently enabled\n"+ + "NOTE: To use experimental decorators in TypeScript with esbuild, you need to enable them by adding \"experimentalDecorators\": true in your \"tsconfig.json\" file. "+ + "TypeScript's experimental decorators are currently the only kind of decorators that esbuild supports.\n") + // Tests of "declare class" - expectPrintedTS(t, "@dec(() => 0) declare class Foo {} {let foo}", "{\n let foo;\n}\n") - expectPrintedTS(t, "@dec(() => 0) declare abstract class Foo {} {let foo}", "{\n let foo;\n}\n") - expectPrintedTS(t, "@dec(() => 0) export declare class Foo {} {let foo}", "{\n let foo;\n}\n") - expectPrintedTS(t, "@dec(() => 0) export declare abstract class Foo {} {let foo}", "{\n let foo;\n}\n") - expectPrintedTS(t, "declare class Foo { @dec(() => 0) foo } {let foo}", "{\n let foo;\n}\n") - expectPrintedTS(t, "declare class Foo { @dec(() => 0) foo() } {let foo}", "{\n let foo;\n}\n") - expectPrintedTS(t, "declare class Foo { foo(@dec(() => 0) x) } {let foo}", "{\n let foo;\n}\n") + expectPrintedExperimentalDecoratorTS(t, "@dec(() => 0) declare class Foo {} {let foo}", "{\n let foo;\n}\n") + expectPrintedExperimentalDecoratorTS(t, "@dec(() => 0) declare abstract class Foo {} {let foo}", "{\n let foo;\n}\n") + expectPrintedExperimentalDecoratorTS(t, "@dec(() => 0) export declare class Foo {} {let foo}", "{\n let foo;\n}\n") + expectPrintedExperimentalDecoratorTS(t, "@dec(() => 0) export declare abstract class Foo {} {let foo}", "{\n let foo;\n}\n") + expectPrintedExperimentalDecoratorTS(t, "declare class Foo { @dec(() => 0) foo } {let foo}", "{\n let foo;\n}\n") + expectPrintedExperimentalDecoratorTS(t, "declare class Foo { @dec(() => 0) foo() } {let foo}", "{\n let foo;\n}\n") + expectPrintedExperimentalDecoratorTS(t, "declare class Foo { foo(@dec(() => 0) x) } {let foo}", "{\n let foo;\n}\n") // Decorators must only work on class statements notes := ": NOTE: The preceding decorator is here:\n" + "NOTE: Decorators can only be used with class declarations.\n" - expectParseErrorTS(t, "@dec enum foo {}", ": ERROR: Expected \"class\" after decorator but found \"enum\"\n"+notes) - expectParseErrorTS(t, "@dec namespace foo {}", ": ERROR: Expected \"class\" after decorator but found \"namespace\"\n"+notes) - expectParseErrorTS(t, "@dec function foo() {}", ": ERROR: Expected \"class\" after decorator but found \"function\"\n"+notes) - expectParseErrorTS(t, "@dec abstract", ": ERROR: Expected \"class\" but found end of file\n") - expectParseErrorTS(t, "@dec declare: x", ": ERROR: Expected \"class\" after decorator but found \":\"\n"+notes) - expectParseErrorTS(t, "@dec declare enum foo {}", ": ERROR: Expected \"class\" after decorator but found \"enum\"\n"+notes) - expectParseErrorTS(t, "@dec declare namespace foo {}", ": ERROR: Expected \"class\" after decorator but found \"namespace\"\n"+notes) - expectParseErrorTS(t, "@dec declare function foo()", ": ERROR: Expected \"class\" after decorator but found \"function\"\n"+notes) - expectParseErrorTS(t, "@dec export {}", ": ERROR: Expected \"class\" after decorator but found \"{\"\n"+notes) - expectParseErrorTS(t, "@dec export enum foo {}", ": ERROR: Expected \"class\" after decorator but found \"enum\"\n"+notes) - expectParseErrorTS(t, "@dec export namespace foo {}", ": ERROR: Expected \"class\" after decorator but found \"namespace\"\n"+notes) - expectParseErrorTS(t, "@dec export function foo() {}", ": ERROR: Expected \"class\" after decorator but found \"function\"\n"+notes) - expectParseErrorTS(t, "@dec export default abstract", ": ERROR: Expected \"class\" but found end of file\n") - expectParseErrorTS(t, "@dec export declare enum foo {}", ": ERROR: Expected \"class\" after decorator but found \"enum\"\n"+notes) - expectParseErrorTS(t, "@dec export declare namespace foo {}", ": ERROR: Expected \"class\" after decorator but found \"namespace\"\n"+notes) - expectParseErrorTS(t, "@dec export declare function foo()", ": ERROR: Expected \"class\" after decorator but found \"function\"\n"+notes) + expectParseErrorExperimentalDecoratorTS(t, "@dec enum foo {}", ": ERROR: Expected \"class\" after decorator but found \"enum\"\n"+notes) + expectParseErrorExperimentalDecoratorTS(t, "@dec namespace foo {}", ": ERROR: Expected \"class\" after decorator but found \"namespace\"\n"+notes) + expectParseErrorExperimentalDecoratorTS(t, "@dec function foo() {}", ": ERROR: Expected \"class\" after decorator but found \"function\"\n"+notes) + expectParseErrorExperimentalDecoratorTS(t, "@dec abstract", ": ERROR: Expected \"class\" but found end of file\n") + expectParseErrorExperimentalDecoratorTS(t, "@dec declare: x", ": ERROR: Expected \"class\" after decorator but found \":\"\n"+notes) + expectParseErrorExperimentalDecoratorTS(t, "@dec declare enum foo {}", ": ERROR: Expected \"class\" after decorator but found \"enum\"\n"+notes) + expectParseErrorExperimentalDecoratorTS(t, "@dec declare namespace foo {}", ": ERROR: Expected \"class\" after decorator but found \"namespace\"\n"+notes) + expectParseErrorExperimentalDecoratorTS(t, "@dec declare function foo()", ": ERROR: Expected \"class\" after decorator but found \"function\"\n"+notes) + expectParseErrorExperimentalDecoratorTS(t, "@dec export {}", ": ERROR: Expected \"class\" after decorator but found \"{\"\n"+notes) + expectParseErrorExperimentalDecoratorTS(t, "@dec export enum foo {}", ": ERROR: Expected \"class\" after decorator but found \"enum\"\n"+notes) + expectParseErrorExperimentalDecoratorTS(t, "@dec export namespace foo {}", ": ERROR: Expected \"class\" after decorator but found \"namespace\"\n"+notes) + expectParseErrorExperimentalDecoratorTS(t, "@dec export function foo() {}", ": ERROR: Expected \"class\" after decorator but found \"function\"\n"+notes) + expectParseErrorExperimentalDecoratorTS(t, "@dec export default abstract", ": ERROR: Expected \"class\" but found end of file\n") + expectParseErrorExperimentalDecoratorTS(t, "@dec export declare enum foo {}", ": ERROR: Expected \"class\" after decorator but found \"enum\"\n"+notes) + expectParseErrorExperimentalDecoratorTS(t, "@dec export declare namespace foo {}", ": ERROR: Expected \"class\" after decorator but found \"namespace\"\n"+notes) + expectParseErrorExperimentalDecoratorTS(t, "@dec export declare function foo()", ": ERROR: Expected \"class\" after decorator but found \"function\"\n"+notes) // Decorators must be forbidden outside class statements note := ": NOTE: This is a class expression, not a class declaration:\n" - expectParseErrorTS(t, "(class { @dec foo })", ": ERROR: Experimental decorators can only be used with class declarations in TypeScript\n"+note) - expectParseErrorTS(t, "(class { @dec foo() {} })", ": ERROR: Experimental decorators can only be used with class declarations in TypeScript\n"+note) - expectParseErrorTS(t, "(class { foo(@dec x) {} })", ": ERROR: Experimental decorators can only be used with class declarations in TypeScript\n"+note) - expectParseErrorTS(t, "({ @dec foo })", ": ERROR: Expected identifier but found \"@\"\n") - expectParseErrorTS(t, "({ @dec foo() {} })", ": ERROR: Expected identifier but found \"@\"\n") - expectParseErrorTS(t, "({ foo(@dec x) {} })", ": ERROR: Expected identifier but found \"@\"\n") + expectParseErrorExperimentalDecoratorTS(t, "(class { @dec foo })", ": ERROR: Experimental decorators can only be used with class declarations in TypeScript\n"+note) + expectParseErrorExperimentalDecoratorTS(t, "(class { @dec foo() {} })", ": ERROR: Experimental decorators can only be used with class declarations in TypeScript\n"+note) + expectParseErrorExperimentalDecoratorTS(t, "(class { foo(@dec x) {} })", ": ERROR: Experimental decorators can only be used with class declarations in TypeScript\n"+note) + expectParseErrorExperimentalDecoratorTS(t, "({ @dec foo })", ": ERROR: Expected identifier but found \"@\"\n") + expectParseErrorExperimentalDecoratorTS(t, "({ @dec foo() {} })", ": ERROR: Expected identifier but found \"@\"\n") + expectParseErrorExperimentalDecoratorTS(t, "({ foo(@dec x) {} })", ": ERROR: Expected identifier but found \"@\"\n") // Decorators aren't allowed with private names - expectParseErrorTS(t, "class Foo { @dec #foo }", ": ERROR: Expected identifier but found \"#foo\"\n") - expectParseErrorTS(t, "class Foo { @dec #foo = 1 }", ": ERROR: Expected identifier but found \"#foo\"\n") - expectParseErrorTS(t, "class Foo { @dec #foo() {} }", ": ERROR: Expected identifier but found \"#foo\"\n") - expectParseErrorTS(t, "class Foo { @dec *#foo() {} }", ": ERROR: Expected identifier but found \"#foo\"\n") - expectParseErrorTS(t, "class Foo { @dec async #foo() {} }", ": ERROR: Expected identifier but found \"#foo\"\n") - expectParseErrorTS(t, "class Foo { @dec async* #foo() {} }", ": ERROR: Expected identifier but found \"#foo\"\n") - expectParseErrorTS(t, "class Foo { @dec static #foo }", ": ERROR: Expected identifier but found \"#foo\"\n") - expectParseErrorTS(t, "class Foo { @dec static #foo = 1 }", ": ERROR: Expected identifier but found \"#foo\"\n") - expectParseErrorTS(t, "class Foo { @dec static #foo() {} }", ": ERROR: Expected identifier but found \"#foo\"\n") - expectParseErrorTS(t, "class Foo { @dec static *#foo() {} }", ": ERROR: Expected identifier but found \"#foo\"\n") - expectParseErrorTS(t, "class Foo { @dec static async #foo() {} }", ": ERROR: Expected identifier but found \"#foo\"\n") - expectParseErrorTS(t, "class Foo { @dec static async* #foo() {} }", ": ERROR: Expected identifier but found \"#foo\"\n") + expectParseErrorExperimentalDecoratorTS(t, "class Foo { @dec #foo }", ": ERROR: Expected identifier but found \"#foo\"\n") + expectParseErrorExperimentalDecoratorTS(t, "class Foo { @dec #foo = 1 }", ": ERROR: Expected identifier but found \"#foo\"\n") + expectParseErrorExperimentalDecoratorTS(t, "class Foo { @dec #foo() {} }", ": ERROR: Expected identifier but found \"#foo\"\n") + expectParseErrorExperimentalDecoratorTS(t, "class Foo { @dec *#foo() {} }", ": ERROR: Expected identifier but found \"#foo\"\n") + expectParseErrorExperimentalDecoratorTS(t, "class Foo { @dec async #foo() {} }", ": ERROR: Expected identifier but found \"#foo\"\n") + expectParseErrorExperimentalDecoratorTS(t, "class Foo { @dec async* #foo() {} }", ": ERROR: Expected identifier but found \"#foo\"\n") + expectParseErrorExperimentalDecoratorTS(t, "class Foo { @dec static #foo }", ": ERROR: Expected identifier but found \"#foo\"\n") + expectParseErrorExperimentalDecoratorTS(t, "class Foo { @dec static #foo = 1 }", ": ERROR: Expected identifier but found \"#foo\"\n") + expectParseErrorExperimentalDecoratorTS(t, "class Foo { @dec static #foo() {} }", ": ERROR: Expected identifier but found \"#foo\"\n") + expectParseErrorExperimentalDecoratorTS(t, "class Foo { @dec static *#foo() {} }", ": ERROR: Expected identifier but found \"#foo\"\n") + expectParseErrorExperimentalDecoratorTS(t, "class Foo { @dec static async #foo() {} }", ": ERROR: Expected identifier but found \"#foo\"\n") + expectParseErrorExperimentalDecoratorTS(t, "class Foo { @dec static async* #foo() {} }", ": ERROR: Expected identifier but found \"#foo\"\n") // Decorators aren't allowed on class constructors - expectParseErrorTS(t, "class Foo { @dec constructor() {} }", ": ERROR: Decorators are not allowed on class constructors\n") - expectParseErrorTS(t, "class Foo { @dec public constructor() {} }", ": ERROR: Decorators are not allowed on class constructors\n") + expectParseErrorExperimentalDecoratorTS(t, "class Foo { @dec constructor() {} }", ": ERROR: Decorators are not allowed on class constructors\n") + expectParseErrorExperimentalDecoratorTS(t, "class Foo { @dec public constructor() {} }", ": ERROR: Decorators are not allowed on class constructors\n") // Check use of "await" friendlyAwaitErrorWithNote := ": ERROR: \"await\" can only be used inside an \"async\" function\n" + ": NOTE: Consider adding the \"async\" keyword here:\n" - expectPrintedTS(t, "async function foo() { @dec(await x) class Foo {} }", + expectPrintedExperimentalDecoratorTS(t, "async function foo() { @dec(await x) class Foo {} }", "async function foo() {\n let Foo = class {\n };\n Foo = __decorateClass([\n dec(await x)\n ], Foo);\n}\n") - expectPrintedTS(t, "async function foo() { class Foo { @dec(await x) foo() {} } }", + expectPrintedExperimentalDecoratorTS(t, "async function foo() { class Foo { @dec(await x) foo() {} } }", "async function foo() {\n class Foo {\n foo() {\n }\n }\n __decorateClass([\n dec(await x)\n ], Foo.prototype, \"foo\", 1);\n}\n") - expectPrintedTS(t, "async function foo() { class Foo { foo(@dec(await x) y) {} } }", + expectPrintedExperimentalDecoratorTS(t, "async function foo() { class Foo { foo(@dec(await x) y) {} } }", "async function foo() {\n class Foo {\n foo(y) {\n }\n }\n __decorateClass([\n __decorateParam(0, dec(await x))\n ], Foo.prototype, \"foo\", 1);\n}\n") - expectParseErrorTS(t, "function foo() { @dec(await x) class Foo {} }", friendlyAwaitErrorWithNote) - expectParseErrorTS(t, "function foo() { class Foo { @dec(await x) foo() {} } }", friendlyAwaitErrorWithNote) - expectParseErrorTS(t, "function foo() { class Foo { foo(@dec(await x) y) {} } }", friendlyAwaitErrorWithNote) - expectParseErrorTS(t, "function foo() { class Foo { @dec(await x) async foo() {} } }", friendlyAwaitErrorWithNote) - expectParseErrorTS(t, "function foo() { class Foo { async foo(@dec(await x) y) {} } }", + expectParseErrorExperimentalDecoratorTS(t, "function foo() { @dec(await x) class Foo {} }", friendlyAwaitErrorWithNote) + expectParseErrorExperimentalDecoratorTS(t, "function foo() { class Foo { @dec(await x) foo() {} } }", friendlyAwaitErrorWithNote) + expectParseErrorExperimentalDecoratorTS(t, "function foo() { class Foo { foo(@dec(await x) y) {} } }", friendlyAwaitErrorWithNote) + expectParseErrorExperimentalDecoratorTS(t, "function foo() { class Foo { @dec(await x) async foo() {} } }", friendlyAwaitErrorWithNote) + expectParseErrorExperimentalDecoratorTS(t, "function foo() { class Foo { async foo(@dec(await x) y) {} } }", ": ERROR: The keyword \"await\" cannot be used here:\n: ERROR: Expected \")\" but found \"x\"\n") // Check lowered use of "await" - expectPrintedTargetTS(t, 2015, "async function foo() { @dec(await x) class Foo {} }", + expectPrintedTargetExperimentalDecoratorTS(t, 2015, "async function foo() { @dec(await x) class Foo {} }", `function foo() { return __async(this, null, function* () { let Foo = class { @@ -1772,7 +1815,7 @@ func TestTSExperimentalDecorator(t *testing.T) { }); } `) - expectPrintedTargetTS(t, 2015, "async function foo() { class Foo { @dec(await x) foo() {} } }", + expectPrintedTargetExperimentalDecoratorTS(t, 2015, "async function foo() { class Foo { @dec(await x) foo() {} } }", `function foo() { return __async(this, null, function* () { class Foo { @@ -1785,7 +1828,7 @@ func TestTSExperimentalDecorator(t *testing.T) { }); } `) - expectPrintedTargetTS(t, 2015, "async function foo() { class Foo { foo(@dec(await x) y) {} } }", + expectPrintedTargetExperimentalDecoratorTS(t, 2015, "async function foo() { class Foo { foo(@dec(await x) y) {} } }", `function foo() { return __async(this, null, function* () { class Foo { @@ -1800,20 +1843,24 @@ func TestTSExperimentalDecorator(t *testing.T) { `) // Check use of "yield" - expectPrintedTS(t, "function *foo() { @dec(yield x) class Foo {} }", // We currently allow this but TypeScript doesn't + expectPrintedExperimentalDecoratorTS(t, "function *foo() { @dec(yield x) class Foo {} }", // We currently allow this but TypeScript doesn't "function* foo() {\n let Foo = class {\n };\n Foo = __decorateClass([\n dec(yield x)\n ], Foo);\n}\n") - expectPrintedTS(t, "function *foo() { class Foo { @dec(yield x) foo() {} } }", // We currently allow this but TypeScript doesn't + expectPrintedExperimentalDecoratorTS(t, "function *foo() { class Foo { @dec(yield x) foo() {} } }", // We currently allow this but TypeScript doesn't "function* foo() {\n class Foo {\n foo() {\n }\n }\n __decorateClass([\n dec(yield x)\n ], Foo.prototype, \"foo\", 1);\n}\n") - expectParseErrorTS(t, "function *foo() { class Foo { foo(@dec(yield x) y) {} } }", // TypeScript doesn't allow this (although it could because it would work fine) + expectParseErrorExperimentalDecoratorTS(t, "function *foo() { class Foo { foo(@dec(yield x) y) {} } }", // TypeScript doesn't allow this (although it could because it would work fine) ": ERROR: Cannot use \"yield\" outside a generator function\n") - expectParseErrorTS(t, "function foo() { @dec(yield x) class Foo {} }", ": ERROR: Cannot use \"yield\" outside a generator function\n") - expectParseErrorTS(t, "function foo() { class Foo { @dec(yield x) foo() {} } }", ": ERROR: Cannot use \"yield\" outside a generator function\n") + expectParseErrorExperimentalDecoratorTS(t, "function foo() { @dec(yield x) class Foo {} }", ": ERROR: Cannot use \"yield\" outside a generator function\n") + expectParseErrorExperimentalDecoratorTS(t, "function foo() { class Foo { @dec(yield x) foo() {} } }", ": ERROR: Cannot use \"yield\" outside a generator function\n") // Check inline function expressions - expectPrintedTS(t, "@((x, y) => x + y) class Foo {}", "let Foo = class {\n};\nFoo = __decorateClass([\n (x, y) => x + y\n], Foo);\n") - expectPrintedTS(t, "@((x, y) => x + y) export class Foo {}", "export let Foo = class {\n};\nFoo = __decorateClass([\n (x, y) => x + y\n], Foo);\n") - expectPrintedTS(t, "@(function(x, y) { return x + y }) class Foo {}", "let Foo = class {\n};\nFoo = __decorateClass([\n function(x, y) {\n return x + y;\n }\n], Foo);\n") - expectPrintedTS(t, "@(function(x, y) { return x + y }) export class Foo {}", "export let Foo = class {\n};\nFoo = __decorateClass([\n function(x, y) {\n return x + y;\n }\n], Foo);\n") + expectPrintedExperimentalDecoratorTS(t, "@((x, y) => x + y) class Foo {}", + "let Foo = class {\n};\nFoo = __decorateClass([\n (x, y) => x + y\n], Foo);\n") + expectPrintedExperimentalDecoratorTS(t, "@((x, y) => x + y) export class Foo {}", + "export let Foo = class {\n};\nFoo = __decorateClass([\n (x, y) => x + y\n], Foo);\n") + expectPrintedExperimentalDecoratorTS(t, "@(function(x, y) { return x + y }) class Foo {}", + "let Foo = class {\n};\nFoo = __decorateClass([\n function(x, y) {\n return x + y;\n }\n], Foo);\n") + expectPrintedExperimentalDecoratorTS(t, "@(function(x, y) { return x + y }) export class Foo {}", + "export let Foo = class {\n};\nFoo = __decorateClass([\n function(x, y) {\n return x + y;\n }\n], Foo);\n") } func TestTSTry(t *testing.T) { diff --git a/internal/resolver/tsconfig_json.go b/internal/resolver/tsconfig_json.go index 181dc5b1002..856600e41e8 100644 --- a/internal/resolver/tsconfig_json.go +++ b/internal/resolver/tsconfig_json.go @@ -154,6 +154,17 @@ func ParseTSConfigJSON( } } + // Parse "experimentalDecorators" + if valueJSON, _, ok := getProperty(compilerOptionsJSON, "experimentalDecorators"); ok { + if value, ok := getBool(valueJSON); ok { + if value { + result.Settings.ExperimentalDecorators = config.True + } else { + result.Settings.ExperimentalDecorators = config.False + } + } + } + // Parse "useDefineForClassFields" if valueJSON, _, ok := getProperty(compilerOptionsJSON, "useDefineForClassFields"); ok { if value, ok := getBool(valueJSON); ok { diff --git a/scripts/js-api-tests.js b/scripts/js-api-tests.js index 65690e7587e..401e43a5083 100644 --- a/scripts/js-api-tests.js +++ b/scripts/js-api-tests.js @@ -2881,28 +2881,32 @@ import "after/alias"; new Function(outputFiles[0].text)() }, - async bundleTSDecoratorAvoidTDZ({ esbuild }) { - var { outputFiles } = await esbuild.build({ - stdin: { - contents: ` - class Bar {} - var oldFoo - function swap(target) { - oldFoo = target - return Bar - } - @swap - class Foo { - bar() { return new Foo } - static foo = new Foo - } - if (!(oldFoo.foo instanceof oldFoo)) - throw 'fail: foo' - if (!(oldFoo.foo.bar() instanceof Bar)) - throw 'fail: bar' - `, - loader: 'ts', + async bundleTSDecoratorAvoidTDZ({ testDir, esbuild }) { + const input = path.join(testDir, 'input.ts') + await writeFileAsync(input, ` + class Bar {} + var oldFoo + function swap(target) { + oldFoo = target + return Bar + } + @swap + class Foo { + bar() { return new Foo } + static foo = new Foo + } + if (!(oldFoo.foo instanceof oldFoo)) + throw 'fail: foo' + if (!(oldFoo.foo.bar() instanceof Bar)) + throw 'fail: bar' + `) + await writeFileAsync(path.join(testDir, 'tsconfig.json'), `{ + "compilerOptions": { + "experimentalDecorators": true, }, + }`) + var { outputFiles } = await esbuild.build({ + entryPoints: [input], bundle: true, write: false, }) @@ -5057,6 +5061,7 @@ let transformTests = { tsconfigRaw: { compilerOptions: { useDefineForClassFields: false, + experimentalDecorators: true, }, }, }) @@ -6212,7 +6217,14 @@ class Foo { ]; return {observed, expected}; - `, { loader: 'ts' }); + `, { + loader: 'ts', + tsconfigRaw: { + compilerOptions: { + experimentalDecorators: true, + }, + }, + }); const { observed, expected } = new Function(code)(); assert.deepStrictEqual(observed, expected); },