diff options
author | 2023-09-20 08:10:03 -0700 | |
---|---|---|
committer | 2023-09-20 08:10:03 -0700 | |
commit | 689b28455c943897a3d286271e6f182afb17848f (patch) | |
tree | 470132b70476e7ead623b4945ebaa72c27dfe839 | |
parent | 4439f1615571f3558704a7b48cfa84273be4221d (diff) | |
download | bun-689b28455c943897a3d286271e6f182afb17848f.tar.gz bun-689b28455c943897a3d286271e6f182afb17848f.tar.zst bun-689b28455c943897a3d286271e6f182afb17848f.zip |
add `emitDecoratorMetadata` (#5777)
* some progess
* needs more tests
* make tests easier to debug
* get metadata for constructor arg decorators
* fix some things
* merge `emitDecoratorMetadata` option
* remove `^`
* bundler tests and get option from tsconfig earlier
* remove spaces
* fix tests
-rw-r--r-- | src/bun.js/module_loader.zig | 2 | ||||
-rw-r--r-- | src/bundler.zig | 16 | ||||
-rw-r--r-- | src/bundler/bundle_v2.zig | 3 | ||||
-rw-r--r-- | src/js_ast.zig | 7 | ||||
-rw-r--r-- | src/js_parser.zig | 890 | ||||
-rw-r--r-- | src/options.zig | 1 | ||||
-rw-r--r-- | src/resolver/resolver.zig | 4 | ||||
-rw-r--r-- | src/resolver/tsconfig_json.zig | 9 | ||||
-rw-r--r-- | src/runtime.footer.bun.js | 5 | ||||
-rw-r--r-- | src/runtime.footer.js | 5 | ||||
-rw-r--r-- | src/runtime.footer.node.js | 5 | ||||
-rw-r--r-- | src/runtime.footer.with-refresh.js | 5 | ||||
-rw-r--r-- | src/runtime.js | 22 | ||||
-rw-r--r-- | src/runtime.zig | 18 | ||||
-rw-r--r-- | test/bundler/bundler_decorator_metadata.test.ts | 505 | ||||
-rw-r--r-- | test/package.json | 1 | ||||
-rw-r--r-- | test/transpiler/decorator-metadata.test.ts | 494 | ||||
-rw-r--r-- | test/tsconfig.json | 1 | ||||
-rw-r--r-- | tsconfig.json | 1 |
19 files changed, 1884 insertions, 110 deletions
diff --git a/src/bun.js/module_loader.zig b/src/bun.js/module_loader.zig index db8df00c2..135bd5e94 100644 --- a/src/bun.js/module_loader.zig +++ b/src/bun.js/module_loader.zig @@ -402,6 +402,7 @@ pub const RuntimeTranspilerStore = struct { .file_hash = hash, .macro_remappings = macro_remappings, .jsx = bundler.options.jsx, + .emit_decorator_metadata = bundler.options.emit_decorator_metadata, .virtual_source = null, .dont_bundle_twice = true, .allow_commonjs = true, @@ -1427,6 +1428,7 @@ pub const ModuleLoader = struct { .file_hash = hash, .macro_remappings = macro_remappings, .jsx = jsc_vm.bundler.options.jsx, + .emit_decorator_metadata = jsc_vm.bundler.options.emit_decorator_metadata, .virtual_source = virtual_source, .dont_bundle_twice = true, .allow_commonjs = true, diff --git a/src/bundler.zig b/src/bundler.zig index 76fbc02ee..15089e241 100644 --- a/src/bundler.zig +++ b/src/bundler.zig @@ -435,13 +435,14 @@ pub const Bundler = struct { ); if (auto_jsx) { - // If we don't explicitly pass JSX, try to get it from the root tsconfig - if (bundler.options.transform_options.jsx == null) { - // Most of the time, this will already be cached - if (bundler.resolver.readDirInfo(bundler.fs.top_level_dir) catch null) |root_dir| { - if (root_dir.tsconfig_json) |tsconfig| { + // Most of the time, this will already be cached + if (bundler.resolver.readDirInfo(bundler.fs.top_level_dir) catch null) |root_dir| { + if (root_dir.tsconfig_json) |tsconfig| { + // If we don't explicitly pass JSX, try to get it from the root tsconfig + if (bundler.options.transform_options.jsx == null) { bundler.options.jsx = tsconfig.jsx; } + bundler.options.emit_decorator_metadata = tsconfig.emit_decorator_metadata; } } } @@ -780,6 +781,7 @@ pub const Bundler = struct { .file_descriptor = file_descriptor, .file_hash = filepath_hash, .macro_remappings = bundler.options.macro_remap, + .emit_decorator_metadata = resolve_result.emit_decorator_metadata, .jsx = resolve_result.jsx, }, client_entry_point, @@ -899,6 +901,7 @@ pub const Bundler = struct { .file_hash = null, .macro_remappings = bundler.options.macro_remap, .jsx = resolve_result.jsx, + .emit_decorator_metadata = resolve_result.emit_decorator_metadata, }, client_entry_point_, ) orelse { @@ -1188,6 +1191,7 @@ pub const Bundler = struct { replace_exports: runtime.Runtime.Features.ReplaceableExport.Map = .{}, inject_jest_globals: bool = false, set_breakpoint_on_first_line: bool = false, + emit_decorator_metadata: bool = false, dont_bundle_twice: bool = false, allow_commonjs: bool = false, @@ -1300,7 +1304,9 @@ pub const Bundler = struct { jsx.parse = loader.isJSX(); var opts = js_parser.Parser.Options.init(jsx, loader); + opts.legacy_transform_require_to_import = bundler.options.allow_runtime and !bundler.options.target.isBun(); + opts.features.emit_decorator_metadata = this_parse.emit_decorator_metadata; opts.features.allow_runtime = bundler.options.allow_runtime; opts.features.set_breakpoint_on_first_line = this_parse.set_breakpoint_on_first_line; opts.features.trim_unused_imports = bundler.options.trim_unused_imports orelse loader.isTypeScript(); diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index 359272e5f..e5b73814c 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -2276,6 +2276,7 @@ pub const ParseTask = struct { tree_shaking: bool = false, known_target: ?options.Target = null, module_type: options.ModuleType = .unknown, + emit_decorator_metadata: bool = false, ctx: *BundleV2, package_version: string = "", @@ -2298,6 +2299,7 @@ pub const ParseTask = struct { .jsx = resolve_result.jsx, .source_index = source_index orelse Index.invalid, .module_type = resolve_result.module_type, + .emit_decorator_metadata = resolve_result.emit_decorator_metadata, .package_version = if (resolve_result.package_json) |package_json| package_json.version else "", }; } @@ -2600,6 +2602,7 @@ pub const ParseTask = struct { opts.features.minify_syntax = bundler.options.minify_syntax; opts.features.minify_identifiers = bundler.options.minify_identifiers; opts.features.should_fold_typescript_constant_expressions = opts.features.inlining or loader.isTypeScript(); + opts.features.emit_decorator_metadata = bundler.options.emit_decorator_metadata; opts.tree_shaking = if (source.index.isRuntime()) true else bundler.options.tree_shaking; opts.module_type = task.module_type; diff --git a/src/js_ast.zig b/src/js_ast.zig index 7811541f4..b9e34d279 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -26,6 +26,7 @@ const is_bindgen = std.meta.globalOption("bindgen", bool) orelse false; const ComptimeStringMap = bun.ComptimeStringMap; const JSPrinter = @import("./js_printer.zig"); const js_lexer = @import("./js_lexer.zig"); +const TypeScript = @import("./js_parser.zig").TypeScript; const ThreadlocalArena = @import("./mimalloc_arena.zig").Arena; /// This is the index to the automatically-generated part containing code that @@ -834,6 +835,8 @@ pub const G = struct { // This is omitted for class fields value: ?ExprNodeIndex = null, + ts_metadata: TypeScript.Metadata = .m_none, + pub const List = BabyList(Property); pub const Kind = enum(u3) { @@ -865,6 +868,8 @@ pub const G = struct { arguments_ref: ?Ref = null, flags: Flags.Function.Set = Flags.Function.None, + + return_ts_metadata: TypeScript.Metadata = .m_none, }; pub const Arg = struct { ts_decorators: ExprNodeList = ExprNodeList{}, @@ -873,6 +878,8 @@ pub const G = struct { // "constructor(public x: boolean) {}" is_typescript_ctor_field: bool = false, + + ts_metadata: TypeScript.Metadata = .m_none, }; }; diff --git a/src/js_parser.zig b/src/js_parser.zig index a61657128..a9f346caf 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -70,7 +70,7 @@ const RefHashCtx = @import("./ast/base.zig").RefHashCtx; pub const StringHashMap = bun.StringHashMap; pub const AutoHashMap = std.AutoHashMap; -const StringHashMapUnamanged = bun.StringHashMapUnmanaged; +const StringHashMapUnmanaged = bun.StringHashMapUnmanaged; const ObjectPool = @import("./pool.zig").ObjectPool; const NodeFallbackModules = @import("./node_fallbacks.zig"); @@ -439,6 +439,155 @@ pub const TypeScript = struct { }; } + pub const Metadata = union(enum) { + m_none: void, + + m_never: void, + m_unknown: void, + m_any: void, + m_void: void, + m_null: void, + m_undefined: void, + m_function: void, + m_array: void, + m_boolean: void, + m_string: void, + m_object: void, + m_number: void, + m_bigint: void, + m_symbol: void, + m_promise: void, + m_identifier: Ref, + m_dot: List(Ref), + + pub const default: @This() = .m_none; + + // the logic in finishUnion, mergeUnion, finishIntersection and mergeIntersection is + // translated from: + // https://github.com/microsoft/TypeScript/blob/e0a324b0503be479f2b33fd2e17c6e86c94d1297/src/compiler/transformers/typeSerializer.ts#L402 + + /// Return the final union type if possible, or return null to continue merging. + /// + /// If the current type is m_never, m_null, or m_undefined assign the current type + /// to m_none and return null to ensure it's always replaced by the next type. + pub fn finishUnion(current: *@This(), p: anytype) ?@This() { + return switch (current.*) { + .m_identifier => |ref| { + if (strings.eqlComptime(p.loadNameFromRef(ref), "Object")) { + return .m_object; + } + return null; + }, + + .m_unknown, + .m_any, + .m_object, + => .m_object, + + .m_never, + .m_null, + .m_undefined, + => { + current.* = .m_none; + return null; + }, + + else => null, + }; + } + + pub fn mergeUnion(result: *@This(), left: @This()) void { + if (left != .m_none) { + if (std.meta.activeTag(result.*) != std.meta.activeTag(left)) { + result.* = switch (result.*) { + .m_never, + .m_undefined, + .m_null, + => left, + + else => .m_object, + }; + } else { + switch (result.*) { + .m_identifier => |ref| { + if (!ref.eql(left.m_identifier)) { + result.* = .m_object; + } + }, + else => {}, + } + } + } else { + // always take the next value if left is m_none + } + } + + /// Return the final intersection type if possible, or return null to continue merging. + /// + /// If the current type is m_unknown, m_null, or m_undefined assign the current type + /// to m_none and return null to ensure it's always replaced by the next type. + pub fn finishIntersection(current: *@This(), p: anytype) ?@This() { + return switch (current.*) { + .m_identifier => |ref| { + if (strings.eqlComptime(p.loadNameFromRef(ref), "Object")) { + return .m_object; + } + return null; + }, + + // ensure m_never is the final type + .m_never => .m_never, + + .m_any, + .m_object, + => .m_object, + + .m_unknown, + .m_null, + .m_undefined, + => { + current.* = .m_none; + return null; + }, + + else => null, + }; + } + + pub fn mergeIntersection(result: *@This(), left: @This()) void { + if (left != .m_none) { + if (std.meta.activeTag(result.*) != std.meta.activeTag(left)) { + result.* = switch (result.*) { + .m_unknown, + .m_undefined, + .m_null, + => left, + + // ensure m_never is the final type + .m_never => .m_never, + + else => .m_object, + }; + } else { + switch (result.*) { + .m_identifier => |ref| { + if (!ref.eql(left.m_identifier)) { + result.* = .m_object; + } + }, + else => {}, + } + } + } else { + // make sure intersection of only m_unknown serializes to "undefined" + // instead of "Object" + if (result.* == .m_unknown) { + result.* = .m_undefined; + } + } + } + }; + pub fn isTSArrowFnJSX(p: anytype) !bool { var oldLexer = std.mem.toBytes(p.lexer); @@ -664,19 +813,19 @@ pub const TypeScript = struct { .{ "abstract", .abstract }, .{ "asserts", .asserts }, - .{ "keyof", .prefix }, - .{ "readonly", .prefix }, + .{ "keyof", .prefix_keyof }, + .{ "readonly", .prefix_readonly }, - .{ "any", .primitive }, - .{ "never", .primitive }, - .{ "unknown", .primitive }, - .{ "undefined", .primitive }, - .{ "object", .primitive }, - .{ "number", .primitive }, - .{ "string", .primitive }, - .{ "boolean", .primitive }, - .{ "bigint", .primitive }, - .{ "symbol", .primitive }, + .{ "any", .primitive_any }, + .{ "never", .primitive_never }, + .{ "unknown", .primitive_unknown }, + .{ "undefined", .primitive_undefined }, + .{ "object", .primitive_object }, + .{ "number", .primitive_number }, + .{ "string", .primitive_string }, + .{ "boolean", .primitive_boolean }, + .{ "bigint", .primitive_bigint }, + .{ "symbol", .primitive_symbol }, .{ "infer", .infer }, }); @@ -685,17 +834,30 @@ pub const TypeScript = struct { unique, abstract, asserts, - prefix, - primitive, + prefix_keyof, + prefix_readonly, + primitive_any, + primitive_never, + primitive_unknown, + primitive_undefined, + primitive_object, + primitive_number, + primitive_string, + primitive_boolean, + primitive_bigint, + primitive_symbol, infer, }; }; - pub const SkipTypeOptions = struct { - is_return_type: bool = false, - is_index_signature: bool = false, - allow_tuple_labels: bool = false, - disallow_conditional_types: bool = false, + pub const SkipTypeOptions = enum { + is_return_type, + is_index_signature, + allow_tuple_labels, + disallow_conditional_types, + + pub const Bitset = std.enums.EnumSet(@This()); + pub const empty = Bitset.initEmpty(); }; }; @@ -1398,8 +1560,9 @@ const StaticSymbolName = struct { pub const __HMRClient = NewStaticSymbol("Bun"); pub const __FastRefreshModule = NewStaticSymbol("FastHMR"); pub const __FastRefreshRuntime = NewStaticSymbol("FastRefresh"); - pub const __decorateClass = NewStaticSymbol("__decorateClass"); - pub const __decorateParam = NewStaticSymbol("__decorateParam"); + pub const __legacyDecorateClassTS = NewStaticSymbol("__legacyDecorateClassTS"); + pub const __legacyDecorateParamTS = NewStaticSymbol("__legacyDecorateParamTS"); + pub const __legacyMetadataTS = NewStaticSymbol("__legacyMetadataTS"); pub const @"$$typeof" = NewStaticSymbol("$$typeof"); pub const @"$$m" = NewStaticSymbol("$$m"); @@ -2437,6 +2600,7 @@ const FnOrArrowDataParse = struct { is_typescript_declare: bool = false, has_argument_decorators: bool = false, + has_decorators: bool = false, is_return_disallowed: bool = false, is_this_disallowed: bool = false, @@ -2573,6 +2737,7 @@ const PropertyOpts = struct { is_ts_abstract: bool = false, ts_decorators: []Expr = &[_]Expr{}, has_argument_decorators: bool = false, + has_class_decorators: bool = false, }; pub const ScanPassResult = struct { @@ -4304,7 +4469,7 @@ fn NewParser_( emitted_namespace_vars: RefMap = RefMap{}, is_exported_inside_namespace: RefRefMap = .{}, - known_enum_values: Map(Ref, StringHashMapUnamanged(f64)) = .{}, + known_enum_values: Map(Ref, StringHashMapUnmanaged(f64)) = .{}, local_type_names: StringBoolMap = StringBoolMap{}, // This is the reference to the generated function argument for the namespace, @@ -6737,7 +6902,7 @@ fn NewParser_( try p.lexer.next(); if (p.lexer.token == T.t_colon) { try p.lexer.next(); - try p.skipTypeScriptType(js_ast.Op.Level.lowest); + try p.skipTypeScriptType(.lowest); } if (p.lexer.token != T.t_comma) { break; @@ -6765,6 +6930,7 @@ fn NewParser_( var is_identifier = p.lexer.token == T.t_identifier; var text = p.lexer.identifier; var arg = try p.parseBinding(); + var ts_metadata = TypeScript.Metadata.default; if (comptime is_typescript_enabled) { if (is_identifier and opts.is_constructor) { @@ -6804,7 +6970,11 @@ fn NewParser_( // "function foo(a: any) {}" if (p.lexer.token == .t_colon) { try p.lexer.next(); - try p.skipTypeScriptType(.lowest); + if (p.options.features.emit_decorator_metadata and opts.allow_ts_decorators and (opts.has_argument_decorators or opts.has_decorators or arg_has_decorators)) { + ts_metadata = try p.skipTypeScriptTypeWithMetadata(.lowest); + } else { + try p.skipTypeScriptType(.lowest); + } } } @@ -6825,6 +6995,7 @@ fn NewParser_( // We need to track this because it affects code generation .is_typescript_ctor_field = is_typescript_ctor_field, + .ts_metadata = ts_metadata, }) catch unreachable; if (p.lexer.token != .t_comma) { @@ -6864,9 +7035,18 @@ fn NewParser_( p.fn_or_arrow_data_parse.has_argument_decorators = arg_has_decorators; // "function foo(): any {}" - if (is_typescript_enabled and p.lexer.token == .t_colon) { - try p.lexer.next(); - try p.skipTypescriptReturnType(); + if (is_typescript_enabled) { + if (p.lexer.token == .t_colon) { + try p.lexer.next(); + + if (p.options.features.emit_decorator_metadata and opts.allow_ts_decorators and (opts.has_argument_decorators or opts.has_decorators)) { + func.return_ts_metadata = try p.skipTypescriptReturnTypeWithMetadata(); + } else { + try p.skipTypescriptReturnType(); + } + } else if (func.flags.contains(.is_async) and p.options.features.emit_decorator_metadata and opts.allow_ts_decorators and (opts.has_argument_decorators or opts.has_decorators)) { + func.return_ts_metadata = .m_promise; + } } // "function foo(): any;" @@ -6881,10 +7061,15 @@ fn NewParser_( return func; } - // pub fn parseBinding(p: *P) - pub inline fn skipTypescriptReturnType(p: *P) anyerror!void { - try p.skipTypeScriptTypeWithOpts(.lowest, .{ .is_return_type = true }); + var result = TypeScript.Metadata.default; + try p.skipTypeScriptTypeWithOpts(.lowest, TypeScript.SkipTypeOptions.Bitset.initOne(.is_return_type), false, &result); + } + + pub inline fn skipTypescriptReturnTypeWithMetadata(p: *P) anyerror!TypeScript.Metadata { + var result = TypeScript.Metadata.default; + try p.skipTypeScriptTypeWithOpts(.lowest, TypeScript.SkipTypeOptions.Bitset.initOne(.is_return_type), true, &result); + return result; } pub fn parseTypeScriptDecorators(p: *P) ![]ExprNodeIndex { @@ -6912,7 +7097,15 @@ fn NewParser_( inline fn skipTypeScriptType(p: *P, level: js_ast.Op.Level) anyerror!void { p.markTypeScriptOnly(); - try p.skipTypeScriptTypeWithOpts(level, .{}); + var result = TypeScript.Metadata.default; + try p.skipTypeScriptTypeWithOpts(level, TypeScript.SkipTypeOptions.empty, false, &result); + } + + inline fn skipTypeScriptTypeWithMetadata(p: *P, level: js_ast.Op.Level) anyerror!TypeScript.Metadata { + p.markTypeScriptOnly(); + var result = TypeScript.Metadata.default; + try p.skipTypeScriptTypeWithOpts(level, TypeScript.SkipTypeOptions.empty, true, &result); + return result; } fn skipTypeScriptBinding(p: *P) anyerror!void { @@ -7057,41 +7250,77 @@ fn NewParser_( // let x = (y: any): (y) => {return 0}; // let x = (y: any): asserts y is (y) => {}; // - fn skipTypeScriptParenOrFnType(p: *P) anyerror!void { + fn skipTypeScriptParenOrFnType(p: *P, comptime get_metadata: bool, result: *TypeScript.Metadata) anyerror!void { p.markTypeScriptOnly(); if (p.trySkipTypeScriptArrowArgsWithBacktracking()) { try p.skipTypescriptReturnType(); + if (comptime get_metadata) + result.* = .m_function; } else { try p.lexer.expect(.t_open_paren); - try p.skipTypeScriptType(.lowest); + if (comptime get_metadata) { + result.* = try p.skipTypeScriptTypeWithMetadata(.lowest); + } else { + try p.skipTypeScriptType(.lowest); + } try p.lexer.expect(.t_close_paren); } } - fn skipTypeScriptTypeWithOpts(p: *P, level: js_ast.Op.Level, opts: TypeScript.SkipTypeOptions) anyerror!void { + fn skipTypeScriptTypeWithOpts( + p: *P, + level: js_ast.Op.Level, + opts: TypeScript.SkipTypeOptions.Bitset, + comptime get_metadata: bool, + result: *TypeScript.Metadata, + ) anyerror!void { p.markTypeScriptOnly(); while (true) { switch (p.lexer.token) { - .t_numeric_literal, - .t_big_integer_literal, - .t_string_literal, - .t_no_substitution_template_literal, - .t_true, - .t_false, - .t_null, - .t_void, - => { + .t_numeric_literal => { try p.lexer.next(); + if (comptime get_metadata) { + result.* = .m_number; + } + }, + .t_big_integer_literal => { + try p.lexer.next(); + if (comptime get_metadata) { + result.* = .m_bigint; + } + }, + .t_string_literal, .t_no_substitution_template_literal => { + try p.lexer.next(); + if (comptime get_metadata) { + result.* = .m_string; + } + }, + .t_true, .t_false => { + try p.lexer.next(); + if (comptime get_metadata) { + result.* = .m_boolean; + } + }, + .t_null => { + try p.lexer.next(); + if (comptime get_metadata) { + result.* = .m_null; + } + }, + .t_void => { + try p.lexer.next(); + if (comptime get_metadata) { + result.* = .m_void; + } }, - .t_const => { const r = p.lexer.range(); try p.lexer.next(); // ["const: number]" - if (opts.allow_tuple_labels and p.lexer.token == .t_colon) { + if (opts.contains(.allow_tuple_labels) and p.lexer.token == .t_colon) { try p.log.addRangeError(p.source, r, "Unexpected \"const\""); } }, @@ -7105,6 +7334,10 @@ fn NewParser_( try p.skipTypeScriptType(.lowest); return; } + + if (comptime get_metadata) { + result.* = .m_object; + } }, .t_minus => { // "-123" @@ -7113,8 +7346,14 @@ fn NewParser_( if (p.lexer.token == .t_big_integer_literal) { try p.lexer.next(); + if (comptime get_metadata) { + result.* = .m_bigint; + } } else { try p.lexer.expect(.t_numeric_literal); + if (comptime get_metadata) { + result.* = .m_number; + } } }, .t_ampersand, .t_bar => { @@ -7127,7 +7366,7 @@ fn NewParser_( try p.lexer.next(); // "[import: number]" - if (opts.allow_tuple_labels and p.lexer.token == .t_colon) { + if (opts.contains(.allow_tuple_labels) and p.lexer.token == .t_colon) { return; } @@ -7155,21 +7394,21 @@ fn NewParser_( try p.lexer.next(); // "[new: number]" - if (opts.allow_tuple_labels and p.lexer.token == .t_colon) { + if (opts.contains(.allow_tuple_labels) and p.lexer.token == .t_colon) { return; } _ = try p.skipTypeScriptTypeParameters(.{ .allow_const_modifier = true }); - try p.skipTypeScriptParenOrFnType(); + try p.skipTypeScriptParenOrFnType(get_metadata, result); }, .t_less_than => { // "<T>() => Foo<T>" _ = try p.skipTypeScriptTypeParameters(.{ .allow_const_modifier = true }); - try p.skipTypeScriptParenOrFnType(); + try p.skipTypeScriptParenOrFnType(get_metadata, result); }, .t_open_paren => { // "(number | string)" - try p.skipTypeScriptParenOrFnType(); + try p.skipTypeScriptParenOrFnType(get_metadata, result); }, .t_identifier => { const kind = TypeScript.Identifier.IMap.get(p.lexer.identifier) orelse .normal; @@ -7177,7 +7416,7 @@ fn NewParser_( var check_type_parameters = true; switch (kind) { - .prefix => { + .prefix_keyof => { try p.lexer.next(); // Valid: @@ -7188,10 +7427,28 @@ fn NewParser_( // Invalid: // "A extends B ? keyof : string" // - if ((p.lexer.token != .t_colon and p.lexer.token != .t_in) or (!opts.is_index_signature and !opts.allow_tuple_labels)) { + if ((p.lexer.token != .t_colon and p.lexer.token != .t_in) or (!opts.contains(.is_index_signature) and !opts.contains(.allow_tuple_labels))) { try p.skipTypeScriptType(.prefix); } + if (comptime get_metadata) { + result.* = .m_object; + } + + break; + }, + .prefix_readonly => { + try p.lexer.next(); + + if ((p.lexer.token != .t_colon and p.lexer.token != .t_in) or (!opts.contains(.is_index_signature) and !opts.contains(.allow_tuple_labels))) { + try p.skipTypeScriptType(.prefix); + } + + // assume array or tuple literal + if (comptime get_metadata) { + result.* = .m_array; + } + break; }, .infer => { @@ -7201,7 +7458,7 @@ fn NewParser_( // "type Foo = Bar extends [infer T extends string] ? T : null" // "type Foo = Bar extends [infer T extends string ? infer T : never] ? T : null" // "type Foo = { [infer in Bar]: number }" - if ((p.lexer.token != .t_colon and p.lexer.token != .t_in) or (!opts.is_index_signature and !opts.allow_tuple_labels)) { + if ((p.lexer.token != .t_colon and p.lexer.token != .t_in) or (!opts.contains(.is_index_signature) and !opts.contains(.allow_tuple_labels))) { try p.lexer.expect(.t_identifier); if (p.lexer.token == .t_extends) { _ = p.trySkipTypeScriptConstraintOfInferTypeWithBacktracking(opts); @@ -7232,15 +7489,90 @@ fn NewParser_( // "function assert(x: boolean): asserts x" // "function assert(x: boolean): asserts x is boolean" - if (opts.is_return_type and !p.lexer.has_newline_before and (p.lexer.token == .t_identifier or p.lexer.token == .t_this)) { + if (opts.contains(.is_return_type) and !p.lexer.has_newline_before and (p.lexer.token == .t_identifier or p.lexer.token == .t_this)) { try p.lexer.next(); } }, - .primitive => { + .primitive_any => { + try p.lexer.next(); + check_type_parameters = false; + if (comptime get_metadata) { + result.* = .m_any; + } + }, + .primitive_never => { try p.lexer.next(); check_type_parameters = false; + if (comptime get_metadata) { + result.* = .m_never; + } + }, + .primitive_unknown => { + try p.lexer.next(); + check_type_parameters = false; + if (comptime get_metadata) { + result.* = .m_unknown; + } + }, + .primitive_undefined => { + try p.lexer.next(); + check_type_parameters = false; + if (comptime get_metadata) { + result.* = .m_undefined; + } + }, + .primitive_object => { + try p.lexer.next(); + check_type_parameters = false; + if (comptime get_metadata) { + result.* = .m_object; + } + }, + .primitive_number => { + try p.lexer.next(); + check_type_parameters = false; + if (comptime get_metadata) { + result.* = .m_number; + } + }, + .primitive_string => { + try p.lexer.next(); + check_type_parameters = false; + if (comptime get_metadata) { + result.* = .m_string; + } + }, + .primitive_boolean => { + try p.lexer.next(); + check_type_parameters = false; + if (comptime get_metadata) { + result.* = .m_boolean; + } + }, + .primitive_bigint => { + try p.lexer.next(); + check_type_parameters = false; + if (comptime get_metadata) { + result.* = .m_bigint; + } + }, + .primitive_symbol => { + try p.lexer.next(); + check_type_parameters = false; + if (comptime get_metadata) { + result.* = .m_symbol; + } }, else => { + if (comptime get_metadata) { + const find_result = p.findSymbol(logger.Loc.Empty, p.lexer.identifier) catch unreachable; + if (p.known_enum_values.contains(find_result.ref)) { + result.* = .m_number; + } else { + result.* = .{ .m_identifier = find_result.ref }; + } + } + try p.lexer.next(); }, } @@ -7261,10 +7593,15 @@ fn NewParser_( try p.lexer.next(); // "[typeof: number]" - if (opts.allow_tuple_labels and p.lexer.token == .t_colon) { + if (opts.contains(.allow_tuple_labels) and p.lexer.token == .t_colon) { return; } + // always `Object` + if (comptime get_metadata) { + result.* = .m_object; + } + if (p.lexer.token == .t_import) { // "typeof import('fs')" continue; @@ -7296,13 +7633,16 @@ fn NewParser_( // "[first: number, second: string]" try p.lexer.next(); + if (comptime get_metadata) { + result.* = .m_array; + } + while (p.lexer.token != .t_close_bracket) { if (p.lexer.token == .t_dot_dot_dot) { try p.lexer.next(); } - try p.skipTypeScriptTypeWithOpts(.lowest, TypeScript.SkipTypeOptions{ - .allow_tuple_labels = true, - }); + var dummy_result = TypeScript.Metadata.default; + try p.skipTypeScriptTypeWithOpts(.lowest, TypeScript.SkipTypeOptions.Bitset.initOne(.allow_tuple_labels), false, &dummy_result); if (p.lexer.token == .t_question) { try p.lexer.next(); } @@ -7319,10 +7659,12 @@ fn NewParser_( }, .t_open_brace => { try p.skipTypeScriptObjectType(); + if (comptime get_metadata) { + result.* = .m_object; + } }, .t_template_head => { // "`${'a' | 'b'}-${'c' | 'd'}`" - while (true) { try p.lexer.next(); try p.skipTypeScriptType(.lowest); @@ -7333,11 +7675,14 @@ fn NewParser_( break; } } + if (comptime get_metadata) { + result.* = .m_string; + } }, else => { // "[function: number]" - if (opts.allow_tuple_labels and p.lexer.isIdentifierOrKeyword()) { + if (opts.contains(.allow_tuple_labels) and p.lexer.isIdentifierOrKeyword()) { if (p.lexer.token != .t_function) { try p.lexer.unexpected(); } @@ -7362,8 +7707,22 @@ fn NewParser_( if (level.gte(.bitwise_or)) { return; } + try p.lexer.next(); - try p.skipTypeScriptType(.bitwise_or); + + if (comptime get_metadata) { + var left = result.*; + if (left.finishUnion(p)) |final| { + // finish skipping the rest of the type without collecting type metadata. + result.* = final; + try p.skipTypeScriptType(.bitwise_or); + } else { + try p.skipTypeScriptTypeWithOpts(.bitwise_or, TypeScript.SkipTypeOptions.empty, get_metadata, result); + result.mergeUnion(left); + } + } else { + try p.skipTypeScriptType(.bitwise_or); + } }, .t_ampersand => { if (level.gte(.bitwise_and)) { @@ -7371,7 +7730,20 @@ fn NewParser_( } try p.lexer.next(); - try p.skipTypeScriptType(.bitwise_and); + + if (comptime get_metadata) { + var left = result.*; + if (left.finishIntersection(p)) |final| { + // finish skipping the rest of the type without collecting type metadata. + result.* = final; + try p.skipTypeScriptType(.bitwise_and); + } else { + try p.skipTypeScriptTypeWithOpts(.bitwise_and, TypeScript.SkipTypeOptions.empty, get_metadata, result); + result.mergeIntersection(left); + } + } else { + try p.skipTypeScriptType(.bitwise_and); + } }, .t_exclamation => { // A postfix "!" is allowed in JSDoc types in TypeScript, which are only @@ -7390,6 +7762,22 @@ fn NewParser_( if (!p.lexer.isIdentifierOrKeyword()) { try p.lexer.expect(.t_identifier); } + + if (comptime get_metadata) { + if (result.* == .m_identifier) { + var dot = List(Ref).initCapacity(p.allocator, 2) catch unreachable; + dot.appendAssumeCapacity(result.m_identifier); + const find_result = p.findSymbol(logger.Loc.Empty, p.lexer.identifier) catch unreachable; + dot.appendAssumeCapacity(find_result.ref); + result.* = .{ .m_dot = dot }; + } else if (result.* == .m_dot) { + if (p.lexer.isIdentifierOrKeyword()) { + const find_result = p.findSymbol(logger.Loc.Empty, p.lexer.identifier) catch unreachable; + result.m_dot.append(p.allocator, find_result.ref) catch unreachable; + } + } + } + try p.lexer.next(); // "{ <A extends B>(): c.d \n <E extends F>(): g.h }" must not become a single type @@ -7403,26 +7791,56 @@ fn NewParser_( return; } try p.lexer.next(); + var skipped = false; if (p.lexer.token != .t_close_bracket) { + skipped = true; try p.skipTypeScriptType(.lowest); } try p.lexer.expect(.t_close_bracket); + + if (comptime get_metadata) { + if (result.* == .m_none) { + result.* = .m_array; + } else { + // if something was skipped, it is object type + if (skipped) { + result.* = .m_object; + } else { + result.* = .m_array; + } + } + } }, .t_extends => { // "{ x: number \n extends: boolean }" must not become a single type - if (p.lexer.has_newline_before or opts.disallow_conditional_types) { + if (p.lexer.has_newline_before or opts.contains(.disallow_conditional_types)) { return; } try p.lexer.next(); // The type following "extends" is not permitted to be another conditional type - try p.skipTypeScriptTypeWithOpts(.lowest, .{ .disallow_conditional_types = true }); + var extends_type = TypeScript.Metadata.default; + try p.skipTypeScriptTypeWithOpts(.lowest, TypeScript.SkipTypeOptions.Bitset.initOne(.disallow_conditional_types), get_metadata, &extends_type); - try p.lexer.expect(.t_question); - try p.skipTypeScriptType(.lowest); - try p.lexer.expect(.t_colon); - try p.skipTypeScriptType(.lowest); + if (comptime get_metadata) { + // intersection + try p.lexer.expect(.t_question); + var left = try p.skipTypeScriptTypeWithMetadata(.lowest); + try p.lexer.expect(.t_colon); + if (left.finishIntersection(p)) |final| { + result.* = final; + try p.skipTypeScriptType(.lowest); + } else { + try p.skipTypeScriptTypeWithOpts(.bitwise_and, TypeScript.SkipTypeOptions.empty, get_metadata, result); + result.mergeIntersection(left); + } + } else { + try p.lexer.expect(.t_question); + try p.skipTypeScriptType(.lowest); + try p.lexer.expect(.t_colon); + try p.skipTypeScriptType(.lowest); + } }, else => { return; @@ -7452,7 +7870,8 @@ fn NewParser_( if (p.lexer.token == .t_open_bracket) { // Index signature or computed property try p.lexer.next(); - try p.skipTypeScriptTypeWithOpts(.lowest, .{ .is_index_signature = true }); + var metadata = TypeScript.Metadata.default; + try p.skipTypeScriptTypeWithOpts(.lowest, TypeScript.SkipTypeOptions.Bitset.initOne(.is_index_signature), false, &metadata); // "{ [key: string]: number }" // "{ readonly [K in keyof T]: T[K] }" @@ -11122,13 +11541,12 @@ fn NewParser_( return result; } - pub fn skipTypeScriptConstraintOfInferTypeWithBacktracking(p: *P, flags: TypeScript.SkipTypeOptions) anyerror!bool { + pub fn skipTypeScriptConstraintOfInferTypeWithBacktracking(p: *P, flags: TypeScript.SkipTypeOptions.Bitset) anyerror!bool { try p.lexer.expect(.t_extends); - try p.skipTypeScriptTypeWithOpts(.prefix, TypeScript.SkipTypeOptions{ - .disallow_conditional_types = true, - }); + var metadata = TypeScript.Metadata.default; + try p.skipTypeScriptTypeWithOpts(.prefix, TypeScript.SkipTypeOptions.Bitset.initOne(.disallow_conditional_types), false, &metadata); - if (!flags.disallow_conditional_types and p.lexer.token == .t_question) { + if (!flags.contains(.disallow_conditional_types) and p.lexer.token == .t_question) { return error.Backtrack; } @@ -11181,7 +11599,7 @@ fn NewParser_( return Backtracking.lexerBacktracker(p, Backtracking.skipTypeScriptArrowArgsWithBacktracking, bool); } - pub fn trySkipTypeScriptConstraintOfInferTypeWithBacktracking(p: *P, flags: TypeScript.SkipTypeOptions) bool { + pub fn trySkipTypeScriptConstraintOfInferTypeWithBacktracking(p: *P, flags: TypeScript.SkipTypeOptions.Bitset) bool { return Backtracking.lexerBacktrackerWithArgs(p, Backtracking.skipTypeScriptConstraintOfInferTypeWithBacktracking, .{ p, flags }, bool); } @@ -11621,6 +12039,7 @@ fn NewParser_( (p.lexer.token != .t_open_paren or has_definite_assignment_assertion_operator)) { var initializer: ?Expr = null; + var ts_metadata = TypeScript.Metadata.default; // Forbid the names "constructor" and "prototype" in some cases if (!is_computed) { @@ -11639,7 +12058,11 @@ fn NewParser_( // Skip over types if (p.lexer.token == .t_colon) { try p.lexer.next(); - try p.skipTypeScriptType(.lowest); + if (p.options.features.emit_decorator_metadata and opts.is_class and opts.ts_decorators.len > 0) { + ts_metadata = try p.skipTypeScriptTypeWithMetadata(.lowest); + } else { + try p.skipTypeScriptType(.lowest); + } } } @@ -11693,6 +12116,7 @@ fn NewParser_( }), .key = key, .initializer = initializer, + .ts_metadata = ts_metadata, }; } @@ -11739,6 +12163,7 @@ fn NewParser_( .allow_super_property = true, .allow_ts_decorators = opts.allow_ts_decorators, .is_constructor = is_constructor, + .has_decorators = opts.ts_decorators.len > 0 or (opts.has_class_decorators and is_constructor), // Only allow omitting the body if we're parsing TypeScript class .allow_missing_body_for_type_script = is_typescript_enabled and opts.is_class, @@ -11816,6 +12241,7 @@ fn NewParser_( }), .key = key, .value = value, + .ts_metadata = .m_function, }; } @@ -11895,6 +12321,7 @@ fn NewParser_( const first_decorator_loc = p.lexer.loc(); if (opts.allow_ts_decorators) { opts.ts_decorators = try p.parseTypeScriptDecorators(); + opts.has_class_decorators = class_opts.ts_decorators.len > 0; has_decorators = has_decorators or opts.ts_decorators.len > 0; } else { opts.ts_decorators = &[_]Expr{}; @@ -18123,7 +18550,7 @@ fn NewParser_( // Track values so they can be used by constant folding. We need to follow // links here in case the enum was merged with a preceding namespace - var values_so_far = StringHashMapUnamanged(f64){}; + var values_so_far = StringHashMapUnmanaged(f64){}; p.known_enum_values.put(allocator, data.name.ref orelse p.panic("Expected data.name.ref", .{}), values_so_far) catch unreachable; p.known_enum_values.put(allocator, data.arg, values_so_far) catch unreachable; @@ -18765,7 +19192,7 @@ fn NewParser_( const args = p.allocator.alloc(Expr, 2) catch unreachable; args[0] = p.newExpr(E.Number{ .value = @as(f64, @floatFromInt(i)) }, arg_decorator.loc); args[1] = arg_decorator; - decorators.append(p.callRuntime(arg_decorator.loc, "__decorateParam", args)) catch unreachable; + decorators.append(p.callRuntime(arg_decorator.loc, "__legacyDecorateParamTS", args)) catch unreachable; if (is_constructor) { class.ts_decorators.update(decorators); } else { @@ -18792,7 +19219,12 @@ fn NewParser_( else => bun.unreachablePanic("Unexpected AST node type {any}", .{prop.key.?}), }; - const descriptor_kind: f64 = if (!prop.flags.contains(.is_method)) 2 else 1; + // TODO: when we have the `accessor` modifier, add `and !prop.flags.contains(.has_accessor_modifier)` to + // the if statement. + const descriptor_kind: Expr = if (!prop.flags.contains(.is_method)) + p.newExpr(E.Undefined{}, loc) + else + p.newExpr(E.Null{}, loc); var target: Expr = undefined; if (prop.flags.contains(.is_static)) { @@ -18802,13 +19234,60 @@ fn NewParser_( target = p.newExpr(E.Dot{ .target = p.newExpr(E.Identifier{ .ref = class.class_name.?.ref.? }, class.class_name.?.loc), .name = "prototype", .name_loc = loc }, loc); } + var array = prop.ts_decorators.listManaged(p.allocator); + + if (p.options.features.emit_decorator_metadata) { + { + // design:type + var args = p.allocator.alloc(Expr, 2) catch unreachable; + args[0] = p.newExpr(E.String{ .data = "design:type" }, logger.Loc.Empty); + args[1] = p.serializeMetadata(prop.ts_metadata) catch unreachable; + array.append(p.callRuntime(loc, "__legacyMetadataTS", args)) catch unreachable; + } + { + // design:paramtypes and design:returntype if method + if (prop.flags.contains(.is_method)) { + if (prop.value) |prop_value| { + { + var args = p.allocator.alloc(Expr, 2) catch unreachable; + args[0] = p.newExpr(E.String{ .data = "design:paramtypes" }, logger.Loc.Empty); + + const method_args = prop_value.data.e_function.func.args; + + if (method_args.len > 0) { + var args_array = p.allocator.alloc(Expr, method_args.len) catch unreachable; + + for (method_args, 0..) |method_arg, i| { + args_array[i] = p.serializeMetadata(method_arg.ts_metadata) catch unreachable; + } + + args[1] = p.newExpr(E.Array{ .items = ExprNodeList.init(args_array) }, logger.Loc.Empty); + } else { + args[1] = p.newExpr(E.Array{ .items = ExprNodeList.init(&[_]Expr{}) }, logger.Loc.Empty); + } + + array.append(p.callRuntime(loc, "__legacyMetadataTS", args)) catch unreachable; + } + { + var args = p.allocator.alloc(Expr, 2) catch unreachable; + args[0] = p.newExpr(E.String{ .data = "design:returntype" }, logger.Loc.Empty); + + args[1] = p.serializeMetadata(prop_value.data.e_function.func.return_ts_metadata) catch unreachable; + + array.append(p.callRuntime(loc, "__legacyMetadataTS", args)) catch unreachable; + } + } + } + } + } + const args = p.allocator.alloc(Expr, 4) catch unreachable; - args[0] = p.newExpr(E.Array{ .items = prop.ts_decorators }, loc); + args[0] = p.newExpr(E.Array{ .items = ExprNodeList.init(array.items) }, loc); args[1] = target; args[2] = descriptor_key; - args[3] = p.newExpr(E.Number{ .value = descriptor_kind }, loc); + args[3] = descriptor_kind; - const decorator = p.callRuntime(prop.key.?.loc, "__decorateClass", args); + const decorator = p.callRuntime(prop.key.?.loc, "__legacyDecorateClassTS", args); const decorator_stmt = p.s(S.SExpr{ .value = decorator }, decorator.loc); if (prop.flags.contains(.is_static)) { @@ -18916,13 +19395,38 @@ fn NewParser_( stmts.appendSliceAssumeCapacity(instance_decorators.items); stmts.appendSliceAssumeCapacity(static_decorators.items); if (class.ts_decorators.len > 0) { + var array = class.ts_decorators.listManaged(p.allocator); + + if (p.options.features.emit_decorator_metadata) { + if (constructor_function != null) { + // design:paramtypes + var args = p.allocator.alloc(Expr, 2) catch unreachable; + args[0] = p.newExpr(E.String{ .data = "design:paramtypes" }, logger.Loc.Empty); + + const constructor_args = constructor_function.?.func.args; + if (constructor_args.len > 0) { + var param_array = p.allocator.alloc(Expr, constructor_args.len) catch unreachable; + + for (constructor_args, 0..) |constructor_arg, i| { + param_array[i] = p.serializeMetadata(constructor_arg.ts_metadata) catch unreachable; + } + + args[1] = p.newExpr(E.Array{ .items = ExprNodeList.init(param_array) }, logger.Loc.Empty); + } else { + args[1] = p.newExpr(E.Array{ .items = ExprNodeList.init(&[_]Expr{}) }, logger.Loc.Empty); + } + + array.append(p.callRuntime(stmt.loc, "__legacyMetadataTS", args)) catch unreachable; + } + } + const args = p.allocator.alloc(Expr, 2) catch unreachable; - args[0] = p.newExpr(E.Array{ .items = class.ts_decorators }, stmt.loc); + args[0] = p.newExpr(E.Array{ .items = ExprNodeList.init(array.items) }, stmt.loc); args[1] = p.newExpr(E.Identifier{ .ref = class.class_name.?.ref.? }, class.class_name.?.loc); stmts.appendAssumeCapacity(Stmt.assign( p.newExpr(E.Identifier{ .ref = class.class_name.?.ref.? }, class.class_name.?.loc), - p.callRuntime(stmt.loc, "__decorateClass", args), + p.callRuntime(stmt.loc, "__legacyDecorateClassTS", args), p.allocator, )); @@ -18939,6 +19443,220 @@ fn NewParser_( } } + fn serializeMetadata(p: *P, ts_metadata: TypeScript.Metadata) !Expr { + return switch (ts_metadata) { + .m_none => p.newExpr( + E.Undefined{}, + logger.Loc.Empty, + ), + + .m_any, + .m_unknown, + .m_object, + => p.newExpr( + E.Identifier{ + .ref = (p.findSymbol(logger.Loc.Empty, "Object") catch unreachable).ref, + }, + logger.Loc.Empty, + ), + + .m_never, + .m_undefined, + .m_null, + .m_void, + => p.newExpr( + E.Undefined{}, + logger.Loc.Empty, + ), + + .m_string => p.newExpr( + E.Identifier{ + .ref = (p.findSymbol(logger.Loc.Empty, "String") catch unreachable).ref, + }, + logger.Loc.Empty, + ), + .m_number => p.newExpr( + E.Identifier{ + .ref = (p.findSymbol(logger.Loc.Empty, "Number") catch unreachable).ref, + }, + logger.Loc.Empty, + ), + .m_function => p.newExpr( + E.Identifier{ + .ref = (p.findSymbol(logger.Loc.Empty, "Function") catch unreachable).ref, + }, + logger.Loc.Empty, + ), + .m_boolean => p.newExpr( + E.Identifier{ + .ref = (p.findSymbol(logger.Loc.Empty, "Boolean") catch unreachable).ref, + }, + logger.Loc.Empty, + ), + .m_array => p.newExpr( + E.Identifier{ + .ref = (p.findSymbol(logger.Loc.Empty, "Array") catch unreachable).ref, + }, + logger.Loc.Empty, + ), + + .m_bigint => p.maybeDefinedHelper( + p.newExpr( + E.Identifier{ + .ref = (p.findSymbol(logger.Loc.Empty, "BigInt") catch unreachable).ref, + }, + logger.Loc.Empty, + ), + ), + + .m_symbol => p.maybeDefinedHelper( + p.newExpr( + E.Identifier{ + .ref = (p.findSymbol(logger.Loc.Empty, "Symbol") catch unreachable).ref, + }, + logger.Loc.Empty, + ), + ), + + .m_promise => p.newExpr( + E.Identifier{ + .ref = (p.findSymbol(logger.Loc.Empty, "Promise") catch unreachable).ref, + }, + logger.Loc.Empty, + ), + + .m_identifier => |ref| { + p.recordUsage(ref); + return p.maybeDefinedHelper(p.newExpr( + E.Identifier{ .ref = ref }, + logger.Loc.Empty, + )); + }, + + .m_dot => |_refs| { + var refs = _refs; + std.debug.assert(refs.items.len >= 2); + defer refs.deinit(p.allocator); + + var dots = p.newExpr( + E.Dot{ + .name = p.loadNameFromRef(refs.items[refs.items.len - 1]), + .name_loc = logger.Loc.Empty, + .target = undefined, + }, + logger.Loc.Empty, + ); + + var current_expr = &dots.data.e_dot.target; + var i: usize = refs.items.len - 2; + while (i > 0) { + current_expr.* = p.newExpr(E.Dot{ + .name = p.loadNameFromRef(refs.items[i]), + .name_loc = logger.Loc.Empty, + .target = undefined, + }, logger.Loc.Empty); + current_expr = ¤t_expr.data.e_dot.target; + i -= 1; + } + + current_expr.* = p.newExpr( + E.Identifier{ + .ref = refs.items[0], + }, + logger.Loc.Empty, + ); + + const dot_identifier = current_expr.*; + var current_dot = dots; + + var maybe_defined_dots = p.newExpr( + E.Binary{ + .op = .bin_logical_or, + .right = try p.checkIfDefinedHelper(current_dot), + .left = undefined, + }, + logger.Loc.Empty, + ); + + if (i < refs.items.len - 2) { + current_dot = current_dot.data.e_dot.target; + } + current_expr = &maybe_defined_dots.data.e_binary.left; + + while (i < refs.items.len - 2) { + current_expr.* = p.newExpr( + E.Binary{ + .op = .bin_logical_or, + .right = try p.checkIfDefinedHelper(current_dot), + .left = undefined, + }, + logger.Loc.Empty, + ); + + current_expr = ¤t_expr.data.e_binary.left; + i += 1; + if (i < refs.items.len - 2) { + current_dot = current_dot.data.e_dot.target; + } + } + + current_expr.* = try p.checkIfDefinedHelper(dot_identifier); + + var root = p.newExpr( + E.If{ + .yes = p.newExpr( + E.Identifier{ + .ref = (p.findSymbol(logger.Loc.Empty, "Object") catch unreachable).ref, + }, + logger.Loc.Empty, + ), + .no = dots, + .test_ = maybe_defined_dots, + }, + logger.Loc.Empty, + ); + + return root; + }, + }; + } + + fn checkIfDefinedHelper(p: *P, expr: Expr) !Expr { + return p.newExpr( + E.Binary{ + .op = .bin_strict_eq, + .left = p.newExpr( + E.Unary{ + .op = .un_typeof, + .value = expr, + }, + logger.Loc.Empty, + ), + .right = p.newExpr( + E.String{ .data = "undefined" }, + logger.Loc.Empty, + ), + }, + logger.Loc.Empty, + ); + } + + fn maybeDefinedHelper(p: *P, identifier_expr: Expr) !Expr { + return p.newExpr( + E.If{ + .test_ = try p.checkIfDefinedHelper(identifier_expr), + .yes = p.newExpr( + E.Identifier{ + .ref = (p.findSymbol(logger.Loc.Empty, "Object") catch unreachable).ref, + }, + logger.Loc.Empty, + ), + .no = identifier_expr, + }, + logger.Loc.Empty, + ); + } + fn visitForLoopInit(p: *P, stmt: Stmt, is_in_or_of: bool) Stmt { switch (stmt.data) { .s_expr => |st| { diff --git a/src/options.zig b/src/options.zig index aaeb1fce8..73ca9a64b 100644 --- a/src/options.zig +++ b/src/options.zig @@ -1364,6 +1364,7 @@ pub const BundleOptions = struct { loaders: Loader.HashTable, resolve_dir: string = "/", jsx: JSX.Pragma = JSX.Pragma{}, + emit_decorator_metadata: bool = false, auto_import_jsx: bool = true, allow_runtime: bool = true, diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig index b6f571e23..4ddd97dca 100644 --- a/src/resolver/resolver.zig +++ b/src/resolver/resolver.zig @@ -187,6 +187,8 @@ pub const Result = struct { // This is the "type" field from "package.json" module_type: options.ModuleType = options.ModuleType.unknown, + emit_decorator_metadata: bool = false, + debug_meta: ?DebugMeta = null, dirname_fd: StoredFileDescriptorType = 0, @@ -965,6 +967,7 @@ pub const Resolver = struct { if (dir.enclosing_tsconfig_json) |tsconfig| { result.jsx = tsconfig.mergeJSX(result.jsx); + result.emit_decorator_metadata = result.emit_decorator_metadata or tsconfig.emit_decorator_metadata; } // If you use mjs or mts, then you're using esm @@ -3904,6 +3907,7 @@ pub const Resolver = struct { // starting from the base config (end of the list) // successively apply the inheritable attributes to the next config while (parent_configs.popOrNull()) |parent_config| { + merged_config.emit_decorator_metadata = merged_config.emit_decorator_metadata or parent_config.emit_decorator_metadata; if (parent_config.base_url.len > 0) { merged_config.base_url = parent_config.base_url; merged_config.base_url_for_paths = parent_config.base_url_for_paths; diff --git a/src/resolver/tsconfig_json.zig b/src/resolver/tsconfig_json.zig index 3f7be00ba..ba85fc965 100644 --- a/src/resolver/tsconfig_json.zig +++ b/src/resolver/tsconfig_json.zig @@ -56,6 +56,8 @@ pub const TSConfigJSON = struct { preserve_imports_not_used_as_values: ?bool = false, + emit_decorator_metadata: bool = false, + pub fn hasBaseURL(tsconfig: *const TSConfigJSON) bool { return tsconfig.base_url.len > 0; } @@ -137,6 +139,13 @@ pub const TSConfigJSON = struct { } } + // Parse "emitDecoratorMetadata" + if (compiler_opts.expr.asProperty("emitDecoratorMetadata")) |emit_decorator_metadata_prop| { + if (emit_decorator_metadata_prop.expr.asBool()) |val| { + result.emit_decorator_metadata = val; + } + } + // Parse "jsxFactory" if (compiler_opts.expr.asProperty("jsxFactory")) |jsx_prop| { if (jsx_prop.expr.asString(allocator)) |str| { diff --git a/src/runtime.footer.bun.js b/src/runtime.footer.bun.js index fdf7cdefa..810525af7 100644 --- a/src/runtime.footer.bun.js +++ b/src/runtime.footer.bun.js @@ -11,8 +11,9 @@ export var regeneratorRuntime = BUN_RUNTIME.regeneratorRuntime; export var __exportValue = BUN_RUNTIME.__exportValue; export var __exportDefault = BUN_RUNTIME.__exportDefault; export var __merge = BUN_RUNTIME.__merge; -export var __decorateClass = BUN_RUNTIME.__decorateClass; -export var __decorateParam = BUN_RUNTIME.__decorateParam; +export var __legacyDecorateClassTS = BUN_RUNTIME.__legacyDecorateClassTS; +export var __legacyDecorateParamTS = BUN_RUNTIME.__legacyDecorateParamTS; +export var __legacyMetadataTS = BUN_RUNTIME.__legacyMetadataTS; export var $$bun_runtime_json_parse = JSON.parse; export var __internalIsCommonJSNamespace = BUN_RUNTIME.__internalIsCommonJSNamespace; export var $$typeof = BUN_RUNTIME.$$typeof; diff --git a/src/runtime.footer.js b/src/runtime.footer.js index ceeab055d..48c86f47b 100644 --- a/src/runtime.footer.js +++ b/src/runtime.footer.js @@ -18,8 +18,9 @@ export var regeneratorRuntime = BUN_RUNTIME.regeneratorRuntime; export var __exportValue = BUN_RUNTIME.__exportValue; export var __exportDefault = BUN_RUNTIME.__exportDefault; export var __merge = BUN_RUNTIME.__merge; -export var __decorateClass = BUN_RUNTIME.__decorateClass; -export var __decorateParam = BUN_RUNTIME.__decorateParam; +export var __legacyDecorateClassTS = BUN_RUNTIME.__legacyDecorateClassTS; +export var __legacyDecorateParamTS = BUN_RUNTIME.__legacyDecorateParamTS; +export var __legacyMetadataTS = BUN_RUNTIME.__legacyMetadataTS; export var $$bun_runtime_json_parse = JSON.parse; export var __internalIsCommonJSNamespace = BUN_RUNTIME.__internalIsCommonJSNamespace; diff --git a/src/runtime.footer.node.js b/src/runtime.footer.node.js index ef28d3b31..4318195c5 100644 --- a/src/runtime.footer.node.js +++ b/src/runtime.footer.node.js @@ -12,8 +12,9 @@ export var __cJS2eSM = BUN_RUNTIME.__cJS2eSM; export var regeneratorRuntime = BUN_RUNTIME.regeneratorRuntime; export var __exportValue = BUN_RUNTIME.__exportValue; export var __exportDefault = BUN_RUNTIME.__exportDefault; -export var __decorateClass = BUN_RUNTIME.__decorateClass; -export var __decorateParam = BUN_RUNTIME.__decorateParam; +export var __legacyDecorateClassTS = BUN_RUNTIME.__legacyDecorateClassTS; +export var __legacyDecorateParamTS = BUN_RUNTIME.__legacyDecorateParamTS; +export var __legacyMetadataTS = BUN_RUNTIME.__legacyMetadataTS; export var $$bun_runtime_json_parse = JSON.parse; export var __internalIsCommonJSNamespace = BUN_RUNTIME.__internalIsCommonJSNamespace; var require = __$module.createRequire(import.meta.url); diff --git a/src/runtime.footer.with-refresh.js b/src/runtime.footer.with-refresh.js index 784f5f8fa..9aa9472e9 100644 --- a/src/runtime.footer.with-refresh.js +++ b/src/runtime.footer.with-refresh.js @@ -17,8 +17,9 @@ export var __cJS2eSM = BUN_RUNTIME.__cJS2eSM; export var regeneratorRuntime = BUN_RUNTIME.regeneratorRuntime; export var __exportValue = BUN_RUNTIME.__exportValue; export var __exportDefault = BUN_RUNTIME.__exportDefault; -export var __decorateClass = BUN_RUNTIME.__decorateClass; -export var __decorateParam = BUN_RUNTIME.__decorateParam; +export var __legacyDecorateClassTS = BUN_RUNTIME.__legacyDecorateClassTS; +export var __legacyDecorateParamTS = BUN_RUNTIME.__legacyDecorateParamTS; +export var __legacyMetadataTS = BUN_RUNTIME.__legacyMetadataTS; export var $$bun_runtime_json_parse = JSON.parse; export var __FastRefreshRuntime = BUN_RUNTIME.__FastRefreshRuntime; export var __internalIsCommonJSNamespace = BUN_RUNTIME.__internalIsCommonJSNamespace; diff --git a/src/runtime.js b/src/runtime.js index 41242637b..fb1d5e70e 100644 --- a/src/runtime.js +++ b/src/runtime.js @@ -250,15 +250,23 @@ export var __merge = (props, defaultProps) => { : mergeDefaultProps(props, defaultProps); }; -export var __decorateClass = (decorators, target, key, kind) => { - var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target; - for (var i = decorators.length - 1, decorator; i >= 0; i--) - if ((decorator = decorators[i])) result = (kind ? decorator(target, key, result) : decorator(result)) || result; - if (kind && result) __defProp(target, key, result); - return result; +export var __legacyDecorateClassTS = function (decorators, target, key, desc) { + var c = arguments.length, + r = c < 3 ? target : desc === null ? (desc = Object.getOwnPropertyDescriptor(target, key)) : desc, + d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") + r = Reflect.decorate(decorators, target, key, desc); + else + for (var i = decorators.length - 1; i >= 0; i--) + if ((d = decorators[i])) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; }; -export var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index); +export var __legacyDecorateParamTS = (index, decorator) => (target, key) => decorator(target, key, index); + +export var __legacyMetadataTS = (k, v) => { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; export var __esm = (fn, res) => () => (fn && (res = fn((fn = 0))), res); diff --git a/src/runtime.zig b/src/runtime.zig index cf9f1d208..a9a88f979 100644 --- a/src/runtime.zig +++ b/src/runtime.zig @@ -328,6 +328,8 @@ pub const Runtime = struct { commonjs_at_runtime: bool = false, + emit_decorator_metadata: bool = false, + pub fn shouldUnwrapRequire(this: *const Features, package_name: string) bool { return package_name.len > 0 and strings.indexEqualAny(this.unwrap_commonjs_packages, package_name) != null; } @@ -372,8 +374,9 @@ pub const Runtime = struct { __exportDefault: ?GeneratedSymbol = null, __FastRefreshRuntime: ?GeneratedSymbol = null, __merge: ?GeneratedSymbol = null, - __decorateClass: ?GeneratedSymbol = null, - __decorateParam: ?GeneratedSymbol = null, + __legacyDecorateClassTS: ?GeneratedSymbol = null, + __legacyDecorateParamTS: ?GeneratedSymbol = null, + __legacyMetadataTS: ?GeneratedSymbol = null, @"$$typeof": ?GeneratedSymbol = null, pub const all = [_][]const u8{ @@ -395,8 +398,9 @@ pub const Runtime = struct { "__exportDefault", "__FastRefreshRuntime", "__merge", - "__decorateClass", - "__decorateParam", + "__legacyDecorateClassTS", + "__legacyDecorateParamTS", + "__legacyMetadataTS", "$$typeof", }; const all_sorted: [all.len]string = brk: { @@ -540,6 +544,11 @@ pub const Runtime = struct { return Entry{ .key = 18, .value = val.ref }; } }, + 19 => { + if (@field(this.runtime_imports, all[19])) |val| { + return Entry{ .key = 19, .value = val.ref }; + } + }, else => { return null; }, @@ -603,6 +612,7 @@ pub const Runtime = struct { 16 => (@field(imports, all[16]) orelse return null).ref, 17 => (@field(imports, all[17]) orelse return null).ref, 18 => (@field(imports, all[18]) orelse return null).ref, + 19 => (@field(imports, all[19]) orelse return null).ref, else => null, }; } diff --git a/test/bundler/bundler_decorator_metadata.test.ts b/test/bundler/bundler_decorator_metadata.test.ts new file mode 100644 index 000000000..54bbdd6a5 --- /dev/null +++ b/test/bundler/bundler_decorator_metadata.test.ts @@ -0,0 +1,505 @@ +import assert from "assert"; +import { itBundled, testForFile } from "./expectBundled"; +var { describe, test, expect } = testForFile(import.meta.path); + +itBundled("decorator_metadata/TypeSerialization", { + files: { + "/entry.ts": /* ts */ ` + import "reflect-metadata"; + function d1() {} + class Known {} + class Swag {} + class A_1 {} + + // @ts-ignore + @d1 + class Yolo { + constructor( + p0: any, + p1: unknown, + p2: never, + p3: void, + p4: null, + p5: undefined, + p6: number, + p7: string, + p8: boolean, + p9: symbol, + p10: bigint, + p11: object, + p12: () => {}, + p13: [], + p14: {}, + p15: 123, + p16: 123n, + p17: "123", + p18: \`123\`, + p19: true, + p20: false, + // @ts-ignore + p21: Map, + // @ts-ignore + p22: Set, + p23: Known, + // @ts-ignore + p24: Unknown, + p25: never & string, + p26: string & never, + p27: null & string, + p28: string & null, + p29: undefined & string, + p30: string & undefined, + p31: void & string, + p32: string & void, + p33: unknown & string, + p34: string & unknown, + p35: any & string, + p36: string & any, + p37: never | string, + p38: string | never, + p39: null | string, + p40: string | null, + p41: undefined | string, + p42: string | undefined, + p43: void | string, + p44: string | void, + p45: unknown | string, + p46: string | unknown, + p47: any | string, + p48: string | any, + p49: string | string, + p50: string & string, + p51: Known | Swag, + p52: Swag | Known, + p53: Known & Swag, + p54: Swag & Known, + p55: never | Swag, + p56: Swag | never, + p57: null | Swag, + p58: Swag | null, + p59: undefined | Swag, + p60: Swag | undefined, + p61: void | Swag, + p62: Swag | void, + p63: unknown | Swag, + p64: Swag | unknown, + p65: any | Swag, + p66: Swag | any, + p67: never & Swag, + p68: Swag & never, + p69: null & Swag, + p70: Swag & null, + p71: undefined & Swag, + p72: Swag & undefined, + p73: void & Swag, + p74: Swag & void, + p75: unknown & Swag, + p76: Swag & unknown, + p77: any & Swag, + p78: Swag & any, + p79: Swag | Swag, + p80: Swag & Swag, + // @ts-ignore + p81: Unknown | Known, + // @ts-ignore + p82: Known | Unknown, + // @ts-ignore + p83: Unknown & Known, + // @ts-ignore + p84: Known & Unknown, + // @ts-ignore + p85: Unknown | Unknown, + // @ts-ignore + p86: Unknown & Unknown, + p87: never | never, + p88: never & never, + p89: null | null, + p90: null & null, + p91: undefined | undefined, + p92: undefined & undefined, + p93: void | void, + p94: void & void, + p95: unknown | unknown, + p96: unknown & unknown, + p97: any | any, + p98: any & any, + p99: never | void, + p100: void | never, + p101: null | void, + p102: void | null, + p103: undefined | void, + p104: void | undefined, + p105: void | void, + p106: void & void, + p107: unknown | void, + p108: void | unknown, + p109: any | void, + p110: void | any, + p111: never | unknown, + p112: unknown | never, + p113: null | unknown, + p114: unknown | null, + p115: undefined | unknown, + p116: unknown | undefined, + p117: void | unknown, + p118: unknown | void, + p119: unknown | unknown, + p120: unknown & unknown, + p121: any | unknown, + p122: unknown | any, + p123: never | any, + p124: any | never, + p125: null | any, + p126: any | null, + p127: undefined | any, + p128: any | undefined, + p129: void | any, + p130: any | void, + p131: unknown | any, + p132: any | unknown, + p133: any | any, + p134: never & void, + p135: void & never, + p136: null & void, + p137: void & null, + p138: undefined & void, + p139: void & undefined, + p140: void & void, + p141: void | void, + p142: unknown & void, + p143: void & unknown, + p144: any & void, + p145: void & any, + p146: never & unknown, + p147: unknown & never, + p148: null & unknown, + p149: unknown & null, + p150: undefined & unknown, + p151: unknown & undefined, + p152: void & unknown, + p153: unknown & void, + p154: unknown & unknown, + p155: unknown | unknown, + p156: any & unknown, + p157: unknown & any, + p158: never & any, + p159: any & never, + p160: null & any, + p161: any & null, + p162: undefined & any, + p163: any & undefined, + p164: void & any, + p165: any & void, + p166: unknown & any, + p167: any & unknown, + p168: any & any, + p169: string & number & boolean & never & symbol, + p170: "foo" | A_1, + p171: true | boolean, + p172: "foo" | boolean, + p173: A_1 | "foo", + ){} + } + + const received = Reflect.getMetadata("design:paramtypes", Yolo); + console.log(received.length === 174); + console.log(received[0] === Object); + console.log(received[1] === Object); + console.log(received[2] === void 0); + console.log(received[3] === void 0); + console.log(received[4] === void 0); + console.log(received[5] === void 0); + console.log(received[6] === Number); + console.log(received[7] === String); + console.log(received[8] === Boolean); + console.log(received[9] === (typeof Symbol === "function" ? Symbol : Object)); + console.log(received[10] === (typeof BigInt === "function" ? BigInt : Object)); + console.log(received[11] === Object); + console.log(received[12] === Function); + console.log(received[13] === Array); + console.log(received[14] === Object); + console.log(received[15] === Number); + console.log(received[16] === (typeof BigInt === "function" ? BigInt : Object)); + console.log(received[17] === String); + console.log(received[18] === String); + console.log(received[19] === Boolean); + console.log(received[20] === Boolean); + console.log(received[21] === Map); + console.log(received[22] === Set); + console.log(received[23] === Known); + console.log(received[24] === Object); + console.log(received[25] === void 0); + console.log(received[26] === void 0); + console.log(received[27] === String); + console.log(received[28] === String); + console.log(received[29] === String); + console.log(received[30] === String); + console.log(received[31] === Object); + console.log(received[32] === Object); + console.log(received[33] === String); + console.log(received[34] === String); + console.log(received[35] === Object); + console.log(received[36] === Object); + console.log(received[37] === String); + console.log(received[38] === String); + console.log(received[39] === String); + console.log(received[40] === String); + console.log(received[41] === String); + console.log(received[42] === String); + console.log(received[43] === Object); + console.log(received[44] === Object); + console.log(received[45] === Object); + console.log(received[46] === Object); + console.log(received[47] === Object); + console.log(received[48] === Object); + console.log(received[49] === String); + console.log(received[50] === String); + console.log(received[51] === Object); + console.log(received[52] === Object); + console.log(received[53] === Object); + console.log(received[54] === Object); + console.log(received[55] === Swag); + console.log(received[56] === Swag); + console.log(received[57] === Swag); + console.log(received[58] === Swag); + console.log(received[59] === Swag); + console.log(received[60] === Swag); + console.log(received[61] === Object); + console.log(received[62] === Object); + console.log(received[63] === Object); + console.log(received[64] === Object); + console.log(received[65] === Object); + console.log(received[66] === Object); + console.log(received[67] === void 0); + console.log(received[68] === void 0); + console.log(received[69] === Swag); + console.log(received[70] === Swag); + console.log(received[71] === Swag); + console.log(received[72] === Swag); + console.log(received[73] === Object); + console.log(received[74] === Object); + console.log(received[75] === Swag); + console.log(received[76] === Swag); + console.log(received[77] === Object); + console.log(received[78] === Object); + console.log(received[79] === Swag); + console.log(received[80] === Swag); + console.log(received[81] === Object); + console.log(received[82] === Object); + console.log(received[83] === Object); + console.log(received[84] === Object); + console.log(received[85] === Object); + console.log(received[86] === Object); + console.log(received[87] === void 0); + console.log(received[88] === void 0); + console.log(received[89] === void 0); + console.log(received[90] === void 0); + console.log(received[91] === void 0); + console.log(received[92] === void 0); + console.log(received[93] === void 0); + console.log(received[94] === void 0); + console.log(received[95] === Object); + console.log(received[96] === void 0); + console.log(received[97] === Object); + console.log(received[98] === Object); + console.log(received[99] === void 0); + console.log(received[100] === void 0); + console.log(received[101] === void 0); + console.log(received[102] === void 0); + console.log(received[103] === void 0); + console.log(received[104] === void 0); + console.log(received[105] === void 0); + console.log(received[106] === void 0); + console.log(received[107] === Object); + console.log(received[108] === Object); + console.log(received[109] === Object); + console.log(received[110] === Object); + console.log(received[111] === Object); + console.log(received[112] === Object); + console.log(received[113] === Object); + console.log(received[114] === Object); + console.log(received[115] === Object); + console.log(received[116] === Object); + console.log(received[117] === Object); + console.log(received[118] === Object); + console.log(received[119] === Object); + console.log(received[120] === void 0); + console.log(received[121] === Object); + console.log(received[122] === Object); + console.log(received[123] === Object); + console.log(received[124] === Object); + console.log(received[125] === Object); + console.log(received[126] === Object); + console.log(received[127] === Object); + console.log(received[128] === Object); + console.log(received[129] === Object); + console.log(received[130] === Object); + console.log(received[131] === Object); + console.log(received[132] === Object); + console.log(received[133] === Object); + console.log(received[134] === void 0); + console.log(received[135] === void 0); + console.log(received[136] === void 0); + console.log(received[137] === void 0); + console.log(received[138] === void 0); + console.log(received[139] === void 0); + console.log(received[140] === void 0); + console.log(received[141] === void 0); + console.log(received[142] === void 0); + console.log(received[143] === void 0); + console.log(received[144] === Object); + console.log(received[145] === Object); + console.log(received[146] === void 0); + console.log(received[147] === void 0); + console.log(received[148] === void 0); + console.log(received[149] === void 0); + console.log(received[150] === void 0); + console.log(received[151] === void 0); + console.log(received[152] === void 0); + console.log(received[153] === void 0); + console.log(received[154] === void 0); + console.log(received[155] === Object); + console.log(received[156] === Object); + console.log(received[157] === Object); + console.log(received[158] === void 0); + console.log(received[159] === Object); + console.log(received[160] === Object); + console.log(received[161] === Object); + console.log(received[162] === Object); + console.log(received[163] === Object); + console.log(received[164] === Object); + console.log(received[165] === Object); + console.log(received[166] === Object); + console.log(received[167] === Object); + console.log(received[168] === Object); + console.log(received[169] === Object); + console.log(received[170] === Object); + console.log(received[171] === Boolean); + console.log(received[172] === Object); + console.log(received[173] === Object); + + // @ts-ignore + @d1 + class A { + // @ts-ignore + constructor(@d1 arg1: string) {} + // @ts-ignore + @d1 + // @ts-ignore + method1(@d1 arg1: number): boolean { + return true; + } + // @ts-ignore + @d1 + prop1: () => {}; + // @ts-ignore + @d1 + prop2: "foo" = "foo"; + // @ts-ignore + @d1 + prop3: symbol; + } + + console.log(Reflect.getMetadata("design:type", A) === undefined); + console.log(Reflect.getMetadata("design:paramtypes", A)[0] === String); + console.log(Reflect.getMetadata("design:returntype", A) === undefined); + + console.log(Reflect.getMetadata("design:type", A.prototype) === undefined); + console.log(Reflect.getMetadata("design:paramtypes", A.prototype) === undefined); + console.log(Reflect.getMetadata("design:returntype", A.prototype) === undefined); + + console.log(Reflect.getMetadata("design:type", A.prototype.method1) === undefined); + console.log(Reflect.getMetadata("design:paramtypes", A.prototype.method1) === undefined); + console.log(Reflect.getMetadata("design:returntype", A.prototype.method1) === undefined); + + console.log(Reflect.getMetadata("design:type", A.prototype, "method1") === Function); + console.log(Reflect.getMetadata("design:paramtypes", A.prototype, "method1")[0] === Number); + console.log(Reflect.getMetadata("design:returntype", A.prototype, "method1") === Boolean); + + console.log(Reflect.getMetadata("design:type", A.prototype, "prop1") === Function); + console.log(Reflect.getMetadata("design:paramtypes", A.prototype, "prop1") === undefined); + console.log(Reflect.getMetadata("design:returntype", A.prototype, "prop1") === undefined); + + console.log(Reflect.getMetadata("design:type", A.prototype, "prop2") === String); + console.log(Reflect.getMetadata("design:paramtypes", A.prototype, "prop2") === undefined); + console.log(Reflect.getMetadata("design:returntype", A.prototype, "prop2") === undefined); + + console.log(Reflect.getMetadata("design:type", A.prototype, "prop3") === Symbol); + console.log(Reflect.getMetadata("design:paramtypes", A.prototype, "prop3") === undefined); + console.log(Reflect.getMetadata("design:returntype", A.prototype, "prop3") === undefined); + + class HelloWorld { + // @ts-ignore + constructor(@d1 arg1: string) {} + } + + console.log(Reflect.getMetadata("design:type", HelloWorld) === undefined); + console.log(Reflect.getMetadata("design:paramtypes", HelloWorld)[0] === String); + console.log(Reflect.getMetadata("design:returntype", HelloWorld) === undefined); + + type B = "hello" | "world"; + const b = 2; + const c = ["hello", "world"] as const; + type Loser = \`hello \${B}\`; // "hello hello" | "hello world" + function d1() {} + + class AClass { + constructor( + // @ts-ignore + @d1 p0: \`hello \${B}\`, + // @ts-ignore + p1: keyof Something, + p2: typeof b, + p3: readonly ["hello", "world"], + p4: typeof c, + p5: readonly [number, string], + // prettier-ignore + p6: (string | string), + // prettier-ignore + p7: (string & string), + p8: boolean extends true ? "a" : "b", + // @ts-ignore + p9: Loser extends Loser ? string : Foo, + p10: { [keyof in string]: number }, + // @ts-ignore + p11: blah extends blahblah ? number : void, + ) {} + + // @ts-ignore + @d1 + async method1() { + return true; + } + } + + const paramtypes = Reflect.getMetadata("design:paramtypes", AClass); + console.log(paramtypes[0] === String); + console.log(paramtypes[1] === Object); + console.log(paramtypes[2] === Object); + console.log(paramtypes[3] === Array); + console.log(paramtypes[4] === Object); + console.log(paramtypes[5] === Array); + console.log(paramtypes[6] === String); + console.log(paramtypes[7] === String); + console.log(paramtypes[8] === String); + console.log(paramtypes[9] === Object); + console.log(paramtypes[10] === Object); + console.log(paramtypes[11] === Object); + + console.log(Reflect.getMetadata("design:returntype", AClass.prototype, "method1") === Promise); + `, + "/tsconfig.json": /* json */ ` + { + "compilerOptions": { + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + } + } + `, + }, + install: ["reflect-metadata"], + bundling: true, + run: { + stdout: "true\n".repeat(212), + }, +}); diff --git a/test/package.json b/test/package.json index ad28f114a..9e55cc543 100644 --- a/test/package.json +++ b/test/package.json @@ -9,6 +9,7 @@ }, "dependencies": { "@prisma/client": "5.1.1", + "reflect-metadata": "0.1.13", "@resvg/resvg-js": "2.4.1", "@swc/core": "1.3.38", "body-parser": "1.20.2", diff --git a/test/transpiler/decorator-metadata.test.ts b/test/transpiler/decorator-metadata.test.ts new file mode 100644 index 000000000..861604430 --- /dev/null +++ b/test/transpiler/decorator-metadata.test.ts @@ -0,0 +1,494 @@ +import "reflect-metadata"; + +describe("decorator metadata", () => { + test("type serialization", () => { + function d1() {} + class Known {} + class Swag {} + class A_1 {} + + // @ts-ignore + @d1 + class A { + constructor( + p0: any, + p1: unknown, + p2: never, + p3: void, + p4: null, + p5: undefined, + p6: number, + p7: string, + p8: boolean, + p9: symbol, + p10: bigint, + p11: object, + p12: () => {}, + p13: [], + p14: {}, + p15: 123, + p16: 123n, + p17: "123", + p18: `123`, + p19: true, + p20: false, + // @ts-ignore + p21: Map, + // @ts-ignore + p22: Set, + p23: Known, + // @ts-ignore + p24: Unknown, + p25: never & string, + p26: string & never, + p27: null & string, + p28: string & null, + p29: undefined & string, + p30: string & undefined, + p31: void & string, + p32: string & void, + p33: unknown & string, + p34: string & unknown, + p35: any & string, + p36: string & any, + p37: never | string, + p38: string | never, + p39: null | string, + p40: string | null, + p41: undefined | string, + p42: string | undefined, + p43: void | string, + p44: string | void, + p45: unknown | string, + p46: string | unknown, + p47: any | string, + p48: string | any, + p49: string | string, + p50: string & string, + p51: Known | Swag, + p52: Swag | Known, + p53: Known & Swag, + p54: Swag & Known, + p55: never | Swag, + p56: Swag | never, + p57: null | Swag, + p58: Swag | null, + p59: undefined | Swag, + p60: Swag | undefined, + p61: void | Swag, + p62: Swag | void, + p63: unknown | Swag, + p64: Swag | unknown, + p65: any | Swag, + p66: Swag | any, + p67: never & Swag, + p68: Swag & never, + p69: null & Swag, + p70: Swag & null, + p71: undefined & Swag, + p72: Swag & undefined, + p73: void & Swag, + p74: Swag & void, + p75: unknown & Swag, + p76: Swag & unknown, + p77: any & Swag, + p78: Swag & any, + p79: Swag | Swag, + p80: Swag & Swag, + // @ts-ignore + p81: Unknown | Known, + // @ts-ignore + p82: Known | Unknown, + // @ts-ignore + p83: Unknown & Known, + // @ts-ignore + p84: Known & Unknown, + // @ts-ignore + p85: Unknown | Unknown, + // @ts-ignore + p86: Unknown & Unknown, + p87: never | never, + p88: never & never, + p89: null | null, + p90: null & null, + p91: undefined | undefined, + p92: undefined & undefined, + p93: void | void, + p94: void & void, + p95: unknown | unknown, + p96: unknown & unknown, + p97: any | any, + p98: any & any, + p99: never | void, + p100: void | never, + p101: null | void, + p102: void | null, + p103: undefined | void, + p104: void | undefined, + p105: void | void, + p106: void & void, + p107: unknown | void, + p108: void | unknown, + p109: any | void, + p110: void | any, + p111: never | unknown, + p112: unknown | never, + p113: null | unknown, + p114: unknown | null, + p115: undefined | unknown, + p116: unknown | undefined, + p117: void | unknown, + p118: unknown | void, + p119: unknown | unknown, + p120: unknown & unknown, + p121: any | unknown, + p122: unknown | any, + p123: never | any, + p124: any | never, + p125: null | any, + p126: any | null, + p127: undefined | any, + p128: any | undefined, + p129: void | any, + p130: any | void, + p131: unknown | any, + p132: any | unknown, + p133: any | any, + p134: never & void, + p135: void & never, + p136: null & void, + p137: void & null, + p138: undefined & void, + p139: void & undefined, + p140: void & void, + p141: void | void, + p142: unknown & void, + p143: void & unknown, + p144: any & void, + p145: void & any, + p146: never & unknown, + p147: unknown & never, + p148: null & unknown, + p149: unknown & null, + p150: undefined & unknown, + p151: unknown & undefined, + p152: void & unknown, + p153: unknown & void, + p154: unknown & unknown, + p155: unknown | unknown, + p156: any & unknown, + p157: unknown & any, + p158: never & any, + p159: any & never, + p160: null & any, + p161: any & null, + p162: undefined & any, + p163: any & undefined, + p164: void & any, + p165: any & void, + p166: unknown & any, + p167: any & unknown, + p168: any & any, + p169: string & number & boolean & never & symbol, + p170: "foo" | A_1, + p171: true | boolean, + p172: "foo" | boolean, + p173: A_1 | "foo", + ) {} + } + + const received = Reflect.getMetadata("design:paramtypes", A); + expect(received.length).toBe(174); + expect(received[0]).toBe(Object); + expect(received[1]).toBe(Object); + expect(received[2]).toBe(void 0); + expect(received[3]).toBe(void 0); + expect(received[4]).toBe(void 0); + expect(received[5]).toBe(void 0); + expect(received[6]).toBe(Number); + expect(received[7]).toBe(String); + expect(received[8]).toBe(Boolean); + expect(received[9]).toBe(typeof Symbol === "function" ? Symbol : Object); + expect(received[10]).toBe(typeof BigInt === "function" ? BigInt : Object); + expect(received[11]).toBe(Object); + expect(received[12]).toBe(Function); + expect(received[13]).toBe(Array); + expect(received[14]).toBe(Object); + expect(received[15]).toBe(Number); + expect(received[16]).toBe(typeof BigInt === "function" ? BigInt : Object); + expect(received[17]).toBe(String); + expect(received[18]).toBe(String); + expect(received[19]).toBe(Boolean); + expect(received[20]).toBe(Boolean); + expect(received[21]).toBe(Map); + expect(received[22]).toBe(Set); + expect(received[23]).toBe(Known); + expect(received[24]).toBe(Object); + expect(received[25]).toBe(void 0); + expect(received[26]).toBe(void 0); + expect(received[27]).toBe(String); + expect(received[28]).toBe(String); + expect(received[29]).toBe(String); + expect(received[30]).toBe(String); + expect(received[31]).toBe(Object); + expect(received[32]).toBe(Object); + expect(received[33]).toBe(String); + expect(received[34]).toBe(String); + expect(received[35]).toBe(Object); + expect(received[36]).toBe(Object); + expect(received[37]).toBe(String); + expect(received[38]).toBe(String); + expect(received[39]).toBe(String); + expect(received[40]).toBe(String); + expect(received[41]).toBe(String); + expect(received[42]).toBe(String); + expect(received[43]).toBe(Object); + expect(received[44]).toBe(Object); + expect(received[45]).toBe(Object); + expect(received[46]).toBe(Object); + expect(received[47]).toBe(Object); + expect(received[48]).toBe(Object); + expect(received[49]).toBe(String); + expect(received[50]).toBe(String); + expect(received[51]).toBe(Object); + expect(received[52]).toBe(Object); + expect(received[53]).toBe(Object); + expect(received[54]).toBe(Object); + expect(received[55]).toBe(Swag); + expect(received[56]).toBe(Swag); + expect(received[57]).toBe(Swag); + expect(received[58]).toBe(Swag); + expect(received[59]).toBe(Swag); + expect(received[60]).toBe(Swag); + expect(received[61]).toBe(Object); + expect(received[62]).toBe(Object); + expect(received[63]).toBe(Object); + expect(received[64]).toBe(Object); + expect(received[65]).toBe(Object); + expect(received[66]).toBe(Object); + expect(received[67]).toBe(void 0); + expect(received[68]).toBe(void 0); + expect(received[69]).toBe(Swag); + expect(received[70]).toBe(Swag); + expect(received[71]).toBe(Swag); + expect(received[72]).toBe(Swag); + expect(received[73]).toBe(Object); + expect(received[74]).toBe(Object); + expect(received[75]).toBe(Swag); + expect(received[76]).toBe(Swag); + expect(received[77]).toBe(Object); + expect(received[78]).toBe(Object); + expect(received[79]).toBe(Swag); + expect(received[80]).toBe(Swag); + expect(received[81]).toBe(Object); + expect(received[82]).toBe(Object); + expect(received[83]).toBe(Object); + expect(received[84]).toBe(Object); + expect(received[85]).toBe(Object); + expect(received[86]).toBe(Object); + expect(received[87]).toBe(void 0); + expect(received[88]).toBe(void 0); + expect(received[89]).toBe(void 0); + expect(received[90]).toBe(void 0); + expect(received[91]).toBe(void 0); + expect(received[92]).toBe(void 0); + expect(received[93]).toBe(void 0); + expect(received[94]).toBe(void 0); + expect(received[95]).toBe(Object); + expect(received[96]).toBe(void 0); + expect(received[97]).toBe(Object); + expect(received[98]).toBe(Object); + expect(received[99]).toBe(void 0); + expect(received[100]).toBe(void 0); + expect(received[101]).toBe(void 0); + expect(received[102]).toBe(void 0); + expect(received[103]).toBe(void 0); + expect(received[104]).toBe(void 0); + expect(received[105]).toBe(void 0); + expect(received[106]).toBe(void 0); + expect(received[107]).toBe(Object); + expect(received[108]).toBe(Object); + expect(received[109]).toBe(Object); + expect(received[110]).toBe(Object); + expect(received[111]).toBe(Object); + expect(received[112]).toBe(Object); + expect(received[113]).toBe(Object); + expect(received[114]).toBe(Object); + expect(received[115]).toBe(Object); + expect(received[116]).toBe(Object); + expect(received[117]).toBe(Object); + expect(received[118]).toBe(Object); + expect(received[119]).toBe(Object); + expect(received[120]).toBe(void 0); + expect(received[121]).toBe(Object); + expect(received[122]).toBe(Object); + expect(received[123]).toBe(Object); + expect(received[124]).toBe(Object); + expect(received[125]).toBe(Object); + expect(received[126]).toBe(Object); + expect(received[127]).toBe(Object); + expect(received[128]).toBe(Object); + expect(received[129]).toBe(Object); + expect(received[130]).toBe(Object); + expect(received[131]).toBe(Object); + expect(received[132]).toBe(Object); + expect(received[133]).toBe(Object); + expect(received[134]).toBe(void 0); + expect(received[135]).toBe(void 0); + expect(received[136]).toBe(void 0); + expect(received[137]).toBe(void 0); + expect(received[138]).toBe(void 0); + expect(received[139]).toBe(void 0); + expect(received[140]).toBe(void 0); + expect(received[141]).toBe(void 0); + expect(received[142]).toBe(void 0); + expect(received[143]).toBe(void 0); + expect(received[144]).toBe(Object); + expect(received[145]).toBe(Object); + expect(received[146]).toBe(void 0); + expect(received[147]).toBe(void 0); + expect(received[148]).toBe(void 0); + expect(received[149]).toBe(void 0); + expect(received[150]).toBe(void 0); + expect(received[151]).toBe(void 0); + expect(received[152]).toBe(void 0); + expect(received[153]).toBe(void 0); + expect(received[154]).toBe(void 0); + expect(received[155]).toBe(Object); + expect(received[156]).toBe(Object); + expect(received[157]).toBe(Object); + expect(received[158]).toBe(void 0); + expect(received[159]).toBe(Object); + expect(received[160]).toBe(Object); + expect(received[161]).toBe(Object); + expect(received[162]).toBe(Object); + expect(received[163]).toBe(Object); + expect(received[164]).toBe(Object); + expect(received[165]).toBe(Object); + expect(received[166]).toBe(Object); + expect(received[167]).toBe(Object); + expect(received[168]).toBe(Object); + expect(received[169]).toBe(Object); + expect(received[170]).toBe(Object); + expect(received[171]).toBe(Boolean); + expect(received[172]).toBe(Object); + expect(received[173]).toBe(Object); + }); + test("design: type, paramtypes, returntype", () => { + function d1() {} + // @ts-ignore + @d1 + class A { + // @ts-ignore + constructor(@d1 arg1: string) {} + // @ts-ignore + @d1 + // @ts-ignore + method1(@d1 arg1: number): boolean { + return true; + } + // @ts-ignore + @d1 + prop1: () => {}; + // @ts-ignore + @d1 + prop2: "foo" = "foo"; + // @ts-ignore + @d1 + prop3: symbol; + } + + expect(Reflect.getMetadata("design:type", A)).toBeUndefined(); + expect(Reflect.getMetadata("design:paramtypes", A)[0]).toBe(String); + expect(Reflect.getMetadata("design:returntype", A)).toBeUndefined(); + + expect(Reflect.getMetadata("design:type", A.prototype)).toBeUndefined(); + expect(Reflect.getMetadata("design:paramtypes", A.prototype)).toBeUndefined(); + expect(Reflect.getMetadata("design:returntype", A.prototype)).toBeUndefined(); + + expect(Reflect.getMetadata("design:type", A.prototype.method1)).toBeUndefined(); + expect(Reflect.getMetadata("design:paramtypes", A.prototype.method1)).toBeUndefined(); + expect(Reflect.getMetadata("design:returntype", A.prototype.method1)).toBeUndefined(); + + expect(Reflect.getMetadata("design:type", A.prototype, "method1")).toBe(Function); + expect(Reflect.getMetadata("design:paramtypes", A.prototype, "method1")[0]).toBe(Number); + expect(Reflect.getMetadata("design:returntype", A.prototype, "method1")).toBe(Boolean); + + expect(Reflect.getMetadata("design:type", A.prototype, "prop1")).toBe(Function); + expect(Reflect.getMetadata("design:paramtypes", A.prototype, "prop1")).toBeUndefined(); + expect(Reflect.getMetadata("design:returntype", A.prototype, "prop1")).toBeUndefined(); + + expect(Reflect.getMetadata("design:type", A.prototype, "prop2")).toBe(String); + expect(Reflect.getMetadata("design:paramtypes", A.prototype, "prop2")).toBeUndefined(); + expect(Reflect.getMetadata("design:returntype", A.prototype, "prop2")).toBeUndefined(); + + expect(Reflect.getMetadata("design:type", A.prototype, "prop3")).toBe(Symbol); + expect(Reflect.getMetadata("design:paramtypes", A.prototype, "prop3")).toBeUndefined(); + expect(Reflect.getMetadata("design:returntype", A.prototype, "prop3")).toBeUndefined(); + }); + + test("class with only constructor argument decorators", () => { + function d1() {} + class A { + // @ts-ignore + constructor(@d1 arg1: string) {} + } + + expect(Reflect.getMetadata("design:type", A)).toBeUndefined(); + expect(Reflect.getMetadata("design:paramtypes", A)[0]).toBe(String); + expect(Reflect.getMetadata("design:returntype", A)).toBeUndefined(); + }); + + test("more types", () => { + type B = "hello" | "world"; + const b = 2; + const c = ["hello", "world"] as const; + type Loser = `hello ${B}`; // "hello hello" | "hello world" + function d1() {} + + class A { + constructor( + // @ts-ignore + @d1 p0: `hello ${B}`, + // @ts-ignore + p1: keyof Something, + p2: typeof b, + p3: readonly ["hello", "world"], + p4: typeof c, + p5: readonly [number, string], + // prettier-ignore + p6: (string | string), + // prettier-ignore + p7: (string & string), + p8: boolean extends true ? "a" : "b", + // @ts-ignore + p9: Loser extends Loser ? string : Foo, + p10: { [keyof in string]: number }, + // @ts-ignore + p11: blah extends blahblah ? number : void, + ) {} + + // @ts-ignore + @d1 + async method1() { + return true; + } + } + + const paramtypes = Reflect.getMetadata("design:paramtypes", A); + expect(paramtypes[0]).toBe(String); + expect(paramtypes[1]).toBe(Object); + expect(paramtypes[2]).toBe(Object); + expect(paramtypes[3]).toBe(Array); + expect(paramtypes[4]).toBe(Object); + expect(paramtypes[5]).toBe(Array); + expect(paramtypes[6]).toBe(String); + expect(paramtypes[7]).toBe(String); + expect(paramtypes[8]).toBe(String); + expect(paramtypes[9]).toBe(Object); + expect(paramtypes[10]).toBe(Object); + expect(paramtypes[11]).toBe(Object); + + expect(Reflect.getMetadata("design:returntype", A.prototype, "method1")).toBe(Promise); + }); +}); diff --git a/test/tsconfig.json b/test/tsconfig.json index af2af2bb5..d308b40b7 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -7,6 +7,7 @@ "moduleResolution": "bundler", "moduleDetection": "force", "allowImportingTsExtensions": true, + "experimentalDecorators": true, "noEmit": true, "composite": true, "strict": true, diff --git a/tsconfig.json b/tsconfig.json index e8b7322b4..b19b46d1f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,7 @@ "extends": "./tsconfig.base.json", "compilerOptions": { "experimentalDecorators": true, + "emitDecoratorMetadata": true, // "skipLibCheck": true, "allowJs": true }, |