Skip to content

Commit

Permalink
parse and pass through auto accessors (#3009)
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Jun 17, 2023
1 parent 5508cc2 commit 2455820
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 6 deletions.
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,19 @@

* Pass through JavaScript decorators in JavaScript files ([#104](https://github.com/evanw/esbuild/issues/104))

In this release, esbuild now parses [JavaScript decorators](https://github.com/tc39/proposal-decorators) and passes them through to the output unmodified, at least as long as the language target is set to `esnext`. Transforming JavaScript decorators to environments that don't support them has not been implemented yet.
In this release, esbuild now parses decorators from the upcoming [JavaScript decorators proposal](https://github.com/tc39/proposal-decorators) and passes them through to the output unmodified, at least as long as the language target is set to `esnext`. Transforming JavaScript decorators to environments that don't support them has not been implemented yet.

* Pass through auto accessors ([#3009](https://github.com/evanw/esbuild/issues/3009))

This release now parses the new auto-accessor syntax from the upcoming [JavaScript decorators proposal](https://github.com/tc39/proposal-decorators) and passes them through to the output unmodified, at least as long as the language target is set to `esnext`. Transforming auto-accessors to environments that don't support them has not been implemented yet. The auto-accessor syntax looks like this:

```js
class Foo {
accessor foo;
static accessor bar;
}
new Foo().foo = Foo.bar;
```

## 0.18.4

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 @@ -259,6 +259,7 @@ const (
PropertyNormal PropertyKind = iota
PropertyGet
PropertySet
PropertyAutoAccessor
PropertySpread
PropertyDeclare
PropertyClassStaticBlock
Expand Down
16 changes: 11 additions & 5 deletions internal/js_parser/js_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -2053,6 +2053,12 @@ func (p *parser) parseProperty(startLoc logger.Loc, kind js_ast.PropertyKind, op
return p.parseProperty(startLoc, js_ast.PropertySet, opts, nil)
}

case "accessor":
if !p.lexer.HasNewlineBefore && !opts.isAsync && opts.isClass && raw == name.String {
p.markSyntaxFeature(compat.Decorators, nameRange)
return p.parseProperty(startLoc, js_ast.PropertyAutoAccessor, opts, nil)
}

case "async":
if !p.lexer.HasNewlineBefore && !opts.isAsync && raw == name.String {
opts.isAsync = true
Expand Down Expand Up @@ -2186,8 +2192,8 @@ func (p *parser) parseProperty(startLoc logger.Loc, kind js_ast.PropertyKind, op
// "class X { foo?: number }"
// "class X { foo?(): number }"
p.lexer.Next()
} else if p.lexer.Token == js_lexer.TExclamation && !p.lexer.HasNewlineBefore &&
kind == js_ast.PropertyNormal && !opts.isAsync && !opts.isGenerator {
} else if p.lexer.Token == js_lexer.TExclamation && !p.lexer.HasNewlineBefore && !opts.isAsync &&
!opts.isGenerator && (kind == js_ast.PropertyNormal || kind == js_ast.PropertyAutoAccessor) {
// "class X { foo!: number }"
p.lexer.Next()
hasDefiniteAssignmentAssertionOperator = true
Expand All @@ -2196,14 +2202,14 @@ func (p *parser) parseProperty(startLoc logger.Loc, kind js_ast.PropertyKind, op

// "class X { foo?<T>(): T }"
// "const x = { foo<T>(): T {} }"
if !hasDefiniteAssignmentAssertionOperator {
if !hasDefiniteAssignmentAssertionOperator && kind != js_ast.PropertyAutoAccessor {
hasTypeParameters = p.skipTypeScriptTypeParameters(allowConstModifier) != didNotSkipAnything
}
}

// Parse a class field with an optional initial value
if opts.isClass && kind == js_ast.PropertyNormal && !opts.isAsync && !opts.isGenerator &&
!hasTypeParameters && (p.lexer.Token != js_lexer.TOpenParen || hasDefiniteAssignmentAssertionOperator) {
if kind == js_ast.PropertyAutoAccessor || (opts.isClass && kind == js_ast.PropertyNormal && !opts.isAsync && !opts.isGenerator &&
!hasTypeParameters && (p.lexer.Token != js_lexer.TOpenParen || hasDefiniteAssignmentAssertionOperator)) {
var initializerOrNil js_ast.Expr

// Forbid the names "constructor" and "prototype" in some cases
Expand Down
40 changes: 40 additions & 0 deletions internal/js_parser/js_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1942,6 +1942,46 @@ func TestClassStaticBlocks(t *testing.T) {
expectPrintedMangle(t, "class Foo { static { foo() } }", "class Foo {\n static {\n foo();\n }\n}\n")
}

func TestAutoAccessors(t *testing.T) {
expectPrinted(t, "class Foo { accessor }", "class Foo {\n accessor;\n}\n")
expectPrinted(t, "class Foo { accessor \n x }", "class Foo {\n accessor;\n x;\n}\n")
expectPrinted(t, "class Foo { static accessor }", "class Foo {\n static accessor;\n}\n")
expectPrinted(t, "class Foo { static accessor \n x }", "class Foo {\n static accessor;\n x;\n}\n")

expectPrinted(t, "class Foo { accessor x }", "class Foo {\n accessor x;\n}\n")
expectPrinted(t, "class Foo { accessor x = y }", "class Foo {\n accessor x = y;\n}\n")
expectPrinted(t, "class Foo { accessor [x] }", "class Foo {\n accessor [x];\n}\n")
expectPrinted(t, "class Foo { accessor [x] = y }", "class Foo {\n accessor [x] = y;\n}\n")
expectPrinted(t, "class Foo { static accessor x }", "class Foo {\n static accessor x;\n}\n")
expectPrinted(t, "class Foo { static accessor [x] }", "class Foo {\n static accessor [x];\n}\n")
expectPrinted(t, "class Foo { static accessor x = y }", "class Foo {\n static accessor x = y;\n}\n")
expectPrinted(t, "class Foo { static accessor [x] = y }", "class Foo {\n static accessor [x] = y;\n}\n")

expectPrinted(t, "Foo = class { accessor x }", "Foo = class {\n accessor x;\n};\n")
expectPrinted(t, "Foo = class { accessor [x] }", "Foo = class {\n accessor [x];\n};\n")
expectPrinted(t, "Foo = class { accessor x = y }", "Foo = class {\n accessor x = y;\n};\n")
expectPrinted(t, "Foo = class { accessor [x] = y }", "Foo = class {\n accessor [x] = y;\n};\n")
expectPrinted(t, "Foo = class { static accessor x }", "Foo = class {\n static accessor x;\n};\n")
expectPrinted(t, "Foo = class { static accessor [x] }", "Foo = class {\n static accessor [x];\n};\n")
expectPrinted(t, "Foo = class { static accessor x = y }", "Foo = class {\n static accessor x = y;\n};\n")

expectPrinted(t, "class Foo { accessor get }", "class Foo {\n accessor get;\n}\n")
expectPrinted(t, "class Foo { get accessor() {} }", "class Foo {\n get accessor() {\n }\n}\n")
expectParseError(t, "class Foo { accessor x() {} }", "<stdin>: ERROR: Expected \";\" but found \"(\"\n")
expectParseError(t, "class Foo { accessor get x() {} }", "<stdin>: ERROR: Expected \";\" but found \"x\"\n")
expectParseError(t, "class Foo { get accessor x() {} }", "<stdin>: ERROR: Expected \"(\" but found \"x\"\n")

expectPrinted(t, "Foo = { get accessor() {} }", "Foo = { get accessor() {\n} };\n")
expectParseError(t, "Foo = { accessor x }", "<stdin>: ERROR: Expected \"}\" but found \"x\"\n")
expectParseError(t, "Foo = { accessor x() {} }", "<stdin>: ERROR: Expected \"}\" but found \"x\"\n")
expectParseError(t, "Foo = { get accessor x() {} }", "<stdin>: ERROR: Expected \"(\" but found \"x\"\n")

expectParseError(t, "class Foo { accessor x, y }", "<stdin>: ERROR: Expected \";\" but found \",\"\n")
expectParseError(t, "class Foo { static accessor x, y }", "<stdin>: ERROR: Expected \";\" but found \",\"\n")
expectParseError(t, "Foo = class { accessor x, y }", "<stdin>: ERROR: Expected \";\" but found \",\"\n")
expectParseError(t, "Foo = class { static accessor x, y }", "<stdin>: ERROR: Expected \";\" but found \",\"\n")
}

func TestGenerator(t *testing.T) {
expectParseError(t, "(class { * foo })", "<stdin>: ERROR: Expected \"(\" but found \"}\"\n")
expectParseError(t, "(class { * *foo() {} })", "<stdin>: ERROR: Unexpected \"*\"\n")
Expand Down
31 changes: 31 additions & 0 deletions internal/js_parser/ts_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,37 @@ func TestTSClass(t *testing.T) {
expectParseErrorTS(t, "class Foo { [foo]!<T>() {} }", "<stdin>: ERROR: Expected \";\" but found \"<\"\n")
}

func TestTSAutoAccessors(t *testing.T) {
expectPrintedTS(t, "class Foo { accessor }", "class Foo {\n accessor;\n}\n")
expectPrintedTS(t, "class Foo { accessor x }", "class Foo {\n accessor x;\n}\n")
expectPrintedTS(t, "class Foo { accessor x? }", "class Foo {\n accessor x;\n}\n")
expectPrintedTS(t, "class Foo { accessor x! }", "class Foo {\n accessor x;\n}\n")
expectPrintedTS(t, "class Foo { accessor x = y }", "class Foo {\n accessor x = y;\n}\n")
expectPrintedTS(t, "class Foo { accessor x? = y }", "class Foo {\n accessor x = y;\n}\n")
expectPrintedTS(t, "class Foo { accessor x! = y }", "class Foo {\n accessor x = y;\n}\n")
expectPrintedTS(t, "class Foo { accessor x: any }", "class Foo {\n accessor x;\n}\n")
expectPrintedTS(t, "class Foo { accessor x?: any }", "class Foo {\n accessor x;\n}\n")
expectPrintedTS(t, "class Foo { accessor x!: any }", "class Foo {\n accessor x;\n}\n")
expectPrintedTS(t, "class Foo { accessor x: any = y }", "class Foo {\n accessor x = y;\n}\n")
expectPrintedTS(t, "class Foo { accessor x?: any = y }", "class Foo {\n accessor x = y;\n}\n")
expectPrintedTS(t, "class Foo { accessor x!: any = y }", "class Foo {\n accessor x = y;\n}\n")
expectPrintedTS(t, "class Foo { accessor [x] }", "class Foo {\n accessor [x];\n}\n")
expectPrintedTS(t, "class Foo { accessor [x]? }", "class Foo {\n accessor [x];\n}\n")
expectPrintedTS(t, "class Foo { accessor [x]! }", "class Foo {\n accessor [x];\n}\n")
expectPrintedTS(t, "class Foo { accessor [x] = y }", "class Foo {\n accessor [x] = y;\n}\n")
expectPrintedTS(t, "class Foo { accessor [x]? = y }", "class Foo {\n accessor [x] = y;\n}\n")
expectPrintedTS(t, "class Foo { accessor [x]! = y }", "class Foo {\n accessor [x] = y;\n}\n")
expectPrintedTS(t, "class Foo { accessor [x]: any }", "class Foo {\n accessor [x];\n}\n")
expectPrintedTS(t, "class Foo { accessor [x]?: any }", "class Foo {\n accessor [x];\n}\n")
expectPrintedTS(t, "class Foo { accessor [x]!: any }", "class Foo {\n accessor [x];\n}\n")
expectPrintedTS(t, "class Foo { accessor [x]: any = y }", "class Foo {\n accessor [x] = y;\n}\n")
expectPrintedTS(t, "class Foo { accessor [x]?: any = y }", "class Foo {\n accessor [x] = y;\n}\n")
expectPrintedTS(t, "class Foo { accessor [x]!: any = y }", "class Foo {\n accessor [x] = y;\n}\n")

expectParseErrorTS(t, "class Foo { accessor x<T> }", "<stdin>: ERROR: Expected \";\" but found \"<\"\n")
expectParseErrorTS(t, "class Foo { accessor x<T>() {} }", "<stdin>: ERROR: Expected \";\" but found \"<\"\n")
}

func TestTSPrivateIdentifiers(t *testing.T) {
// The TypeScript compiler still moves private field initializers into the
// constructor, but it has to leave the private field declaration in place so
Expand Down
6 changes: 6 additions & 0 deletions internal/js_printer/js_printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -1041,6 +1041,12 @@ func (p *printer) printProperty(property js_ast.Property) {
p.addSourceMapping(property.Loc)
p.print("set")
p.printSpace()

case js_ast.PropertyAutoAccessor:
p.printSpaceBeforeIdentifier()
p.addSourceMapping(property.Loc)
p.print("accessor")
p.printSpace()
}

if fn, ok := property.ValueOrNil.Data.(*js_ast.EFunction); property.Flags.Has(js_ast.PropertyIsMethod) && ok {
Expand Down
7 changes: 7 additions & 0 deletions internal/js_printer/js_printer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,13 @@ func TestClass(t *testing.T) {
expectPrinted(t, "class Foo { static set foo(x) {} }", "class Foo {\n static set foo(x) {\n }\n}\n")
}

func TestAutoAccessors(t *testing.T) {
expectPrinted(t, "class Foo { accessor x; static accessor y }", "class Foo {\n accessor x;\n static accessor y;\n}\n")
expectPrinted(t, "class Foo { accessor [x]; static accessor [y] }", "class Foo {\n accessor [x];\n static accessor [y];\n}\n")
expectPrintedMinify(t, "class Foo { accessor x; static accessor y }", "class Foo{accessor x;static accessor y}")
expectPrintedMinify(t, "class Foo { accessor [x]; static accessor [y] }", "class Foo{accessor[x];static accessor[y]}")
}

func TestPrivateIdentifiers(t *testing.T) {
expectPrinted(t, "class Foo { #foo; foo() { return #foo in this } }", "class Foo {\n #foo;\n foo() {\n return #foo in this;\n }\n}\n")
expectPrintedMinify(t, "class Foo { #foo; foo() { return #foo in this } }", "class Foo{#foo;foo(){return#foo in this}}")
Expand Down

0 comments on commit 2455820

Please sign in to comment.