From 758acfe67e89fc3da9e46dbe50722d17b93ebabe Mon Sep 17 00:00:00 2001 From: magic-akari Date: Mon, 11 Apr 2022 19:56:54 +0800 Subject: [PATCH 1/2] feat: support typeof on #private Fields --- internal/js_lexer/js_lexer.go | 4 ++++ internal/js_parser/ts_parser.go | 5 +++-- internal/js_parser/ts_parser_test.go | 3 +++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/internal/js_lexer/js_lexer.go b/internal/js_lexer/js_lexer.go index 7e94faf5410..1f1ee19c676 100644 --- a/internal/js_lexer/js_lexer.go +++ b/internal/js_lexer/js_lexer.go @@ -425,6 +425,10 @@ func (lexer *Lexer) IsIdentifierOrKeyword() bool { return lexer.Token >= TIdentifier } +func (lexer *Lexer) IsPrivateIdentifier() bool { + return lexer.Token == TPrivateIdentifier +} + func (lexer *Lexer) IsContextualKeyword(text string) bool { return lexer.Token == TIdentifier && lexer.Raw() == text } diff --git a/internal/js_parser/ts_parser.go b/internal/js_parser/ts_parser.go index 15e36a1cc41..f309570c3cc 100644 --- a/internal/js_parser/ts_parser.go +++ b/internal/js_parser/ts_parser.go @@ -366,8 +366,9 @@ func (p *parser) skipTypeScriptTypeWithOpts(level js_ast.L, opts skipTypeOpts) { } else { // "typeof x" // "typeof x.y" - for { - if !p.lexer.IsIdentifierOrKeyword() { + // "typeof x.#y" + for isProp := false; ; isProp = true { + if !p.lexer.IsIdentifierOrKeyword() && !(isProp && p.lexer.IsPrivateIdentifier()) { p.lexer.Expected(js_lexer.TIdentifier) } p.lexer.Next() diff --git a/internal/js_parser/ts_parser_test.go b/internal/js_parser/ts_parser_test.go index a9a612e82df..4f586d80eff 100644 --- a/internal/js_parser/ts_parser_test.go +++ b/internal/js_parser/ts_parser_test.go @@ -365,6 +365,9 @@ func TestTSTypes(t *testing.T) { expectParseErrorTSX(t, "() => {}", jsxErrorArrow) expectParseErrorTSX(t, "() => {}", jsxErrorArrow) expectParseErrorTSX(t, "() => {}", jsxErrorArrow) + expectPrintedTS(t, "class Container { get data(): typeof this.#data {} }", "class Container {\n get data() {\n }\n}\n") + expectPrintedTS(t, "const a: typeof this.#a = 1;", "const a = 1;\n") + expectParseErrorTS(t, "const a: typeof #a = 1;", ": ERROR: Expected identifier but found \"#a\"\n") } func TestTSAsCast(t *testing.T) { From a8efb317371f72085450928bf37391b4f461978d Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Mon, 11 Apr 2022 10:40:29 -0400 Subject: [PATCH 2/2] changelog entry --- CHANGELOG.md | 22 ++++++++++++++++++++++ internal/js_lexer/js_lexer.go | 4 ---- internal/js_parser/ts_parser.go | 15 +++++++++------ 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea786bd8a9f..a4eb6e45d7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,28 @@ ## Unreleased +* Add support for parsing `typeof` on #private fields from TypeScript 4.7 ([#2174](https://github.com/evanw/esbuild/pull/2174)) + + The upcoming version of TypeScript now lets you use `#private` fields in `typeof` type expressions: + + https://devblogs.microsoft.com/typescript/announcing-typescript-4-7-beta/#typeof-on-private-fields + + ```ts + class Container { + #data = "hello!"; + + get data(): typeof this.#data { + return this.#data; + } + + set data(value: typeof this.#data) { + this.#data = value; + } + } + ``` + + With this release, esbuild can now parse these new type expressions as well. This feature was contributed by [@magic-akari](https://github.com/magic-akari). + * Change TypeScript class field behavior when targeting ES2022 TypeScript 4.3 introduced a breaking change where class field behavior changes from assign semantics to define semantics when the `target` setting in `tsconfig.json` is set to `ESNext`. Specifically, the default value for TypeScript's `useDefineForClassFields` setting when unspecified is `true` if and only if `target` is `ESNext`. TypeScript 4.6 introduced another change where this behavior now happens for both `ESNext` and `ES2022`. Presumably this will be the case for `ES2023` and up as well. With this release, esbuild's behavior has also been changed to match. Now configuring esbuild with `--target=es2022` will also cause TypeScript files to use the new class field behavior. diff --git a/internal/js_lexer/js_lexer.go b/internal/js_lexer/js_lexer.go index 1f1ee19c676..7e94faf5410 100644 --- a/internal/js_lexer/js_lexer.go +++ b/internal/js_lexer/js_lexer.go @@ -425,10 +425,6 @@ func (lexer *Lexer) IsIdentifierOrKeyword() bool { return lexer.Token >= TIdentifier } -func (lexer *Lexer) IsPrivateIdentifier() bool { - return lexer.Token == TPrivateIdentifier -} - func (lexer *Lexer) IsContextualKeyword(text string) bool { return lexer.Token == TIdentifier && lexer.Raw() == text } diff --git a/internal/js_parser/ts_parser.go b/internal/js_parser/ts_parser.go index f309570c3cc..eb664cadfcb 100644 --- a/internal/js_parser/ts_parser.go +++ b/internal/js_parser/ts_parser.go @@ -365,18 +365,21 @@ func (p *parser) skipTypeScriptTypeWithOpts(level js_ast.L, opts skipTypeOpts) { continue } else { // "typeof x" + if !p.lexer.IsIdentifierOrKeyword() { + p.lexer.Expected(js_lexer.TIdentifier) + } + p.lexer.Next() + // "typeof x.y" // "typeof x.#y" - for isProp := false; ; isProp = true { - if !p.lexer.IsIdentifierOrKeyword() && !(isProp && p.lexer.IsPrivateIdentifier()) { - p.lexer.Expected(js_lexer.TIdentifier) - } + for p.lexer.Token == js_lexer.TDot { p.lexer.Next() - if p.lexer.Token != js_lexer.TDot { - break + if !p.lexer.IsIdentifierOrKeyword() && p.lexer.Token != js_lexer.TPrivateIdentifier { + p.lexer.Expected(js_lexer.TIdentifier) } p.lexer.Next() } + p.skipTypeScriptTypeArguments(false /* isInsideJSXElement */) }