From 3a01bcb5c05f9ca33e8bb51882d9084941603f20 Mon Sep 17 00:00:00 2001 From: magic-akari Date: Mon, 14 Mar 2022 23:46:38 +0800 Subject: [PATCH 1/7] add support of TS `Optional variance annotations` --- internal/js_parser/ts_parser.go | 20 +++++++++++++++++++- internal/js_parser/ts_parser_test.go | 11 +++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/internal/js_parser/ts_parser.go b/internal/js_parser/ts_parser.go index ca0bcdb6305..1d4276c251f 100644 --- a/internal/js_parser/ts_parser.go +++ b/internal/js_parser/ts_parser.go @@ -175,6 +175,7 @@ const ( tsTypeIdentifierAsserts tsTypeIdentifierPrefix tsTypeIdentifierPrimitive + tsTypeIdentifierOut ) // Use a map to improve lookup speed @@ -182,6 +183,7 @@ var tsTypeIdentifierMap = map[string]tsTypeIdentifierKind{ "unique": tsTypeIdentifierUnique, "abstract": tsTypeIdentifierAbstract, "asserts": tsTypeIdentifierAsserts, + "out": tsTypeIdentifierOut, "keyof": tsTypeIdentifierPrefix, "readonly": tsTypeIdentifierPrefix, @@ -610,7 +612,23 @@ func (p *parser) skipTypeScriptTypeParameters() { p.lexer.Next() for { - p.lexer.Expect(js_lexer.TIdentifier) + // type X = out; + inOutMaybeIdentifier := false + + if p.lexer.Token == js_lexer.TIn { + p.lexer.Next() + inOutMaybeIdentifier = true + } + + kind := tsTypeIdentifierMap[p.lexer.Identifier.String] + if kind == tsTypeIdentifierOut { + p.lexer.Next() + inOutMaybeIdentifier = true + } + + if p.lexer.Token == js_lexer.TIdentifier || !inOutMaybeIdentifier { + p.lexer.Expect(js_lexer.TIdentifier) + } // "class Foo {}" if p.lexer.Token == js_lexer.TExtends { diff --git a/internal/js_parser/ts_parser_test.go b/internal/js_parser/ts_parser_test.go index c19dce1b0f2..193c59e49d7 100644 --- a/internal/js_parser/ts_parser_test.go +++ b/internal/js_parser/ts_parser_test.go @@ -158,6 +158,17 @@ func TestTSTypes(t *testing.T) { expectPrintedTS(t, "type Foo = {readonly [P in keyof T]: T[P]}", "") expectPrintedTS(t, "type Foo = {-readonly [P in keyof T]: T[P]}", "") expectPrintedTS(t, "type Foo = {+readonly [P in keyof T]: T[P]}", "") + expectPrintedTS(t, "type Foo = T;", "") + expectPrintedTS(t, "type Foo = keyof T;", "") + expectPrintedTS(t, "type Foo = T[K];", "") + expectPrintedTS(t, "type Foo = T[keyof T];", "") + expectPrintedTS(t, "type Foo = keyof T;", "") + expectPrintedTS(t, "interface Baz {}", "") + expectPrintedTS(t, "interface Baz {}", "") + expectParseErrorTS(t, "type Foo = T;", ": ERROR: Expected \">\" but found \"T\"\n") + expectParseErrorTS(t, "type Foo = T;", ": ERROR: Expected \">\" but found \"in\"\n") + expectParseErrorTS(t, "type Foo = T;", ": ERROR: Expected \">\" but found \"T\"\n") + expectParseErrorTS(t, "type Foo = T;", ": ERROR: Expected \">\" but found \"in\"\n") expectPrintedTS(t, "let x: number! = y", "let x = y;\n") expectPrintedTS(t, "let x: number \n !y", "let x;\n!y;\n") expectPrintedTS(t, "const x: unique = y", "const x = y;\n") From 71f9989f44a2fd3265a9328e280eca1c5290290a Mon Sep 17 00:00:00 2001 From: magic-akari Date: Wed, 23 Mar 2022 16:16:58 +0800 Subject: [PATCH 2/7] chore: add JSX test cases --- internal/js_parser/js_parser.go | 5 +++++ internal/js_parser/ts_parser.go | 31 +++++++++++++++++++--------- internal/js_parser/ts_parser_test.go | 12 +++++++++++ 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index af992cc6dde..6ca2bd3a790 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -3313,16 +3313,21 @@ func (p *parser) parsePrefix(level js_ast.L, errors *deferredErrors, flags exprF // (x) => {} // (x) => {} // (x) => {} + // (x) => {} + // (x) => {} // // An arrow function with type parameters: // (x) => {} // (x) => {} + // (x) => {} + // (x) => {} // // A syntax error: // <[]>(x) // (x) // (x) => {} // (x) => {} + // (x) => {} if p.options.ts.Parse && p.options.jsx.Parse && p.isTSArrowFnJSX() { p.skipTypeScriptTypeParameters() diff --git a/internal/js_parser/ts_parser.go b/internal/js_parser/ts_parser.go index 1d4276c251f..9ff34c4c359 100644 --- a/internal/js_parser/ts_parser.go +++ b/internal/js_parser/ts_parser.go @@ -175,7 +175,6 @@ const ( tsTypeIdentifierAsserts tsTypeIdentifierPrefix tsTypeIdentifierPrimitive - tsTypeIdentifierOut ) // Use a map to improve lookup speed @@ -183,7 +182,6 @@ var tsTypeIdentifierMap = map[string]tsTypeIdentifierKind{ "unique": tsTypeIdentifierUnique, "abstract": tsTypeIdentifierAbstract, "asserts": tsTypeIdentifierAsserts, - "out": tsTypeIdentifierOut, "keyof": tsTypeIdentifierPrefix, "readonly": tsTypeIdentifierPrefix, @@ -613,20 +611,18 @@ func (p *parser) skipTypeScriptTypeParameters() { for { // type X = out; - inOutMaybeIdentifier := false + outMaybeIdentifier := false - if p.lexer.Token == js_lexer.TIn { + if p.isTSModifierIn() { p.lexer.Next() - inOutMaybeIdentifier = true } - kind := tsTypeIdentifierMap[p.lexer.Identifier.String] - if kind == tsTypeIdentifierOut { + if p.isTSModifierInOut() { p.lexer.Next() - inOutMaybeIdentifier = true + outMaybeIdentifier = true } - if p.lexer.Token == js_lexer.TIdentifier || !inOutMaybeIdentifier { + if p.lexer.Token == js_lexer.TIdentifier || !outMaybeIdentifier { p.lexer.Expect(js_lexer.TIdentifier) } @@ -781,11 +777,26 @@ func (p *parser) trySkipTypeScriptArrowArgsWithBacktracking() bool { return true } +func (p *parser) isTSModifierIn() bool { + return p.lexer.Token == js_lexer.TIn +} + +func (p *parser) isTSModifierOut() bool { + return p.lexer.Identifier.String == "out" +} + +func (p *parser) isTSModifierInOut() bool { + return p.isTSModifierIn() || p.isTSModifierOut() +} + // Returns true if the current less-than token is considered to be an arrow // function under TypeScript's rules for files containing JSX syntax func (p *parser) isTSArrowFnJSX() (isTSArrowFn bool) { oldLexer := p.lexer - p.lexer.Next() + + for skip := true; skip; skip = p.isTSModifierInOut() { + p.lexer.Next() + } // Look ahead to see if this should be an arrow function instead if p.lexer.Token == js_lexer.TIdentifier { diff --git a/internal/js_parser/ts_parser_test.go b/internal/js_parser/ts_parser_test.go index 193c59e49d7..0c17226c556 100644 --- a/internal/js_parser/ts_parser_test.go +++ b/internal/js_parser/ts_parser_test.go @@ -165,6 +165,18 @@ func TestTSTypes(t *testing.T) { expectPrintedTS(t, "type Foo = keyof T;", "") expectPrintedTS(t, "interface Baz {}", "") expectPrintedTS(t, "interface Baz {}", "") + expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"in\", {\n T: true\n});\n") + expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"out\", {\n T: true\n});\n") + expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"in\", {\n out: true,\n T: true\n});\n") + expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"in\", {\n T: true,\n extends: true\n});\n") + expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"out\", {\n T: true,\n extends: true\n});\n") + expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"in\", {\n out: true,\n T: true,\n extends: true\n});\n") + expectPrintedTSX(t, "() => {}", "() => {\n};\n") + expectPrintedTSX(t, "() => {}", "() => {\n};\n") + expectPrintedTSX(t, "() => {}", "() => {\n};\n") + expectPrintedTSX(t, "() => {}", "() => {\n};\n") + expectPrintedTSX(t, "() => {}", "() => {\n};\n") + expectPrintedTSX(t, "() => {}", "() => {\n};\n") expectParseErrorTS(t, "type Foo = T;", ": ERROR: Expected \">\" but found \"T\"\n") expectParseErrorTS(t, "type Foo = T;", ": ERROR: Expected \">\" but found \"in\"\n") expectParseErrorTS(t, "type Foo = T;", ": ERROR: Expected \">\" but found \"T\"\n") From 6d40226bcb56e5f521457049911b4c67ed35b52c Mon Sep 17 00:00:00 2001 From: magic-akari Date: Sun, 3 Apr 2022 19:43:49 +0800 Subject: [PATCH 3/7] fix: follow TS1273 --- internal/js_parser/js_parser.go | 17 ++++------ internal/js_parser/ts_parser.go | 47 +++++++++++++--------------- internal/js_parser/ts_parser_test.go | 38 +++++++++++++--------- 3 files changed, 51 insertions(+), 51 deletions(-) diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index 6ca2bd3a790..1d8c7d5acf1 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -1906,7 +1906,7 @@ func (p *parser) parseProperty(kind js_ast.PropertyKind, opts propertyOpts, erro // "class X { foo?(): T }" // "const x = { foo(): T {} }" - p.skipTypeScriptTypeParameters() + p.skipTypeScriptTypeParameters(false) } // Parse a class field with an optional initial value @@ -2468,7 +2468,7 @@ func (p *parser) parseFnExpr(loc logger.Loc, isAsync bool, asyncRange logger.Ran // Even anonymous functions can have TypeScript type parameters if p.options.ts.Parse { - p.skipTypeScriptTypeParameters() + p.skipTypeScriptTypeParameters(false) } await := allowIdent @@ -3117,7 +3117,7 @@ func (p *parser) parsePrefix(level js_ast.L, errors *deferredErrors, flags exprF // Even anonymous classes can have TypeScript type parameters if p.options.ts.Parse { - p.skipTypeScriptTypeParameters() + p.skipTypeScriptTypeParameters(true) } class := p.parseClass(classKeyword, name, parseClassOpts{}) @@ -3313,24 +3313,19 @@ func (p *parser) parsePrefix(level js_ast.L, errors *deferredErrors, flags exprF // (x) => {} // (x) => {} // (x) => {} - // (x) => {} - // (x) => {} // // An arrow function with type parameters: // (x) => {} // (x) => {} - // (x) => {} - // (x) => {} // // A syntax error: // <[]>(x) // (x) // (x) => {} // (x) => {} - // (x) => {} if p.options.ts.Parse && p.options.jsx.Parse && p.isTSArrowFnJSX() { - p.skipTypeScriptTypeParameters() + p.skipTypeScriptTypeParameters(false) p.lexer.Expect(js_lexer.TOpenParen) return p.parseParenExpr(loc, level, parenExprOpts{forceArrowFn: true}) } @@ -5314,7 +5309,7 @@ func (p *parser) parseClassStmt(loc logger.Loc, opts parseStmtOpts) js_ast.Stmt // Even anonymous classes can have TypeScript type parameters if p.options.ts.Parse { - p.skipTypeScriptTypeParameters() + p.skipTypeScriptTypeParameters(true) } classOpts := parseClassOpts{ @@ -5574,7 +5569,7 @@ func (p *parser) parseFnStmt(loc logger.Loc, opts parseStmtOpts, isAsync bool, a // Even anonymous functions can have TypeScript type parameters if p.options.ts.Parse { - p.skipTypeScriptTypeParameters() + p.skipTypeScriptTypeParameters(false) } // Introduce a fake block scope for function declarations inside if statements diff --git a/internal/js_parser/ts_parser.go b/internal/js_parser/ts_parser.go index 9ff34c4c359..e593bee2a7e 100644 --- a/internal/js_parser/ts_parser.go +++ b/internal/js_parser/ts_parser.go @@ -277,12 +277,12 @@ func (p *parser) skipTypeScriptTypeWithOpts(level js_ast.L, opts skipTypeOpts) { return } - p.skipTypeScriptTypeParameters() + p.skipTypeScriptTypeParameters(false) p.skipTypeScriptParenOrFnType() case js_lexer.TLessThan: // "() => Foo" - p.skipTypeScriptTypeParameters() + p.skipTypeScriptTypeParameters(false) p.skipTypeScriptParenOrFnType() case js_lexer.TOpenParen: @@ -562,7 +562,7 @@ func (p *parser) skipTypeScriptObjectType() { } // Type parameters come right after the optional mark - p.skipTypeScriptTypeParameters() + p.skipTypeScriptTypeParameters(false) switch p.lexer.Token { case js_lexer.TColon: @@ -603,9 +603,21 @@ func (p *parser) skipTypeScriptObjectType() { p.lexer.Expect(js_lexer.TCloseBrace) } +func (p *parser) isTSModifierIn() bool { + return p.lexer.Token == js_lexer.TIn +} + +func (p *parser) isTSModifierOut() bool { + return p.lexer.Identifier.String == "out" +} + +func (p *parser) isTSModifierInOut() bool { + return p.isTSModifierIn() || p.isTSModifierOut() +} + // This is the type parameter declarations that go with other symbol // declarations (class, function, type, etc.) -func (p *parser) skipTypeScriptTypeParameters() { +func (p *parser) skipTypeScriptTypeParameters(allowModifier bool) { if p.lexer.Token == js_lexer.TLessThan { p.lexer.Next() @@ -613,11 +625,11 @@ func (p *parser) skipTypeScriptTypeParameters() { // type X = out; outMaybeIdentifier := false - if p.isTSModifierIn() { + if allowModifier && p.isTSModifierIn() { p.lexer.Next() } - if p.isTSModifierInOut() { + if allowModifier && p.isTSModifierInOut() { p.lexer.Next() outMaybeIdentifier = true } @@ -715,7 +727,7 @@ func (p *parser) trySkipTypeScriptTypeParametersThenOpenParenWithBacktracking() } }() - p.skipTypeScriptTypeParameters() + p.skipTypeScriptTypeParameters(false) if p.lexer.Token != js_lexer.TOpenParen { p.lexer.Unexpected() } @@ -777,26 +789,11 @@ func (p *parser) trySkipTypeScriptArrowArgsWithBacktracking() bool { return true } -func (p *parser) isTSModifierIn() bool { - return p.lexer.Token == js_lexer.TIn -} - -func (p *parser) isTSModifierOut() bool { - return p.lexer.Identifier.String == "out" -} - -func (p *parser) isTSModifierInOut() bool { - return p.isTSModifierIn() || p.isTSModifierOut() -} - // Returns true if the current less-than token is considered to be an arrow // function under TypeScript's rules for files containing JSX syntax func (p *parser) isTSArrowFnJSX() (isTSArrowFn bool) { oldLexer := p.lexer - - for skip := true; skip; skip = p.isTSModifierInOut() { - p.lexer.Next() - } + p.lexer.Next() // Look ahead to see if this should be an arrow function instead if p.lexer.Token == js_lexer.TIdentifier { @@ -865,7 +862,7 @@ func (p *parser) skipTypeScriptInterfaceStmt(opts parseStmtOpts) { p.localTypeNames[name] = true } - p.skipTypeScriptTypeParameters() + p.skipTypeScriptTypeParameters(true) if p.lexer.Token == js_lexer.TExtends { p.lexer.Next() @@ -912,7 +909,7 @@ func (p *parser) skipTypeScriptTypeStmt(opts parseStmtOpts) { p.localTypeNames[name] = true } - p.skipTypeScriptTypeParameters() + p.skipTypeScriptTypeParameters(true) p.lexer.Expect(js_lexer.TEquals) p.skipTypeScriptType(js_ast.LLowest) p.lexer.ExpectOrInsertSemicolon() diff --git a/internal/js_parser/ts_parser_test.go b/internal/js_parser/ts_parser_test.go index 0c17226c556..d61ec13ff8d 100644 --- a/internal/js_parser/ts_parser_test.go +++ b/internal/js_parser/ts_parser_test.go @@ -158,29 +158,37 @@ func TestTSTypes(t *testing.T) { expectPrintedTS(t, "type Foo = {readonly [P in keyof T]: T[P]}", "") expectPrintedTS(t, "type Foo = {-readonly [P in keyof T]: T[P]}", "") expectPrintedTS(t, "type Foo = {+readonly [P in keyof T]: T[P]}", "") - expectPrintedTS(t, "type Foo = T;", "") - expectPrintedTS(t, "type Foo = keyof T;", "") - expectPrintedTS(t, "type Foo = T[K];", "") - expectPrintedTS(t, "type Foo = T[keyof T];", "") - expectPrintedTS(t, "type Foo = keyof T;", "") + expectPrintedTS(t, "type Covariant = { x: T;}", "") + expectPrintedTS(t, "type Contravariant = { f: (x: T) => void; }", "") + expectPrintedTS(t, "type Invariant = { f: (x: T) => T; }", "") + expectPrintedTS(t, "type T10 = T;", "") + expectPrintedTS(t, "type T11 = keyof T;", "") + expectPrintedTS(t, "type T12 = T[K];", "") + expectPrintedTS(t, "type T13 = T[keyof T];", "") + expectPrintedTS(t, "type Foo3 = { x: T; f: FooFn3; }", "") + expectParseErrorTS(t, "type T20 = T;", ": ERROR: Expected \">\" but found \"T\"\n") + expectParseErrorTS(t, "type T21 = T;", ": ERROR: Expected \">\" but found \"in\"\n") + expectParseErrorTS(t, "type T22 = T;", ": ERROR: Expected \">\" but found \"T\"\n") + expectParseErrorTS(t, "type T23 = T;", ": ERROR: Expected \">\" but found \"in\"\n") + expectParseErrorTS(t, "declare function f1(x: T): void;", ": ERROR: Expected identifier but found \"in\"\n") + expectParseErrorTS(t, "declare function f2(): T;", ": ERROR: Expected \">\" but found \"T\"\n") + expectParseErrorTS(t, "class C { in a = 0; out b = 0; };", ": ERROR: Expected \";\" but found \"a\"\n") expectPrintedTS(t, "interface Baz {}", "") expectPrintedTS(t, "interface Baz {}", "") + expectPrintedTS(t, "interface Parent { child: Child | null; parent: Parent | null; }", "") + expectPrintedTS(t, "declare class StateNode { _storedEvent: TEvent; _action: ActionObject; _state: StateNode; }", "") expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"in\", {\n T: true\n});\n") expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"out\", {\n T: true\n});\n") expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"in\", {\n out: true,\n T: true\n});\n") expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"in\", {\n T: true,\n extends: true\n});\n") expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"out\", {\n T: true,\n extends: true\n});\n") expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"in\", {\n out: true,\n T: true,\n extends: true\n});\n") - expectPrintedTSX(t, "() => {}", "() => {\n};\n") - expectPrintedTSX(t, "() => {}", "() => {\n};\n") - expectPrintedTSX(t, "() => {}", "() => {\n};\n") - expectPrintedTSX(t, "() => {}", "() => {\n};\n") - expectPrintedTSX(t, "() => {}", "() => {\n};\n") - expectPrintedTSX(t, "() => {}", "() => {\n};\n") - expectParseErrorTS(t, "type Foo = T;", ": ERROR: Expected \">\" but found \"T\"\n") - expectParseErrorTS(t, "type Foo = T;", ": ERROR: Expected \">\" but found \"in\"\n") - expectParseErrorTS(t, "type Foo = T;", ": ERROR: Expected \">\" but found \"T\"\n") - expectParseErrorTS(t, "type Foo = T;", ": ERROR: Expected \">\" but found \"in\"\n") + expectParseErrorTSX(t, "() => {}", ": ERROR: Expected \">\" but found \",\"\n") + expectParseErrorTSX(t, "() => {}", ": ERROR: Expected \">\" but found \",\"\n") + expectParseErrorTSX(t, "() => {}", ": ERROR: Expected \">\" but found \",\"\n") + expectParseErrorTSX(t, "() => {}", ": ERROR: The character \">\" is not valid inside a JSX element\nNOTE: Did you mean to escape it as \"{'>'}\" instead?\n: ERROR: Unexpected end of file\n") + expectParseErrorTSX(t, "() => {}", ": ERROR: The character \">\" is not valid inside a JSX element\nNOTE: Did you mean to escape it as \"{'>'}\" instead?\n: ERROR: Unexpected end of file\n") + expectParseErrorTSX(t, "() => {}", ": ERROR: The character \">\" is not valid inside a JSX element\nNOTE: Did you mean to escape it as \"{'>'}\" instead?\n: ERROR: Unexpected end of file\n") expectPrintedTS(t, "let x: number! = y", "let x = y;\n") expectPrintedTS(t, "let x: number \n !y", "let x;\n!y;\n") expectPrintedTS(t, "const x: unique = y", "const x = y;\n") From d6d6d5aa146b62c5116ba2576b95798604eead84 Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Sun, 3 Apr 2022 10:21:49 -0400 Subject: [PATCH 4/7] relocate new test cases --- internal/js_parser/ts_parser_test.go | 64 ++++++++++++++-------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/internal/js_parser/ts_parser_test.go b/internal/js_parser/ts_parser_test.go index 74d70acc61b..e4991ca2a5e 100644 --- a/internal/js_parser/ts_parser_test.go +++ b/internal/js_parser/ts_parser_test.go @@ -158,37 +158,6 @@ func TestTSTypes(t *testing.T) { expectPrintedTS(t, "type Foo = {readonly [P in keyof T]: T[P]}", "") expectPrintedTS(t, "type Foo = {-readonly [P in keyof T]: T[P]}", "") expectPrintedTS(t, "type Foo = {+readonly [P in keyof T]: T[P]}", "") - expectPrintedTS(t, "type Covariant = { x: T;}", "") - expectPrintedTS(t, "type Contravariant = { f: (x: T) => void; }", "") - expectPrintedTS(t, "type Invariant = { f: (x: T) => T; }", "") - expectPrintedTS(t, "type T10 = T;", "") - expectPrintedTS(t, "type T11 = keyof T;", "") - expectPrintedTS(t, "type T12 = T[K];", "") - expectPrintedTS(t, "type T13 = T[keyof T];", "") - expectPrintedTS(t, "type Foo3 = { x: T; f: FooFn3; }", "") - expectParseErrorTS(t, "type T20 = T;", ": ERROR: Expected \">\" but found \"T\"\n") - expectParseErrorTS(t, "type T21 = T;", ": ERROR: Expected \">\" but found \"in\"\n") - expectParseErrorTS(t, "type T22 = T;", ": ERROR: Expected \">\" but found \"T\"\n") - expectParseErrorTS(t, "type T23 = T;", ": ERROR: Expected \">\" but found \"in\"\n") - expectParseErrorTS(t, "declare function f1(x: T): void;", ": ERROR: Expected identifier but found \"in\"\n") - expectParseErrorTS(t, "declare function f2(): T;", ": ERROR: Expected \">\" but found \"T\"\n") - expectParseErrorTS(t, "class C { in a = 0; out b = 0; };", ": ERROR: Expected \";\" but found \"a\"\n") - expectPrintedTS(t, "interface Baz {}", "") - expectPrintedTS(t, "interface Baz {}", "") - expectPrintedTS(t, "interface Parent { child: Child | null; parent: Parent | null; }", "") - expectPrintedTS(t, "declare class StateNode { _storedEvent: TEvent; _action: ActionObject; _state: StateNode; }", "") - expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"in\", {\n T: true\n});\n") - expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"out\", {\n T: true\n});\n") - expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"in\", {\n out: true,\n T: true\n});\n") - expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"in\", {\n T: true,\n extends: true\n});\n") - expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"out\", {\n T: true,\n extends: true\n});\n") - expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"in\", {\n out: true,\n T: true,\n extends: true\n});\n") - expectParseErrorTSX(t, "() => {}", ": ERROR: Expected \">\" but found \",\"\n") - expectParseErrorTSX(t, "() => {}", ": ERROR: Expected \">\" but found \",\"\n") - expectParseErrorTSX(t, "() => {}", ": ERROR: Expected \">\" but found \",\"\n") - expectParseErrorTSX(t, "() => {}", ": ERROR: The character \">\" is not valid inside a JSX element\nNOTE: Did you mean to escape it as \"{'>'}\" instead?\n: ERROR: Unexpected end of file\n") - expectParseErrorTSX(t, "() => {}", ": ERROR: The character \">\" is not valid inside a JSX element\nNOTE: Did you mean to escape it as \"{'>'}\" instead?\n: ERROR: Unexpected end of file\n") - expectParseErrorTSX(t, "() => {}", ": ERROR: The character \">\" is not valid inside a JSX element\nNOTE: Did you mean to escape it as \"{'>'}\" instead?\n: ERROR: Unexpected end of file\n") expectPrintedTS(t, "let x: number! = y", "let x = y;\n") expectPrintedTS(t, "let x: number \n !y", "let x;\n!y;\n") expectPrintedTS(t, "const x: unique = y", "const x = y;\n") @@ -318,6 +287,39 @@ func TestTSTypes(t *testing.T) { expectParseErrorTS(t, "let x: abstract () => void = Foo", ": ERROR: Expected \";\" but found \"(\"\n") expectParseErrorTS(t, "let x: abstract () => Foo", ": ERROR: Expected \";\" but found \"(\"\n") expectParseErrorTS(t, "let x: abstract () => Foo", ": ERROR: Expected \"?\" but found \">\"\n") + + // TypeScript 4.7 + expectPrintedTS(t, "type Covariant = { x: T; }", "") + expectPrintedTS(t, "type Contravariant = { f: (x: T) => void; }", "") + expectPrintedTS(t, "type Invariant = { f: (x: T) => T; }", "") + expectPrintedTS(t, "type T10 = T;", "") + expectPrintedTS(t, "type T11 = keyof T;", "") + expectPrintedTS(t, "type T12 = T[K];", "") + expectPrintedTS(t, "type T13 = T[keyof T];", "") + expectPrintedTS(t, "type Foo3 = { x: T; f: FooFn3; }", "") + expectParseErrorTS(t, "type T20 = T;", ": ERROR: Expected \">\" but found \"T\"\n") + expectParseErrorTS(t, "type T21 = T;", ": ERROR: Expected \">\" but found \"in\"\n") + expectParseErrorTS(t, "type T22 = T;", ": ERROR: Expected \">\" but found \"T\"\n") + expectParseErrorTS(t, "type T23 = T;", ": ERROR: Expected \">\" but found \"in\"\n") + expectParseErrorTS(t, "declare function f1(x: T): void;", ": ERROR: Expected identifier but found \"in\"\n") + expectParseErrorTS(t, "declare function f2(): T;", ": ERROR: Expected \">\" but found \"T\"\n") + expectParseErrorTS(t, "class C { in a = 0; out b = 0; };", ": ERROR: Expected \";\" but found \"a\"\n") + expectPrintedTS(t, "interface Baz {}", "") + expectPrintedTS(t, "interface Baz {}", "") + expectPrintedTS(t, "interface Parent { child: Child | null; parent: Parent | null; }", "") + expectPrintedTS(t, "declare class StateNode { _storedEvent: TEvent; _action: ActionObject; _state: StateNode; }", "") + expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"in\", {\n T: true\n});\n") + expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"out\", {\n T: true\n});\n") + expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"in\", {\n out: true,\n T: true\n});\n") + expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"in\", {\n T: true,\n extends: true\n});\n") + expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"out\", {\n T: true,\n extends: true\n});\n") + expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"in\", {\n out: true,\n T: true,\n extends: true\n});\n") + expectParseErrorTSX(t, "() => {}", ": ERROR: Expected \">\" but found \",\"\n") + expectParseErrorTSX(t, "() => {}", ": ERROR: Expected \">\" but found \",\"\n") + expectParseErrorTSX(t, "() => {}", ": ERROR: Expected \">\" but found \",\"\n") + expectParseErrorTSX(t, "() => {}", ": ERROR: The character \">\" is not valid inside a JSX element\nNOTE: Did you mean to escape it as \"{'>'}\" instead?\n: ERROR: Unexpected end of file\n") + expectParseErrorTSX(t, "() => {}", ": ERROR: The character \">\" is not valid inside a JSX element\nNOTE: Did you mean to escape it as \"{'>'}\" instead?\n: ERROR: Unexpected end of file\n") + expectParseErrorTSX(t, "() => {}", ": ERROR: The character \">\" is not valid inside a JSX element\nNOTE: Did you mean to escape it as \"{'>'}\" instead?\n: ERROR: Unexpected end of file\n") } func TestTSAsCast(t *testing.T) { From f1a4686ea06d5b10766d01add1c9b313abc00787 Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Sun, 3 Apr 2022 10:37:24 -0400 Subject: [PATCH 5/7] fix bug with `out` keyword and escapes --- internal/js_parser/ts_parser.go | 2 +- internal/js_parser/ts_parser_test.go | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/js_parser/ts_parser.go b/internal/js_parser/ts_parser.go index 805db52a3a0..36e5b031f2e 100644 --- a/internal/js_parser/ts_parser.go +++ b/internal/js_parser/ts_parser.go @@ -608,7 +608,7 @@ func (p *parser) isTSModifierIn() bool { } func (p *parser) isTSModifierOut() bool { - return p.lexer.Identifier.String == "out" + return p.lexer.IsContextualKeyword("out") } func (p *parser) isTSModifierInOut() bool { diff --git a/internal/js_parser/ts_parser_test.go b/internal/js_parser/ts_parser_test.go index e4991ca2a5e..ffa1b58df7c 100644 --- a/internal/js_parser/ts_parser_test.go +++ b/internal/js_parser/ts_parser_test.go @@ -304,6 +304,8 @@ func TestTSTypes(t *testing.T) { expectParseErrorTS(t, "declare function f1(x: T): void;", ": ERROR: Expected identifier but found \"in\"\n") expectParseErrorTS(t, "declare function f2(): T;", ": ERROR: Expected \">\" but found \"T\"\n") expectParseErrorTS(t, "class C { in a = 0; out b = 0; };", ": ERROR: Expected \";\" but found \"a\"\n") + expectParseErrorTS(t, "type T = T", ": ERROR: Expected identifier but found \"i\\\\u006E\"\n") + expectParseErrorTS(t, "type T = T", ": ERROR: Expected \">\" but found \"T\"\n") expectPrintedTS(t, "interface Baz {}", "") expectPrintedTS(t, "interface Baz {}", "") expectPrintedTS(t, "interface Parent { child: Child | null; parent: Parent | null; }", "") From 4484e2d98d20fd3728b1088cec1e7be1ebd8de22 Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Sun, 3 Apr 2022 11:50:56 -0400 Subject: [PATCH 6/7] various fixes - increase test coverage - fix bug: anonymous classes don't support in/out - better error messages when in/out isn't allowed - more robust parsing of invalid in/out sequences --- internal/js_parser/js_parser.go | 12 ++-- internal/js_parser/ts_parser.go | 89 +++++++++++++++++++-------- internal/js_parser/ts_parser_test.go | 91 ++++++++++++++++++++-------- 3 files changed, 137 insertions(+), 55 deletions(-) diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index e7a547ee87e..0acad6535a7 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -1928,7 +1928,7 @@ func (p *parser) parseProperty(kind js_ast.PropertyKind, opts propertyOpts, erro // "class X { foo?(): T }" // "const x = { foo(): T {} }" - p.skipTypeScriptTypeParameters(false) + p.skipTypeScriptTypeParameters(typeParametersNormal) } // Parse a class field with an optional initial value @@ -2490,7 +2490,7 @@ func (p *parser) parseFnExpr(loc logger.Loc, isAsync bool, asyncRange logger.Ran // Even anonymous functions can have TypeScript type parameters if p.options.ts.Parse { - p.skipTypeScriptTypeParameters(false) + p.skipTypeScriptTypeParameters(typeParametersNormal) } await := allowIdent @@ -3139,7 +3139,7 @@ func (p *parser) parsePrefix(level js_ast.L, errors *deferredErrors, flags exprF // Even anonymous classes can have TypeScript type parameters if p.options.ts.Parse { - p.skipTypeScriptTypeParameters(true) + p.skipTypeScriptTypeParameters(typeParametersNormal) } class := p.parseClass(classKeyword, name, parseClassOpts{}) @@ -3347,7 +3347,7 @@ func (p *parser) parsePrefix(level js_ast.L, errors *deferredErrors, flags exprF // (x) => {} if p.options.ts.Parse && p.options.jsx.Parse && p.isTSArrowFnJSX() { - p.skipTypeScriptTypeParameters(false) + p.skipTypeScriptTypeParameters(typeParametersNormal) p.lexer.Expect(js_lexer.TOpenParen) return p.parseParenExpr(loc, level, parenExprOpts{forceArrowFn: true}) } @@ -5387,7 +5387,7 @@ func (p *parser) parseClassStmt(loc logger.Loc, opts parseStmtOpts) js_ast.Stmt // Even anonymous classes can have TypeScript type parameters if p.options.ts.Parse { - p.skipTypeScriptTypeParameters(true) + p.skipTypeScriptTypeParameters(typeParametersWithInOutVarianceAnnotations) } classOpts := parseClassOpts{ @@ -5647,7 +5647,7 @@ func (p *parser) parseFnStmt(loc logger.Loc, opts parseStmtOpts, isAsync bool, a // Even anonymous functions can have TypeScript type parameters if p.options.ts.Parse { - p.skipTypeScriptTypeParameters(false) + p.skipTypeScriptTypeParameters(typeParametersNormal) } // Introduce a fake block scope for function declarations inside if statements diff --git a/internal/js_parser/ts_parser.go b/internal/js_parser/ts_parser.go index 36e5b031f2e..15e36a1cc41 100644 --- a/internal/js_parser/ts_parser.go +++ b/internal/js_parser/ts_parser.go @@ -277,12 +277,12 @@ func (p *parser) skipTypeScriptTypeWithOpts(level js_ast.L, opts skipTypeOpts) { return } - p.skipTypeScriptTypeParameters(false) + p.skipTypeScriptTypeParameters(typeParametersNormal) p.skipTypeScriptParenOrFnType() case js_lexer.TLessThan: // "() => Foo" - p.skipTypeScriptTypeParameters(false) + p.skipTypeScriptTypeParameters(typeParametersNormal) p.skipTypeScriptParenOrFnType() case js_lexer.TOpenParen: @@ -562,7 +562,7 @@ func (p *parser) skipTypeScriptObjectType() { } // Type parameters come right after the optional mark - p.skipTypeScriptTypeParameters(false) + p.skipTypeScriptTypeParameters(typeParametersNormal) switch p.lexer.Token { case js_lexer.TColon: @@ -603,38 +603,77 @@ func (p *parser) skipTypeScriptObjectType() { p.lexer.Expect(js_lexer.TCloseBrace) } -func (p *parser) isTSModifierIn() bool { - return p.lexer.Token == js_lexer.TIn -} - -func (p *parser) isTSModifierOut() bool { - return p.lexer.IsContextualKeyword("out") -} +type typeParameters uint8 -func (p *parser) isTSModifierInOut() bool { - return p.isTSModifierIn() || p.isTSModifierOut() -} +const ( + typeParametersNormal typeParameters = iota + typeParametersWithInOutVarianceAnnotations +) // This is the type parameter declarations that go with other symbol // declarations (class, function, type, etc.) -func (p *parser) skipTypeScriptTypeParameters(allowModifier bool) { +func (p *parser) skipTypeScriptTypeParameters(mode typeParameters) { if p.lexer.Token == js_lexer.TLessThan { p.lexer.Next() for { - // type X = out; - outMaybeIdentifier := false + hasIn := false + hasOut := false + expectIdentifier := true + invalidModifierRange := logger.Range{} - if allowModifier && p.isTSModifierIn() { - p.lexer.Next() + // Scan over a sequence of "in" and "out" modifiers (a.k.a. optional variance annotations) + for { + if p.lexer.Token == js_lexer.TIn { + if invalidModifierRange.Len == 0 && (mode != typeParametersWithInOutVarianceAnnotations || hasIn || hasOut) { + // Valid: + // "type Foo = T" + // Invalid: + // "type Foo = T" + // "type Foo = T" + invalidModifierRange = p.lexer.Range() + } + p.lexer.Next() + hasIn = true + expectIdentifier = true + continue + } + + if p.lexer.IsContextualKeyword("out") { + r := p.lexer.Range() + if invalidModifierRange.Len == 0 && mode != typeParametersWithInOutVarianceAnnotations { + invalidModifierRange = r + } + p.lexer.Next() + if invalidModifierRange.Len == 0 && hasOut && (p.lexer.Token == js_lexer.TIn || p.lexer.Token == js_lexer.TIdentifier) { + // Valid: + // "type Foo = T" + // "type Foo = T" + // "type Foo = T" + // "type Foo = T" + // "type Foo = T" + // Invalid: + // "type Foo = T" + // "type Foo = T" + invalidModifierRange = r + } + hasOut = true + expectIdentifier = false + continue + } + + break } - if allowModifier && p.isTSModifierInOut() { - p.lexer.Next() - outMaybeIdentifier = true + // Only report an error for the first invalid modifier + if invalidModifierRange.Len > 0 { + p.log.Add(logger.Error, &p.tracker, invalidModifierRange, fmt.Sprintf( + "The modifier %q is not valid here:", p.source.TextForRange(invalidModifierRange))) } - if p.lexer.Token == js_lexer.TIdentifier || !outMaybeIdentifier { + // expectIdentifier => Mandatory identifier (e.g. after "type Foo Optional identifier (e.g. after "type Foo () => Foo", ": ERROR: Expected \"?\" but found \">\"\n") // TypeScript 4.7 - expectPrintedTS(t, "type Covariant = { x: T; }", "") - expectPrintedTS(t, "type Contravariant = { f: (x: T) => void; }", "") - expectPrintedTS(t, "type Invariant = { f: (x: T) => T; }", "") - expectPrintedTS(t, "type T10 = T;", "") - expectPrintedTS(t, "type T11 = keyof T;", "") - expectPrintedTS(t, "type T12 = T[K];", "") - expectPrintedTS(t, "type T13 = T[keyof T];", "") - expectPrintedTS(t, "type Foo3 = { x: T; f: FooFn3; }", "") - expectParseErrorTS(t, "type T20 = T;", ": ERROR: Expected \">\" but found \"T\"\n") - expectParseErrorTS(t, "type T21 = T;", ": ERROR: Expected \">\" but found \"in\"\n") - expectParseErrorTS(t, "type T22 = T;", ": ERROR: Expected \">\" but found \"T\"\n") - expectParseErrorTS(t, "type T23 = T;", ": ERROR: Expected \">\" but found \"in\"\n") - expectParseErrorTS(t, "declare function f1(x: T): void;", ": ERROR: Expected identifier but found \"in\"\n") - expectParseErrorTS(t, "declare function f2(): T;", ": ERROR: Expected \">\" but found \"T\"\n") - expectParseErrorTS(t, "class C { in a = 0; out b = 0; };", ": ERROR: Expected \";\" but found \"a\"\n") - expectParseErrorTS(t, "type T = T", ": ERROR: Expected identifier but found \"i\\\\u006E\"\n") - expectParseErrorTS(t, "type T = T", ": ERROR: Expected \">\" but found \"T\"\n") - expectPrintedTS(t, "interface Baz {}", "") - expectPrintedTS(t, "interface Baz {}", "") - expectPrintedTS(t, "interface Parent { child: Child | null; parent: Parent | null; }", "") - expectPrintedTS(t, "declare class StateNode { _storedEvent: TEvent; _action: ActionObject; _state: StateNode; }", "") + jsxErrorArrow := ": ERROR: The character \">\" is not valid inside a JSX element\n" + + "NOTE: Did you mean to escape it as \"{'>'}\" instead?\n" + + ": ERROR: Unexpected end of file\n" + expectPrintedTS(t, "type Foo = T", "") + expectPrintedTS(t, "type Foo = T", "") + expectPrintedTS(t, "type Foo = T", "") + expectPrintedTS(t, "type Foo = T", "") + expectPrintedTS(t, "type Foo = T", "") + expectPrintedTS(t, "type Foo = [X, Y]", "") + expectPrintedTS(t, "type Foo = [X, Y]", "") + expectPrintedTS(t, "type Foo = [X, Y]", "") + expectParseErrorTS(t, "type Foo = T", ": ERROR: Expected identifier but found \"i\\\\u006E\"\n") + expectParseErrorTS(t, "type Foo = T", ": ERROR: Expected \">\" but found \"T\"\n") + expectParseErrorTS(t, "type Foo = T", ": ERROR: The modifier \"in\" is not valid here:\n: ERROR: Expected identifier but found \">\"\n") + expectParseErrorTS(t, "type Foo = T", ": ERROR: The modifier \"in\" is not valid here:\n: ERROR: Expected identifier but found \">\"\n") + expectParseErrorTS(t, "type Foo = T", ": ERROR: The modifier \"in\" is not valid here:\n") + expectParseErrorTS(t, "type Foo = T", ": ERROR: Expected \">\" but found \"T\"\n") + expectParseErrorTS(t, "type Foo = T", ": ERROR: The modifier \"in\" is not valid here:\n") + expectParseErrorTS(t, "type Foo = T", ": ERROR: The modifier \"out\" is not valid here:\n") + expectPrintedTS(t, "class Foo {}", "class Foo {\n}\n") + expectPrintedTS(t, "class Foo {}", "class Foo {\n}\n") + expectPrintedTS(t, "export default class Foo {}", "export default class Foo {\n}\n") + expectPrintedTS(t, "export default class Foo {}", "export default class Foo {\n}\n") + expectPrintedTS(t, "export default class {}", "export default class {\n}\n") + expectPrintedTS(t, "export default class {}", "export default class {\n}\n") + expectPrintedTS(t, "interface Foo {}", "") + expectPrintedTS(t, "interface Foo {}", "") + expectPrintedTS(t, "declare class Foo {}", "") + expectPrintedTS(t, "declare class Foo {}", "") + expectPrintedTS(t, "declare interface Foo {}", "") + expectPrintedTS(t, "declare interface Foo {}", "") + expectParseErrorTS(t, "function foo() {}", ": ERROR: The modifier \"in\" is not valid here:\n") + expectParseErrorTS(t, "function foo() {}", ": ERROR: The modifier \"out\" is not valid here:\n") + expectParseErrorTS(t, "export default function foo() {}", ": ERROR: The modifier \"in\" is not valid here:\n") + expectParseErrorTS(t, "export default function foo() {}", ": ERROR: The modifier \"out\" is not valid here:\n") + expectParseErrorTS(t, "export default function () {}", ": ERROR: The modifier \"in\" is not valid here:\n") + expectParseErrorTS(t, "export default function () {}", ": ERROR: The modifier \"out\" is not valid here:\n") + expectParseErrorTS(t, "let foo: Foo", ": ERROR: Unexpected \"in\"\n") + expectParseErrorTS(t, "let foo: Foo", ": ERROR: Expected \">\" but found \"T\"\n") + expectParseErrorTS(t, "declare function foo()", ": ERROR: The modifier \"in\" is not valid here:\n") + expectParseErrorTS(t, "declare function foo()", ": ERROR: The modifier \"out\" is not valid here:\n") + expectParseErrorTS(t, "declare let foo: Foo", ": ERROR: Unexpected \"in\"\n") + expectParseErrorTS(t, "declare let foo: Foo", ": ERROR: Expected \">\" but found \"T\"\n") + expectParseErrorTS(t, "Foo = class {}", ": ERROR: The modifier \"in\" is not valid here:\n") + expectParseErrorTS(t, "Foo = class {}", ": ERROR: The modifier \"out\" is not valid here:\n") + expectParseErrorTS(t, "foo = function () {}", ": ERROR: The modifier \"in\" is not valid here:\n") + expectParseErrorTS(t, "foo = function () {}", ": ERROR: The modifier \"out\" is not valid here:\n") + expectParseErrorTS(t, "class Foo { foo(): T {} }", ": ERROR: The modifier \"in\" is not valid here:\n") + expectParseErrorTS(t, "class Foo { foo(): T {} }", ": ERROR: The modifier \"out\" is not valid here:\n") + expectParseErrorTS(t, "foo = { foo(): T {} }", ": ERROR: The modifier \"in\" is not valid here:\n") + expectParseErrorTS(t, "foo = { foo(): T {} }", ": ERROR: The modifier \"out\" is not valid here:\n") + expectParseErrorTS(t, "() => {}", ": ERROR: The modifier \"in\" is not valid here:\n") + expectParseErrorTS(t, "() => {}", ": ERROR: The modifier \"out\" is not valid here:\n") + expectParseErrorTS(t, "() => {}", ": ERROR: The modifier \"in\" is not valid here:\n: ERROR: The modifier \"out\" is not valid here:\n") + expectParseErrorTS(t, "let x: () => {}", ": ERROR: The modifier \"in\" is not valid here:\n") + expectParseErrorTS(t, "let x: () => {}", ": ERROR: The modifier \"out\" is not valid here:\n") + expectParseErrorTS(t, "let x: () => {}", ": ERROR: The modifier \"in\" is not valid here:\n: ERROR: The modifier \"out\" is not valid here:\n") + expectParseErrorTS(t, "let x: new () => {}", ": ERROR: The modifier \"in\" is not valid here:\n") + expectParseErrorTS(t, "let x: new () => {}", ": ERROR: The modifier \"out\" is not valid here:\n") + expectParseErrorTS(t, "let x: new () => {}", ": ERROR: The modifier \"in\" is not valid here:\n: ERROR: The modifier \"out\" is not valid here:\n") + expectParseErrorTS(t, "let x: { y(): any }", ": ERROR: The modifier \"in\" is not valid here:\n") + expectParseErrorTS(t, "let x: { y(): any }", ": ERROR: The modifier \"out\" is not valid here:\n") + expectParseErrorTS(t, "let x: { y(): any }", ": ERROR: The modifier \"in\" is not valid here:\n: ERROR: The modifier \"out\" is not valid here:\n") expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"in\", {\n T: true\n});\n") expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"out\", {\n T: true\n});\n") expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"in\", {\n out: true,\n T: true\n});\n") + expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"out\", {\n in: true,\n T: true\n});\n") expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"in\", {\n T: true,\n extends: true\n});\n") expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"out\", {\n T: true,\n extends: true\n});\n") expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"in\", {\n out: true,\n T: true,\n extends: true\n});\n") expectParseErrorTSX(t, "() => {}", ": ERROR: Expected \">\" but found \",\"\n") expectParseErrorTSX(t, "() => {}", ": ERROR: Expected \">\" but found \",\"\n") expectParseErrorTSX(t, "() => {}", ": ERROR: Expected \">\" but found \",\"\n") - expectParseErrorTSX(t, "() => {}", ": ERROR: The character \">\" is not valid inside a JSX element\nNOTE: Did you mean to escape it as \"{'>'}\" instead?\n: ERROR: Unexpected end of file\n") - expectParseErrorTSX(t, "() => {}", ": ERROR: The character \">\" is not valid inside a JSX element\nNOTE: Did you mean to escape it as \"{'>'}\" instead?\n: ERROR: Unexpected end of file\n") - expectParseErrorTSX(t, "() => {}", ": ERROR: The character \">\" is not valid inside a JSX element\nNOTE: Did you mean to escape it as \"{'>'}\" instead?\n: ERROR: Unexpected end of file\n") + expectParseErrorTSX(t, "() => {}", jsxErrorArrow) + expectParseErrorTSX(t, "() => {}", jsxErrorArrow) + expectParseErrorTSX(t, "() => {}", jsxErrorArrow) } func TestTSAsCast(t *testing.T) { From d34bc9c5fd9f0612ee74bba5e49b7b71f7167a71 Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Sun, 3 Apr 2022 12:00:03 -0400 Subject: [PATCH 7/7] add a changelog entry --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a8f519bca2..38c9cd834a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## Unreleased + +* Add support for parsing "optional variance annotations" from TypeScript 4.7 ([#2102](https://github.com/evanw/esbuild/pull/2102)) + + The upcoming version of TypeScript now lets you specify `in` and/or `out` on certain type parameters (specifically only on a type alias, an interface declaration, or a class declaration). These modifiers control type paramemter covariance and contravariance: + + ```ts + type Provider = () => T; + type Consumer = (x: T) => void; + type Mapper = (x: T) => U; + type Processor = (x: T) => T; + ``` + + With this release, esbuild can now parse these new type parameter modifiers. This feature was contributed by [@magic-akari](https://github.com/magic-akari). + ## 0.14.30 * Change the context of TypeScript parameter decorators ([#2147](https://github.com/evanw/esbuild/issues/2147))