diff --git a/internal/bundler_tests/bundler_ts_test.go b/internal/bundler_tests/bundler_ts_test.go index 9c2b323a23d..a5b43e92b8e 100644 --- a/internal/bundler_tests/bundler_ts_test.go +++ b/internal/bundler_tests/bundler_ts_test.go @@ -990,14 +990,12 @@ func TestTSExperimentalDecoratorsNoConfig(t *testing.T) { @x @y mUndef: any @x @y mDef = 1 @x @y method() { return new Foo } - @x @y declare mDecl: any @x @y accessor aUndef: any @x @y accessor aDef = 1 @x @y static sUndef: any @x @y static sDef = new Foo @x @y static sMethod() { return new Foo } - @x @y static declare sDecl: any @x @y static accessor asUndef: any @x @y static accessor asDef = 1 diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index 9862761ef07..077f911d8a3 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -2181,10 +2181,11 @@ func (p *parser) parseProperty(startLoc logger.Loc, kind js_ast.PropertyKind, op scopeIndex := len(p.scopesInOrder) if prop, ok := p.parseProperty(startLoc, kind, opts, nil); ok && - prop.Kind == js_ast.PropertyNormal && prop.ValueOrNil.Data == nil && len(opts.decorators) > 0 { + prop.Kind == js_ast.PropertyNormal && prop.ValueOrNil.Data == nil && + (p.options.ts.Config.ExperimentalDecorators == config.True && len(opts.decorators) > 0) { // If this is a well-formed class field with the "declare" keyword, // only keep the declaration to preserve its side-effects when - // there are TypeScript decorators present: + // there are TypeScript experimental decorators present: // // class Foo { // // Remove this @@ -2194,6 +2195,15 @@ func (p *parser) parseProperty(startLoc logger.Loc, kind js_ast.PropertyKind, op // @decorator(console.log('side effect 2')) declare bar // } // + // This behavior is surprisingly somehow valid with TypeScript + // experimental decorators, which was possibly by accident. + // TypeScript does not allow this with JavaScript decorators. + // + // References: + // + // https://github.com/evanw/esbuild/issues/1675 + // https://github.com/microsoft/TypeScript/issues/46345 + // prop.Kind = js_ast.PropertyDeclare return prop, true } @@ -6241,6 +6251,7 @@ func (p *parser) parseClass(classKeyword logger.Range, name *ast.LocRef, classOp // Parse decorators for this property firstDecoratorLoc := p.lexer.Loc() + scopeIndex := len(p.scopesInOrder) opts.decorators = p.parseDecorators(p.currentScope, classKeyword, opts.decoratorContext) // This property may turn out to be a type in TypeScript, which should be ignored @@ -6261,6 +6272,9 @@ func (p *parser) parseClass(classKeyword logger.Range, name *ast.LocRef, classOp hasConstructor = true } } + } else if !classOpts.isTypeScriptDeclare && len(opts.decorators) > 0 { + p.log.AddError(&p.tracker, logger.Range{Loc: firstDecoratorLoc, Len: 1}, "Decorators are not valid here") + p.discardScopesUpTo(scopeIndex) } } diff --git a/internal/js_parser/ts_parser_test.go b/internal/js_parser/ts_parser_test.go index ed879b34408..832c31a59fc 100644 --- a/internal/js_parser/ts_parser_test.go +++ b/internal/js_parser/ts_parser_test.go @@ -2029,6 +2029,10 @@ func TestTSExperimentalDecorator(t *testing.T) { expectParseErrorExperimentalDecoratorTS(t, "@x export @y class Foo {}", ": ERROR: Decorators are not valid here\n") expectParseErrorExperimentalDecoratorTS(t, "@x export default abstract", ": ERROR: Decorators are not valid here\n") expectParseErrorExperimentalDecoratorTS(t, "@x export @y default class {}", ": ERROR: Decorators are not valid here\n: ERROR: Unexpected \"default\"\n") + + // TypeScript experimental decorators are actually allowed on declared fields + expectPrintedExperimentalDecoratorTS(t, "class Foo { @(() => {}) declare foo: any; @(() => {}) bar: any }", + "class Foo {\n bar;\n}\n__decorateClass([\n () => {\n }\n], Foo.prototype, \"foo\", 2);\n__decorateClass([\n () => {\n }\n], Foo.prototype, \"bar\", 2);\n") } func TestTSDecorators(t *testing.T) { @@ -2104,6 +2108,10 @@ func TestTSDecorators(t *testing.T) { expectParseErrorTS(t, "@x export @y class Foo {}", ": ERROR: Decorators are not valid here\n") expectParseErrorTS(t, "@x export default abstract", ": ERROR: Decorators are not valid here\n") expectParseErrorTS(t, "@x export @y default class {}", ": ERROR: Decorators are not valid here\n: ERROR: Unexpected \"default\"\n") + + // JavaScript decorators are not allowed on declared fields + expectParseErrorTS(t, "class Foo { @(() => {}) declare foo: any; @(() => {}) bar: any }", + ": ERROR: Decorators are not valid here\n") } func TestTSTry(t *testing.T) {