Skip to content

Commit

Permalink
fix #2201: more ts instantiation expression fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Apr 21, 2022
1 parent 980c9ae commit 1a57168
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 21 deletions.
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Changelog

## Unreleased

* Further fixes to TypeScript 4.7 instantiation expression parsing ([#2201](https://github.com/evanw/esbuild/issues/2201))

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:

| 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 |
|----------------|--------------|---------------|------------------|-----------------|-----------------|-----------------|
| `a<b> == c<d>` | Invalid | `a == c` | Invalid | `a == c` | `a == c` | Invalid |
| `a<b> in c<d>` | Invalid | Invalid | Invalid | Invalid | `a in c` | Invalid |
| `a<b>>=c<d>` | Invalid | Invalid | Invalid | Invalid | `a >= c` | Invalid |
| `a<b>=c<d>` | Invalid | `a < b >= c` | `a = c` | `a < b >= c` | `a = c` | `a = c` |
| `a<b>>c<d>` | `a < b >> c` | `a < b >> c` | `a < b >> c` | `a < b >> c` | `a > c` | `a < b >> c` |

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.

## 0.14.37

* Add support for TypeScript's `moduleSuffixes` field from TypeScript 4.7
Expand Down
6 changes: 4 additions & 2 deletions internal/js_parser/js_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -2984,8 +2984,10 @@ func (p *parser) parsePrefix(level js_ast.L, errors *deferredErrors, flags exprF
})}
}

// Allow `const a = b<c>`
p.trySkipTypeScriptTypeArgumentsWithBacktracking()
// Allow "const a = b<c>"
if p.options.ts.Parse {
p.trySkipTypeScriptTypeArgumentsWithBacktracking()
}

ref := p.storeNameInRef(name)
return js_ast.Expr{Loc: loc, Data: &js_ast.EIdentifier{Ref: ref}}
Expand Down
43 changes: 37 additions & 6 deletions internal/js_parser/ts_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -745,11 +745,11 @@ func (p *parser) trySkipTypeScriptTypeArgumentsWithBacktracking() bool {
}
}()

p.skipTypeScriptTypeArguments(false /* isInsideJSXElement */)

