Skip to content

Commit

Permalink
fix #1675: run decorators for "declare" fields
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Oct 14, 2021
1 parent a7f0ec6 commit 6f6a77d
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 13 deletions.
46 changes: 46 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,52 @@

## Unreleased

* Emit decorators for `declare` class fields ([#1675](https://github.com/evanw/esbuild/issues/1675))

In version 3.7, TypeScript introduced the `declare` keyword for class fields that avoids generating any code for that field:

```ts
// TypeScript input
class Foo {
a: number
declare b: number
}

// JavaScript output
class Foo {
a;
}
```

However, it turns out that TypeScript still emits decorators for these omitted fields. With this release, esbuild will now do this too:

```ts
// TypeScript input
class Foo {
@decorator a: number;
@decorator declare b: number;
}

// Old JavaScript output
class Foo {
a;
}
__decorateClass([
decorator
], Foo.prototype, "a", 2);

// New JavaScript output
class Foo {
a;
}
__decorateClass([
decorator
], Foo.prototype, "a", 2);
__decorateClass([
decorator
], Foo.prototype, "b", 2);
```

* Experimental support for esbuild on NetBSD ([#1624](https://github.com/evanw/esbuild/pull/1624))

With this release, esbuild now has a published binary executable for [NetBSD](https://www.netbsd.org/) in the [`esbuild-netbsd-64`](https://www.npmjs.com/package/esbuild-netbsd-64) npm package, and esbuild's installer has been modified to attempt to use it when on NetBSD. Hopefully this makes installing esbuild via npm work on NetBSD. This change was contributed by [@gdt](https://github.com/gdt).
Expand Down
4 changes: 4 additions & 0 deletions internal/bundler/bundler_ts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -607,11 +607,13 @@ func TestTypeScriptDecorators(t *testing.T) {
@x @y mUndef
@x @y mDef = 1
@x @y method(@x0 @y0 arg0, @x1 @y1 arg1) { return new Foo }
@x @y declare mDecl
constructor(@x0 @y0 arg0, @x1 @y1 arg1) {}
@x @y static sUndef
@x @y static sDef = new Foo
@x @y static sMethod(@x0 @y0 arg0, @x1 @y1 arg1) { return new Foo }
@x @y static declare mDecl
}
`,
"/all_computed.ts": `
Expand All @@ -621,6 +623,7 @@ func TestTypeScriptDecorators(t *testing.T) {
@x @y [mUndef()]
@x @y [mDef()] = 1
@x @y [method()](@x0 @y0 arg0, @x1 @y1 arg1) { return new Foo }
@x @y declare [mDecl()]
// Side effect order must be preserved even for fields without decorators
[xUndef()]
Expand All @@ -631,6 +634,7 @@ func TestTypeScriptDecorators(t *testing.T) {
@x @y static [sUndef()]
@x @y static [sDef()] = new Foo
@x @y static [sMethod()](@x0 @y0 arg0, @x1 @y1 arg1) { return new Foo }
@x @y static declare [mDecl()]
}
`,
"/a.ts": `
Expand Down
35 changes: 26 additions & 9 deletions internal/bundler/snapshots/snapshots_ts.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ TestTSDeclareClassFields
// define-false/index.ts
var Foo = class {
};
() => null, c, () => null, C;
() => null, c, () => null, d, () => null, C, () => null, D;
(() => new Foo())();

// define-true/index.ts
Expand All @@ -54,7 +54,7 @@ var Bar = class {
__publicField(this, _a);
}
};
_a = (() => null, c), _b = (() => null, C);
_a = (() => null, c), () => null, d, _b = (() => null, C), () => null, D;
__publicField(Bar, "A");
__publicField(Bar, _b);
(() => new Bar())();
Expand Down Expand Up @@ -385,6 +385,10 @@ __decorateClass([
__decorateParam(1, x1),
__decorateParam(1, y1)
], Foo.prototype, "method", 1);
__decorateClass([
x,
y
], Foo.prototype, "mDecl", 2);
__decorateClass([
x,
y
Expand All @@ -401,6 +405,10 @@ __decorateClass([
__decorateParam(1, x1),
__decorateParam(1, y1)
], Foo, "sMethod", 1);
__decorateClass([
x,
y
], Foo, "mDecl", 2);
Foo = __decorateClass([
x.y(),
new y.x(),
Expand All @@ -411,21 +419,22 @@ Foo = __decorateClass([
], Foo);

// all_computed.ts
var _a, _b, _c, _d, _e, _f, _g, _h;
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
var Foo2 = class {
constructor() {
this[_b] = 1;
this[_d] = 2;
this[_e] = 2;
}
[(_a = mUndef(), _b = mDef(), _c = method())](arg0, arg1) {
return new Foo2();
}
static [(xUndef(), _d = xDef(), yUndef(), _e = yDef(), _f = sUndef(), _g = sDef(), _h = sMethod())](arg0, arg1) {
static [(_d = mDecl(), xUndef(), _e = xDef(), yUndef(), _f = yDef(), _g = sUndef(), _h = sDef(), _i = sMethod())](arg0, arg1) {
return new Foo2();
}
};
Foo2[_e] = 3;
Foo2[_g] = new Foo2();
_j = mDecl();
Foo2[_f] = 3;
Foo2[_h] = new Foo2();
__decorateClass([
x,
y
Expand All @@ -445,19 +454,27 @@ __decorateClass([
__decorateClass([
x,
y
], Foo2, _f, 2);
], Foo2.prototype, _d, 2);
__decorateClass([
x,
y
], Foo2, _g, 2);
__decorateClass([
x,
y
], Foo2, _h, 2);
__decorateClass([
x,
y,
__decorateParam(0, x0),
__decorateParam(0, y0),
__decorateParam(1, x1),
__decorateParam(1, y1)
], Foo2, _h, 1);
], Foo2, _i, 1);
__decorateClass([
x,
y
], Foo2, _j, 2);
Foo2 = __decorateClass([
x?.[_ + "y"](),
new y?.[_ + "x"]()
Expand Down
1 change: 1 addition & 0 deletions internal/js_ast/js_ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ const (
PropertyGet
PropertySet
PropertySpread
PropertyDeclare
)

type Property struct {
Expand Down
17 changes: 16 additions & 1 deletion internal/js_parser/js_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -1941,7 +1941,22 @@ func (p *parser) parseProperty(kind js_ast.PropertyKind, opts propertyOpts, erro
if opts.isClass && p.options.ts.Parse && opts.tsDeclareRange.Len == 0 && raw == name {
opts.tsDeclareRange = nameRange
scopeIndex := len(p.scopesInOrder)
p.parseProperty(kind, opts, nil)

if prop, ok := p.parseProperty(kind, opts, nil); ok &&
prop.Kind == js_ast.PropertyNormal && prop.ValueOrNil.Data == nil {
// If this is a well-formed class field with the "declare" keyword,
// keep the declaration to preserve its side-effects, which may
// include the computed key and/or the TypeScript decorators:
//
// class Foo {
// declare [(console.log('side effect 1'), 'foo')]
// @decorator(console.log('side effect 2')) declare bar
// }
//
prop.Kind = js_ast.PropertyDeclare
return prop, true
}

p.discardScopesUpTo(scopeIndex)
return js_ast.Property{}, false
}
Expand Down
10 changes: 9 additions & 1 deletion internal/js_parser/js_parser_lower.go
Original file line number Diff line number Diff line change
Expand Up @@ -2085,13 +2085,21 @@ func (p *parser) lowerClass(stmt js_ast.Stmt, expr js_ast.Expr, shadowRef js_ast
}
}

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

// Make sure the order of computed property keys doesn't change. These
// expressions have side effects and must be evaluated in order.
keyExprNoSideEffects := prop.Key
if prop.IsComputed && (len(prop.TSDecorators) > 0 ||
mustLowerField || computedPropertyCache.Data != nil) {
needsKey := true
if len(prop.TSDecorators) == 0 && (prop.IsMethod || shouldOmitFieldInitializer) {
if len(prop.TSDecorators) == 0 && (prop.IsMethod || shouldOmitFieldInitializer || !mustLowerField) {
needsKey = false
}

Expand Down
4 changes: 2 additions & 2 deletions scripts/end-to-end-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -3543,7 +3543,7 @@
declare [(() => ++b)()]
}
const foo = new Foo
if (b !== 1 || 'a' in foo || 1 in foo || 'c' in foo || 2 in foo) throw 'fail'
if (b !== 2 || 'a' in foo || 1 in foo || 'c' in foo || 2 in foo) throw 'fail'
`,
}),
test(['in.ts', '--outfile=node.js', '--target=es6'], {
Expand All @@ -3556,7 +3556,7 @@
declare [(() => ++b)()]
}
const foo = new Foo
if (b !== 1 || !('a' in foo) || !(1 in foo) || 'c' in foo || 2 in foo) throw 'fail'
if (b !== 2 || !('a' in foo) || !(1 in foo) || 'c' in foo || 2 in foo) throw 'fail'
`,
'tsconfig.json': `{
"compilerOptions": {
Expand Down

0 comments on commit 6f6a77d

Please sign in to comment.