Skip to content

Commit 1a57168

Browse files
committed
fix #2201: more ts instantiation expression fixes
1 parent 980c9ae commit 1a57168

File tree

4 files changed

+107
-21
lines changed

4 files changed

+107
-21
lines changed

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
* Further fixes to TypeScript 4.7 instantiation expression parsing ([#2201](https://github.com/evanw/esbuild/issues/2201))
6+
7+
This release fixes some additional edge cases with parsing instantiation expressions from the upcoming version 4.7 of TypeScript. Previously it was allowed for an instantiation expression to precede a binary operator but with this release, that's no longer allowed. This was sometimes valid in the TypeScript 4.7 beta but is no longer allowed in the latest version of TypeScript 4.7. Fixing this also fixed a regression that was introduced by the previous release of esbuild:
8+
9+
| Code | TS 4.6.3 | TS 4.7.0 beta | TS 4.7.0 nightly | esbuild 0.14.36 | esbuild 0.14.37 | esbuild 0.14.38 |
10+
|----------------|--------------|---------------|------------------|-----------------|-----------------|-----------------|
11+
| `a<b> == c<d>` | Invalid | `a == c` | Invalid | `a == c` | `a == c` | Invalid |
12+
| `a<b> in c<d>` | Invalid | Invalid | Invalid | Invalid | `a in c` | Invalid |
13+
| `a<b>>=c<d>` | Invalid | Invalid | Invalid | Invalid | `a >= c` | Invalid |
14+
| `a<b>=c<d>` | Invalid | `a < b >= c` | `a = c` | `a < b >= c` | `a = c` | `a = c` |
15+
| `a<b>>c<d>` | `a < b >> c` | `a < b >> c` | `a < b >> c` | `a < b >> c` | `a > c` | `a < b >> c` |
16+
17+
This table illustrates some of the more significant changes between all of these parsers. The most important part is that esbuild 0.14.38 now matches the behavior of the latest TypeScript compiler for all of these cases.
18+
319
## 0.14.37
420

521
* Add support for TypeScript's `moduleSuffixes` field from TypeScript 4.7

internal/js_parser/js_parser.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2984,8 +2984,10 @@ func (p *parser) parsePrefix(level js_ast.L, errors *deferredErrors, flags exprF
29842984
})}
29852985
}
29862986

2987-
// Allow `const a = b<c>`
2988-
p.trySkipTypeScriptTypeArgumentsWithBacktracking()
2987+
// Allow "const a = b<c>"
2988+
if p.options.ts.Parse {
2989+
p.trySkipTypeScriptTypeArgumentsWithBacktracking()
2990+
}
29892991

29902992
ref := p.storeNameInRef(name)
29912993
return js_ast.Expr{Loc: loc, Data: &js_ast.EIdentifier{Ref: ref}}

internal/js_parser/ts_parser.go

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -745,11 +745,11 @@ func (p *parser) trySkipTypeScriptTypeArgumentsWithBacktracking() bool {
745745
}
746746
}()
747747

