Skip to content

Commit 7034fca

Browse files
committed
fix #2330: implement extends after infer in ts
1 parent 465ae2a commit 7034fca

File tree

3 files changed

+34
-2
lines changed

3 files changed

+34
-2
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,19 @@
8282

8383
_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._
8484

85+
* Implement `extends` constraints on `infer` type variables ([#2330](https://github.com/evanw/esbuild/issues/2330))
86+
87+
TypeScript 4.7 introduced the ability to write an `extends` constraint after an `infer` type variable, which looks like this:
88+
89+
```ts
90+
type FirstIfString<T> =
91+
T extends [infer S extends string, ...unknown[]]
92+
? S
93+
: never;
94+
```
95+
96+
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.
97+
8598
* Allow `define` to match optional chain expressions ([#2324](https://github.com/evanw/esbuild/issues/2324))
8699

87100
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:

internal/js_parser/ts_parser.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ const (
175175
tsTypeIdentifierAsserts
176176
tsTypeIdentifierPrefix
177177
tsTypeIdentifierPrimitive
178+
tsTypeIdentifierInfer
178179
)
179180

180181
// Use a map to improve lookup speed
@@ -185,7 +186,6 @@ var tsTypeIdentifierMap = map[string]tsTypeIdentifierKind{
185186

186187
"keyof": tsTypeIdentifierPrefix,
187188
"readonly": tsTypeIdentifierPrefix,
188-
"infer": tsTypeIdentifierPrefix,
189189

190190
"any": tsTypeIdentifierPrimitive,
191191
"never": tsTypeIdentifierPrimitive,
@@ -197,6 +197,8 @@ var tsTypeIdentifierMap = map[string]tsTypeIdentifierKind{
197197
"boolean": tsTypeIdentifierPrimitive,
198198
"bigint": tsTypeIdentifierPrimitive,
199199
"symbol": tsTypeIdentifierPrimitive,
200+
201+
"infer": tsTypeIdentifierInfer,
200202
}
201203

202204
func (p *parser) skipTypeScriptTypeWithOpts(level js_ast.L, opts skipTypeOpts) {
@@ -310,6 +312,20 @@ loop:
310312
}
311313
break loop
312314

315+
case tsTypeIdentifierInfer:
316+
p.lexer.Next()
317+
318+
// "type Foo = Bar extends [infer T] ? T : null"
319+
// "type Foo = Bar extends [infer T extends string] ? T : null"
320+
if p.lexer.Token != js_lexer.TColon || (!opts.isIndexSignature && !opts.allowTupleLabels) {
321+
p.lexer.Expect(js_lexer.TIdentifier)
322+
if p.lexer.Token == js_lexer.TExtends {
323+
p.lexer.Next()
324+
p.skipTypeScriptType(js_ast.LPrefix)
325+
}
326+
}
327+
break loop
328+
313329
case tsTypeIdentifierUnique:
314330
p.lexer.Next()
315331

internal/js_parser/ts_parser_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ func TestTSTypes(t *testing.T) {
135135
expectPrintedTS(t, "let x: [infer: string]", "let x;\n")
136136
expectParseErrorTS(t, "let x: A extends B ? keyof : string", "<stdin>: ERROR: Unexpected \":\"\n")
137137
expectParseErrorTS(t, "let x: A extends B ? readonly : string", "<stdin>: ERROR: Unexpected \":\"\n")
138-
expectParseErrorTS(t, "let x: A extends B ? infer : string", "<stdin>: ERROR: Unexpected \":\"\n")
138+
expectParseErrorTS(t, "let x: A extends B ? infer : string", "<stdin>: ERROR: Expected identifier but found \":\"\n")
139139
expectParseErrorTS(t, "let x: {[new: string]: number}", "<stdin>: ERROR: Expected \"(\" but found \":\"\n")
140140
expectParseErrorTS(t, "let x: {[import: string]: number}", "<stdin>: ERROR: Expected \"(\" but found \":\"\n")
141141
expectParseErrorTS(t, "let x: {[typeof: string]: number}", "<stdin>: ERROR: Expected identifier but found \":\"\n")
@@ -177,6 +177,9 @@ func TestTSTypes(t *testing.T) {
177177
expectPrintedTS(t, "type Foo = a.b \n & c.d", "")
178178
expectPrintedTS(t, "type Foo = \n | a.b \n | c.d", "")
179179
expectPrintedTS(t, "type Foo = \n & a.b \n & c.d", "")
180+
expectPrintedTS(t, "type Foo = Bar extends [infer T] ? T : null", "")
181+
expectPrintedTS(t, "type Foo = Bar extends [infer T extends string] ? T : null", "")
182+
expectPrintedTS(t, "let x: A extends B<infer C extends D> ? D : never", "let x;\n")
180183

181184
expectPrintedTS(t, "let x: A.B<X.Y>", "let x;\n")
182185
expectPrintedTS(t, "let x: A.B<X.Y>=2", "let x = 2;\n")

0 commit comments

Comments
 (0)