From 9ce5f94a49ff37635dfc221477640dfee11e036b Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Fri, 2 Dec 2022 23:00:07 -0500 Subject: [PATCH] fix #2712: parsing bug with `infer` and `extends` --- CHANGELOG.md | 13 +++++++++++++ internal/js_parser/ts_parser.go | 13 +++++++------ internal/js_parser/ts_parser_test.go | 2 ++ 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a7e2013437..cab99938883 100644 --- a/CHANGELOG.md +++ b/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 = {}; + type B = {} extends infer T extends {} ? A : 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)) diff --git a/internal/js_parser/ts_parser.go b/internal/js_parser/ts_parser.go index 5bf089ec1a6..bf611a5eeb1 100644 --- a/internal/js_parser/ts_parser.go +++ b/internal/js_parser/ts_parser.go @@ -165,6 +165,7 @@ const ( isReturnTypeFlag skipTypeFlags = 1 << iota isIndexSignatureFlag allowTupleLabelsFlag + disallowConditionalTypesFlag ) func (flags skipTypeFlags) has(flag skipTypeFlags) bool { @@ -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 @@ -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) @@ -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 @@ -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() } diff --git a/internal/js_parser/ts_parser_test.go b/internal/js_parser/ts_parser_test.go index 07c40cc7209..10d88b6e936 100644 --- a/internal/js_parser/ts_parser_test.go +++ b/internal/js_parser/ts_parser_test.go @@ -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 : never", "") + expectPrintedTS(t, "type Foo = {} extends (infer T extends {}) ? A : never", "") expectPrintedTS(t, "let x: A extends B ? D : never", "let x;\n") expectPrintedTS(t, "let x: A extends B ? D : never", "let x;\n")