Skip to content

Commit ae5cc17

Browse files
committed
fix #3684: abstract experimental decorators
1 parent c809af0 commit ae5cc17

7 files changed

Lines changed: 88 additions & 18 deletions

File tree

CHANGELOG.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,36 @@
22

33
## Unreleased
44

5+
* Support TypeScript experimental decorators on `abstract` class fields ([#3684](https://github.com/evanw/esbuild/issues/3684))
6+
7+
With this release, you can now use TypeScript experimental decorators on `abstract` class fields. This was silently compiled incorrectly in esbuild 0.19.7 and below, and was an error from esbuild 0.19.8 to esbuild 0.20.1. Code such as the following should now work correctly:
8+
9+
```ts
10+
// Original code
11+
const log = (x: any, y: string) => console.log(y)
12+
abstract class Foo { @log abstract foo: string }
13+
new class extends Foo { foo = '' }
14+
15+
// Old output (with --loader=ts --tsconfig-raw={\"compilerOptions\":{\"experimentalDecorators\":true}})
16+
const log = (x, y) => console.log(y);
17+
class Foo {
18+
}
19+
new class extends Foo {
20+
foo = "";
21+
}();
22+
23+
// New output (with --loader=ts --tsconfig-raw={\"compilerOptions\":{\"experimentalDecorators\":true}})
24+
const log = (x, y) => console.log(y);
25+
class Foo {
26+
}
27+
__decorateClass([
28+
log
29+
], Foo.prototype, "foo", 2);
30+
new class extends Foo {
31+
foo = "";
32+
}();
33+
```
34+
535
* Improve dead code removal of `switch` statements ([#3659](https://github.com/evanw/esbuild/issues/3659))
636

737
With this release, esbuild will now remove `switch` statements in branches when minifying if they are known to never be evaluated:

internal/bundler_tests/bundler_ts_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,6 +1054,7 @@ func TestTSExperimentalDecorators(t *testing.T) {
10541054
@x @y mDef = 1
10551055
@x @y method(@x0 @y0 arg0, @x1 @y1 arg1) { return new Foo }
10561056
@x @y declare mDecl
1057+
@x @y abstract mAbst
10571058
constructor(@x0 @y0 arg0, @x1 @y1 arg1) {}
10581059
10591060
@x @y static sUndef
@@ -1070,6 +1071,7 @@ func TestTSExperimentalDecorators(t *testing.T) {
10701071
@x @y [mDef()] = 1
10711072
@x @y [method()](@x0 @y0 arg0, @x1 @y1 arg1) { return new Foo }
10721073
@x @y declare [mDecl()]
1074+
@x @y abstract [mAbst()]
10731075
10741076
// Side effect order must be preserved even for fields without decorators
10751077
[xUndef()]

internal/bundler_tests/snapshots/snapshots_ts.txt

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,10 @@ __decorateClass([
717717
x,
718718
y
719719
], Foo.prototype, "mDecl", 2);
720+
__decorateClass([
721+
x,
722+
y
723+
], Foo.prototype, "mAbst", 2);
720724
__decorateClass([
721725
x,
722726
y
@@ -747,24 +751,24 @@ Foo = __decorateClass([
747751
], Foo);
748752

749753
// all_computed.ts
750-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
754+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
751755
var Foo2 = class {
752756
constructor() {
753757
this[_b] = 1;
754-
this[_e] = 2;
758+
this[_f] = 2;
755759
}
756760
static {
757-
_j = mDecl();
761+
_k = mDecl();
758762
}
759763
[(_a = mUndef(), _b = mDef(), _c = method())](arg0, arg1) {
760764
return new Foo2();
761765
}
762-
static [(_d = mDecl(), xUndef(), _e = xDef(), yUndef(), _f = yDef(), _g = sUndef(), _h = sDef(), _i = sMethod())](arg0, arg1) {
766+
static [(_d = mDecl(), _e = mAbst(), xUndef(), _f = xDef(), yUndef(), _g = yDef(), _h = sUndef(), _i = sDef(), _j = sMethod())](arg0, arg1) {
763767
return new Foo2();
764768
}
765769
};
766-
Foo2[_f] = 3;
767-
Foo2[_h] = new Foo2();
770+
Foo2[_g] = 3;
771+
Foo2[_i] = new Foo2();
768772
__decorateClass([
769773
x,
770774
y
@@ -788,23 +792,27 @@ __decorateClass([
788792
__decorateClass([
789793
x,
790794
y
791-
], Foo2, _g, 2);
795+
], Foo2.prototype, _e, 2);
792796
__decorateClass([
793797
x,
794798
y
795799
], Foo2, _h, 2);
800+
__decorateClass([
801+
x,
802+
y
803+
], Foo2, _i, 2);
796804
__decorateClass([
797805
x,
798806
y,
799807
__decorateParam(0, x0),
800808
__decorateParam(0, y0),
801809
__decorateParam(1, x1),
802810
__decorateParam(1, y1)
803-
], Foo2, _i, 1);
811+
], Foo2, _j, 1);
804812
__decorateClass([
805813
x,
806814
y
807-
], Foo2, _j, 2);
815+
], Foo2, _k, 2);
808816
Foo2 = __decorateClass([
809817
x?.[_ + "y"](),
810818
new y?.[_ + "x"]()

internal/js_ast/js_ast.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ const (
255255
PropertySet
256256
PropertyAutoAccessor
257257
PropertySpread
258-
PropertyDeclare
258+
PropertyDeclareOrAbstract
259259
PropertyClassStaticBlock
260260
)
261261

internal/js_parser/js_parser.go

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2204,7 +2204,7 @@ func (p *parser) parseProperty(startLoc logger.Loc, kind js_ast.PropertyKind, op
22042204
// https://github.com/evanw/esbuild/issues/1675
22052205
// https://github.com/microsoft/TypeScript/issues/46345
22062206
//
2207-
prop.Kind = js_ast.PropertyDeclare
2207+
prop.Kind = js_ast.PropertyDeclareOrAbstract
22082208
return prop, true
22092209
}
22102210

@@ -2216,7 +2216,33 @@ func (p *parser) parseProperty(startLoc logger.Loc, kind js_ast.PropertyKind, op
22162216
if !p.lexer.HasNewlineBefore && opts.isClass && p.options.ts.Parse && !opts.isTSAbstract && raw == name.String {
22172217
opts.isTSAbstract = true
22182218
scopeIndex := len(p.scopesInOrder)
2219-
p.parseProperty(startLoc, kind, opts, nil)
2219+
2220+
if prop, ok := p.parseProperty(startLoc, kind, opts, nil); ok &&
2221+
prop.Kind == js_ast.PropertyNormal && prop.ValueOrNil.Data == nil &&
2222+
(p.options.ts.Config.ExperimentalDecorators == config.True && len(opts.decorators) > 0) {
2223+
// If this is a well-formed class field with the "abstract" keyword,
2224+
// only keep the declaration to preserve its side-effects when
2225+
// there are TypeScript experimental decorators present:
2226+
//
2227+
// abstract class Foo {
2228+
// // Remove this
2229+
// abstract [(console.log('side effect 1'), 'foo')]
2230+
//
2231+
// // Keep this
2232+
// @decorator(console.log('side effect 2')) abstract bar
2233+
// }
2234+
//
2235+
// This behavior is valid with TypeScript experimental decorators.
2236+
// TypeScript does not allow this with JavaScript decorators.
2237+
//
2238+
// References:
2239+
//
2240+
// https://github.com/evanw/esbuild/issues/3684
2241+
//
2242+
prop.Kind = js_ast.PropertyDeclareOrAbstract
2243+
return prop, true
2244+
}
2245+
22202246
p.discardScopesUpTo(scopeIndex)
22212247
return js_ast.Property{}, false
22222248
}

internal/js_parser/js_parser_lower_class.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,10 +1062,10 @@ func (p *parser) lowerClass(stmt js_ast.Stmt, expr js_ast.Expr, result visitClas
10621062
}
10631063
}
10641064

1065-
// If the field uses the TypeScript "declare" keyword, just omit it entirely.
1066-
// However, we must still keep any side-effects in the computed value and/or
1067-
// in the decorators.
1068-
if prop.Kind == js_ast.PropertyDeclare && prop.ValueOrNil.Data == nil {
1065+
// If the field uses the TypeScript "declare" or "abstract" keyword, just
1066+
// omit it entirely. However, we must still keep any side-effects in the
1067+
// computed value and/or in the decorators.
1068+
if prop.Kind == js_ast.PropertyDeclareOrAbstract && prop.ValueOrNil.Data == nil {
10691069
mustLowerField = true
10701070
shouldOmitFieldInitializer = true
10711071
}

internal/js_parser/ts_parser_test.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2051,9 +2051,11 @@ func TestTSExperimentalDecorator(t *testing.T) {
20512051
expectParseErrorExperimentalDecoratorTS(t, "@x export default abstract", "<stdin>: ERROR: Decorators are not valid here\n")
20522052
expectParseErrorExperimentalDecoratorTS(t, "@x export @y default class {}", "<stdin>: ERROR: Decorators are not valid here\n<stdin>: ERROR: Unexpected \"default\"\n")
20532053

2054-
// TypeScript experimental decorators are actually allowed on declared fields
2054+
// TypeScript experimental decorators are actually allowed on declared and abstract fields
20552055
expectPrintedExperimentalDecoratorTS(t, "class Foo { @(() => {}) declare foo: any; @(() => {}) bar: any }",
20562056
"class Foo {\n bar;\n}\n__decorateClass([\n () => {\n }\n], Foo.prototype, \"foo\", 2);\n__decorateClass([\n () => {\n }\n], Foo.prototype, \"bar\", 2);\n")
2057+
expectPrintedExperimentalDecoratorTS(t, "abstract class Foo { @(() => {}) abstract foo: any; @(() => {}) bar: any }",
2058+
"class Foo {\n bar;\n}\n__decorateClass([\n () => {\n }\n], Foo.prototype, \"foo\", 2);\n__decorateClass([\n () => {\n }\n], Foo.prototype, \"bar\", 2);\n")
20572059
}
20582060

20592061
func TestTSDecorators(t *testing.T) {
@@ -2130,9 +2132,11 @@ func TestTSDecorators(t *testing.T) {
21302132
expectParseErrorTS(t, "@x export default abstract", "<stdin>: ERROR: Decorators are not valid here\n")
21312133
expectParseErrorTS(t, "@x export @y default class {}", "<stdin>: ERROR: Decorators are not valid here\n<stdin>: ERROR: Unexpected \"default\"\n")
21322134

2133-
// JavaScript decorators are not allowed on declared fields
2135+
// JavaScript decorators are not allowed on declared or abstract fields
21342136
expectParseErrorTS(t, "class Foo { @(() => {}) declare foo: any; @(() => {}) bar: any }",
21352137
"<stdin>: ERROR: Decorators are not valid here\n")
2138+
expectParseErrorTS(t, "abstract class Foo { @(() => {}) abstract foo: any; @(() => {}) bar: any }",
2139+
"<stdin>: ERROR: Decorators are not valid here\n")
21362140
}
21372141

21382142
func TestTSTry(t *testing.T) {

0 commit comments

Comments
 (0)