// Check the token after this and backtrack if it's the wrong one
if !p.canFollowTypeArgumentsInExpression() {
p.lexer.Unexpected()
if p.skipTypeScriptTypeArguments(false /* isInsideJSXElement */) {
// Check the token after the type argument list and backtrack if it's invalid
if !p.canFollowTypeArgumentsInExpression() {
p.lexer.Unexpected()
}
}

// Restore the log disabled flag. Note that we can't just set it back to false
Expand Down Expand Up @@ -910,9 +910,40 @@ func (p *parser) canFollowTypeArgumentsInExpression() bool {
js_lexer.TNew,
js_lexer.TSlash,
js_lexer.TSlashEquals,
js_lexer.TIdentifier:
js_lexer.TIdentifier,

// From "isBinaryOperator()"
js_lexer.TQuestionQuestion,
js_lexer.TBarBar,
js_lexer.TAmpersandAmpersand,
js_lexer.TBar,
js_lexer.TCaret,
js_lexer.TAmpersand,
js_lexer.TEqualsEquals,
js_lexer.TExclamationEquals,
js_lexer.TEqualsEqualsEquals,
js_lexer.TExclamationEqualsEquals,
js_lexer.TGreaterThan,
js_lexer.TLessThanEquals,
js_lexer.TGreaterThanEquals,
js_lexer.TInstanceof,
js_lexer.TLessThanLessThan,
js_lexer.TGreaterThanGreaterThan,
js_lexer.TGreaterThanGreaterThanGreaterThan,
js_lexer.TAsterisk,
js_lexer.TPercent,
js_lexer.TAsteriskAsterisk,

// TypeScript always sees "TGreaterThan" instead of these tokens since
// their scanner works a little differently than our lexer. So since
// "TGreaterThan" is forbidden above, we also forbid these too.
js_lexer.TGreaterThanGreaterThanEquals,
js_lexer.TGreaterThanGreaterThanGreaterThanEquals:
return false

case js_lexer.TIn:
return !p.allowIn

case js_lexer.TImport:
return !p.nextTokenIsOpenParenOrLessThanOrDot()

Expand Down
63 changes: 50 additions & 13 deletions internal/js_parser/ts_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1867,11 +1867,11 @@ func TestTSInstantiationExpression(t *testing.T) {
expectPrintedTS(t, "f['g']<number>", "f[\"g\"];\n")
expectPrintedTS(t, "(f<number>)<number>", "f;\n")

// function call
// Function call
expectPrintedTS(t, "const x1 = f<true>\n(true);", "const x1 = f(true);\n")
// relational expression
// Relational expression
expectPrintedTS(t, "const x1 = f<true>\ntrue;", "const x1 = f < true > true;\n")
// instantiation expression
// Instantiation expression
expectPrintedTS(t, "const x1 = f<true>;\n(true);", "const x1 = f;\ntrue;\n")

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

// This behavior matches TypeScript 4.7.0 nightly (specifically "typescript@4.7.0-dev.20220421")
// after various fixes from Microsoft that landed after the TypeScript 4.7.0 beta
expectPrintedTS(t, "f<x>, g<y>;", "f, g;\n")
expectPrintedTS(t, "f<x>g<y>;", "f < x > g;\n")
expectPrintedTS(t, "f<x>=g<y>;", "f = g;\n")
expectPrintedTS(t, "f<x>>g<y>;", "f < x >> g;\n")
expectPrintedTS(t, "f<x>>>g<y>;", "f < x >>> g;\n")
expectParseErrorTS(t, "f<x>>=g<y>;", "<stdin>: ERROR: Invalid assignment target\n")
expectParseErrorTS(t, "f<x>>>=g<y>;", "<stdin>: ERROR: Invalid assignment target\n")
expectPrintedTS(t, "f<x> = g<y>;", "f = g;\n")
expectParseErrorTS(t, "f<x> > g<y>;", "<stdin>: ERROR: Unexpected \">\"\n")
expectParseErrorTS(t, "f<x> >> g<y>;", "<stdin>: ERROR: Unexpected \">>\"\n")
expectParseErrorTS(t, "f<x> >>> g<y>;", "<stdin>: ERROR: Unexpected \">>>\"\n")
expectParseErrorTS(t, "f<x> >= g<y>;", "<stdin>: ERROR: Unexpected \">=\"\n")
expectParseErrorTS(t, "f<x> >>= g<y>;", "<stdin>: ERROR: Unexpected \">>=\"\n")
expectParseErrorTS(t, "f<x> >>>= g<y>;", "<stdin>: ERROR: Unexpected \">>>=\"\n")
expectPrintedTS(t, "[f<x>];", "[f];\n")
expectPrintedTS(t, "f<x> ? g<y> : h<z>;", "f ? g : h;\n")
expectPrintedTS(t, "f<x> ^ g<y>;", "f ^ g;\n")
expectPrintedTS(t, "f<x> & g<y>;", "f & g;\n")
expectPrintedTS(t, "f<x> | g<y>;", "f | g;\n")
expectPrintedTS(t, "f<x> && g<y>;", "f && g;\n")
expectPrintedTS(t, "f<x> || g<y>;", "f || g;\n")
expectPrintedTS(t, "f<x> ?? g<y>;", "f ?? g;\n")
expectPrintedTS(t, "{ f<x> }", "{\n f;\n}\n")
expectPrintedTS(t, "f<x> == g<y>;", "f == g;\n")
expectPrintedTS(t, "f<x> === g<y>;", "f === g;\n")
expectPrintedTS(t, "f<x> != g<y>;", "f != g;\n")
expectPrintedTS(t, "f<x> !== g<y>;", "f !== g;\n")
expectPrintedTS(t, "f<x> + g<y>;", "f < x > +g;\n")
expectPrintedTS(t, "f<x> - g<y>;", "f < x > -g;\n")
expectParseErrorTS(t, "f<x> * g<y>;", "<stdin>: ERROR: Unexpected \"*\"\n")
expectParseErrorTS(t, "f<x> == g<y>;", "<stdin>: ERROR: Unexpected \"==\"\n")
expectParseErrorTS(t, "f<x> ?? g<y>;", "<stdin>: ERROR: Unexpected \"??\"\n")
expectParseErrorTS(t, "f<x> in g<y>;", "<stdin>: ERROR: Unexpected \"in\"\n")
expectParseErrorTS(t, "f<x> instanceof g<y>;", "<stdin>: ERROR: Unexpected \"instanceof\"\n")

expectParseErrorTS(t, "const a8 = f<number><number>;", "<stdin>: ERROR: Unexpected \";\"\n")
expectParseErrorTS(t, "const b1 = f?.<number>;", "<stdin>: ERROR: Expected \"(\" but found \";\"\n")
Expand Down Expand Up @@ -1929,6 +1941,31 @@ func TestTSInstantiationExpression(t *testing.T) {
// See: https://github.com/microsoft/TypeScript/issues/48759
expectParseErrorTS(t, "x<true>\nimport<T>('y')", "<stdin>: ERROR: Expected \"(\" but found \"<\"\n")
expectParseErrorTS(t, "new x<true>\nimport<T>('y')", "<stdin>: ERROR: Expected \"(\" but found \"<\"\n")

// See: https://github.com/evanw/esbuild/issues/2201
expectParseErrorTS(t, "return Array < ;", "<stdin>: ERROR: Unexpected \";\"\n")
expectParseErrorTS(t, "return Array < > ;", "<stdin>: ERROR: Unexpected \">\"\n")
expectParseErrorTS(t, "return Array < , > ;", "<stdin>: ERROR: Unexpected \",\"\n")
expectPrintedTS(t, "return Array < number > ;", "return Array;\n")
expectPrintedTS(t, "return Array < number > 1;", "return Array < number > 1;\n")
expectPrintedTS(t, "return Array < number > +1;", "return Array < number > 1;\n")
expectPrintedTS(t, "return Array < number > (1);", "return Array(1);\n")
expectPrintedTS(t, "return Array < number >> 1;", "return Array < number >> 1;\n")
expectPrintedTS(t, "return Array < number >>> 1;", "return Array < number >>> 1;\n")
expectPrintedTS(t, "return Array < Array < number >> ;", "return Array;\n")
expectPrintedTS(t, "return Array < Array < number > > ;", "return Array;\n")
expectParseErrorTS(t, "return Array < Array < number > > 1;", "<stdin>: ERROR: Unexpected \">\"\n")
expectPrintedTS(t, "return Array < Array < number >> 1;", "return Array < Array < number >> 1;\n")
expectParseErrorTS(t, "return Array < Array < number > > +1;", "<stdin>: ERROR: Unexpected \">\"\n")
expectPrintedTS(t, "return Array < Array < number >> +1;", "return Array < Array < number >> 1;\n")
expectPrintedTS(t, "return Array < Array < number >> (1);", "return Array(1);\n")
expectPrintedTS(t, "return Array < Array < number > > (1);", "return Array(1);\n")
expectParseErrorTS(t, "return Array < number > in x;", "<stdin>: ERROR: Unexpected \"in\"\n")
expectParseErrorTS(t, "return Array < Array < number >> in x;", "<stdin>: ERROR: Unexpected \"in\"\n")
expectParseErrorTS(t, "return Array < Array < number > > in x;", "<stdin>: ERROR: Unexpected \">\"\n")
expectPrintedTS(t, "for (var x = Array < number > in y) ;", "x = Array;\nfor (var x in y)\n ;\n")
expectPrintedTS(t, "for (var x = Array < Array < number >> in y) ;", "x = Array;\nfor (var x in y)\n ;\n")
expectPrintedTS(t, "for (var x = Array < Array < number > > in y) ;", "x = Array;\nfor (var x in y)\n ;\n")
}

func TestTSExponentiation(t *testing.T) {
Expand Down

0 comments on commit 1a57168

Please sign in to comment.