From 7b4b5e3f938a5bddfd7ef96381800a73d9e72898 Mon Sep 17 00:00:00 2001 From: magic-akari Date: Mon, 11 Apr 2022 23:24:30 +0800 Subject: [PATCH] feat: support typeof on #private Fields (#2174) --- CHANGELOG.md | 22 ++++++++++++++++++++++ internal/js_parser/ts_parser.go | 16 ++++++++++------ internal/js_parser/ts_parser_test.go | 3 +++ 3 files changed, 35 insertions(+), 6 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_parser/ts_parser.go b/internal/js_parser/ts_parser.go index 15e36a1cc41..eb664cadfcb 100644 --- a/internal/js_parser/ts_parser.go +++ b/internal/js_parser/ts_parser.go @@ -365,17 +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" - for { - if !p.lexer.IsIdentifierOrKeyword() { - p.lexer.Expected(js_lexer.TIdentifier) - } + // "typeof x.#y" + 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 */) } 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) {