diff options
Diffstat (limited to '')
-rw-r--r-- | src/js_parser/js_parser.zig | 626 |
1 files changed, 408 insertions, 218 deletions
diff --git a/src/js_parser/js_parser.zig b/src/js_parser/js_parser.zig index 26e94e0d3..39055d7b3 100644 --- a/src/js_parser/js_parser.zig +++ b/src/js_parser/js_parser.zig @@ -692,6 +692,98 @@ const StaticSymbolName = struct { }; }; +const BunJSX = struct { + const TagNumberExprArray = std.EnumArray(Expr.Tag, E.Number); + + pub const tag_numbers: TagNumberExprArray = brk: { + var numbers = TagNumberExprArray.initFill(E.Number{ .value = 0 }); + tag_numbers.set(.e_array, E.Number{ .value = @intToFloat(f64, Tag.e_array.toPublicValue()) }); + tag_numbers.set(.e_unary, E.Number{ .value = @intToFloat(f64, Tag.e_unary.toPublicValue()) }); + tag_numbers.set(.e_binary, E.Number{ .value = @intToFloat(f64, Tag.e_binary.toPublicValue()) }); + tag_numbers.set(.e_class, E.Number{ .value = @intToFloat(f64, Tag.e_class.toPublicValue()) }); + tag_numbers.set(.e_new, E.Number{ .value = @intToFloat(f64, Tag.e_new.toPublicValue()) }); + tag_numbers.set(.e_function, E.Number{ .value = @intToFloat(f64, Tag.e_function.toPublicValue()) }); + tag_numbers.set(.e_call, E.Number{ .value = @intToFloat(f64, Tag.e_call.toPublicValue()) }); + tag_numbers.set(.e_dot, E.Number{ .value = @intToFloat(f64, Tag.e_dot.toPublicValue()) }); + tag_numbers.set(.e_index, E.Number{ .value = @intToFloat(f64, Tag.e_index.toPublicValue()) }); + tag_numbers.set(.e_arrow, E.Number{ .value = @intToFloat(f64, Tag.e_arrow.toPublicValue()) }); + tag_numbers.set(.e_identifier, E.Number{ .value = @intToFloat(f64, Tag.e_identifier.toPublicValue()) }); + tag_numbers.set(.e_import_identifier, E.Number{ .value = @intToFloat(f64, Tag.e_import_identifier.toPublicValue()) }); + tag_numbers.set(.e_private_identifier, E.Number{ .value = @intToFloat(f64, Tag.e_private_identifier.toPublicValue()) }); + tag_numbers.set(.e_jsx_element, E.Number{ .value = @intToFloat(f64, Tag.e_jsx_element.toPublicValue()) }); + tag_numbers.set(.e_object, E.Number{ .value = @intToFloat(f64, Tag.e_object.toPublicValue()) }); + tag_numbers.set(.e_spread, E.Number{ .value = @intToFloat(f64, Tag.e_spread.toPublicValue()) }); + tag_numbers.set(.e_template_part, E.Number{ .value = @intToFloat(f64, Tag.e_template_part.toPublicValue()) }); + tag_numbers.set(.e_template, E.Number{ .value = @intToFloat(f64, Tag.e_template.toPublicValue()) }); + tag_numbers.set(.e_reg_exp, E.Number{ .value = @intToFloat(f64, Tag.e_reg_exp.toPublicValue()) }); + tag_numbers.set(.e_await, E.Number{ .value = @intToFloat(f64, Tag.e_await.toPublicValue()) }); + tag_numbers.set(.e_yield, E.Number{ .value = @intToFloat(f64, Tag.e_yield.toPublicValue()) }); + tag_numbers.set(.e_if, E.Number{ .value = @intToFloat(f64, Tag.e_if.toPublicValue()) }); + tag_numbers.set(.e_require, E.Number{ .value = @intToFloat(f64, Tag.e_require.toPublicValue()) }); + tag_numbers.set(.e_require_or_require_resolve, E.Number{ .value = @intToFloat(f64, Tag.e_require_or_require_resolve.toPublicValue()) }); + tag_numbers.set(.e_import, E.Number{ .value = @intToFloat(f64, Tag.e_import.toPublicValue()) }); + tag_numbers.set(.e_boolean, E.Number{ .value = @intToFloat(f64, Tag.e_boolean.toPublicValue()) }); + tag_numbers.set(.e_number, E.Number{ .value = @intToFloat(f64, Tag.e_number.toPublicValue()) }); + tag_numbers.set(.e_big_int, E.Number{ .value = @intToFloat(f64, Tag.e_big_int.toPublicValue()) }); + tag_numbers.set(.e_string, E.Number{ .value = @intToFloat(f64, Tag.e_string.toPublicValue()) }); + tag_numbers.set(.e_missing, E.Number{ .value = @intToFloat(f64, Tag.e_missing.toPublicValue()) }); + tag_numbers.set(.e_this, E.Number{ .value = @intToFloat(f64, Tag.e_this.toPublicValue()) }); + tag_numbers.set(.e_super, E.Number{ .value = @intToFloat(f64, Tag.e_super.toPublicValue()) }); + tag_numbers.set(.e_null, E.Number{ .value = @intToFloat(f64, Tag.e_null.toPublicValue()) }); + tag_numbers.set(.e_undefined, E.Number{ .value = @intToFloat(f64, Tag.e_undefined.toPublicValue()) }); + tag_numbers.set(.e_new_target, E.Number{ .value = @intToFloat(f64, Tag.e_new_target.toPublicValue()) }); + tag_numbers.set(.e_import_meta, E.Number{ .value = @intToFloat(f64, Tag.e_import_meta.toPublicValue()) }); + break :brk tag_numbers; + }; + pub const StaticExpr = struct { + // pub const one: E.Expr = E.Expr{ .tag = .e_number, .data = }; + }; + + pub const tag_name_key: string = "t"; + pub const tag_name_key_string = E.String{ .utf8 = tag_name_key }; + + pub const children_name_key: string = "c"; + pub const children_name_key_string = E.String{ .utf8 = children_name_key }; + + pub const value_name_key: string = "v"; + pub const value_name_string = E.String{ .utf8 = value_name_key }; + + pub const number_name_key: string = "n"; + pub const number_name_string = E.String{ .utf8 = number_name_key }; + + pub const @"undefined" = E.Object{ + .properties = &[_]G.Property{ + .{ .key = &tag_name_key_string, .value = &tag_names.get(.e_undefined) }, + }, + }; + pub const @"null" = E.Object{ + .properties = &[_]G.Property{ + .{ .key = &tag_name_key_string, .value = &tag_names.get(.e_null) }, + }, + }; + pub const @"false" = E.Object{ + .properties = &[_]G.Property{ + .{ .key = &tag_name_key_string, .value = &tag_names.get(.e_boolean) }, + .{ .key = &value_name_key, .value = Expr{ .data = Prefill.Data.Zero, .loc = logger.Loc.Empty } }, + }, + .is_single_line = true, + }; + pub const @"true" = E.Object{ + .properties = &[_]G.Property{ + .{ .key = &tag_name_key_string, .value = &tag_names.get(.e_boolean) }, + .{ .key = &value_name_key, .value = Expr{ .data = Prefill.Data.One, .loc = logger.Loc.Empty } }, + }, + .is_single_line = true, + }; + pub const @"empty_string" = E.Object{ + .properties = &[_]G.Property{ + .{ .key = &tag_name_key_string, .value = &tag_names.get(.e_string) }, + .{ .key = &value_name_key, .value = Expr{ .data = Prefill.Data.EmptyString, .loc = logger.Loc.Empty } }, + }, + .is_single_line = true, + }; +}; + pub const SideEffects = enum(u2) { could_have_side_effects, no_side_effects, @@ -1878,6 +1970,12 @@ pub const Parser = struct { } pub fn parse(self: *Parser) !js_ast.Result { + if (self.options.features.is_macro and self.options.ts) { + return try self._parse(TypeScriptMacroParser); + } else if (self.options.features.is_macro) { + return try self._parse(JavaScriptMacroParser); + } + if (self.options.ts and self.options.jsx.parse) { if (self.options.features.react_fast_refresh) { return try self._parse(TSXParserFastRefresh); @@ -1987,7 +2085,7 @@ pub const Parser = struct { } // Auto-import JSX - if (p.options.jsx.parse) { + if (self.options.jsx.parse and !self.options.features.is_macro) { const jsx_symbol: *const Symbol = &p.symbols.items[p.jsx_runtime.ref.inner_index]; const jsx_static_symbol: *const Symbol = &p.symbols.items[p.jsxs_runtime.ref.inner_index]; const jsx_fragment_symbol: *const Symbol = &p.symbols.items[p.jsx_fragment.ref.inner_index]; @@ -2553,8 +2651,14 @@ pub const Prefill = struct { pub var ColumnNumber = [_]u16{ 'c', 'o', 'l', 'u', 'm', 'n', 'N', 'u', 'm', 'b', 'e', 'r' }; }; pub const Value = struct { - pub var EThis = E.This{}; - pub var Zero = E.Number{ .value = 0.0 }; + pub const EThis = E.This{}; + pub const Zero = E.Number{ .value = 0.0 }; + pub const One = E.Number{ .value = 1.0 }; + pub const Two = E.Number{ .value = 2.0 }; + pub const Three = E.Number{ .value = 3.0 }; + pub const Four = E.Number{ .value = 4.0 }; + pub const Five = E.Number{ .value = 5.0 }; + pub var EmptyString = E.String{}; }; pub const String = struct { pub var Key = E.String{ .value = &Prefill.StringLiteral.Key }; @@ -2564,20 +2668,27 @@ pub const Prefill = struct { pub var ColumnNumber = E.String{ .value = &Prefill.StringLiteral.ColumnNumber }; }; pub const Data = struct { - pub var BMissing = B{ .b_missing = BMissing_ }; - pub var BMissing_ = B.Missing{}; - - pub var EMissing = Expr.Data{ .e_missing = EMissing_ }; - pub var EMissing_ = E.Missing{}; - - pub var SEmpty = Stmt.Data{ .s_empty = SEmpty_ }; - pub var SEmpty_ = S.Empty{}; - - pub var Filename = Expr.Data{ .e_string = &Prefill.String.Filename }; - pub var LineNumber = Expr.Data{ .e_string = &Prefill.String.LineNumber }; - pub var ColumnNumber = Expr.Data{ .e_string = &Prefill.String.ColumnNumber }; - pub var This = Expr.Data{ .e_this = E.This{} }; - pub var Zero = Expr.Data{ .e_number = &Value.Zero }; + pub const BMissing = B{ .b_missing = BMissing_ }; + pub const BMissing_ = B.Missing{}; + + pub const EMissing = Expr.Data{ .e_missing = EMissing_ }; + pub const EMissing_ = E.Missing{}; + + pub const SEmpty = Stmt.Data{ .s_empty = SEmpty_ }; + pub const SEmpty_ = S.Empty{}; + + pub const EmptyString = Expr.Data{ .e_string = &Prefill.Value.EmptyString }; + + pub const Filename = Expr.Data{ .e_string = &Prefill.String.Filename }; + pub const LineNumber = Expr.Data{ .e_string = &Prefill.String.LineNumber }; + pub const ColumnNumber = Expr.Data{ .e_string = &Prefill.String.ColumnNumber }; + pub const This = Expr.Data{ .e_this = E.This{} }; + pub const Zero = Expr.Data{ .e_number = .{ .value = 0 } }; + pub const One = Expr.Data{ .e_number = .{ .value = 1 } }; + pub const Two = Expr.Data{ .e_number = .{ .value = 2 } }; + pub const Three = Expr.Data{ .e_number = .{ .value = 3 } }; + pub const Four = Expr.Data{ .e_number = .{ .value = 4 } }; + pub const Five = Expr.Data{ .e_number = .{ .value = 5 } }; }; pub const Runtime = struct { pub var JSXFilename = "__jsxFilename"; @@ -2605,6 +2716,8 @@ const ParserFeatures = struct { jsx: bool = false, scan_only: bool = false, + is_macro: bool = false, + // *** How React Fast Refresh works *** // // Implmenetations: @@ -2662,21 +2775,37 @@ const ImportItemForNamespaceMap = std.StringArrayHashMap(LocRef); const MacroRefs = std.AutoArrayHashMap(Ref, u32); +const JSXTransformType = enum { + none, + react, + bun_macro, +}; + pub fn NewParser( comptime js_parser_features: ParserFeatures, ) type { - const is_typescript_enabled = js_parser_features.typescript; - const is_jsx_enabled = js_parser_features.jsx; - const only_scan_imports_and_do_not_visit = js_parser_features.scan_only; - const is_react_fast_refresh_enabled = js_parser_features.react_fast_refresh; - - const ImportRecordList = if (only_scan_imports_and_do_not_visit) *std.ArrayList(ImportRecord) else std.ArrayList(ImportRecord); - const NamedImportsType = if (only_scan_imports_and_do_not_visit) *js_ast.Ast.NamedImports else js_ast.Ast.NamedImports; - const NeedsJSXType = if (only_scan_imports_and_do_not_visit) bool else void; - const ParsePassSymbolUsageType = if (only_scan_imports_and_do_not_visit and is_typescript_enabled) *ScanPassResult.ParsePassSymbolUsageMap else void; + // P is for Parser! // public only because of Binding.ToExpr return struct { + pub const is_typescript_enabled = js_parser_features.typescript; + pub const is_jsx_enabled = js_parser_features.jsx; + pub const jsx_transform_type: JSXTransformType = brk: { + if (!is_jsx_enabled) break :brk JSXTransformType.none; + + if (js_parser_features.is_macro) { + break :brk JSXTransformType.bun_macro; + } else { + break :brk JSXTransformType.react; + } + }; + pub const only_scan_imports_and_do_not_visit = js_parser_features.scan_only; + pub const is_react_fast_refresh_enabled = js_parser_features.react_fast_refresh; + + const ImportRecordList = if (only_scan_imports_and_do_not_visit) *std.ArrayList(ImportRecord) else std.ArrayList(ImportRecord); + const NamedImportsType = if (only_scan_imports_and_do_not_visit) *js_ast.Ast.NamedImports else js_ast.Ast.NamedImports; + const NeedsJSXType = if (only_scan_imports_and_do_not_visit) bool else void; + const ParsePassSymbolUsageType = if (only_scan_imports_and_do_not_visit and is_typescript_enabled) *ScanPassResult.ParsePassSymbolUsageMap else void; const P = @This(); allocator: *std.mem.Allocator, options: Parser.Options, @@ -3534,7 +3663,7 @@ pub fn NewParser( } } - if (is_jsx_enabled) { + if (jsx_transform_type == .react) { generated_symbols_count += 7; if (p.options.jsx.development) generated_symbols_count += 1; @@ -3577,22 +3706,28 @@ pub fn NewParser( p.recordUsage(p.runtime_imports.__HMRClient.?.ref); } - if (is_jsx_enabled) { - if (p.options.jsx.development) { - p.jsx_filename = p.declareGeneratedSymbol(.other, "jsxFilename") catch unreachable; - } - p.jsx_fragment = p.declareGeneratedSymbol(.other, "Fragment") catch unreachable; - p.jsx_runtime = p.declareGeneratedSymbol(.other, "jsx") catch unreachable; - p.jsxs_runtime = p.declareGeneratedSymbol(.other, "jsxs") catch unreachable; - p.jsx_factory = p.declareGeneratedSymbol(.other, "Factory") catch unreachable; + switch (comptime jsx_transform_type) { + .react => { + if (p.options.jsx.development) { + p.jsx_filename = p.declareGeneratedSymbol(.other, "jsxFilename") catch unreachable; + } + p.jsx_fragment = p.declareGeneratedSymbol(.other, "Fragment") catch unreachable; + p.jsx_runtime = p.declareGeneratedSymbol(.other, "jsx") catch unreachable; + p.jsxs_runtime = p.declareGeneratedSymbol(.other, "jsxs") catch unreachable; + p.jsx_factory = p.declareGeneratedSymbol(.other, "Factory") catch unreachable; - if (p.options.jsx.factory.len > 1 or FeatureFlags.jsx_runtime_is_cjs) { - p.jsx_classic = p.declareGeneratedSymbol(.other, "ClassicImportSource") catch unreachable; - } + if (p.options.jsx.factory.len > 1 or FeatureFlags.jsx_runtime_is_cjs) { + p.jsx_classic = p.declareGeneratedSymbol(.other, "ClassicImportSource") catch unreachable; + } - if (p.options.jsx.import_source.len > 0) { - p.jsx_automatic = p.declareGeneratedSymbol(.other, "ImportSource") catch unreachable; - } + if (p.options.jsx.import_source.len > 0) { + p.jsx_automatic = p.declareGeneratedSymbol(.other, "ImportSource") catch unreachable; + } + }, + .bun_macro => { + p.jsx_fragment = p.declareGeneratedSymbol(.other, "Fragment") catch unreachable; + }, + else => {}, } } @@ -10037,7 +10172,7 @@ pub fn NewParser( // <A[]>(x) // <A>(x) => {} // <A = B>(x) => {} - if (is_typescript_enabled and is_jsx_enabled) { + if (comptime is_typescript_enabled and is_jsx_enabled) { var oldLexer = std.mem.toBytes(p.lexer); try p.lexer.next(); @@ -10064,7 +10199,7 @@ pub fn NewParser( } } - if (is_jsx_enabled) { + if (comptime is_jsx_enabled) { // Use NextInsideJSXElement() instead of Next() so we parse "<<" as "<" try p.lexer.nextInsideJSXElement(); const element = try p.parseJSXElement(loc); @@ -10077,7 +10212,7 @@ pub fn NewParser( return element; } - if (is_typescript_enabled) { + if (comptime is_typescript_enabled) { // This is either an old-style type cast or a generic lambda function // "<T>(x)" @@ -10883,204 +11018,257 @@ pub fn NewParser( p.panic("Unexpected private identifier. This is an internal error - not your fault.", .{}); }, .e_jsx_element => |e_| { - const tag: Expr = tagger: { - if (e_.tag) |_tag| { - break :tagger p.visitExpr(_tag); - } else { - break :tagger p.jsxStringsToMemberExpression(expr.loc, p.jsx_fragment.ref); - } - }; - - for (e_.properties) |property, i| { - if (property.kind != .spread) { - e_.properties[i].key = p.visitExpr(e_.properties[i].key.?); - } - - if (property.value != null) { - e_.properties[i].value = p.visitExpr(e_.properties[i].value.?); - } + switch (comptime jsx_transform_type) { + .bun_macro => { + const IdentifierOrNodeType = union(Tag) { + identifier: Expr, + expression: Expr.Tag, + pub const Tag = enum { identifier, expression }; + }; + const tag: IdentifierOrNodeType = tagger: { + if (e_.tag) |_tag| { + switch (_tag.data) { + .e_string => |str| { + if (Expr.Tag.find(str.utf8)) |tagname| { + break :tagger IdentifierOrNodeType{ .expression = tagname }; + } - if (property.initializer != null) { - e_.properties[i].initializer = p.visitExpr(e_.properties[i].initializer.?); - } - } + p.log.addErrorFmt( + p.source, + expr.loc, + p.allocator, + "Invalid expression tag: \"<{s}>\". Valid tags are:\n" ++ Expr.Tag.valid_names_list ++ "\n", + .{str.utf8}, + ) catch unreachable; + break :tagger IdentifierOrNodeType{ .identifier = p.visitExpr(_tag) }; + }, + else => { + break :tagger IdentifierOrNodeType{ .identifier = p.visitExpr(_tag) }; + }, + } + } else { + break :tagger IdentifierOrNodeType{ .expression = Expr.Tag.e_array }; + } + }; - const runtime = if (p.options.jsx.runtime == .automatic and !e_.flags.is_key_before_rest) options.JSX.Runtime.automatic else options.JSX.Runtime.classic; - var children_count = e_.children.len; + for (e_.properties) |property, i| { + if (property.kind != .spread) { + e_.properties[i].key = p.visitExpr(e_.properties[i].key.?); + } - const is_childless_tag = FeatureFlags.react_specific_warnings and children_count > 0 and tag.data == .e_string and tag.data.e_string.isUTF8() and js_lexer.ChildlessJSXTags.has(tag.data.e_string.utf8); + if (property.value != null) { + e_.properties[i].value = p.visitExpr(e_.properties[i].value.?); + } - children_count = if (is_childless_tag) 0 else children_count; + if (property.initializer != null) { + e_.properties[i].initializer = p.visitExpr(e_.properties[i].initializer.?); + } + } - if (children_count != e_.children.len) { - // Error: meta is a void element tag and must neither have `children` nor use `dangerouslySetInnerHTML`. - // ^ from react-dom - p.log.addWarningFmt(p.source, tag.loc, p.allocator, "<{s} /> is a void element and must not have \"children\"", .{tag.data.e_string.utf8}) catch {}; - } + return p.e(E.Missing{}, expr.loc); + }, + .react => { + const tag: Expr = tagger: { + if (e_.tag) |_tag| { + break :tagger p.visitExpr(_tag); + } else { + break :tagger p.jsxStringsToMemberExpression(expr.loc, p.jsx_fragment.ref); + } + }; - // TODO: maybe we should split these into two different AST Nodes - // That would reduce the amount of allocations a little - switch (runtime) { - .classic => { - // Arguments to createElement() - const args = p.allocator.alloc(Expr, 2 + children_count) catch unreachable; - // There are at least two args: - // - name of the tag - // - props - var i: usize = 1; - args[0] = tag; - if (e_.properties.len > 0) { - for (e_.properties) |prop, prop_i| { - if (prop.key) |key| { - e_.properties[prop_i].key = p.visitExpr(key); - } + for (e_.properties) |property, i| { + if (property.kind != .spread) { + e_.properties[i].key = p.visitExpr(e_.properties[i].key.?); + } - if (prop.value) |val| { - e_.properties[prop_i].value = p.visitExpr(val); - } + if (property.value != null) { + e_.properties[i].value = p.visitExpr(e_.properties[i].value.?); } - if (e_.key) |key| { - var props = p.allocator.alloc(G.Property, e_.properties.len + 1) catch unreachable; - std.mem.copy(G.Property, props, e_.properties); - props[props.len - 1] = G.Property{ .key = Expr{ .loc = key.loc, .data = keyExprData }, .value = key }; - args[1] = p.e(E.Object{ .properties = props }, expr.loc); - } else { - args[1] = p.e(E.Object{ .properties = e_.properties }, expr.loc); + if (property.initializer != null) { + e_.properties[i].initializer = p.visitExpr(e_.properties[i].initializer.?); } - i = 2; - } else { - args[1] = p.e(E.Null{}, expr.loc); - i = 2; } - for (e_.children[0..children_count]) |child| { - args[i] = p.visitExpr(child); - i += @intCast(usize, @boolToInt(args[i].data != .e_missing)); + const runtime = if (p.options.jsx.runtime == .automatic and !e_.flags.is_key_before_rest) options.JSX.Runtime.automatic else options.JSX.Runtime.classic; + var children_count = e_.children.len; + + const is_childless_tag = FeatureFlags.react_specific_warnings and children_count > 0 and tag.data == .e_string and tag.data.e_string.isUTF8() and js_lexer.ChildlessJSXTags.has(tag.data.e_string.utf8); + + children_count = if (is_childless_tag) 0 else children_count; + + if (children_count != e_.children.len) { + // Error: meta is a void element tag and must neither have `children` nor use `dangerouslySetInnerHTML`. + // ^ from react-dom + p.log.addWarningFmt(p.source, tag.loc, p.allocator, "<{s} /> is a void element and must not have \"children\"", .{tag.data.e_string.utf8}) catch {}; } - // Call createElement() - return p.e(E.Call{ - .target = p.jsxStringsToMemberExpression(expr.loc, p.jsx_factory.ref), - .args = args[0..i], - // Enable tree shaking - .can_be_unwrapped_if_unused = !p.options.ignore_dce_annotations, - }, expr.loc); - }, - // function jsxDEV(type, config, maybeKey, source, self) { - .automatic => { - // Either: - // jsxDEV(type, arguments, key, isStaticChildren, source, self) - // jsx(type, arguments, key) - 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); - // arguments needs to be like - // { - // ...props, - // children: [el1, el2] - // } + // TODO: maybe we should split these into two different AST Nodes + // That would reduce the amount of allocations a little + switch (runtime) { + .classic => { + // Arguments to createElement() + const args = p.allocator.alloc(Expr, 2 + children_count) catch unreachable; + // There are at least two args: + // - name of the tag + // - props + var i: usize = 1; + args[0] = tag; + if (e_.properties.len > 0) { + for (e_.properties) |prop, prop_i| { + if (prop.key) |key| { + e_.properties[prop_i].key = p.visitExpr(key); + } - const is_static_jsx = e_.children.len == 0 or e_.children.len > 1 or e_.children[0].data != .e_array; - - // if (p.options.jsx.development) { - switch (children_count) { - 0 => {}, - 1 => { - // static jsx must always be an array - if (is_static_jsx) { - const children_key = Expr{ .data = jsxChildrenKeyData, .loc = expr.loc }; - e_.children[0] = p.visitExpr(e_.children[0]); - props.append(G.Property{ - .key = children_key, - .value = p.e(E.Array{ - .items = e_.children[0..children_count], - .is_single_line = e_.children.len < 2, - }, expr.loc), - }) catch unreachable; + if (prop.value) |val| { + e_.properties[prop_i].value = p.visitExpr(val); + } + } + + if (e_.key) |key| { + var props = p.allocator.alloc(G.Property, e_.properties.len + 1) catch unreachable; + std.mem.copy(G.Property, props, e_.properties); + props[props.len - 1] = G.Property{ .key = Expr{ .loc = key.loc, .data = keyExprData }, .value = key }; + args[1] = p.e(E.Object{ .properties = props }, expr.loc); + } else { + args[1] = p.e(E.Object{ .properties = e_.properties }, expr.loc); + } + i = 2; } else { - const children_key = Expr{ .data = jsxChildrenKeyData, .loc = expr.loc }; - props.append(G.Property{ - .key = children_key, - .value = p.visitExpr(e_.children[0]), - }) catch unreachable; + args[1] = p.e(E.Null{}, expr.loc); + i = 2; } - }, - else => { - for (e_.children[0..children_count]) |child, i| { - e_.children[i] = p.visitExpr(child); + + for (e_.children[0..children_count]) |child| { + args[i] = p.visitExpr(child); + i += @intCast(usize, @boolToInt(args[i].data != .e_missing)); } - const children_key = Expr{ .data = jsxChildrenKeyData, .loc = expr.loc }; - props.append(G.Property{ - .key = children_key, - .value = p.e(E.Array{ - .items = e_.children[0..children_count], - .is_single_line = e_.children.len < 2, - }, expr.loc), - }) catch unreachable; + + // Call createElement() + return p.e(E.Call{ + .target = p.jsxStringsToMemberExpression(expr.loc, p.jsx_factory.ref), + .args = args[0..i], + // Enable tree shaking + .can_be_unwrapped_if_unused = !p.options.ignore_dce_annotations, + }, expr.loc); }, - } + // function jsxDEV(type, config, maybeKey, source, self) { + .automatic => { + // Either: + // jsxDEV(type, arguments, key, isStaticChildren, source, self) + // jsx(type, arguments, key) + 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); + // arguments needs to be like + // { + // ...props, + // children: [el1, el2] + // } - args[1] = p.e(E.Object{ - .properties = props.toOwnedSlice(), - }, expr.loc); + const is_static_jsx = e_.children.len == 0 or e_.children.len > 1 or e_.children[0].data != .e_array; + + // if (p.options.jsx.development) { + switch (children_count) { + 0 => {}, + 1 => { + // static jsx must always be an array + if (is_static_jsx) { + const children_key = Expr{ .data = jsxChildrenKeyData, .loc = expr.loc }; + e_.children[0] = p.visitExpr(e_.children[0]); + props.append(G.Property{ + .key = children_key, + .value = p.e(E.Array{ + .items = e_.children[0..children_count], + .is_single_line = e_.children.len < 2, + }, expr.loc), + }) catch unreachable; + } else { + const children_key = Expr{ .data = jsxChildrenKeyData, .loc = expr.loc }; + props.append(G.Property{ + .key = children_key, + .value = p.visitExpr(e_.children[0]), + }) catch unreachable; + } + }, + else => { + for (e_.children[0..children_count]) |child, i| { + e_.children[i] = p.visitExpr(child); + } + const children_key = Expr{ .data = jsxChildrenKeyData, .loc = expr.loc }; + props.append(G.Property{ + .key = children_key, + .value = p.e(E.Array{ + .items = e_.children[0..children_count], + .is_single_line = e_.children.len < 2, + }, expr.loc), + }) catch unreachable; + }, + } - if (e_.key) |key| { - args[2] = key; - } else { - // if (maybeKey !== undefined) - args[2] = Expr{ - .loc = expr.loc, - .data = .{ - .e_undefined = E.Undefined{}, - }, - }; - } + args[1] = p.e(E.Object{ + .properties = props.toOwnedSlice(), + }, expr.loc); - if (p.options.jsx.development) { - // is the return type of the first child an array? - // It's dynamic - // Else, it's static - args[3] = Expr{ - .loc = expr.loc, - .data = .{ - .e_boolean = .{ - .value = is_static_jsx, - }, - }, - }; + if (e_.key) |key| { + args[2] = key; + } else { + // if (maybeKey !== undefined) + args[2] = Expr{ + .loc = expr.loc, + .data = .{ + .e_undefined = E.Undefined{}, + }, + }; + } - 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 (p.options.jsx.development) { + // is the return type of the first child an array? + // It's dynamic + // Else, it's static + args[3] = Expr{ + .loc = expr.loc, + .data = .{ + .e_boolean = .{ + .value = is_static_jsx, + }, + }, + }; - 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), - }; + 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), + }; - // 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); - args[5] = Expr{ .data = Prefill.Data.This, .loc = expr.loc }; - } - - return p.e(E.Call{ - .target = p.jsxStringsToMemberExpressionAutomatic(expr.loc, is_static_jsx), - .args = args, - // Enable tree shaking - .can_be_unwrapped_if_unused = !p.options.ignore_dce_annotations, - .was_jsx_element = 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), + }; + + // 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); + args[5] = Expr{ .data = Prefill.Data.This, .loc = expr.loc }; + } + + return p.e(E.Call{ + .target = p.jsxStringsToMemberExpressionAutomatic(expr.loc, is_static_jsx), + .args = args, + // Enable tree shaking + .can_be_unwrapped_if_unused = !p.options.ignore_dce_annotations, + .was_jsx_element = true, + }, expr.loc); + }, + else => unreachable, + } }, else => unreachable, } @@ -11100,7 +11288,6 @@ pub fn NewParser( const ref = tag.data.e_import_identifier.ref; if (p.macro_refs.get(ref)) |import_record_id| { const name = p.symbols.items[ref.inner_index].original_name; - Output.prettyln("Calling Macro!! {s}\n", .{name}); const record = &p.import_records.items[import_record_id]; return p.options.macro_context.call( record.path.text, @@ -15080,6 +15267,9 @@ const JSXParser = NewParser(.{ .jsx = true }); const TSXParser = NewParser(.{ .jsx = true, .typescript = true }); const TypeScriptParser = NewParser(.{ .typescript = true }); +const JavaScriptMacroParser = NewParser(.{ .is_macro = true }); +const TypeScriptMacroParser = NewParser(.{ .typescript = true, .is_macro = true, .jsx = true }); + const JavaScriptParserFastRefresh = NewParser(.{ .react_fast_refresh = true }); const JSXParserFastRefresh = NewParser(.{ .jsx = true, .react_fast_refresh = true }); const TSXParserFastRefresh = NewParser(.{ .jsx = true, .typescript = true, .react_fast_refresh = true }); |