From 9eca46464ed5615cb36a3beb3f7a7b9a8ffbe7cf Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Mon, 21 Oct 2024 16:49:24 -0400 Subject: [PATCH] fix ASI after `get`/`set` before `*` in `class` --- CHANGELOG.md | 17 +++++++++++++++++ internal/js_parser/js_parser.go | 25 ++++++++++++++----------- internal/js_parser/js_parser_test.go | 22 ++++++++++++++++++++-- internal/js_parser/ts_parser_test.go | 10 ++++++++++ 4 files changed, 61 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a939318264..679b81de505 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index 45daca94c3f..444cc144ef2 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -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) @@ -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) @@ -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) } } diff --git a/internal/js_parser/js_parser_test.go b/internal/js_parser/js_parser_test.go index 5efa385ab6d..732ed029f6a 100644 --- a/internal/js_parser/js_parser_test.go +++ b/internal/js_parser/js_parser_test.go @@ -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() {} }", ": ERROR: Expected \"}\" but found \"*\"\n") + expectParseError(t, "x = { set \n *x(_) {} }", ": ERROR: Expected \"}\" but found \"*\"\n") + expectParseError(t, "x = { get \n async x() {} }", ": ERROR: Expected \"(\" but found \"x\"\n") + expectParseError(t, "x = { set \n async x(_) {} }", ": ERROR: Expected \"(\" but found \"x\"\n") } func TestComputedProperty(t *testing.T) { @@ -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() {} }", ": ERROR: Expected \"(\" but found \"x\"\n") + expectParseError(t, "class A { set \n async x(_) {} }", ": ERROR: Expected \"(\" but found \"x\"\n") + expectParseError(t, "class A { async get \n *x() {} }", ": ERROR: Expected \"(\" but found \"*\"\n") + expectParseError(t, "class A { async set \n *x(_) {} }", ": ERROR: Expected \"(\" but found \"*\"\n") } func TestSuperCall(t *testing.T) { @@ -2185,8 +2203,8 @@ __privateAdd(Foo, _x, __runInitializers(_init, 8, Foo)), __runInitializers(_init func TestGenerator(t *testing.T) { expectParseError(t, "(class { * foo })", ": ERROR: Expected \"(\" but found \"}\"\n") expectParseError(t, "(class { * *foo() {} })", ": ERROR: Unexpected \"*\"\n") - expectParseError(t, "(class { get*foo() {} })", ": ERROR: Unexpected \"*\"\n") - expectParseError(t, "(class { set*foo() {} })", ": ERROR: Unexpected \"*\"\n") + expectParseError(t, "(class { get*foo() {} })", ": ERROR: Expected \";\" but found \"*\"\n") + expectParseError(t, "(class { set*foo() {} })", ": ERROR: Expected \";\" but found \"*\"\n") expectParseError(t, "(class { *get foo() {} })", ": ERROR: Expected \"(\" but found \"foo\"\n") expectParseError(t, "(class { *set foo() {} })", ": ERROR: Expected \"(\" but found \"foo\"\n") expectParseError(t, "(class { *static foo() {} })", ": ERROR: Expected \"(\" but found \"foo\"\n") diff --git a/internal/js_parser/ts_parser_test.go b/internal/js_parser/ts_parser_test.go index 20b8483dfb7..b775f84b55d 100644 --- a/internal/js_parser/ts_parser_test.go +++ b/internal/js_parser/ts_parser_test.go @@ -820,6 +820,16 @@ func TestTSClass(t *testing.T) { expectParseErrorTS(t, "class Foo { [foo] }", ": ERROR: Expected \"(\" but found \"}\"\n") expectParseErrorTS(t, "class Foo { [foo]? }", ": ERROR: Expected \"(\" but found \"}\"\n") expectParseErrorTS(t, "class Foo { [foo]!() {} }", ": 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() {} }", ": ERROR: Expected \"(\" but found \"x\"\n") + expectParseErrorTS(t, "class A { set \n async x(_) {} }", ": ERROR: Expected \"(\" but found \"x\"\n") + expectParseErrorTS(t, "class A { async get \n *x() {} }", ": ERROR: Expected \"(\" but found \"*\"\n") + expectParseErrorTS(t, "class A { async set \n *x(_) {} }", ": ERROR: Expected \"(\" but found \"*\"\n") } func TestTSAutoAccessors(t *testing.T) {