748-
p.skipTypeScriptTypeArguments(false /* isInsideJSXElement */)
749-
750-
// Check the token after this and backtrack if it's the wrong one
751-
if !p.canFollowTypeArgumentsInExpression() {
752-
p.lexer.Unexpected()
748+
if p.skipTypeScriptTypeArguments(false /* isInsideJSXElement */) {
749+
// Check the token after the type argument list and backtrack if it's invalid
750+
if !p.canFollowTypeArgumentsInExpression() {
751+
p.lexer.Unexpected()
752+
}
753753
}
754754

755755
// Restore the log disabled flag. Note that we can't just set it back to false
@@ -910,9 +910,40 @@ func (p *parser) canFollowTypeArgumentsInExpression() bool {
910910
js_lexer.TNew,
911911
js_lexer.TSlash,
912912
js_lexer.TSlashEquals,
913-
js_lexer.TIdentifier:
913+
js_lexer.TIdentifier,
914+
915+
// From "isBinaryOperator()"
916+
js_lexer.TQuestionQuestion,
917+
js_lexer.TBarBar,
918+
js_lexer.TAmpersandAmpersand,
919+
js_lexer.TBar,
920+
js_lexer.TCaret,
921+
js_lexer.TAmpersand,
922+
js_lexer.TEqualsEquals,
923+
js_lexer.TExclamationEquals,
924+
js_lexer.TEqualsEqualsEquals,
925+
js_lexer.TExclamationEqualsEquals,
926+
js_lexer.TGreaterThan,
927+
js_lexer.TLessThanEquals,
928+
js_lexer.TGreaterThanEquals,
929+
js_lexer.TInstanceof,
930+
js_lexer.TLessThanLessThan,
931+
js_lexer.TGreaterThanGreaterThan,
932+
js_lexer.TGreaterThanGreaterThanGreaterThan,
933+
js_lexer.TAsterisk,
934+
js_lexer.TPercent,
935+
js_lexer.TAsteriskAsterisk,
936+
937+
// TypeScript always sees "TGreaterThan" instead of these tokens since
938+
// their scanner works a little differently than our lexer. So since
939+
// "TGreaterThan" is forbidden above, we also forbid these too.
940+
js_lexer.TGreaterThanGreaterThanEquals,
941+
js_lexer.TGreaterThanGreaterThanGreaterThanEquals:
914942
return false
915943

944+
case js_lexer.TIn:
945+
return !p.allowIn
946+
916947
case js_lexer.TImport:
917948
return !p.nextTokenIsOpenParenOrLessThanOrDot()
918949

internal/js_parser/ts_parser_test.go

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1867,11 +1867,11 @@ func TestTSInstantiationExpression(t *testing.T) {
18671867
expectPrintedTS(t, "f['g']<number>", "f[\"g\"];\n")
18681868
expectPrintedTS(t, "(f<number>)<number>", "f;\n")
18691869

1870-
// function call
1870+
// Function call
18711871
expectPrintedTS(t, "const x1 = f<true>\n(true);", "const x1 = f(true);\n")
1872-
// relational expression
1872+
// Relational expression
18731873
expectPrintedTS(t, "const x1 = f<true>\ntrue;", "const x1 = f < true > true;\n")
1874-
// instantiation expression
1874+
// Instantiation expression
18751875
expectPrintedTS(t, "const x1 = f<true>;\n(true);", "const x1 = f;\ntrue;\n")
18761876

18771877
expectPrintedTS(t, "f<number>?.();", "f?.();\n")
@@ -1882,20 +1882,32 @@ func TestTSInstantiationExpression(t *testing.T) {
18821882
expectPrintedTS(t, "type T21 = typeof Array<string>; f();", "f();\n")
18831883
expectPrintedTS(t, "type T22 = typeof Array<string, number>; f();", "f();\n")
18841884

1885+
// This behavior matches TypeScript 4.7.0 nightly (specifically "[email protected]")
1886+
// after various fixes from Microsoft that landed after the TypeScript 4.7.0 beta
18851887
expectPrintedTS(t, "f<x>, g<y>;", "f, g;\n")
1888+
expectPrintedTS(t, "f<x>g<y>;", "f < x > g;\n")
1889+
expectPrintedTS(t, "f<x>=g<y>;", "f = g;\n")
1890+
expectPrintedTS(t, "f<x>>g<y>;", "f < x >> g;\n")
1891+
expectPrintedTS(t, "f<x>>>g<y>;", "f < x >>> g;\n")
1892+
expectParseErrorTS(t, "f<x>>=g<y>;", "<stdin>: ERROR: Invalid assignment target\n")
1893+
expectParseErrorTS(t, "f<x>>>=g<y>;", "<stdin>: ERROR: Invalid assignment target\n")
1894+
expectPrintedTS(t, "f<x> = g<y>;", "f = g;\n")
1895+
expectParseErrorTS(t, "f<x> > g<y>;", "<stdin>: ERROR: Unexpected \">\"\n")
1896+
expectParseErrorTS(t, "f<x> >> g<y>;", "<stdin>: ERROR: Unexpected \">>\"\n")
1897+
expectParseErrorTS(t, "f<x> >>> g<y>;", "<stdin>: ERROR: Unexpected \">>>\"\n")
1898+
expectParseErrorTS(t, "f<x> >= g<y>;", "<stdin>: ERROR: Unexpected \">=\"\n")
1899+
expectParseErrorTS(t, "f<x> >>= g<y>;", "<stdin>: ERROR: Unexpected \">>=\"\n")
1900+
expectParseErrorTS(t, "f<x> >>>= g<y>;", "<stdin>: ERROR: Unexpected \">>>=\"\n")
18861901
expectPrintedTS(t, "[f<x>];", "[f];\n")
18871902
expectPrintedTS(t, "f<x> ? g<y> : h<z>;", "f ? g : h;\n")
1888-
expectPrintedTS(t, "f<x> ^ g<y>;", "f ^ g;\n")
1889-
expectPrintedTS(t, "f<x> & g<y>;", "f & g;\n")
1890-
expectPrintedTS(t, "f<x> | g<y>;", "f | g;\n")
1891-
expectPrintedTS(t, "f<x> && g<y>;", "f && g;\n")
1892-
expectPrintedTS(t, "f<x> || g<y>;", "f || g;\n")
1893-
expectPrintedTS(t, "f<x> ?? g<y>;", "f ?? g;\n")
18941903
expectPrintedTS(t, "{ f<x> }", "{\n f;\n}\n")
1895-
expectPrintedTS(t, "f<x> == g<y>;", "f == g;\n")
1896-
expectPrintedTS(t, "f<x> === g<y>;", "f === g;\n")
1897-
expectPrintedTS(t, "f<x> != g<y>;", "f != g;\n")
1898-
expectPrintedTS(t, "f<x> !== g<y>;", "f !== g;\n")
1904+
expectPrintedTS(t, "f<x> + g<y>;", "f < x > +g;\n")
1905+
expectPrintedTS(t, "f<x> - g<y>;", "f < x > -g;\n")
1906+
expectParseErrorTS(t, "f<x> * g<y>;", "<stdin>: ERROR: Unexpected \"*\"\n")
1907+
expectParseErrorTS(t, "f<x> == g<y>;", "<stdin>: ERROR: Unexpected \"==\"\n")
1908+
expectParseErrorTS(t, "f<x> ?? g<y>;", "<stdin>: ERROR: Unexpected \"??\"\n")
1909+
expectParseErrorTS(t, "f<x> in g<y>;", "<stdin>: ERROR: Unexpected \"in\"\n")
1910+
expectParseErrorTS(t, "f<x> instanceof g<y>;", "<stdin>: ERROR: Unexpected \"instanceof\"\n")
18991911

19001912
expectParseErrorTS(t, "const a8 = f<number><number>;", "<stdin>: ERROR: Unexpected \";\"\n")
19011913
expectParseErrorTS(t, "const b1 = f?.<number>;", "<stdin>: ERROR: Expected \"(\" but found \";\"\n")
@@ -1929,6 +1941,31 @@ func TestTSInstantiationExpression(t *testing.T) {
19291941
// See: https://github.com/microsoft/TypeScript/issues/48759
19301942
expectParseErrorTS(t, "x<true>\nimport<T>('y')", "<stdin>: ERROR: Expected \"(\" but found \"<\"\n")
19311943
expectParseErrorTS(t, "new x<true>\nimport<T>('y')", "<stdin>: ERROR: Expected \"(\" but found \"<\"\n")
1944+
1945+
// See: https://github.com/evanw/esbuild/issues/2201
1946+
expectParseErrorTS(t, "return Array < ;", "<stdin>: ERROR: Unexpected \";\"\n")
1947+
expectParseErrorTS(t, "return Array < > ;", "<stdin>: ERROR: Unexpected \">\"\n")
1948+
expectParseErrorTS(t, "return Array < , > ;", "<stdin>: ERROR: Unexpected \",\"\n")
1949+
expectPrintedTS(t, "return Array < number > ;", "return Array;\n")
1950+
expectPrintedTS(t, "return Array < number > 1;", "return Array < number > 1;\n")
1951+
expectPrintedTS(t, "return Array < number > +1;", "return Array < number > 1;\n")
1952+
expectPrintedTS(t, "return Array < number > (1);", "return Array(1);\n")
1953+
expectPrintedTS(t, "return Array < number >> 1;", "return Array < number >> 1;\n")
1954+
expectPrintedTS(t, "return Array < number >>> 1;", "return Array < number >>> 1;\n")
1955+
expectPrintedTS(t, "return Array < Array < number >> ;", "return Array;\n")
1956+
expectPrintedTS(t, "return Array < Array < number > > ;", "return Array;\n")
1957+
expectParseErrorTS(t, "return Array < Array < number > > 1;", "<stdin>: ERROR: Unexpected \">\"\n")
1958+
expectPrintedTS(t, "return Array < Array < number >> 1;", "return Array < Array < number >> 1;\n")
1959+
expectParseErrorTS(t, "return Array < Array < number > > +1;", "<stdin>: ERROR: Unexpected \">\"\n")
1960+
expectPrintedTS(t, "return Array < Array < number >> +1;", "return Array < Array < number >> 1;\n")
1961+
expectPrintedTS(t, "return Array < Array < number >> (1);", "return Array(1);\n")
1962+
expectPrintedTS(t, "return Array < Array < number > > (1);", "return Array(1);\n")
1963+
expectParseErrorTS(t, "return Array < number > in x;", "<stdin>: ERROR: Unexpected \"in\"\n")
1964+
expectParseErrorTS(t, "return Array < Array < number >> in x;", "<stdin>: ERROR: Unexpected \"in\"\n")
1965+
expectParseErrorTS(t, "return Array < Array < number > > in x;", "<stdin>: ERROR: Unexpected \">\"\n")
1966+
expectPrintedTS(t, "for (var x = Array < number > in y) ;", "x = Array;\nfor (var x in y)\n ;\n")
1967+
expectPrintedTS(t, "for (var x = Array < Array < number >> in y) ;", "x = Array;\nfor (var x in y)\n ;\n")
1968+
expectPrintedTS(t, "for (var x = Array < Array < number > > in y) ;", "x = Array;\nfor (var x in y)\n ;\n")
19321969
}
19331970

19341971
func TestTSExponentiation(t *testing.T) {

0 commit comments

Comments
 (0)