diff options
author | 2021-09-26 20:03:49 -0700 | |
---|---|---|
committer | 2021-09-26 20:03:49 -0700 | |
commit | 018ba2c83bf3c2e925ec3273c5f936cb4aff434f (patch) | |
tree | 9b028dd9911c8ecc17fec29076c91cf035728a7c | |
parent | 66ed7c1f30d1ba6569efa114c9d90ccac45fb86a (diff) | |
download | bun-018ba2c83bf3c2e925ec3273c5f936cb4aff434f.tar.gz bun-018ba2c83bf3c2e925ec3273c5f936cb4aff434f.tar.zst bun-018ba2c83bf3c2e925ec3273c5f936cb4aff434f.zip |
Most of macro implementation
-rw-r--r-- | package.json | 5 | ||||
-rw-r--r-- | src/bundler.zig | 1 | ||||
-rw-r--r-- | src/js_ast.zig | 542 | ||||
-rw-r--r-- | src/js_lexer.zig | 10 | ||||
-rw-r--r-- | src/js_parser/js_parser.zig | 147 | ||||
-rw-r--r-- | src/js_printer.zig | 7 | ||||
-rw-r--r-- | src/runtime.zig | 1 |
7 files changed, 616 insertions, 97 deletions
diff --git a/package.json b/package.json index 6a5e8d71c..2052081df 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,10 @@ { "dependencies": { + "moment": "^2.29.1", "peechy": "^0.4.18", - "puppeteer": "^10.2.0" + "puppeteer": "^10.2.0", + "react": "^17.0.2", + "react-dom": "^17.0.2" }, "scripts": { "build-runtime": "esbuild --target=esnext --bundle src/runtime/index.ts --format=iife --platform=browser --global-name=BUN_RUNTIME > src/runtime.out.js; cat src/runtime.footer.js >> src/runtime.out.js", diff --git a/src/bundler.zig b/src/bundler.zig index d36795d52..64982c822 100644 --- a/src/bundler.zig +++ b/src/bundler.zig @@ -133,6 +133,7 @@ pub const Bundler = struct { to.log = try allocator.create(logger.Log); to.log.* = logger.Log.init(allocator); to.setLog(to.log); + to.macro_context = null; } pub fn setLog(this: *ThisBundler, log: *logger.Log) void { diff --git a/src/js_ast.zig b/src/js_ast.zig index 28512365a..ab36c995d 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -1034,6 +1034,7 @@ pub const E = struct { pub const Number = struct { value: f64, + pub fn jsonStringify(self: *const Number, opts: anytype, o: anytype) !void { return try std.json.stringify(self.value, opts, o); } @@ -1188,8 +1189,45 @@ pub const E = struct { pub const RegExp = struct { value: string, + // This exists for JavaScript bindings + // The RegExp constructor expects flags as a second argument. + // We want to avoid re-lexing the flags, so we store them here. + // This is the index of the first character in a flag, not the "/" + // /foo/gim + // ^ + flags_offset: ?u16 = null, + pub var empty = RegExp{ .value = "" }; + pub fn pattern(this: RegExp) string { + // rewind until we reach the /foo/gim + // ^ + // should only ever be a single character + // but we're being cautious + if (this.flags_offset) |i_| { + var i = i_; + while (i > 0 and this.value[i] != '/') { + i -= 1; + } + + return this.value[0..i]; + } + + return this.value; + } + + pub fn flags(this: RegExp) string { + // rewind until we reach the /foo/gim + // ^ + // should only ever be a single character + // but we're being cautious + if (this.flags_offset) |i| { + return this.value[i..]; + } + + return ""; + } + pub fn jsonStringify(self: *const RegExp, opts: anytype, o: anytype) !void { return try std.json.stringify(self.value, opts, o); } @@ -4078,28 +4116,372 @@ pub const Macro = struct { }, .{ .toString = .{ - .rfn = toString, + .rfn = JSBindings.toString, }, + + // .getAt = .{ + // .rfn = JSBindings.getAt, + // }, + // .valueAt = .{ + // .rfn = JSBindings.valueAt, + // }, // .toNumber = .{ // .rfn = toNumber, // }, + .get = .{ + .rfn = JSBindings.get, + .ro = true, + }, }, .{ .tag = .{ - .get = getTag, + .get = JSBindings.getTag, .ro = true, }, + .tagName = .{ - .get = getTagName, + .get = JSBindings.getTagName, .ro = true, }, .position = .{ - .get = getPosition, + .get = JSBindings.getPosition, + .ro = true, + }, + .value = .{ + .get = JSBindings.getValue, + .ro = true, + }, + .arguments = .{ + .get = JSBindings.getCallArgs, .ro = true, }, }, ); + pub fn makeFromExpr(allocator: *std.mem.Allocator, expr: Expr) js.JSObjectRef { + var ptr = allocator.create(JSNode) catch unreachable; + ptr.* = JSNode.initExpr(expr); + // If we look at JSObjectMake, we can see that all it does with the ctx value is lookup what the global object is + // so it's safe to just avoid that and do it here like this: + return JSNode.Class.make(JavaScript.VirtualMachine.vm.global.ref(), ptr); + } + pub const JSBindings = struct { + const getAllocator = JSCBase.getAllocator; + + threadlocal var temporary_call_args_array: [256]js.JSValueRef = undefined; + pub fn getCallArgs( + this: *JSNode, + ctx: js.JSContextRef, + thisObject: js.JSValueRef, + prop: js.JSStringRef, + exception: js.ExceptionRef, + ) js.JSObjectRef { + const args = this.data.callArgs(); + + switch (args.len) { + 0 => return js.JSObjectMakeArray(ctx, 0, null, exception), + 1...255 => { + var slice = temporary_call_args_array[0..args.len]; + for (slice) |_, i| { + var node = JSCBase.getAllocator(ctx).create(JSNode) catch unreachable; + node.* = JSNode.initExpr(args[i]); + slice[i] = JSNode.Class.make(ctx, node); + } + return js.JSObjectMakeArray(ctx, args.len, slice.ptr, exception); + }, + else => { + Output.prettyErrorln("are you for real? {d} args to your call expression? that has to be a bug.\n", .{args.len}); + Output.flush(); + return js.JSObjectMakeArray(ctx, 0, null, exception); + }, + } + } + + fn toNumberValue(this: *JSNode, number: E.Number) js.JSValueRef { + return JSC.JSValue.jsNumberFromDouble(number.value).asRef(); + } + + fn toStringValue(this: *JSNode, ctx: js.JSContextRef, str: E.String) js.JSObjectRef { + if (str.isBlank()) { + return JSC.ZigString.init("").toValue(JavaScript.VirtualMachine.vm.global).asRef(); + } + + if (str.isUTF8()) { + return JSC.ZigString.init(str.utf8).toValue(JavaScript.VirtualMachine.vm.global).asRef(); + } else { + return js.JSValueMakeString(ctx, js.JSStringCreateWithCharactersNoCopy(str.value.ptr, str.value.len)); + } + } + + threadlocal var regex_value_array: [2]js.JSValueRef = undefined; + + fn toRegexValue(this: *JSNode, ctx: js.JSContextRef, regex: *E.RegExp, exception: js.ExceptionRef) js.JSObjectRef { + if (regex.value.len == 0) { + return js.JSObjectMakeRegExp(ctx, 0, null, exception); + } + + regex_value_array[0] = JSC.ZigString.init(regex.pattern()).toValue(JavaScript.VirtualMachine.vm.global).asRef(); + regex_value_array[1] = JSC.ZigString.init(regex.flags()).toValue(JavaScript.VirtualMachine.vm.global).asRef(); + + return js.JSObjectMakeRegExp(ctx, 2, ®ex_value_array, exception); + } + + fn toArrayValue(this: *JSNode, ctx: js.JSContextRef, array: E.Array, exception: js.ExceptionRef) js.JSObjectRef { + if (array.items.len == 0) { + return js.JSObjectMakeArray(ctx, 0, null, exception); + } + + for (array.items) |expr, i| { + var node = JSCBase.getAllocator(ctx).create(JSNode) catch unreachable; + node.* = JSNode.initExpr(expr); + temporary_call_args_array[i] = JSNode.Class.make(ctx, node); + } + + return js.JSObjectMakeArray(ctx, array.items.len, &temporary_call_args_array, exception); + } + + fn toArrayPrimitive(this: *JSNode, ctx: js.JSContextRef, array: E.Array, exception: js.ExceptionRef) js.JSObjectRef { + if (array.items.len == 0) { + return js.JSObjectMakeArray(ctx, 0, null, exception); + } + + var node: JSNode = undefined; + for (array.items) |expr, i| { + node = JSNode.initExpr(expr); + temporary_call_args_array[i] = toPrimitive(&node, ctx, exception); + } + + return js.JSObjectMakeArray(ctx, array.items.len, temporary_call_args_array[0..array.items.len].ptr, exception); + } + + fn toObjectValue(this: *JSNode, ctx: js.JSContextRef, obj: E.Object, exception: js.ExceptionRef) js.JSObjectRef { + if (obj.properties.len == 0) { + return js.JSObjectMakeArray(ctx, 0, null, exception); + } + + for (obj.properties) |*prop, i| { + var node = JSCBase.getAllocator(ctx).create(JSNode) catch unreachable; + node.* = JSNode{ + .data = .{ + .g_property = prop, + }, + .loc = this.loc, + }; + temporary_call_args_array[i] = JSNode.Class.make(ctx, node); + } + + return js.JSObjectMakeArray(ctx, obj.properties.len, &temporary_call_args_array, exception); + } + + fn toObjectPrimitive(this: *JSNode, ctx: js.JSContextRef, obj: E.Object, exception: js.ExceptionRef) js.JSObjectRef { + return toObjectValue(this, ctx, obj, exception); + } + + fn toPropertyPrimitive(this: *JSNode, ctx: js.JSContextRef, prop: G.Property, exception: js.ExceptionRef) js.JSObjectRef { + var entries: [3]js.JSValueRef = undefined; + + entries[0] = js.JSValueMakeUndefined(ctx); + entries[1] = entries[0]; + entries[2] = entries[0]; + + var other: JSNode = undefined; + + if (prop.key) |key| { + other = JSNode.initExpr(key); + entries[0] = toPrimitive( + &other, + ctx, + exception, + ) orelse js.JSValueMakeUndefined(ctx); + } + + if (prop.value) |value| { + other = JSNode.initExpr(value); + entries[1] = toPrimitive( + &other, + ctx, + exception, + ) orelse js.JSValueMakeUndefined(ctx); + } + + if (prop.initializer) |value| { + other = JSNode.initExpr(value); + entries[2] = toPrimitive( + &other, + ctx, + exception, + ) orelse js.JSValueMakeUndefined(ctx); + } + + const out = js.JSObjectMakeArray(ctx, 3, &entries, exception); + return out; + } + + pub fn toString( + this: *JSNode, + ctx: js.JSContextRef, + function: js.JSObjectRef, + thisObject: js.JSObjectRef, + arguments: []const js.JSValueRef, + exception: js.ExceptionRef, + ) js.JSObjectRef { + switch (this.data) { + .e_string => |str| { + return toStringValue(this, ctx, str.*); + }, + .e_template => |template| { + const str = template.head; + + if (str.isBlank()) { + return JSC.ZigString.init("").toValue(JavaScript.VirtualMachine.vm.global).asRef(); + } + + if (str.isUTF8()) { + return JSC.ZigString.init(str.utf8).toValue(JavaScript.VirtualMachine.vm.global).asRef(); + } else { + return js.JSValueMakeString(ctx, js.JSStringCreateWithCharactersNoCopy(str.value.ptr, str.value.len)); + } + }, + // .e_number => |number| { + + // }, + else => { + return JSC.ZigString.init("").toValue(JavaScript.VirtualMachine.vm.global).asRef(); + }, + } + } + + fn toPrimitive( + this: *JSNode, + ctx: js.JSContextRef, + exception: js.ExceptionRef, + ) js.JSValueRef { + return @call(.{ .modifier = .always_inline }, toPrimitiveAllowRecursion, .{ this, ctx, exception, false }); + } + + fn toPrimitiveWithRecursion( + this: *JSNode, + ctx: js.JSContextRef, + exception: js.ExceptionRef, + ) js.JSValueRef { + return @call(.{ .modifier = .always_inline }, toPrimitiveAllowRecursion, .{ this, ctx, exception, true }); + } + + fn toPrimitiveAllowRecursion(this: *JSNode, ctx: js.JSContextRef, exception: js.ExceptionRef, comptime allow_recursion: bool) js.JSValueRef { + switch (this.data) { + .e_string => |str| { + return JSBindings.toStringValue(this, ctx, str.*); + }, + .e_template => |template| { + return JSBindings.toStringValue(this, ctx, template.head); + // return JSBindings.toTemplatePrimitive(this, ctx, template.*); + }, + .e_number => |number| { + return JSBindings.toNumberValue(this, number); + }, + .e_reg_exp => |regex| { + return JSBindings.toRegexValue(this, ctx, regex, exception); + }, + .e_object => |object| { + if (comptime !allow_recursion) return js.JSValueMakeUndefined(ctx); + return JSBindings.toObjectPrimitive(this, ctx, object.*, exception); + }, + .e_array => |array| { + if (comptime !allow_recursion) return js.JSValueMakeUndefined(ctx); + return JSBindings.toArrayPrimitive(this, ctx, array.*, exception); + }, + + // Returns an Entry + // [string, number | regex | object | string | null | undefined] + .g_property => |property| { + return JSBindings.toPropertyPrimitive(this, ctx, property.*, exception); + }, + .e_null => { + return js.JSValueMakeNull(ctx); + }, + else => { + return js.JSValueMakeUndefined(ctx); + }, + } + } + + fn toValue(this: *JSNode, ctx: js.JSContextRef, exception: js.ExceptionRef) js.JSObjectRef { + switch (this.data) { + .e_await => |aw| { + return JSNode.makeFromExpr(getAllocator(ctx), aw.value); + }, + .e_yield => |yi| { + return JSNode.makeFromExpr(getAllocator(ctx), yi.value orelse return null); + }, + .e_spread => |spread| { + return JSNode.makeFromExpr(getAllocator(ctx), spread.value); + }, + .e_reg_exp => |reg| { + return JSC.ZigString.toRef(reg.value, JavaScript.VirtualMachine.vm.global); + }, + + .e_array => |array| { + return toArrayValue(this, ctx, array.*, exception); + }, + .e_object => |obj| { + return toObjectValue(this, ctx, obj.*, exception); + }, + else => { + return null; + }, + } + } + + pub fn getValue( + this: *JSNode, + ctx: js.JSContextRef, + thisObject: js.JSValueRef, + prop: js.JSStringRef, + exception: js.ExceptionRef, + ) js.JSObjectRef { + return toValue(this, ctx, exception) orelse return thisObject; + } + + pub fn get( + this: *JSNode, + ctx: js.JSContextRef, + function: js.JSObjectRef, + thisObject: js.JSObjectRef, + arguments: []const js.JSValueRef, + exception: js.ExceptionRef, + ) js.JSObjectRef { + return toPrimitiveWithRecursion(this, ctx, exception) orelse return js.JSValueMakeUndefined(ctx); + } + + pub fn getTag( + this: *JSNode, + ctx: js.JSContextRef, + thisObject: js.JSValueRef, + prop: js.JSStringRef, + exception: js.ExceptionRef, + ) js.JSObjectRef { + return JSC.JSValue.jsNumberFromU16(@intCast(u16, @enumToInt(std.meta.activeTag(this.data)))).asRef(); + } + pub fn getTagName( + this: *JSNode, + ctx: js.JSContextRef, + thisObject: js.JSValueRef, + prop: js.JSStringRef, + exception: js.ExceptionRef, + ) js.JSObjectRef { + return JSC.ZigString.init(@tagName(this.data)).toValue(JavaScript.VirtualMachine.vm.global).asRef(); + } + pub fn getPosition( + this: *JSNode, + ctx: js.JSContextRef, + thisObject: js.JSValueRef, + prop: js.JSStringRef, + exception: js.ExceptionRef, + ) js.JSObjectRef { + return JSC.JSValue.jsNumberFromInt32(this.loc.start).asRef(); + } + }; + pub fn initExpr(this: Expr) JSNode { switch (this.data) { .e_array => |value| { @@ -4373,6 +4755,21 @@ pub const Macro = struct { s_block: *S.Block, g_property: *G.Property, + + pub fn callArgs(this: Data) ExprNodeList { + if (this == .e_call) + return this.e_call.args + else + return &[_]Expr{}; + } + + pub fn booleanValue(this: Data) bool { + return switch (this) { + .inline_false => false, + .inline_true => true, + .e_boolean => this.e_boolean.value, + }; + } }; pub const Tag = enum(u8) { e_array, @@ -5316,6 +5713,28 @@ pub const Macro = struct { invalid, }; + pub fn fromJSValueRefNoValidate(ctx: js.JSContextRef, value: js.JSValueRef) TagOrJSNode { + switch (js.JSValueGetType(ctx, value)) { + js.JSType.kJSTypeNumber => { + const tag_int = @floatToInt(u8, JSC.JSValue.fromRef(value).asNumber()); + if (tag_int < Tag.min_tag or tag_int > Tag.max_tag) { + return TagOrJSNode{ .invalid = .{} }; + } + return TagOrJSNode{ .tag = @intToEnum(JSNode.Tag, tag_int) }; + }, + js.JSType.kJSTypeObject => { + if (JSCBase.GetJSPrivateData(JSNode, value)) |node| { + return TagOrJSNode{ .node = node.* }; + } + + return TagOrJSNode{ .invalid = .{} }; + }, + else => { + return TagOrJSNode{ .invalid = .{} }; + }, + } + } + pub fn fromJSValueRef(writer: *Writer, ctx: js.JSContextRef, value: js.JSValueRef) TagOrJSNode { switch (js.JSValueGetType(ctx, value)) { js.JSType.kJSTypeNumber => { @@ -5622,10 +6041,56 @@ pub const Macro = struct { pub const BunJSXCallbackFunction = JSCBase.NewClass( void, .{ .name = "bunJSX" }, - .{ .call = .{ .rfn = createFromJavaScript } }, + .{ + .call = .{ + .rfn = createFromJavaScript, + .ro = true, + }, + .isNodeType = .{ + .rfn = isNodeType, + .ro = true, + }, + }, .{}, ); + pub fn isNodeType( + this: void, + ctx: js.JSContextRef, + function: js.JSObjectRef, + thisObject: js.JSObjectRef, + arguments: []const js.JSValueRef, + exception: js.ExceptionRef, + ) js.JSObjectRef { + if (arguments.len != 2) { + throwTypeError(ctx, "bunJSX.isNodeType() requires 2 arguments", exception); + return null; + } + + const TagOrNodeType = Writer.TagOrJSNode.TagOrNodeType; + + const left = Writer.TagOrJSNode.fromJSValueRefNoValidate(ctx, arguments[0]); + const right = Writer.TagOrJSNode.fromJSValueRefNoValidate(ctx, arguments[1]); + + if (left == TagOrNodeType.invalid or right == TagOrNodeType.invalid) { + return js.JSValueMakeBoolean(ctx, false); + } + + if (left == TagOrNodeType.node and right == TagOrNodeType.node) { + return js.JSValueMakeBoolean(ctx, @as(Tag, left.node.data) == @as(Tag, right.node.data)); + } + + if (left == TagOrNodeType.node) { + return js.JSValueMakeBoolean(ctx, @as(Tag, left.node.data) == right.tag); + } + + if (right == TagOrNodeType.node) { + return js.JSValueMakeBoolean(ctx, @as(Tag, right.node.data) == left.tag); + } + + unreachable; + } + pub fn createFromJavaScript( this: void, ctx: js.JSContextRef, @@ -5662,73 +6127,6 @@ pub const Macro = struct { return null; } - - pub fn toString( - this: *JSNode, - ctx: js.JSContextRef, - function: js.JSObjectRef, - thisObject: js.JSObjectRef, - arguments: []const js.JSValueRef, - exception: js.ExceptionRef, - ) js.JSObjectRef { - switch (this.data) { - .e_string => |str| { - if (str.isBlank()) { - return JSC.ZigString.init("").toValue(JavaScript.VirtualMachine.vm.global).asRef(); - } - - if (str.isUTF8()) { - return JSC.ZigString.init(str.utf8).toValue(JavaScript.VirtualMachine.vm.global).asRef(); - } else { - return js.JSValueMakeString(ctx, js.JSStringCreateWithCharactersNoCopy(str.value.ptr, str.value.len)); - } - }, - .e_template => |template| { - const str = template.head; - - if (str.isBlank()) { - return JSC.ZigString.init("").toValue(JavaScript.VirtualMachine.vm.global).asRef(); - } - - if (str.isUTF8()) { - return JSC.ZigString.init(str.utf8).toValue(JavaScript.VirtualMachine.vm.global).asRef(); - } else { - return js.JSValueMakeString(ctx, js.JSStringCreateWithCharactersNoCopy(str.value.ptr, str.value.len)); - } - }, - else => { - return JSC.ZigString.init("").toValue(JavaScript.VirtualMachine.vm.global).asRef(); - }, - } - } - - pub fn getTag( - this: *JSNode, - ctx: js.JSContextRef, - thisObject: js.JSValueRef, - prop: js.JSStringRef, - exception: js.ExceptionRef, - ) js.JSObjectRef { - return JSC.JSValue.jsNumberFromU16(@intCast(u16, @enumToInt(std.meta.activeTag(this.data)))).asRef(); - } - pub fn getTagName( - this: *JSNode, - ctx: js.JSContextRef, - thisObject: js.JSValueRef, - prop: js.JSStringRef, - exception: js.ExceptionRef, - ) js.JSObjectRef { - return JSC.ZigString.init(@tagName(this.data)).toValue(JavaScript.VirtualMachine.vm.global).asRef(); - } - pub fn getPosition( - this: *JSNode, - ctx: js.JSContextRef, - thisObject: js.JSValueRef, - prop: js.JSStringRef, - exception: js.ExceptionRef, - ) js.JSObjectRef { - return JSC.JSValue.jsNumberFromInt32(this.loc.start).asRef(); - } }; resolver: *Resolver, diff --git a/src/js_lexer.zig b/src/js_lexer.zig index ec800cfb5..91f4fceef 100644 --- a/src/js_lexer.zig +++ b/src/js_lexer.zig @@ -76,6 +76,7 @@ pub const Lexer = struct { number: f64 = 0.0, rescan_close_brace_as_template_token: bool = false, prev_error_loc: logger.Loc = logger.Loc.Empty, + regex_flags_start: ?u16 = null, allocator: *std.mem.Allocator, /// In JavaScript, strings are stored as UTF-16, but nearly every string is ascii. /// This means, usually, we can skip UTF8 -> UTF16 conversions. @@ -108,6 +109,7 @@ pub const Lexer = struct { .all_original_comments = self.all_original_comments, .code_point = self.code_point, .identifier = self.identifier, + .regex_flags_start = self.regex_flags_start, .jsx_factory_pragma_comment = self.jsx_factory_pragma_comment, .jsx_fragment_pragma_comment = self.jsx_fragment_pragma_comment, .source_mapping_url = self.source_mapping_url, @@ -1756,13 +1758,21 @@ pub const Lexer = struct { } pub fn scanRegExp(lexer: *LexerType) !void { + lexer.regex_flags_start = null; while (true) { switch (lexer.code_point) { '/' => { try lexer.step(); + + var has_set_flags_start = false; while (isIdentifierContinue(lexer.code_point)) { switch (lexer.code_point) { 'g', 'i', 'm', 's', 'u', 'y' => { + if (!has_set_flags_start) { + lexer.regex_flags_start = @truncate(u16, lexer.end - lexer.start); + has_set_flags_start = true; + } + try lexer.step(); }, else => { diff --git a/src/js_parser/js_parser.zig b/src/js_parser/js_parser.zig index 52c21c156..3efa382b3 100644 --- a/src/js_parser/js_parser.zig +++ b/src/js_parser/js_parser.zig @@ -1776,7 +1776,7 @@ pub const Parser = struct { ts: bool = false, keep_names: bool = true, omit_runtime_for_tests: bool = false, - ignore_dce_annotations: bool = true, + ignore_dce_annotations: bool = false, preserve_unused_imports_ts: bool = false, use_define_for_class_fields: bool = false, suppress_warnings_about_weird_code: bool = true, @@ -3640,7 +3640,11 @@ pub fn NewParser( }, .macro => { p.bun_jsx_ref = p.declareSymbol(.other, logger.Loc.Empty, "bunJSX") catch unreachable; - BunJSX.bun_jsx_identifier = E.Identifier{ .ref = p.bun_jsx_ref, .can_be_removed_if_unused = true }; + BunJSX.bun_jsx_identifier = E.Identifier{ + .ref = p.bun_jsx_ref, + .can_be_removed_if_unused = true, + .call_can_be_unwrapped_if_unused = true, + }; p.jsx_fragment = p.declareGeneratedSymbol(.other, "Fragment") catch unreachable; }, else => {}, @@ -9766,9 +9770,12 @@ pub fn NewParser( }, .t_slash, .t_slash_equals => { try p.lexer.scanRegExp(); + // always set regex_flags_start to null to make sure we don't accidentally use the wrong value later + defer p.lexer.regex_flags_start = null; const value = p.lexer.raw(); try p.lexer.next(); - return p.e(E.RegExp{ .value = value }, loc); + + return p.e(E.RegExp{ .value = value, .flags_offset = p.lexer.regex_flags_start }, loc); }, .t_void => { try p.lexer.next(); @@ -10168,7 +10175,11 @@ pub fn NewParser( // do people do <API_URL>? fn jsxStringsToMemberExpression(p: *P, loc: logger.Loc, ref: Ref) Expr { p.recordUsage(ref); - return p.e(E.Identifier{ .ref = ref }, loc); + return p.e(E.Identifier{ + .ref = ref, + .can_be_removed_if_unused = true, + .call_can_be_unwrapped_if_unused = true, + }, loc); } // Note: The caller has already parsed the "import" keyword @@ -11028,6 +11039,7 @@ pub fn NewParser( // Either: // jsxDEV(type, arguments, key, isStaticChildren, source, self) // jsx(type, arguments, key) + const include_filename = FeatureFlags.include_filename_in_jsx and p.options.jsx.development; const args = p.allocator.alloc(Expr, if (p.options.jsx.development) @as(usize, 6) else @as(usize, 4)) catch unreachable; args[0] = tag; var props = List(G.Property).fromOwnedSlice(p.allocator, e_.properties); @@ -11106,27 +11118,34 @@ pub fn NewParser( }, }; - var source = p.allocator.alloc(G.Property, 2) catch unreachable; - p.recordUsage(p.jsx_filename.ref); - source[0] = G.Property{ - .key = Expr{ .loc = expr.loc, .data = Prefill.Data.Filename }, - .value = p.e(E.Identifier{ .ref = p.jsx_filename.ref }, expr.loc), - }; + if (include_filename) { + var source = p.allocator.alloc(G.Property, 2) catch unreachable; + p.recordUsage(p.jsx_filename.ref); + source[0] = G.Property{ + .key = Expr{ .loc = expr.loc, .data = Prefill.Data.Filename }, + .value = p.e(E.Identifier{ + .ref = p.jsx_filename.ref, + .can_be_removed_if_unused = true, + }, expr.loc), + }; - source[1] = G.Property{ - .key = Expr{ .loc = expr.loc, .data = Prefill.Data.LineNumber }, - .value = p.e(E.Number{ .value = @intToFloat(f64, expr.loc.start) }, expr.loc), - }; + source[1] = G.Property{ + .key = Expr{ .loc = expr.loc, .data = Prefill.Data.LineNumber }, + .value = p.e(E.Number{ .value = @intToFloat(f64, expr.loc.start) }, expr.loc), + }; - // Officially, they ask for columnNumber. But I don't see any usages of it in the code! - // source[2] = G.Property{ - // .key = Expr{ .loc = expr.loc, .data = Prefill.Data.ColumnNumber }, - // .value = p.e(E.Number{ .value = @intToFloat(f64, expr.loc.start) }, expr.loc), - // }; + // Officially, they ask for columnNumber. But I don't see any usages of it in the code! + // source[2] = G.Property{ + // .key = Expr{ .loc = expr.loc, .data = Prefill.Data.ColumnNumber }, + // .value = p.e(E.Number{ .value = @intToFloat(f64, expr.loc.start) }, expr.loc), + // }; + args[4] = p.e(E.Object{ + .properties = source, + }, expr.loc); + } else { + args[4] = p.e(E.Object{}, expr.loc); + } - args[4] = p.e(E.Object{ - .properties = source, - }, expr.loc); args[5] = Expr{ .data = Prefill.Data.This, .loc = expr.loc }; } @@ -11203,6 +11222,89 @@ pub fn NewParser( const is_stmt_expr = @as(Expr.Tag, p.stmt_expr_value) == .e_binary and expr.data.e_binary == p.stmt_expr_value.e_binary; const was_anonymous_named_expr = p.isAnonymousNamedExpr(e_.right); + if (comptime jsx_transform_type == .macro) { + if (e_.op == Op.Code.bin_instanceof and (e_.right.data == .e_jsx_element or e_.left.data == .e_jsx_element)) { + // foo instanceof <string /> + // -> + // bunJSX.isNodeType(foo, 13) + + // <string /> instanceof foo + // -> + // bunJSX.isNodeType(foo, 13) + var call_args = p.allocator.alloc(Expr, 2) catch unreachable; + call_args[0] = e_.left; + call_args[1] = e_.right; + + if (e_.right.data == .e_jsx_element) { + const jsx_element = e_.right.data.e_jsx_element; + if (jsx_element.tag) |tag| { + if (tag.data == .e_string) { + const tag_string = tag.data.e_string.utf8; + if (js_ast.Macro.JSNode.Tag.names.get(tag_string)) |node_tag| { + call_args[1] = Expr{ .loc = tag.loc, .data = js_ast.Macro.JSNode.Tag.ids.get(node_tag) }; + } else { + p.log.addRangeErrorFmt( + p.source, + js_lexer.rangeOfIdentifier(p.source, tag.loc), + p.allocator, + "Invalid JSX tag: \"{s}\"", + .{tag_string}, + ) catch unreachable; + return expr; + } + } + } else { + call_args[1] = p.visitExpr(call_args[1]); + } + } else { + call_args[1] = p.visitExpr(call_args[1]); + } + + if (e_.left.data == .e_jsx_element) { + const jsx_element = e_.left.data.e_jsx_element; + if (jsx_element.tag) |tag| { + if (tag.data == .e_string) { + const tag_string = tag.data.e_string.utf8; + if (js_ast.Macro.JSNode.Tag.names.get(tag_string)) |node_tag| { + call_args[0] = Expr{ .loc = tag.loc, .data = js_ast.Macro.JSNode.Tag.ids.get(node_tag) }; + } else { + p.log.addRangeErrorFmt( + p.source, + js_lexer.rangeOfIdentifier(p.source, tag.loc), + p.allocator, + "Invalid JSX tag: \"{s}\"", + .{tag_string}, + ) catch unreachable; + return expr; + } + } + } else { + call_args[0] = p.visitExpr(call_args[0]); + } + } else { + call_args[0] = p.visitExpr(call_args[0]); + } + + return p.e( + E.Call{ + .target = p.e( + E.Dot{ + .name = "isNodeType", + .name_loc = expr.loc, + .target = p.e(BunJSX.bun_jsx_identifier, expr.loc), + .can_be_removed_if_unused = true, + .call_can_be_unwrapped_if_unused = true, + }, + expr.loc, + ), + .args = call_args, + .can_be_unwrapped_if_unused = true, + }, + expr.loc, + ); + } + } + e_.left = p.visitExprInOut(e_.left, ExprIn{ .assign_target = e_.op.binaryAssignTarget(), }); @@ -12283,6 +12385,7 @@ pub fn NewParser( .un_typeof, .un_void, .un_not => { return p.exprCanBeRemovedIfUnused(&ex.value); }, + else => {}, } }, diff --git a/src/js_printer.zig b/src/js_printer.zig index 983444d1a..1419dbef2 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -857,6 +857,9 @@ pub fn NewPrinter( } } + // noop for now + pub inline fn printPure(p: *Printer) void {} + pub fn printQuotedUTF8(p: *Printer, str: string, allow_backtick: bool) void { const quote = p.bestQuoteCharForString(str, allow_backtick); p.print(quote); @@ -930,7 +933,7 @@ pub fn NewPrinter( } if (has_pure_comment) { - p.print("/* @__PURE__ */ "); + p.printPure(); } p.printSpaceBeforeIdentifier(); @@ -978,7 +981,7 @@ pub fn NewPrinter( if (has_pure_comment) { const was_stmt_start = p.stmt_start == p.writer.written; - p.print("/* @__PURE__ */ "); + p.printPure(); if (was_stmt_start) { p.stmt_start = p.writer.written; } diff --git a/src/runtime.zig b/src/runtime.zig index c8aed0dc7..e0dd3bab7 100644 --- a/src/runtime.zig +++ b/src/runtime.zig @@ -252,6 +252,7 @@ pub const Runtime = struct { "__exportDefault", }; pub const Name = "<RUNTIME"; + pub const alt_name = "__runtime.js"; pub const Iterator = struct { i: usize = 0, |