Skip to content

Commit

Permalink
fix #2712: parsing bug with infer and extends
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Dec 3, 2022
1 parent 92dc995 commit 9ce5f94
Show file tree
Hide file tree
Showing 3 changed files with 22 additions and 6 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,18 @@
# Changelog

## Unreleased

* Fix parsing bug with TypeScript `infer` and `extends` ([#2712](https://github.com/evanw/esbuild/issues/2712))

This release fixes a bug where esbuild incorrectly failed to parse valid TypeScript code that nests `extends` inside `infer` inside `extends`, such as in the example below:

```ts
type A<T> = {};
type B = {} extends infer T extends {} ? A<T> : never;
```

TypeScript code that does this should now be parsed correctly.

## 0.15.16

* Add a package alias feature ([#2191](https://github.com/evanw/esbuild/issues/2191))
Expand Down
13 changes: 7 additions & 6 deletions internal/js_parser/ts_parser.go
Expand Up @@ -165,6 +165,7 @@ const (
isReturnTypeFlag skipTypeFlags = 1 << iota
isIndexSignatureFlag
allowTupleLabelsFlag
disallowConditionalTypesFlag
)

func (flags skipTypeFlags) has(flag skipTypeFlags) bool {
Expand Down Expand Up @@ -326,7 +327,7 @@ loop:
if p.lexer.Token != js_lexer.TColon || (!flags.has(isIndexSignatureFlag) && !flags.has(allowTupleLabelsFlag)) {
p.lexer.Expect(js_lexer.TIdentifier)
if p.lexer.Token == js_lexer.TExtends {
p.trySkipTypeScriptConstraintOfInferTypeWithBacktracking()
p.trySkipTypeScriptConstraintOfInferTypeWithBacktracking(flags)
}
}
break loop
Expand Down Expand Up @@ -518,13 +519,13 @@ loop:

case js_lexer.TExtends:
// "{ x: number \n extends: boolean }" must not become a single type
if p.lexer.HasNewlineBefore || level >= js_ast.LConditional {
if p.lexer.HasNewlineBefore || flags.has(disallowConditionalTypesFlag) {
return
}
p.lexer.Next()

// The type following "extends" is not permitted to be another conditional type
p.skipTypeScriptType(js_ast.LConditional)
p.skipTypeScriptTypeWithFlags(js_ast.LLowest, disallowConditionalTypesFlag)
p.lexer.Expect(js_lexer.TQuestion)
p.skipTypeScriptType(js_ast.LLowest)
p.lexer.Expect(js_lexer.TColon)
Expand Down Expand Up @@ -859,7 +860,7 @@ func (p *parser) trySkipTypeScriptArrowArgsWithBacktracking() bool {
return true
}

func (p *parser) trySkipTypeScriptConstraintOfInferTypeWithBacktracking() bool {
func (p *parser) trySkipTypeScriptConstraintOfInferTypeWithBacktracking(flags skipTypeFlags) bool {
oldLexer := p.lexer
p.lexer.IsLogDisabled = true

Expand All @@ -874,8 +875,8 @@ func (p *parser) trySkipTypeScriptConstraintOfInferTypeWithBacktracking() bool {
}()

p.lexer.Expect(js_lexer.TExtends)
p.skipTypeScriptType(js_ast.LPrefix)
if p.lexer.Token == js_lexer.TQuestion {
p.skipTypeScriptTypeWithFlags(js_ast.LPrefix, disallowConditionalTypesFlag)
if !flags.has(disallowConditionalTypesFlag) && p.lexer.Token == js_lexer.TQuestion {
p.lexer.Unexpected()
}

Expand Down
2 changes: 2 additions & 0 deletions internal/js_parser/ts_parser_test.go
Expand Up @@ -179,6 +179,8 @@ func TestTSTypes(t *testing.T) {
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, "type Foo = {} extends infer T extends {} ? A<T> : never", "")
expectPrintedTS(t, "type Foo = {} extends (infer T extends {}) ? A<T> : never", "")
expectPrintedTS(t, "let x: A extends B<infer C extends D> ? D : never", "let x;\n")
expectPrintedTS(t, "let x: A extends B<infer C extends D ? infer C : never> ? D : never", "let x;\n")

Expand Down

0 comments on commit 9ce5f94

Please sign in to comment.