From 1a5716883079f944de96ab0cd479d3c8a3980c29 Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Thu, 21 Apr 2022 17:54:28 -0400 Subject: [PATCH] fix #2201: more ts instantiation expression fixes --- CHANGELOG.md | 16 +++++++ internal/js_parser/js_parser.go | 6 ++- internal/js_parser/ts_parser.go | 43 ++++++++++++++++--- internal/js_parser/ts_parser_test.go | 63 ++++++++++++++++++++++------ 4 files changed, 107 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1663771372f..54a58771b3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 == c` | Invalid | `a == c` | Invalid | `a == c` | `a == c` | Invalid | + | `a in c` | Invalid | Invalid | Invalid | Invalid | `a in c` | Invalid | + | `a>=c` | Invalid | Invalid | Invalid | Invalid | `a >= c` | Invalid | + | `a=c` | Invalid | `a < b >= c` | `a = c` | `a < b >= c` | `a = c` | `a = c` | + | `a>c` | `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 diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index 9454941490d..722488f1b50 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -2984,8 +2984,10 @@ func (p *parser) parsePrefix(level js_ast.L, errors *deferredErrors, flags exprF })} } - // Allow `const a = b` - p.trySkipTypeScriptTypeArgumentsWithBacktracking() + // Allow "const a = b" + if p.options.ts.Parse { + p.trySkipTypeScriptTypeArgumentsWithBacktracking() + } ref := p.storeNameInRef(name) return js_ast.Expr{Loc: loc, Data: &js_ast.EIdentifier{Ref: ref}} diff --git a/internal/js_parser/ts_parser.go b/internal/js_parser/ts_parser.go index 00904f4897a..fb7db819d11 100644 --- a/internal/js_parser/ts_parser.go +++ b/internal/js_parser/ts_parser.go @@ -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 @@ -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() diff --git a/internal/js_parser/ts_parser_test.go b/internal/js_parser/ts_parser_test.go index 0c27c726e13..c3212c3d321 100644 --- a/internal/js_parser/ts_parser_test.go +++ b/internal/js_parser/ts_parser_test.go @@ -1867,11 +1867,11 @@ func TestTSInstantiationExpression(t *testing.T) { expectPrintedTS(t, "f['g']", "f[\"g\"];\n") expectPrintedTS(t, "(f)", "f;\n") - // function call + // Function call expectPrintedTS(t, "const x1 = f\n(true);", "const x1 = f(true);\n") - // relational expression + // Relational expression expectPrintedTS(t, "const x1 = f\ntrue;", "const x1 = f < true > true;\n") - // instantiation expression + // Instantiation expression expectPrintedTS(t, "const x1 = f;\n(true);", "const x1 = f;\ntrue;\n") expectPrintedTS(t, "f?.();", "f?.();\n") @@ -1882,20 +1882,32 @@ func TestTSInstantiationExpression(t *testing.T) { expectPrintedTS(t, "type T21 = typeof Array; f();", "f();\n") expectPrintedTS(t, "type T22 = typeof Array; 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, g;", "f, g;\n") + expectPrintedTS(t, "fg;", "f < x > g;\n") + expectPrintedTS(t, "f=g;", "f = g;\n") + expectPrintedTS(t, "f>g;", "f < x >> g;\n") + expectPrintedTS(t, "f>>g;", "f < x >>> g;\n") + expectParseErrorTS(t, "f>=g;", ": ERROR: Invalid assignment target\n") + expectParseErrorTS(t, "f>>=g;", ": ERROR: Invalid assignment target\n") + expectPrintedTS(t, "f = g;", "f = g;\n") + expectParseErrorTS(t, "f > g;", ": ERROR: Unexpected \">\"\n") + expectParseErrorTS(t, "f >> g;", ": ERROR: Unexpected \">>\"\n") + expectParseErrorTS(t, "f >>> g;", ": ERROR: Unexpected \">>>\"\n") + expectParseErrorTS(t, "f >= g;", ": ERROR: Unexpected \">=\"\n") + expectParseErrorTS(t, "f >>= g;", ": ERROR: Unexpected \">>=\"\n") + expectParseErrorTS(t, "f >>>= g;", ": ERROR: Unexpected \">>>=\"\n") expectPrintedTS(t, "[f];", "[f];\n") expectPrintedTS(t, "f ? g : h;", "f ? g : h;\n") - expectPrintedTS(t, "f ^ g;", "f ^ g;\n") - expectPrintedTS(t, "f & g;", "f & g;\n") - expectPrintedTS(t, "f | g;", "f | g;\n") - expectPrintedTS(t, "f && g;", "f && g;\n") - expectPrintedTS(t, "f || g;", "f || g;\n") - expectPrintedTS(t, "f ?? g;", "f ?? g;\n") expectPrintedTS(t, "{ f }", "{\n f;\n}\n") - expectPrintedTS(t, "f == g;", "f == g;\n") - expectPrintedTS(t, "f === g;", "f === g;\n") - expectPrintedTS(t, "f != g;", "f != g;\n") - expectPrintedTS(t, "f !== g;", "f !== g;\n") + expectPrintedTS(t, "f + g;", "f < x > +g;\n") + expectPrintedTS(t, "f - g;", "f < x > -g;\n") + expectParseErrorTS(t, "f * g;", ": ERROR: Unexpected \"*\"\n") + expectParseErrorTS(t, "f == g;", ": ERROR: Unexpected \"==\"\n") + expectParseErrorTS(t, "f ?? g;", ": ERROR: Unexpected \"??\"\n") + expectParseErrorTS(t, "f in g;", ": ERROR: Unexpected \"in\"\n") + expectParseErrorTS(t, "f instanceof g;", ": ERROR: Unexpected \"instanceof\"\n") expectParseErrorTS(t, "const a8 = f;", ": ERROR: Unexpected \";\"\n") expectParseErrorTS(t, "const b1 = f?.;", ": ERROR: Expected \"(\" but found \";\"\n") @@ -1929,6 +1941,31 @@ func TestTSInstantiationExpression(t *testing.T) { // See: https://github.com/microsoft/TypeScript/issues/48759 expectParseErrorTS(t, "x\nimport('y')", ": ERROR: Expected \"(\" but found \"<\"\n") expectParseErrorTS(t, "new x\nimport('y')", ": ERROR: Expected \"(\" but found \"<\"\n") + + // See: https://github.com/evanw/esbuild/issues/2201 + expectParseErrorTS(t, "return Array < ;", ": ERROR: Unexpected \";\"\n") + expectParseErrorTS(t, "return Array < > ;", ": ERROR: Unexpected \">\"\n") + expectParseErrorTS(t, "return Array < , > ;", ": 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;", ": ERROR: Unexpected \">\"\n") + expectPrintedTS(t, "return Array < Array < number >> 1;", "return Array < Array < number >> 1;\n") + expectParseErrorTS(t, "return Array < Array < number > > +1;", ": 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;", ": ERROR: Unexpected \"in\"\n") + expectParseErrorTS(t, "return Array < Array < number >> in x;", ": ERROR: Unexpected \"in\"\n") + expectParseErrorTS(t, "return Array < Array < number > > in x;", ": 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) {