aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-09-26 20:03:49 -0700
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-09-26 20:03:49 -0700
commit018ba2c83bf3c2e925ec3273c5f936cb4aff434f (patch)
tree9b028dd9911c8ecc17fec29076c91cf035728a7c
parent66ed7c1f30d1ba6569efa114c9d90ccac45fb86a (diff)
downloadbun-018ba2c83bf3c2e925ec3273c5f936cb4aff434f.tar.gz
bun-018ba2c83bf3c2e925ec3273c5f936cb4aff434f.tar.zst
bun-018ba2c83bf3c2e925ec3273c5f936cb4aff434f.zip
Most of macro implementation
-rw-r--r--package.json5
-rw-r--r--src/bundler.zig1
-rw-r--r--src/js_ast.zig542
-rw-r--r--src/js_lexer.zig10
-rw-r--r--src/js_parser/js_parser.zig147
-rw-r--r--src/js_printer.zig7
-rw-r--r--src/runtime.zig1
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, &regex_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,