Skip to content

Commit

Permalink
fix ASI after get/set before * in class
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Oct 21, 2024
1 parent d34e79e commit 9eca464
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 13 deletions.
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
# Changelog

## Unreleased

* Allow automatic semicolon insertion after `get`/`set`

This change fixes a grammar bug in the parser that incorrectly treated the following code as a syntax error:

```ts
class Foo {
get
*x() {}
set
*y() {}
}
```

The above code will be considered valid starting with this release. This change to esbuild follows a [similar change to TypeScript](https://github.com/microsoft/TypeScript/pull/60225) which will allow this syntax starting with TypeScript 5.7.

## 0.24.0

**_This release deliberately contains backwards-incompatible changes._** To avoid automatically picking up releases like this, you should either be pinning the exact version of `esbuild` in your `package.json` file (recommended) or be using a version range syntax that only accepts patch upgrades such as `^0.23.0` or `~0.23.0`. See npm's documentation about [semver](https://docs.npmjs.com/cli/v6/using-npm/semver/) for more information.
Expand Down
25 changes: 14 additions & 11 deletions internal/js_parser/js_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -2136,47 +2136,50 @@ func (p *parser) parseProperty(startLoc logger.Loc, kind js_ast.PropertyKind, op
couldBeModifierKeyword := p.lexer.IsIdentifierOrKeyword()
if !couldBeModifierKeyword {
switch p.lexer.Token {
case js_lexer.TOpenBracket, js_lexer.TNumericLiteral, js_lexer.TStringLiteral,
js_lexer.TAsterisk, js_lexer.TPrivateIdentifier:
case js_lexer.TOpenBracket, js_lexer.TNumericLiteral, js_lexer.TStringLiteral, js_lexer.TPrivateIdentifier:
couldBeModifierKeyword = true
case js_lexer.TAsterisk:
if opts.isAsync || (raw != "get" && raw != "set") {
couldBeModifierKeyword = true
}
}
}

// If so, check for a modifier keyword
if couldBeModifierKeyword {
switch name.String {
switch raw {
case "get":
if !opts.isAsync && raw == name.String {
if !opts.isAsync {
p.markSyntaxFeature(compat.ObjectAccessors, nameRange)
return p.parseProperty(startLoc, js_ast.PropertyGetter, opts, nil)
}

case "set":
if !opts.isAsync && raw == name.String {
if !opts.isAsync {
p.markSyntaxFeature(compat.ObjectAccessors, nameRange)
return p.parseProperty(startLoc, js_ast.PropertySetter, opts, nil)
}

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

case "async":
if !p.lexer.HasNewlineBefore && !opts.isAsync && raw == name.String {
if !p.lexer.HasNewlineBefore && !opts.isAsync {
opts.isAsync = true
opts.asyncRange = nameRange
return p.parseProperty(startLoc, js_ast.PropertyMethod, opts, nil)
}

case "static":
if !opts.isStatic && !opts.isAsync && opts.isClass && raw == name.String {
if !opts.isStatic && !opts.isAsync && opts.isClass {
opts.isStatic = true
return p.parseProperty(startLoc, kind, opts, nil)
}

case "declare":
if !p.lexer.HasNewlineBefore && opts.isClass && p.options.ts.Parse && opts.tsDeclareRange.Len == 0 && raw == name.String {
if !p.lexer.HasNewlineBefore && opts.isClass && p.options.ts.Parse && opts.tsDeclareRange.Len == 0 {
opts.tsDeclareRange = nameRange
scopeIndex := len(p.scopesInOrder)

Expand Down Expand Up @@ -2213,7 +2216,7 @@ func (p *parser) parseProperty(startLoc logger.Loc, kind js_ast.PropertyKind, op
}

case "abstract":
if !p.lexer.HasNewlineBefore && opts.isClass && p.options.ts.Parse && !opts.isTSAbstract && raw == name.String {
if !p.lexer.HasNewlineBefore && opts.isClass && p.options.ts.Parse && !opts.isTSAbstract {
opts.isTSAbstract = true
scopeIndex := len(p.scopesInOrder)

Expand Down Expand Up @@ -2249,7 +2252,7 @@ func (p *parser) parseProperty(startLoc logger.Loc, kind js_ast.PropertyKind, op

case "private", "protected", "public", "readonly", "override":
// Skip over TypeScript keywords
if opts.isClass && p.options.ts.Parse && raw == name.String {
if opts.isClass && p.options.ts.Parse {
return p.parseProperty(startLoc, kind, opts, nil)
}
}
Expand Down
22 changes: 20 additions & 2 deletions internal/js_parser/js_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1372,6 +1372,14 @@ func TestObject(t *testing.T) {
expectPrintedMangle(t, "x = { '2147483648': y }", "x = { \"2147483648\": y };\n")
expectPrintedMangle(t, "x = { '-2147483648': y }", "x = { \"-2147483648\": y };\n")
expectPrintedMangle(t, "x = { '-2147483649': y }", "x = { \"-2147483649\": y };\n")

// See: https://github.com/microsoft/TypeScript/pull/60225
expectPrinted(t, "x = { get \n x() {} }", "x = { get x() {\n} };\n")
expectPrinted(t, "x = { set \n x(_) {} }", "x = { set x(_) {\n} };\n")
expectParseError(t, "x = { get \n *x() {} }", "<stdin>: ERROR: Expected \"}\" but found \"*\"\n")
expectParseError(t, "x = { set \n *x(_) {} }", "<stdin>: ERROR: Expected \"}\" but found \"*\"\n")
expectParseError(t, "x = { get \n async x() {} }", "<stdin>: ERROR: Expected \"(\" but found \"x\"\n")
expectParseError(t, "x = { set \n async x(_) {} }", "<stdin>: ERROR: Expected \"(\" but found \"x\"\n")
}

func TestComputedProperty(t *testing.T) {
Expand Down Expand Up @@ -1797,6 +1805,16 @@ func TestClass(t *testing.T) {

// Make sure direct "eval" doesn't cause the class name to change
expectPrinted(t, "class Foo { foo = [Foo, eval(bar)] }", "class Foo {\n foo = [Foo, eval(bar)];\n}\n")

// See: https://github.com/microsoft/TypeScript/pull/60225
expectPrinted(t, "class A { get \n x() {} }", "class A {\n get x() {\n }\n}\n")
expectPrinted(t, "class A { set \n x(_) {} }", "class A {\n set x(_) {\n }\n}\n")
expectPrinted(t, "class A { get \n *x() {} }", "class A {\n get;\n *x() {\n }\n}\n")
expectPrinted(t, "class A { set \n *x(_) {} }", "class A {\n set;\n *x(_) {\n }\n}\n")
expectParseError(t, "class A { get \n async x() {} }", "<stdin>: ERROR: Expected \"(\" but found \"x\"\n")
expectParseError(t, "class A { set \n async x(_) {} }", "<stdin>: ERROR: Expected \"(\" but found \"x\"\n")
expectParseError(t, "class A { async get \n *x() {} }", "<stdin>: ERROR: Expected \"(\" but found \"*\"\n")
expectParseError(t, "class A { async set \n *x(_) {} }", "<stdin>: ERROR: Expected \"(\" but found \"*\"\n")
}

func TestSuperCall(t *testing.T) {
Expand Down Expand Up @@ -2185,8 +2203,8 @@ __privateAdd(Foo, _x, __runInitializers(_init, 8, Foo)), __runInitializers(_init
func TestGenerator(t *testing.T) {
expectParseError(t, "(class { * foo })", "<stdin>: ERROR: Expected \"(\" but found \"}\"\n")
expectParseError(t, "(class { * *foo() {} })", "<stdin>: ERROR: Unexpected \"*\"\n")
expectParseError(t, "(class { get*foo() {} })", "<stdin>: ERROR: Unexpected \"*\"\n")
expectParseError(t, "(class { set*foo() {} })", "<stdin>: ERROR: Unexpected \"*\"\n")
expectParseError(t, "(class { get*foo() {} })", "<stdin>: ERROR: Expected \";\" but found \"*\"\n")
expectParseError(t, "(class { set*foo() {} })", "<stdin>: ERROR: Expected \";\" but found \"*\"\n")
expectParseError(t, "(class { *get foo() {} })", "<stdin>: ERROR: Expected \"(\" but found \"foo\"\n")
expectParseError(t, "(class { *set foo() {} })", "<stdin>: ERROR: Expected \"(\" but found \"foo\"\n")
expectParseError(t, "(class { *static foo() {} })", "<stdin>: ERROR: Expected \"(\" but found \"foo\"\n")
Expand Down
10 changes: 10 additions & 0 deletions internal/js_parser/ts_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -820,6 +820,16 @@ func TestTSClass(t *testing.T) {
expectParseErrorTS(t, "class Foo { [foo]<T> }", "<stdin>: ERROR: Expected \"(\" but found \"}\"\n")
expectParseErrorTS(t, "class Foo { [foo]?<T> }", "<stdin>: ERROR: Expected \"(\" but found \"}\"\n")
expectParseErrorTS(t, "class Foo { [foo]!<T>() {} }", "<stdin>: ERROR: Expected \";\" but found \"<\"\n")

// See: https://github.com/microsoft/TypeScript/pull/60225
expectPrintedTS(t, "class A { get \n x() {} }", "class A {\n get x() {\n }\n}\n")
expectPrintedTS(t, "class A { set \n x(_) {} }", "class A {\n set x(_) {\n }\n}\n")
expectPrintedTS(t, "class A { get \n *x() {} }", "class A {\n get;\n *x() {\n }\n}\n")
expectPrintedTS(t, "class A { set \n *x(_) {} }", "class A {\n set;\n *x(_) {\n }\n}\n")
expectParseErrorTS(t, "class A { get \n async x() {} }", "<stdin>: ERROR: Expected \"(\" but found \"x\"\n")
expectParseErrorTS(t, "class A { set \n async x(_) {} }", "<stdin>: ERROR: Expected \"(\" but found \"x\"\n")
expectParseErrorTS(t, "class A { async get \n *x() {} }", "<stdin>: ERROR: Expected \"(\" but found \"*\"\n")
expectParseErrorTS(t, "class A { async set \n *x(_) {} }", "<stdin>: ERROR: Expected \"(\" but found \"*\"\n")
}

func TestTSAutoAccessors(t *testing.T) {
Expand Down

0 comments on commit 9eca464

Please sign in to comment.