From 1d71dff0ad17522bf9f5fc3e142d41b40c57e525 Mon Sep 17 00:00:00 2001 From: Pig Fang Date: Tue, 19 Apr 2022 12:04:55 +0800 Subject: [PATCH] Make TS InstantiationExpr parsing more permissive (#2188) --- CHANGELOG.md | 17 +++++-- internal/js_parser/ts_parser.go | 74 ++++++++++++++++++---------- internal/js_parser/ts_parser_test.go | 16 ++++++ 3 files changed, 78 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b78d3817ae..b55e136afd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,22 @@ The upcoming version of TypeScript adds the `moduleSuffixes` field to `tsconfig.json` that introduces more rules to import path resolution. Setting `moduleSuffixes` to `[".ios", ".native", ""]` will try to look at the the relative files `./foo.ios.ts`, `./foo.native.ts`, and finally `./foo.ts` for an import path of `./foo`. Note that the empty string `""` in `moduleSuffixes` is necessary for TypeScript to also look-up `./foo.ts`. This was announced in the [TypeScript 4.7 beta blog post](https://devblogs.microsoft.com/typescript/announcing-typescript-4-7-beta/#resolution-customization-with-modulesuffixes). -* Match the new ASI behavior from TypeScript nightly builds +* Match the new ASI behavior from TypeScript nightly builds ([#2188](https://github.com/evanw/esbuild/pull/2188)) - This release updates esbuild to match some very recent behavior changes in the TypeScript parser regarding automatic semicolon insertion. For more information, see the following issues: + This release updates esbuild to match some very recent behavior changes in the TypeScript parser regarding automatic semicolon insertion. For more information, see TypeScript issues #48711 and #48654 (I'm not linking to them directly to avoid Dependabot linkback spam on these issues due to esbuild's popularity). The result is that the following TypeScript code is now considered valid TypeScript syntax: - * https://github.com/microsoft/TypeScript/issues/48711 + ```ts + class A {} + new A /* ASI now happens here */ + if (0) {} + + interface B { + (a: number): typeof a /* ASI now happens here */ + (): void + } + ``` + + This fix was contributed by [@g-plane](https://github.com/g-plane). ## 0.14.36 diff --git a/internal/js_parser/ts_parser.go b/internal/js_parser/ts_parser.go index d27b67a5d1f..00904f4897a 100644 --- a/internal/js_parser/ts_parser.go +++ b/internal/js_parser/ts_parser.go @@ -856,6 +856,19 @@ func (p *parser) isTSArrowFnJSX() (isTSArrowFn bool) { return } +func (p *parser) nextTokenIsOpenParenOrLessThanOrDot() (result bool) { + oldLexer := p.lexer + p.lexer.Next() + + result = p.lexer.Token == js_lexer.TOpenParen || + p.lexer.Token == js_lexer.TLessThan || + p.lexer.Token == js_lexer.TDot + + // Restore the lexer + p.lexer = oldLexer + return +} + // This function is taken from the official TypeScript compiler source code: // https://github.com/microsoft/TypeScript/blob/master/src/compiler/parser.ts func (p *parser) canFollowTypeArgumentsInExpression() bool { @@ -867,35 +880,44 @@ func (p *parser) canFollowTypeArgumentsInExpression() bool { js_lexer.TTemplateHead: // foo `...${100}...` return true + // Consider something a type argument list only if the following token can't start an expression. case - // These tokens can't follow in a call expression, nor can they start an - // expression. So, consider the type argument list part of an instantiation - // expression. - js_lexer.TComma, // foo, - js_lexer.TDot, // foo. - js_lexer.TQuestionDot, // foo?. - js_lexer.TCloseParen, // foo) - js_lexer.TCloseBracket, // foo] - js_lexer.TColon, // foo: - js_lexer.TSemicolon, // foo; - js_lexer.TQuestion, // foo? - js_lexer.TEqualsEquals, // foo == - js_lexer.TEqualsEqualsEquals, // foo === - js_lexer.TExclamationEquals, // foo != - js_lexer.TExclamationEqualsEquals, // foo !== - js_lexer.TAmpersandAmpersand, // foo && - js_lexer.TBarBar, // foo || - js_lexer.TQuestionQuestion, // foo ?? - js_lexer.TCaret, // foo ^ - js_lexer.TAmpersand, // foo & - js_lexer.TBar, // foo | - js_lexer.TCloseBrace, // foo } - js_lexer.TEndOfFile: // foo - return true + // From "isStartOfExpression()" + js_lexer.TPlus, + js_lexer.TMinus, + js_lexer.TTilde, + js_lexer.TExclamation, + js_lexer.TDelete, + js_lexer.TTypeof, + js_lexer.TVoid, + js_lexer.TPlusPlus, + js_lexer.TMinusMinus, + js_lexer.TLessThan, + + // From "isStartOfLeftHandSideExpression()" + js_lexer.TThis, + js_lexer.TSuper, + js_lexer.TNull, + js_lexer.TTrue, + js_lexer.TFalse, + js_lexer.TNumericLiteral, + js_lexer.TBigIntegerLiteral, + js_lexer.TStringLiteral, + js_lexer.TOpenBracket, + js_lexer.TOpenBrace, + js_lexer.TFunction, + js_lexer.TClass, + js_lexer.TNew, + js_lexer.TSlash, + js_lexer.TSlashEquals, + js_lexer.TIdentifier: + return false + + case js_lexer.TImport: + return !p.nextTokenIsOpenParenOrLessThanOrDot() default: - // Anything else treat as an expression. - return false + return true } } diff --git a/internal/js_parser/ts_parser_test.go b/internal/js_parser/ts_parser_test.go index 5887d8139bf..0c27c726e13 100644 --- a/internal/js_parser/ts_parser_test.go +++ b/internal/js_parser/ts_parser_test.go @@ -1913,6 +1913,22 @@ func TestTSInstantiationExpression(t *testing.T) { expectParseErrorTSX(t, "type x = y\n\nz", ": ERROR: Unexpected end of file\n") expectParseErrorTS(t, "type x = typeof y\n\nz\n", ": ERROR: Unterminated regular expression\n") expectParseErrorTSX(t, "type x = typeof y\n\nz", ": ERROR: Unexpected end of file\n") + + // See: https://github.com/microsoft/TypeScript/issues/48654 + expectPrintedTS(t, "x\ny", "x < true > y;\n") + expectPrintedTS(t, "x\nif (y) {}", "x;\nif (y) {\n}\n") + expectPrintedTS(t, "x\nimport 'y'", "x;\nimport \"y\";\n") + expectPrintedTS(t, "x\nimport('y')", "x < true > import(\"y\");\n") + expectPrintedTS(t, "x\nimport.meta", "x < true > import.meta;\n") + expectPrintedTS(t, "new x\ny", "new x() < number > y;\n") + expectPrintedTS(t, "new x\nif (y) {}", "new x();\nif (y) {\n}\n") + expectPrintedTS(t, "new x\nimport 'y'", "new x();\nimport \"y\";\n") + expectPrintedTS(t, "new x\nimport('y')", "new x() < true > import(\"y\");\n") + expectPrintedTS(t, "new x\nimport.meta", "new x() < true > import.meta;\n") + + // 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") } func TestTSExponentiation(t *testing.T) {