From 7034fca6b1a6e626f1ebe1c88c33abf9ec62f952 Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Sat, 18 Jun 2022 16:13:17 -0400 Subject: [PATCH] fix #2330: implement `extends` after `infer` in ts --- CHANGELOG.md | 13 +++++++++++++ internal/js_parser/ts_parser.go | 18 +++++++++++++++++- internal/js_parser/ts_parser_test.go | 5 ++++- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf084749fa4..d758195ddc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -82,6 +82,19 @@ _Note that JavaScript feature transformation is very complex and allowing full customization of the set of supported syntax features could cause bugs in esbuild due to new interactions between multiple features that were never possible before. Consider this to be an experimental feature._ +* Implement `extends` constraints on `infer` type variables ([#2330](https://github.com/evanw/esbuild/issues/2330)) + + TypeScript 4.7 introduced the ability to write an `extends` constraint after an `infer` type variable, which looks like this: + + ```ts + type FirstIfString = + T extends [infer S extends string, ...unknown[]] + ? S + : never; + ``` + + You can read the blog post for more details: https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#extends-constraints-on-infer-type-variables. Previously this was a syntax error in esbuild but with this release, esbuild can now parse this syntax correctly. + * Allow `define` to match optional chain expressions ([#2324](https://github.com/evanw/esbuild/issues/2324)) Previously esbuild's `define` feature only matched member expressions that did not use optional chaining. With this release, esbuild will now also match those that use optional chaining: diff --git a/internal/js_parser/ts_parser.go b/internal/js_parser/ts_parser.go index 179d5e49a5e..811d1a3b52d 100644 --- a/internal/js_parser/ts_parser.go +++ b/internal/js_parser/ts_parser.go @@ -175,6 +175,7 @@ const ( tsTypeIdentifierAsserts tsTypeIdentifierPrefix tsTypeIdentifierPrimitive + tsTypeIdentifierInfer ) // Use a map to improve lookup speed @@ -185,7 +186,6 @@ var tsTypeIdentifierMap = map[string]tsTypeIdentifierKind{ "keyof": tsTypeIdentifierPrefix, "readonly": tsTypeIdentifierPrefix, - "infer": tsTypeIdentifierPrefix, "any": tsTypeIdentifierPrimitive, "never": tsTypeIdentifierPrimitive, @@ -197,6 +197,8 @@ var tsTypeIdentifierMap = map[string]tsTypeIdentifierKind{ "boolean": tsTypeIdentifierPrimitive, "bigint": tsTypeIdentifierPrimitive, "symbol": tsTypeIdentifierPrimitive, + + "infer": tsTypeIdentifierInfer, } func (p *parser) skipTypeScriptTypeWithOpts(level js_ast.L, opts skipTypeOpts) { @@ -310,6 +312,20 @@ loop: } break loop + case tsTypeIdentifierInfer: + p.lexer.Next() + + // "type Foo = Bar extends [infer T] ? T : null" + // "type Foo = Bar extends [infer T extends string] ? T : null" + if p.lexer.Token != js_lexer.TColon || (!opts.isIndexSignature && !opts.allowTupleLabels) { + p.lexer.Expect(js_lexer.TIdentifier) + if p.lexer.Token == js_lexer.TExtends { + p.lexer.Next() + p.skipTypeScriptType(js_ast.LPrefix) + } + } + break loop + case tsTypeIdentifierUnique: p.lexer.Next() diff --git a/internal/js_parser/ts_parser_test.go b/internal/js_parser/ts_parser_test.go index e1ee1305923..079f949aa7f 100644 --- a/internal/js_parser/ts_parser_test.go +++ b/internal/js_parser/ts_parser_test.go @@ -135,7 +135,7 @@ func TestTSTypes(t *testing.T) { expectPrintedTS(t, "let x: [infer: string]", "let x;\n") expectParseErrorTS(t, "let x: A extends B ? keyof : string", ": ERROR: Unexpected \":\"\n") expectParseErrorTS(t, "let x: A extends B ? readonly : string", ": ERROR: Unexpected \":\"\n") - expectParseErrorTS(t, "let x: A extends B ? infer : string", ": ERROR: Unexpected \":\"\n") + expectParseErrorTS(t, "let x: A extends B ? infer : string", ": ERROR: Expected identifier but found \":\"\n") expectParseErrorTS(t, "let x: {[new: string]: number}", ": ERROR: Expected \"(\" but found \":\"\n") expectParseErrorTS(t, "let x: {[import: string]: number}", ": ERROR: Expected \"(\" but found \":\"\n") expectParseErrorTS(t, "let x: {[typeof: string]: number}", ": ERROR: Expected identifier but found \":\"\n") @@ -177,6 +177,9 @@ func TestTSTypes(t *testing.T) { expectPrintedTS(t, "type Foo = a.b \n & c.d", "") expectPrintedTS(t, "type Foo = \n | a.b \n | c.d", "") expectPrintedTS(t, "type Foo = \n & a.b \n & c.d", "") + expectPrintedTS(t, "type Foo = Bar extends [infer T] ? T : null", "") + expectPrintedTS(t, "type Foo = Bar extends [infer T extends string] ? T : null", "") + expectPrintedTS(t, "let x: A extends B ? D : never", "let x;\n") expectPrintedTS(t, "let x: A.B", "let x;\n") expectPrintedTS(t, "let x: A.B=2", "let x = 2;\n")