Skip to content

Commit

Permalink
feat: support typeof on #private Fields (#2174)
Browse files Browse the repository at this point in the history
  • Loading branch information
magic-akari committed Apr 11, 2022
1 parent ddd53f9 commit 7b4b5e3
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 6 deletions.
22 changes: 22 additions & 0 deletions CHANGELOG.md
Expand Up @@ -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.
Expand Down
16 changes: 10 additions & 6 deletions internal/js_parser/ts_parser.go
Expand Up @@ -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 */)
}

Expand Down
3 changes: 3 additions & 0 deletions internal/js_parser/ts_parser_test.go
Expand Up @@ -365,6 +365,9 @@ func TestTSTypes(t *testing.T) {
expectParseErrorTSX(t, "<in T extends any>() => {}", jsxErrorArrow)
expectParseErrorTSX(t, "<out T extends any>() => {}", jsxErrorArrow)
expectParseErrorTSX(t, "<in out T extends any>() => {}", 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;", "<stdin>: ERROR: Expected identifier but found \"#a\"\n")
}

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

0 comments on commit 7b4b5e3

Please sign in to comment.