From 6b2d248339bcae69778142ba1b44d0da5e8a28f4 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Thu, 4 Apr 2024 18:07:45 -0700 Subject: [PATCH 1/3] tests --- test/transpiler/transpiler.test.js | 106 +++++++++++------------------ 1 file changed, 40 insertions(+), 66 deletions(-) diff --git a/test/transpiler/transpiler.test.js b/test/transpiler/transpiler.test.js index e5da47813755b..56aa1336e1e4a 100644 --- a/test/transpiler/transpiler.test.js +++ b/test/transpiler/transpiler.test.js @@ -54,10 +54,9 @@ describe("Bun.Transpiler", () => { } catch (er) { var err = er; if (er instanceof AggregateError) { - err = err.errors[0]; + err = er.errors[0]; } - - expect(er.message).toBe(message); + expect(err.message).toBe(message); return; } @@ -113,6 +112,16 @@ describe("Bun.Transpiler", () => { ); }); + it("should parse empty type parameters", () => { + const exp = ts.expectPrinted_; + const err = ts.expectParseError; + exp("type X<> = never;var x: X", "var x"); + exp("interface X<> {};var x: X", "var x"); + err("class Foo<> {}", 'Expected identifier but found ">"'); + err("function foo<>(): void {}", 'Expected identifier but found ">"'); + err("const x: Foo<> = {}", "Unexpected >"); + }); + it.todo("instantiation expressions", async () => { const exp = ts.expectPrinted_; const err = ts.expectParseError; @@ -305,14 +314,10 @@ describe("Bun.Transpiler", () => { exp("let x: [infer: string]", "let x;\n"); err("let x: A extends B ? keyof : string", "Unexpected :"); err("let x: A extends B ? readonly : string", "Unexpected :"); - // err("let x: A extends B ? infer : string", 'Expected identifier but found ":"\n'); - err("let x: A extends B ? infer : string", "Parse error"); - // err("let x: {[new: string]: number}", 'Expected "(" but found ":"\n'); - err("let x: {[new: string]: number}", "Parse error"); - // err("let x: {[import: string]: number}", 'Expected "(" but found ":"\n'); - err("let x: {[import: string]: number}", "Parse error"); - // err("let x: {[typeof: string]: number}", 'Expected identifier but found ":"\n'); - err("let x: {[typeof: string]: number}", "Parse error"); + err("let x: A extends B ? infer : string", 'Expected identifier but found ":"'); + err("let x: {[new: string]: number}", 'Expected "(" but found ":"'); + err("let x: {[import: string]: number}", 'Expected "(" but found ":"'); + err("let x: {[typeof: string]: number}", 'Expected identifier but found ":"'); exp("let x: () => void = Foo", "let x = Foo;\n"); exp("let x: new () => void = Foo", "let x = Foo;\n"); exp("let x = 'x' as keyof T", 'let x = "x";\n'); @@ -392,11 +397,9 @@ describe("Bun.Transpiler", () => { exp("let foo = bar as (null)", "let foo = bar;\n"); exp("let foo = bar\nas (null)", "let foo = bar;\nas(null);\n"); - // err("let foo = (bar\nas (null))", 'Expected ")" but found "as"'); - err("let foo = (bar\nas (null))", "Parse error"); + err("let foo = (bar\nas (null))", 'Expected ")" but found "as"'); exp("a as any ? b : c;", "a ? b : c;\n"); - // exp("a as any ? async () => b : c;", "a ? async () => b : c;\n"); exp("a as any ? async () => b : c;", "a || c;\n"); exp("foo as number extends Object ? any : any;", "foo;\n"); @@ -406,8 +409,7 @@ describe("Bun.Transpiler", () => { "let a = b ? c : d ? e : f;\n", ); err("type a = b extends c", 'Expected "?" but found end of file'); - err("type a = b extends c extends d", "Parse error"); - // err("type a = b extends c extends d", 'Expected "?" but found "extends"'); + err("type a = b extends c extends d", 'Expected "?" but found "extends"'); err("type a = b ? c : d", 'Expected ";" but found "?"'); exp("let foo: keyof Object = 'toString'", 'let foo = "toString";\n'); @@ -488,12 +490,9 @@ describe("Bun.Transpiler", () => { exp("let x: abstract new () => void = Foo", "let x = Foo;\n"); exp("let x: abstract new () => Foo", "let x;\n"); exp("let x: abstract new () => Foo", "let x;\n"); - // err("let x: abstract () => void = Foo", 'Expected ";" but found "("'); - err("let x: abstract () => void = Foo", "Parse error"); - // err("let x: abstract () => Foo", 'Expected ";" but found "("'); - err("let x: abstract () => Foo", "Parse error"); - // err("let x: abstract () => Foo", 'Expected "?" but found ">"'); - err("let x: abstract () => Foo", "Parse error"); + err("let x: abstract () => void = Foo", 'Expected ";" but found "("'); + err("let x: abstract () => Foo", 'Expected ";" but found "("'); + err("let x: abstract () => Foo", 'Expected "?" but found ">"'); // TypeScript 4.7 // jsxErrorArrow := "The character \">\" is not valid inside a JSX element\n" + @@ -506,17 +505,12 @@ describe("Bun.Transpiler", () => { exp("type Foo = [X, Y]", ""); exp("type Foo = [X, Y]", ""); exp("type Foo = [X, Y]", ""); - // err( "type Foo = T", "Expected identifier but found \"i\\\\u006E\"\n") - err("type Foo = T", "Parse error"); - // err( "type Foo = T", "Expected \">\" but found \"T\"\n") - err("type Foo = T", "Parse error"); - // err( "type Foo = T", "The modifier \"in\" is not valid here:\nExpected identifier but found \">\"\n") - err("type Foo = T", "Parse error"); - // err( "type Foo = T", "The modifier \"in\" is not valid here:\nExpected identifier but found \">\"\n") - err("type Foo = T", "Parse error"); + err("type Foo = T", 'Expected identifier but found "i\\u006E"'); + err("type Foo = T", 'Expected ">" but found "T"'); + err("type Foo = T", 'The modifier "in" is not valid here'); + err("type Foo = T", 'The modifier "in" is not valid here'); err("type Foo = T", 'The modifier "in" is not valid here'); - // err( "type Foo = T", "Expected \">\" but found \"T\"\n") - err("type Foo = T", "Parse error"); + err("type Foo = T", 'Expected ">" but found "T"'); err("type Foo = T", 'The modifier "in" is not valid here'); err("type Foo = T", 'The modifier "out" is not valid here'); exp("class Foo {}", "class Foo {\n}"); @@ -537,16 +531,12 @@ describe("Bun.Transpiler", () => { err("export default function foo() {}", 'The modifier "out" is not valid here'); err("export default function () {}", 'The modifier "in" is not valid here'); err("export default function () {}", 'The modifier "out" is not valid here'); - // err("let foo: Foo", 'Unexpected "in"'); - err("let foo: Foo", "Parse error"); - // err("let foo: Foo", 'Expected ">" but found "T"'); - err("let foo: Foo", "Parse error"); + err("let foo: Foo", "Unexpected in"); + err("let foo: Foo", 'Expected ">" but found "T"'); err("declare function foo()", 'The modifier "in" is not valid here'); err("declare function foo()", 'The modifier "out" is not valid here'); - // err("declare let foo: Foo", 'Unexpected "in"'); - err("declare let foo: Foo", "Parse error"); - // err("declare let foo: Foo", 'Expected ">" but found "T"'); - err("declare let foo: Foo", "Parse error"); + err("declare let foo: Foo", "Unexpected in"); + err("declare let foo: Foo", 'Expected ">" but found "T"'); exp("Foo = class {}", "Foo = class {\n}"); exp("Foo = class {}", "Foo = class {\n}"); exp("Foo = class Bar {}", "Foo = class Bar {\n}"); @@ -559,27 +549,17 @@ describe("Bun.Transpiler", () => { err("foo = { foo(): T {} }", 'The modifier "out" is not valid here'); err("() => {}", 'The modifier "in" is not valid here'); err("() => {}", 'The modifier "out" is not valid here'); - err("() => {}", "Parse error"); - // err("() => {}", 'The modifier "in" is not valid here:\nThe modifier "out" is not valid here'); + err("() => {}", 'The modifier "in" is not valid here'); err("let x: () => {}", 'The modifier "in" is not valid here'); err("let x: () => {}", 'The modifier "out" is not valid here'); - err("let x: () => {}", "Parse error"); - // err("let x: () => {}", 'The modifier "in" is not valid here:\nThe modifier "out" is not valid here'); + err("let x: () => {}", 'The modifier "in" is not valid here'); err("let x: new () => {}", 'The modifier "in" is not valid here'); err("let x: new () => {}", 'The modifier "out" is not valid here'); - err("let x: new () => {}", "Parse error"); + err("let x: new () => {}", 'The modifier "in" is not valid here'); - // err( - // "let x: new () => {}", - // 'The modifier "in" is not valid here:\nThe modifier "out" is not valid here', - // ); err("let x: { y(): any }", 'The modifier "in" is not valid here'); err("let x: { y(): any }", 'The modifier "out" is not valid here'); - // err( - // "let x: { y(): any }", - // 'The modifier "in" is not valid here:\nThe modifier "out" is not valid here', - // ); - err("let x: new () => {}", "Parse error"); + err("let x: new () => {}", 'The modifier "in" is not valid here'); // expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"in\", { T: true });\n") // expectPrintedTSX(t, "", "/* @__PURE__ */ React.createElement(\"out\", { T: true });\n") @@ -623,12 +603,9 @@ describe("Bun.Transpiler", () => { exp("let x: new () => T = y", "let x = y;\n"); err("type Foo = T", 'The modifier "const" is not valid here'); err("interface Foo {}", 'The modifier "const" is not valid here'); - err("let x: () => {}", "Parse error"); - // err("let x: () => {}", 'Expected identifier but found ">"'); - err("let x: new () => {}", "Parse error"); - // err("let x: new () => {}", 'Expected identifier but found ">"'); - // err("let x: Foo", 'Expected ">" but found "T"'); - err("let x: Foo", "Parse error"); + err("let x: () => {}", 'Expected identifier but found ">"'); + err("let x: new () => {}", 'Expected identifier but found ">"'); + err("let x: Foo", 'Expected ">" but found "T"'); err("x = (y)", 'Expected "=>" but found end of file'); err("x = (y)", 'Expected "=>" but found end of file'); err("x = (y)", 'Expected "=>" but found end of file'); @@ -641,12 +618,9 @@ describe("Bun.Transpiler", () => { exp("class Foo {}", "class Foo {\n}"); exp("class Foo {}", "class Foo {\n}"); exp("class Foo {}", "class Foo {\n}"); - // err("class Foo {}", 'Expected identifier but found ">"'); - err("class Foo {}", "Parse error"); - // err("class Foo {}", 'Expected identifier but found ">"'); - err("class Foo {}", "Parse error"); - // err("class Foo {}", 'Expected identifier but found ">"'); - err("class Foo {}", "Parse error"); + err("class Foo {}", 'Expected identifier but found ">"'); + err("class Foo {}", 'Expected identifier but found ">"'); + err("class Foo {}", 'Expected identifier but found ">"'); // expectPrintedTSX(t, "(x)", '/* @__PURE__ */ React.createElement("const", null, "(x)");\n'); // expectPrintedTSX(t, "", '/* @__PURE__ */ React.createElement("const", { const: true });\n'); // expectPrintedTSX(t, "", '/* @__PURE__ */ React.createElement("const", { const: true });\n'); From 594e63690f9f12cfa3213ccdaa503b4d165ff74b Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Thu, 4 Apr 2024 18:10:06 -0700 Subject: [PATCH 2/3] allow_empty_type_parameters --- src/js_parser.zig | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/js_parser.zig b/src/js_parser.zig index 3b971c7d723eb..e3fa7ae638046 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -97,10 +97,8 @@ const TypeParameterFlag = packed struct { /// TypeScript 5.0 allow_const_modifier: bool = false, - pub const all = TypeParameterFlag{ - .allow_in_out_variance_annotations = true, - .allow_const_modifier = true, - }; + /// Allow "<>" without any type parameters + allow_empty_type_parameters: bool = false, }; const JSXImport = enum { @@ -8877,6 +8875,11 @@ fn NewParser_( var result = SkipTypeParameterResult.could_be_type_cast; try p.lexer.next(); + if (p.lexer.token == .t_greater_than and flags.allow_empty_type_parameters) { + try p.lexer.next(); + return .definitely_type_parameters; + } + while (true) { var has_in = false; var has_out = false; @@ -10524,7 +10527,10 @@ fn NewParser_( p.local_type_names.put(p.allocator, name, true) catch unreachable; } - _ = try p.skipTypeScriptTypeParameters(.{ .allow_in_out_variance_annotations = true }); + _ = try p.skipTypeScriptTypeParameters(.{ + .allow_in_out_variance_annotations = true, + .allow_empty_type_parameters = true, + }); try p.lexer.expect(.t_equals); try p.skipTypeScriptType(.lowest); @@ -10652,7 +10658,10 @@ fn NewParser_( p.local_type_names.put(p.allocator, name, true) catch unreachable; } - _ = try p.skipTypeScriptTypeParameters(.{ .allow_in_out_variance_annotations = true }); + _ = try p.skipTypeScriptTypeParameters(.{ + .allow_in_out_variance_annotations = true, + .allow_empty_type_parameters = true, + }); if (p.lexer.token == .t_extends) { try p.lexer.next(); From 92451b6d7c59f7f1c2f53a1f4fe5383a664f7a91 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Thu, 4 Apr 2024 18:51:56 -0700 Subject: [PATCH 3/3] pass options through unions and intersections --- src/js_parser.zig | 44 ++++++++++++++++-------------- test/transpiler/transpiler.test.js | 6 ++++ 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/js_parser.zig b/src/js_parser.zig index e3fa7ae638046..1449d04ff8863 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -7771,8 +7771,7 @@ fn NewParser_( } pub inline fn skipTypescriptReturnType(p: *P) anyerror!void { - var result = TypeScript.Metadata.default; - try p.skipTypeScriptTypeWithOpts(.lowest, TypeScript.SkipTypeOptions.Bitset.initOne(.is_return_type), false, &result); + try p.skipTypeScriptTypeWithOpts(.lowest, TypeScript.SkipTypeOptions.Bitset.initOne(.is_return_type), false, {}); } pub inline fn skipTypescriptReturnTypeWithMetadata(p: *P) anyerror!TypeScript.Metadata { @@ -7806,8 +7805,7 @@ fn NewParser_( inline fn skipTypeScriptType(p: *P, level: js_ast.Op.Level) anyerror!void { p.markTypeScriptOnly(); - var result = TypeScript.Metadata.default; - try p.skipTypeScriptTypeWithOpts(level, TypeScript.SkipTypeOptions.empty, false, &result); + try p.skipTypeScriptTypeWithOpts(level, TypeScript.SkipTypeOptions.empty, false, {}); } inline fn skipTypeScriptTypeWithMetadata(p: *P, level: js_ast.Op.Level) anyerror!TypeScript.Metadata { @@ -7959,7 +7957,11 @@ fn NewParser_( // let x = (y: any): (y) => {return 0}; // let x = (y: any): asserts y is (y) => {}; // - fn skipTypeScriptParenOrFnType(p: *P, comptime get_metadata: bool, result: *TypeScript.Metadata) anyerror!void { + fn skipTypeScriptParenOrFnType( + p: *P, + comptime get_metadata: bool, + result: if (get_metadata) *TypeScript.Metadata else void, + ) anyerror!void { p.markTypeScriptOnly(); if (p.trySkipTypeScriptArrowArgsWithBacktracking()) { @@ -7982,7 +7984,7 @@ fn NewParser_( level: js_ast.Op.Level, opts: TypeScript.SkipTypeOptions.Bitset, comptime get_metadata: bool, - result: *TypeScript.Metadata, + result: if (get_metadata) *TypeScript.Metadata else void, ) anyerror!void { p.markTypeScriptOnly(); @@ -8350,8 +8352,7 @@ fn NewParser_( if (p.lexer.token == .t_dot_dot_dot) { try p.lexer.next(); } - var dummy_result = TypeScript.Metadata.default; - try p.skipTypeScriptTypeWithOpts(.lowest, TypeScript.SkipTypeOptions.Bitset.initOne(.allow_tuple_labels), false, &dummy_result); + try p.skipTypeScriptTypeWithOpts(.lowest, TypeScript.SkipTypeOptions.Bitset.initOne(.allow_tuple_labels), false, {}); if (p.lexer.token == .t_question) { try p.lexer.next(); } @@ -8424,13 +8425,13 @@ fn NewParser_( if (left.finishUnion(p)) |final| { // finish skipping the rest of the type without collecting type metadata. result.* = final; - try p.skipTypeScriptType(.bitwise_or); + try p.skipTypeScriptTypeWithOpts(.bitwise_or, opts, false, {}); } else { - try p.skipTypeScriptTypeWithOpts(.bitwise_or, TypeScript.SkipTypeOptions.empty, get_metadata, result); + try p.skipTypeScriptTypeWithOpts(.bitwise_or, opts, get_metadata, result); result.mergeUnion(left); } } else { - try p.skipTypeScriptType(.bitwise_or); + try p.skipTypeScriptTypeWithOpts(.bitwise_or, opts, false, {}); } }, .t_ampersand => { @@ -8445,13 +8446,13 @@ fn NewParser_( if (left.finishIntersection(p)) |final| { // finish skipping the rest of the type without collecting type metadata. result.* = final; - try p.skipTypeScriptType(.bitwise_and); + try p.skipTypeScriptTypeWithOpts(.bitwise_and, opts, false, {}); } else { - try p.skipTypeScriptTypeWithOpts(.bitwise_and, TypeScript.SkipTypeOptions.empty, get_metadata, result); + try p.skipTypeScriptTypeWithOpts(.bitwise_and, opts, get_metadata, result); result.mergeIntersection(left); } } else { - try p.skipTypeScriptType(.bitwise_and); + try p.skipTypeScriptTypeWithOpts(.bitwise_and, opts, false, {}); } }, .t_exclamation => { @@ -8529,8 +8530,13 @@ fn NewParser_( try p.lexer.next(); // The type following "extends" is not permitted to be another conditional type - var extends_type = TypeScript.Metadata.default; - try p.skipTypeScriptTypeWithOpts(.lowest, TypeScript.SkipTypeOptions.Bitset.initOne(.disallow_conditional_types), get_metadata, &extends_type); + var extends_type = if (get_metadata) TypeScript.Metadata.default else {}; + try p.skipTypeScriptTypeWithOpts( + .lowest, + TypeScript.SkipTypeOptions.Bitset.initOne(.disallow_conditional_types), + get_metadata, + if (get_metadata) &extends_type else {}, + ); if (comptime get_metadata) { // intersection @@ -8579,8 +8585,7 @@ fn NewParser_( if (p.lexer.token == .t_open_bracket) { // Index signature or computed property try p.lexer.next(); - var metadata = TypeScript.Metadata.default; - try p.skipTypeScriptTypeWithOpts(.lowest, TypeScript.SkipTypeOptions.Bitset.initOne(.is_index_signature), false, &metadata); + try p.skipTypeScriptTypeWithOpts(.lowest, TypeScript.SkipTypeOptions.Bitset.initOne(.is_index_signature), false, {}); // "{ [key: string]: number }" // "{ readonly [K in keyof T]: T[K] }" @@ -12461,8 +12466,7 @@ fn NewParser_( pub fn skipTypeScriptConstraintOfInferTypeWithBacktracking(p: *P, flags: TypeScript.SkipTypeOptions.Bitset) anyerror!bool { try p.lexer.expect(.t_extends); - var metadata = TypeScript.Metadata.default; - try p.skipTypeScriptTypeWithOpts(.prefix, TypeScript.SkipTypeOptions.Bitset.initOne(.disallow_conditional_types), false, &metadata); + try p.skipTypeScriptTypeWithOpts(.prefix, TypeScript.SkipTypeOptions.Bitset.initOne(.disallow_conditional_types), false, {}); if (!flags.contains(.disallow_conditional_types) and p.lexer.token == .t_question) { return error.Backtrack; diff --git a/test/transpiler/transpiler.test.js b/test/transpiler/transpiler.test.js index 56aa1336e1e4a..218081f6746e8 100644 --- a/test/transpiler/transpiler.test.js +++ b/test/transpiler/transpiler.test.js @@ -122,6 +122,12 @@ describe("Bun.Transpiler", () => { err("const x: Foo<> = {}", "Unexpected >"); }); + it("should parse infer extends ternary correctly #9959", () => { + ts.expectPrinted_("type Foo = T extends infer U ? U : never;", ""); + ts.expectPrinted_("var foo: Foo extends string | infer Foo extends string ? Foo : never", "var foo"); + ts.expectPrinted_("var foo: Foo extends string & infer Foo extends string ? Foo : never", "var foo"); + }); + it.todo("instantiation expressions", async () => { const exp = ts.expectPrinted_; const err = ts.expectParseError;