aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/js_ast.zig1
-rw-r--r--src/js_parser.zig225
-rw-r--r--src/runtime.footer.bun.js2
-rw-r--r--src/runtime.footer.js2
-rw-r--r--src/runtime.footer.node.js2
-rw-r--r--src/runtime.footer.with-refresh.js2
-rw-r--r--src/runtime.js12
-rw-r--r--src/runtime.zig16
8 files changed, 258 insertions, 4 deletions
diff --git a/src/js_ast.zig b/src/js_ast.zig
index 452f6044c..31bc056fb 100644
--- a/src/js_ast.zig
+++ b/src/js_ast.zig
@@ -539,6 +539,7 @@ pub const G = struct {
body_loc: logger.Loc = logger.Loc.Empty,
close_brace_loc: logger.Loc = logger.Loc.Empty,
properties: []Property = &([_]Property{}),
+ has_decorators: bool = false,
};
// invalid shadowing if left as Comment
diff --git a/src/js_parser.zig b/src/js_parser.zig
index 052edc6b1..af71b5453 100644
--- a/src/js_parser.zig
+++ b/src/js_parser.zig
@@ -1014,6 +1014,8 @@ 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 @"$$m" = NewStaticSymbol("$$m");
@@ -2010,6 +2012,8 @@ const FnOrArrowDataParse = struct {
is_constructor: bool = false,
is_typescript_declare: bool = false,
+ has_argument_decorators: bool = false,
+
is_return_disallowed: bool = false,
is_this_disallowed: bool = false,
@@ -2140,6 +2144,7 @@ const PropertyOpts = struct {
class_has_extends: bool = false,
allow_ts_decorators: bool = false,
ts_decorators: []Expr = &[_]Expr{},
+ has_argument_decorators: bool = false,
};
pub const ScanPassResult = struct {
@@ -5504,6 +5509,7 @@ fn NewParser_(
// Only allow omitting the body if we're parsing TypeScript
.allow_missing_body_for_type_script = is_typescript_enabled,
});
+ p.fn_or_arrow_data_parse.has_argument_decorators = false;
if (comptime is_typescript_enabled) {
// Don't output anything if it's just a forward declaration of a function
@@ -5606,6 +5612,7 @@ fn NewParser_(
p.fn_or_arrow_data_parse.allow_super_call = opts.allow_super_call;
p.fn_or_arrow_data_parse.allow_super_property = opts.allow_super_property;
+ var arg_has_decorators: bool = false;
var args = List(G.Arg){};
while (p.lexer.token != T.t_close_paren) {
// Skip over "this" type annotations
@@ -5626,6 +5633,9 @@ fn NewParser_(
var ts_decorators: []ExprNodeIndex = &([_]ExprNodeIndex{});
if (opts.allow_ts_decorators) {
ts_decorators = try p.parseTypeScriptDecorators();
+ if (ts_decorators.len > 0) {
+ arg_has_decorators = true;
+ }
}
if (!func.flags.contains(.has_rest_arg) and p.lexer.token == T.t_dot_dot_dot) {
@@ -5734,6 +5744,8 @@ fn NewParser_(
try p.lexer.expect(.t_close_paren);
p.fn_or_arrow_data_parse = std.mem.bytesToValue(@TypeOf(p.fn_or_arrow_data_parse), &old_fn_or_arrow_data);
+ 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();
@@ -9418,6 +9430,7 @@ fn NewParser_(
.allow_await = if (is_async) .allow_expr else .allow_ident,
.allow_yield = if (is_generator) .allow_expr else .allow_ident,
});
+ p.fn_or_arrow_data_parse.has_argument_decorators = false;
p.validateFunctionName(func, .expr);
p.popScope();
@@ -10232,6 +10245,9 @@ fn NewParser_(
.allow_missing_body_for_type_script = is_typescript_enabled and opts.is_class,
});
+ opts.has_argument_decorators = opts.has_argument_decorators or p.fn_or_arrow_data_parse.has_argument_decorators;
+ p.fn_or_arrow_data_parse.has_argument_decorators = false;
+
// "class Foo { foo(): void; foo(): void {} }"
if (func.flags.contains(.is_forward_declaration)) {
// Skip this property entirely
@@ -10335,6 +10351,7 @@ fn NewParser_(
// been parsed. We need to start parsing from the "extends" clause.
pub fn parseClass(p: *P, class_keyword: logger.Range, name: ?js_ast.LocRef, class_opts: ParseClassOptions) !G.Class {
var extends: ?Expr = null;
+ var has_decorators: bool = false;
if (p.lexer.token == .t_extends) {
try p.lexer.next();
@@ -10386,12 +10403,13 @@ fn NewParser_(
continue;
}
- opts = PropertyOpts{ .is_class = true, .allow_ts_decorators = class_opts.allow_ts_decorators, .class_has_extends = extends != null };
+ opts = PropertyOpts{ .is_class = true, .allow_ts_decorators = class_opts.allow_ts_decorators, .class_has_extends = extends != null, .has_argument_decorators = false };
// Parse decorators for this property
const first_decorator_loc = p.lexer.loc();
if (opts.allow_ts_decorators) {
opts.ts_decorators = try p.parseTypeScriptDecorators();
+ has_decorators = has_decorators or opts.ts_decorators.len > 0;
} else {
opts.ts_decorators = &[_]Expr{};
}
@@ -10411,6 +10429,8 @@ fn NewParser_(
else => {},
}
}
+
+ has_decorators = has_decorators or opts.has_argument_decorators;
}
}
@@ -10433,6 +10453,7 @@ fn NewParser_(
.class_keyword = class_keyword,
.body_loc = body_loc,
.properties = properties.toOwnedSlice(),
+ .has_decorators = has_decorators or class_opts.ts_decorators.len > 0,
};
}
@@ -17355,9 +17376,205 @@ fn NewParser_(
) []Stmt {
switch (stmtorexpr) {
.stmt => |stmt| {
- var stmts = p.allocator.alloc(Stmt, 1) catch unreachable;
- stmts[0] = stmt;
- return stmts;
+ if (!stmt.data.s_class.class.has_decorators) {
+ var stmts = p.allocator.alloc(Stmt, 1) catch unreachable;
+ stmts[0] = stmt;
+ return stmts;
+ }
+
+ var class = &stmt.data.s_class.class;
+ var constructor_function: ?*E.Function = null;
+
+ var static_decorators = ListManaged(Stmt).init(p.allocator);
+ var instance_decorators = ListManaged(Stmt).init(p.allocator);
+ var instance_members = ListManaged(Stmt).init(p.allocator);
+ var static_members = ListManaged(Stmt).init(p.allocator);
+ var class_properties = ListManaged(Property).init(p.allocator);
+
+ for (class.properties) |*prop| {
+ // merge parameter decorators with method decorators
+ if (prop.flags.contains(.is_method)) {
+ if (prop.value) |prop_value| {
+ switch (prop_value.data) {
+ .e_function => |func| {
+ const is_constructor = (prop.key.?.data == .e_string and prop.key.?.data.e_string.eqlComptime("constructor"));
+ for (func.func.args) |arg, i| {
+ for (arg.ts_decorators.ptr[0..arg.ts_decorators.len]) |arg_decorator| {
+ var decorators = if (is_constructor) class.ts_decorators.listManaged(p.allocator) else prop.ts_decorators.listManaged(p.allocator);
+ const args = p.allocator.alloc(Expr, 2) catch unreachable;
+ args[0] = p.e(E.Number{ .value = @intToFloat(f64, i) }, arg_decorator.loc);
+ args[1] = arg_decorator;
+ decorators.append(p.callRuntime(arg_decorator.loc, "__decorateParam", args)) catch unreachable;
+ if (is_constructor) {
+ class.ts_decorators.update(decorators);
+ } else {
+ prop.ts_decorators.update(decorators);
+ }
+ }
+ }
+ },
+ else => unreachable,
+ }
+ }
+ }
+
+ if (prop.flags.contains(.is_method)) {
+ if (prop.key.?.data == .e_string and prop.key.?.data.e_string.eqlComptime("constructor")) {
+ if (prop.value) |prop_value| {
+ switch (prop_value.data) {
+ .e_function => |func| {
+ constructor_function = func;
+ },
+ else => unreachable,
+ }
+ }
+ }
+ }
+
+ if (prop.ts_decorators.len > 0) {
+ const loc = prop.key.?.loc;
+ const descriptor_key = switch (prop.key.?.data) {
+ .e_identifier => |k| p.e(E.Identifier{ .ref = k.ref }, loc),
+ .e_number => |k| p.e(E.Number{ .value = k.value }, loc),
+ .e_string => |k| p.e(E.String{ .data = k.data }, loc),
+ else => undefined,
+ };
+
+ const descriptor_kind: f64 = if (!prop.flags.contains(.is_method)) 2 else 1;
+
+ var target: Expr = undefined;
+ if (prop.flags.contains(.is_static)) {
+ p.recordUsage(class.class_name.?.ref.?);
+ target = p.e(E.Identifier{ .ref = class.class_name.?.ref.? }, class.class_name.?.loc);
+ } else {
+ target = p.e(E.Dot{ .target = p.e(E.Identifier{ .ref = class.class_name.?.ref.? }, class.class_name.?.loc), .name = "prototype", .name_loc = loc }, loc);
+ }
+
+ const args = p.allocator.alloc(Expr, 4) catch unreachable;
+ args[0] = p.e(E.Array{ .items = prop.ts_decorators }, loc);
+ args[1] = target;
+ args[2] = descriptor_key;
+ args[3] = p.e(E.Number{ .value = descriptor_kind }, loc);
+
+ const decorator = p.callRuntime(prop.key.?.loc, "__decorateClass", args);
+ const decorator_stmt = p.s(S.SExpr{ .value = decorator }, decorator.loc);
+
+ if (prop.flags.contains(.is_static)) {
+ static_decorators.append(decorator_stmt) catch unreachable;
+ } else {
+ instance_decorators.append(decorator_stmt) catch unreachable;
+ }
+ }
+
+ if (!prop.flags.contains(.is_method) and prop.key.?.data != .e_private_identifier and prop.ts_decorators.len > 0) {
+ // remove decorated fields without initializers to avoid assigning undefined.
+ const initializer = if (prop.initializer) |initializer_value| initializer_value else continue;
+
+ var target: Expr = undefined;
+ if (prop.flags.contains(.is_static)) {
+ p.recordUsage(class.class_name.?.ref.?);
+ target = p.e(E.Identifier{ .ref = class.class_name.?.ref.? }, class.class_name.?.loc);
+ } else {
+ target = p.e(E.This{}, prop.key.?.loc);
+ }
+
+ if (prop.flags.contains(.is_computed)) {
+ target = p.e(E.Index{
+ .target = target,
+ .index = prop.key.?,
+ }, prop.key.?.loc);
+ } else {
+ target = p.e(E.Dot{
+ .target = target,
+ .name = prop.key.?.data.e_string.data,
+ .name_loc = prop.key.?.loc,
+ }, prop.key.?.loc);
+ }
+
+ // remove fields with decorators from class body. Move static members outside of class.
+ if (prop.flags.contains(.is_static)) {
+ static_members.append(Expr.assignStmt(target, initializer, p.allocator)) catch unreachable;
+ } else {
+ instance_members.append(Expr.assignStmt(target, initializer, p.allocator)) catch unreachable;
+ }
+ continue;
+ }
+
+ class_properties.append(prop.*) catch unreachable;
+ }
+
+ class.properties = class_properties.items;
+
+ if (instance_members.items.len > 0 or class.extends != null) {
+ if (constructor_function == null) {
+ var properties = ListManaged(Property).fromOwnedSlice(p.allocator, class.properties);
+ var constructor_stmts = ListManaged(Stmt).init(p.allocator);
+
+ if (class.extends != null) {
+ const target = p.e(E.Super{}, stmt.loc);
+ const arguments_ref = p.newSymbol(.unbound, "arguments") catch unreachable;
+ p.current_scope.generated.append(p.allocator, arguments_ref) catch unreachable;
+
+ const super = p.e(E.Spread{ .value = p.e(E.Identifier{ .ref = arguments_ref }, stmt.loc) }, stmt.loc);
+ const args = ExprNodeList.one(p.allocator, super) catch unreachable;
+
+ constructor_stmts.append(p.s(S.SExpr{ .value = p.e(E.Call{ .target = target, .args = args }, stmt.loc) }, stmt.loc)) catch unreachable;
+ }
+
+ constructor_stmts.appendSlice(instance_members.items) catch unreachable;
+
+ properties.insert(0, G.Property{
+ .flags = Flags.Property.init(.{ .is_method = true }),
+ .key = p.e(E.String{ .data = "constructor" }, stmt.loc),
+ .value = p.e(E.Function{ .func = G.Fn{
+ .name = null,
+ .open_parens_loc = logger.Loc.Empty,
+ .args = &[_]Arg{},
+ .body = .{ .loc = stmt.loc, .stmts = constructor_stmts.items },
+ .flags = Flags.Function.init(.{}),
+ } }, stmt.loc),
+ }) catch unreachable;
+
+ class.properties = properties.items;
+ } else {
+ var constructor_stmts = ListManaged(Stmt).fromOwnedSlice(p.allocator, constructor_function.?.func.body.stmts);
+ // statements coming from class body inserted after super call or beginning of constructor.
+ var has_super = false;
+ for (constructor_stmts.items) |item, index| {
+ if (item.data != .s_expr or item.data.s_expr.value.data != .e_call or item.data.s_expr.value.data.e_call.target.data != .e_super) continue;
+ has_super = true;
+ constructor_stmts.insertSlice(index + 1, instance_members.items) catch unreachable;
+ }
+ if (!has_super) {
+ constructor_stmts.insertSlice(0, instance_members.items) catch unreachable;
+ }
+
+ constructor_function.?.func.body.stmts = constructor_stmts.items;
+ }
+ }
+
+ var stmts_count: usize = 1 + static_members.items.len + instance_decorators.items.len + static_decorators.items.len;
+ if (class.ts_decorators.len > 0) stmts_count += 1;
+ var stmts = ListManaged(Stmt).initCapacity(p.allocator, stmts_count) catch unreachable;
+ stmts.appendAssumeCapacity(stmt);
+ stmts.appendSliceAssumeCapacity(static_members.items);
+ stmts.appendSliceAssumeCapacity(instance_decorators.items);
+ stmts.appendSliceAssumeCapacity(static_decorators.items);
+ if (class.ts_decorators.len > 0) {
+ const args = p.allocator.alloc(Expr, 2) catch unreachable;
+ args[0] = p.e(E.Array{ .items = class.ts_decorators }, stmt.loc);
+ args[1] = p.e(E.Identifier{ .ref = class.class_name.?.ref.? }, class.class_name.?.loc);
+
+ stmts.appendAssumeCapacity(Expr.assignStmt(
+ p.e(E.Identifier{ .ref = class.class_name.?.ref.? }, class.class_name.?.loc),
+ p.callRuntime(stmt.loc, "__decorateClass", args),
+ p.allocator,
+ ));
+
+ p.recordUsage(class.class_name.?.ref.?);
+ p.recordUsage(class.class_name.?.ref.?);
+ }
+ return stmts.items;
},
.expr => |expr| {
var stmts = p.allocator.alloc(Stmt, 1) catch unreachable;
diff --git a/src/runtime.footer.bun.js b/src/runtime.footer.bun.js
index a5466f53a..0c83ebc49 100644
--- a/src/runtime.footer.bun.js
+++ b/src/runtime.footer.bun.js
@@ -11,6 +11,8 @@ 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 $$bun_runtime_json_parse = JSON.parse;
export var __internalIsCommonJSNamespace =
BUN_RUNTIME.__internalIsCommonJSNamespace;
diff --git a/src/runtime.footer.js b/src/runtime.footer.js
index be4516473..062961b1f 100644
--- a/src/runtime.footer.js
+++ b/src/runtime.footer.js
@@ -19,6 +19,8 @@ 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 $$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 7d2b3a649..a6e425e44 100644
--- a/src/runtime.footer.node.js
+++ b/src/runtime.footer.node.js
@@ -12,6 +12,8 @@ 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 $$bun_runtime_json_parse = JSON.parse;
export var __internalIsCommonJSNamespace =
BUN_RUNTIME.__internalIsCommonJSNamespace;
diff --git a/src/runtime.footer.with-refresh.js b/src/runtime.footer.with-refresh.js
index 618329baf..a5b5a3b79 100644
--- a/src/runtime.footer.with-refresh.js
+++ b/src/runtime.footer.with-refresh.js
@@ -18,6 +18,8 @@ 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 $$bun_runtime_json_parse = JSON.parse;
export var __FastRefreshRuntime = BUN_RUNTIME.__FastRefreshRuntime;
export var __internalIsCommonJSNamespace =
diff --git a/src/runtime.js b/src/runtime.js
index e1cf9e3df..fe2f5b9df 100644
--- a/src/runtime.js
+++ b/src/runtime.js
@@ -235,3 +235,15 @@ export var __merge = (props, defaultProps) => {
? 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 __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index); \ No newline at end of file
diff --git a/src/runtime.zig b/src/runtime.zig
index 1a490e4d2..87daae829 100644
--- a/src/runtime.zig
+++ b/src/runtime.zig
@@ -364,6 +364,8 @@ pub const Runtime = struct {
__exportDefault: ?GeneratedSymbol = null,
__FastRefreshRuntime: ?GeneratedSymbol = null,
__merge: ?GeneratedSymbol = null,
+ __decorateClass: ?GeneratedSymbol = null,
+ __decorateParam: ?GeneratedSymbol = null,
pub const all = [_][]const u8{
// __HMRClient goes first
@@ -384,6 +386,8 @@ pub const Runtime = struct {
"__exportDefault",
"__FastRefreshRuntime",
"__merge",
+ "__decorateClass",
+ "__decorateParam",
};
pub const Name = "bun:wrap";
pub const alt_name = "bun:wrap";
@@ -483,6 +487,16 @@ pub const Runtime = struct {
return Entry{ .key = 15, .value = val.ref };
}
},
+ 16 => {
+ if (@field(this.runtime_imports, all[16])) |val| {
+ return Entry{ .key = 16, .value = val.ref };
+ }
+ },
+ 17 => {
+ if (@field(this.runtime_imports, all[17])) |val| {
+ return Entry{ .key = 17, .value = val.ref };
+ }
+ },
else => {
return null;
},
@@ -543,6 +557,8 @@ pub const Runtime = struct {
13 => (@field(imports, all[13]) orelse return null).ref,
14 => (@field(imports, all[14]) orelse return null).ref,
15 => (@field(imports, all[15]) orelse return null).ref,
+ 16 => (@field(imports, all[16]) orelse return null).ref,
+ 17 => (@field(imports, all[17]) orelse return null).ref,
else => null,
};
}