aboutsummaryrefslogtreecommitdiff
path: root/src/js_ast.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/js_ast.zig')
-rw-r--r--src/js_ast.zig1569
1 files changed, 1249 insertions, 320 deletions
diff --git a/src/js_ast.zig b/src/js_ast.zig
index dd5efd65b..f8adaa953 100644
--- a/src/js_ast.zig
+++ b/src/js_ast.zig
@@ -13,6 +13,7 @@ const stringZ = bun.stringZ;
const default_allocator = bun.default_allocator;
const C = bun.C;
const Ref = @import("ast/base.zig").Ref;
+const Index = @import("ast/base.zig").Index;
const RefHashCtx = @import("ast/base.zig").RefHashCtx;
const ObjectPool = @import("./pool.zig").ObjectPool;
const ImportRecord = @import("import_record.zig").ImportRecord;
@@ -23,7 +24,16 @@ const RefCtx = @import("./ast/base.zig").RefCtx;
const JSONParser = bun.JSON;
const is_bindgen = std.meta.globalOption("bindgen", bool) orelse false;
const ComptimeStringMap = bun.ComptimeStringMap;
-const JSPrinter = bun.js_printer;
+const JSPrinter = @import("./js_printer.zig");
+const ThreadlocalArena = @import("./mimalloc_arena.zig").Arena;
+
+/// This is the index to the automatically-generated part containing code that
+/// calls "__export(exports, { ... getters ... })". This is used to generate
+/// getters on an exports object for ES6 export statements, and is both for
+/// ES6 star imports and CommonJS-style modules. All files have one of these,
+/// although it may contain no statements if there is nothing to export.
+pub const namespace_export_part_index = 0;
+
pub fn NewBaseStore(comptime Union: anytype, comptime count: usize) type {
var max_size = 0;
var max_align = 1;
@@ -99,21 +109,38 @@ pub fn NewBaseStore(comptime Union: anytype, comptime count: usize) type {
pub threadlocal var _self: *Self = undefined;
pub fn reclaim() []*Block {
- if (_self.overflow.used == 0) return &[_]*Block{};
+ var overflow = &_self.overflow;
+
+ if (overflow.used == 0) {
+ if (overflow.allocated == 0 or overflow.ptrs[0].used == 0) {
+ return &.{};
+ }
+ }
+
+ var to_move = overflow.ptrs[0..overflow.allocated][overflow.used..];
- var used = _self.overflow.allocator.dupe(*Block, _self.overflow.slice()) catch unreachable;
- var new_head = _self.overflow.allocator.create(Block) catch unreachable;
- new_head.* = .{};
+ // This returns the list of maxed out blocks
+ var used_list = overflow.slice();
- var to_move = _self.overflow.ptrs[0.._self.overflow.allocated][_self.overflow.used..];
- if (to_move.len > 0) {
+ // The last block may be partially used.
+ if (overflow.allocated > overflow.used and to_move.len > 0 and to_move.ptr[0].used > 0) {
to_move = to_move[1..];
+ used_list.len += 1;
}
- bun.copy(*Block, _self.overflow.ptrs[1..], to_move);
- _self.overflow.ptrs[0] = new_head;
- _self.overflow.allocated = 1 + @truncate(Overflow.UsedSize, to_move.len);
- reset();
+ var used = overflow.allocator.dupe(*Block, used_list) catch unreachable;
+
+ for (to_move, overflow.ptrs[0..to_move.len]) |b, *out| {
+ b.* = Block{
+ .items = undefined,
+ .used = 0,
+ };
+ out.* = b;
+ }
+
+ overflow.allocated = @truncate(Overflow.UsedSize, to_move.len);
+ overflow.used = 0;
+
return used;
}
@@ -150,25 +177,27 @@ pub fn NewBaseStore(comptime Union: anytype, comptime count: usize) type {
fn deinit() void {
var sliced = _self.overflow.slice();
+ var allocator = _self.overflow.allocator;
if (sliced.len > 1) {
var i: usize = 1;
const end = sliced.len;
while (i < end) {
var ptrs = @ptrCast(*[2]Block, sliced[i]);
- default_allocator.free(ptrs);
+ allocator.free(ptrs);
i += 2;
}
_self.overflow.allocated = 1;
}
var base_store = @fieldParentPtr(WithBase, "store", _self);
if (_self.overflow.ptrs[0] == &base_store.head) {
- default_allocator.destroy(base_store);
+ allocator.destroy(base_store);
}
_self = undefined;
}
- pub fn append(comptime ValueType: type, value: ValueType) *ValueType {
+ pub fn append(comptime Disabler: type, comptime ValueType: type, value: ValueType) *ValueType {
+ Disabler.assert();
return _self._append(ValueType, value);
}
@@ -219,7 +248,7 @@ pub fn NewBaseStore(comptime Union: anytype, comptime count: usize) type {
pub const BindingNodeIndex = Binding;
pub const StmtNodeIndex = Stmt;
pub const ExprNodeIndex = Expr;
-pub const BabyList = @import("./baby_list.zig").BabyList;
+pub const BabyList = bun.BabyList;
/// Slice that stores capacity and length in the same space as a regular slice.
pub const ExprNodeList = BabyList(Expr);
@@ -249,7 +278,7 @@ pub const AssignTarget = enum(u2) {
}
};
-pub const LocRef = struct { loc: logger.Loc, ref: ?Ref = null };
+pub const LocRef = struct { loc: logger.Loc = logger.Loc.Empty, ref: ?Ref = null };
pub const Flags = struct {
pub const JSXElement = enum {
@@ -337,7 +366,7 @@ pub const Binding = struct {
}
pub fn toExpr(binding: *const Binding, wrapper: anytype) Expr {
- var loc = binding.loc;
+ const loc = binding.loc;
switch (binding.data) {
.b_missing => {
@@ -370,24 +399,26 @@ pub const Binding = struct {
var properties = wrapper
.allocator
.alloc(G.Property, b.properties.len) catch unreachable;
- var i: usize = 0;
- while (i < properties.len) : (i += 1) {
- const item = b.properties[i];
- properties[i] = G.Property{
+ for (properties, b.properties) |*property, item| {
+ property.* = .{
.flags = item.flags,
.key = item.key,
.kind = if (item.flags.contains(.is_spread))
- G.Property.Kind.spread
+ .spread
else
- G.Property.Kind.normal,
+ .normal,
.value = toExpr(&item.value, wrapper),
.initializer = item.default_value,
};
}
- return Expr.init(E.Object, E.Object{
- .properties = G.Property.List.init(properties),
- .is_single_line = b.is_single_line,
- }, loc);
+ return Expr.init(
+ E.Object,
+ E.Object{
+ .properties = G.Property.List.init(properties),
+ .is_single_line = b.is_single_line,
+ },
+ loc,
+ );
},
else => {
Global.panic("Interanl error", .{});
@@ -498,8 +529,8 @@ pub const B = union(Binding.Tag) {
};
pub const ClauseItem = struct {
- alias: string,
- alias_loc: logger.Loc,
+ alias: string = "",
+ alias_loc: logger.Loc = logger.Loc.Empty,
name: LocRef,
/// This is the original name of the symbol stored in "Name". It's needed for
@@ -510,7 +541,7 @@ pub const ClauseItem = struct {
/// In this case both "foo" and "bar" are aliases because it's a re-export.
/// We need to preserve both aliases in case the symbol is renamed. In this
/// example, "foo" is "OriginalName" and "bar" is "Alias".
- original_name: string,
+ original_name: string = "",
pub const default_alias: string = "default";
};
@@ -623,131 +654,191 @@ pub const G = struct {
};
pub const Symbol = struct {
- // This is the name that came from the parser. Printed names may be renamed
- // during minification or to avoid name collisions. Do not use the original
- // name during printing.
+ /// This is the name that came from the parser. Printed names may be renamed
+ /// during minification or to avoid name collisions. Do not use the original
+ /// name during printing.
original_name: string,
- // This is used for symbols that represent items in the import clause of an
- // ES6 import statement. These should always be referenced by EImportIdentifier
- // instead of an EIdentifier. When this is present, the expression should
- // be printed as a property access off the namespace instead of as a bare
- // identifier.
- //
- // For correctness, this must be stored on the symbol instead of indirectly
- // associated with the Ref for the symbol somehow. In ES6 "flat bundling"
- // mode, re-exported symbols are collapsed using MergeSymbols() and renamed
- // symbols from other files that end up at this symbol must be able to tell
- // if it has a namespace alias.
+ /// This is used for symbols that represent items in the import clause of an
+ /// ES6 import statement. These should always be referenced by EImportIdentifier
+ /// instead of an EIdentifier. When this is present, the expression should
+ /// be printed as a property access off the namespace instead of as a bare
+ /// identifier.
+ ///
+ /// For correctness, this must be stored on the symbol instead of indirectly
+ /// associated with the Ref for the symbol somehow. In ES6 "flat bundling"
+ /// mode, re-exported symbols are collapsed using MergeSymbols() and renamed
+ /// symbols from other files that end up at this symbol must be able to tell
+ /// if it has a namespace alias.
namespace_alias: ?G.NamespaceAlias = null,
- // Used by the parser for single pass parsing.
+ /// Used by the parser for single pass parsing.
link: Ref = Ref.None,
- // An estimate of the number of uses of this symbol. This is used to detect
- // whether a symbol is used or not. For example, TypeScript imports that are
- // unused must be removed because they are probably type-only imports. This
- // is an estimate and may not be completely accurate due to oversights in the
- // code. But it should always be non-zero when the symbol is used.
+ /// An estimate of the number of uses of this symbol. This is used to detect
+ /// whether a symbol is used or not. For example, TypeScript imports that are
+ /// unused must be removed because they are probably type-only imports. This
+ /// is an estimate and may not be completely accurate due to oversights in the
+ /// code. But it should always be non-zero when the symbol is used.
use_count_estimate: u32 = 0,
- // This is for generating cross-chunk imports and exports for code splitting.
- chunk_index: ?u32 = null,
-
- // This is used for minification. Symbols that are declared in sibling scopes
- // can share a name. A good heuristic (from Google Closure Compiler) is to
- // assign names to symbols from sibling scopes in declaration order. That way
- // local variable names are reused in each global function like this, which
- // improves gzip compression:
- //
- // function x(a, b) { ... }
- // function y(a, b, c) { ... }
- //
- // The parser fills this in for symbols inside nested scopes. There are three
- // slot namespaces: regular symbols, label symbols, and private symbols.
- nested_scope_slot: ?u32 = null,
+ /// This is for generating cross-chunk imports and exports for code splitting.
+ ///
+ /// Do not use this directly. Use `chunkIndex()` instead.
+ chunk_index: u32 = invalid_chunk_index,
+
+ /// This is used for minification. Symbols that are declared in sibling scopes
+ /// can share a name. A good heuristic (from Google Closure Compiler) is to
+ /// assign names to symbols from sibling scopes in declaration order. That way
+ /// local variable names are reused in each global function like this, which
+ /// improves gzip compression:
+ ///
+ /// function x(a, b) { ... }
+ /// function y(a, b, c) { ... }
+ ///
+ /// The parser fills this in for symbols inside nested scopes. There are three
+ /// slot namespaces: regular symbols, label symbols, and private symbols.
+ ///
+ /// Do not use this directly. Use `nestedScopeSlot()` instead.
+ nested_scope_slot: u32 = invalid_nested_scope_slot,
+ /// The kind of symbol. This is used to determine how to print the symbol
+ /// and how to deal with conflicts, renaming, etc.
kind: Kind = Kind.other,
- // Certain symbols must not be renamed or minified. For example, the
- // "arguments" variable is declared by the runtime for every function.
- // Renaming can also break any identifier used inside a "with" statement.
+ /// Certain symbols must not be renamed or minified. For example, the
+ /// "arguments" variable is declared by the runtime for every function.
+ /// Renaming can also break any identifier used inside a "with" statement.
must_not_be_renamed: bool = false,
- // We automatically generate import items for property accesses off of
- // namespace imports. This lets us remove the expensive namespace imports
- // while bundling in many cases, replacing them with a cheap import item
- // instead:
- //
- // import * as ns from 'path'
- // ns.foo()
- //
- // That can often be replaced by this, which avoids needing the namespace:
- //
- // import {foo} from 'path'
- // foo()
- //
- // However, if the import is actually missing then we don't want to report a
- // compile-time error like we do for real import items. This status lets us
- // avoid this. We also need to be able to replace such import items with
- // undefined, which this status is also used for.
+ /// We automatically generate import items for property accesses off of
+ /// namespace imports. This lets us remove the expensive namespace imports
+ /// while bundling in many cases, replacing them with a cheap import item
+ /// instead:
+ ///
+ /// import * as ns from 'path'
+ /// ns.foo()
+ ///
+ /// That can often be replaced by this, which avoids needing the namespace:
+ ///
+ /// import {foo} from 'path'
+ /// foo()
+ ///
+ /// However, if the import is actually missing then we don't want to report a
+ /// compile-time error like we do for real import items. This status lets us
+ /// avoid this. We also need to be able to replace such import items with
+ /// undefined, which this status is also used for.
import_item_status: ImportItemStatus = ImportItemStatus.none,
- // Sometimes we lower private symbols even if they are supported. For example,
- // consider the following TypeScript code:
- //
- // class Foo {
- // #foo = 123
- // bar = this.#foo
- // }
- //
- // If "useDefineForClassFields: false" is set in "tsconfig.json", then "bar"
- // must use assignment semantics instead of define semantics. We can compile
- // that to this code:
- //
- // class Foo {
- // constructor() {
- // this.#foo = 123;
- // this.bar = this.#foo;
- // }
- // #foo;
- // }
- //
- // However, we can't do the same for static fields:
- //
- // class Foo {
- // static #foo = 123
- // static bar = this.#foo
- // }
- //
- // Compiling these static fields to something like this would be invalid:
- //
- // class Foo {
- // static #foo;
- // }
- // Foo.#foo = 123;
- // Foo.bar = Foo.#foo;
- //
- // Thus "#foo" must be lowered even though it's supported. Another case is
- // when we're converting top-level class declarations to class expressions
- // to avoid the TDZ and the class shadowing symbol is referenced within the
- // class body:
- //
- // class Foo {
- // static #foo = Foo
- // }
- //
- // This cannot be converted into something like this:
- //
- // var Foo = class {
- // static #foo;
- // };
- // Foo.#foo = Foo;
- //
+ /// --- Not actually used yet -----------------------------------------------
+ /// Sometimes we lower private symbols even if they are supported. For example,
+ /// consider the following TypeScript code:
+ ///
+ /// class Foo {
+ /// #foo = 123
+ /// bar = this.#foo
+ /// }
+ ///
+ /// If "useDefineForClassFields: false" is set in "tsconfig.json", then "bar"
+ /// must use assignment semantics instead of define semantics. We can compile
+ /// that to this code:
+ ///
+ /// class Foo {
+ /// constructor() {
+ /// this.#foo = 123;
+ /// this.bar = this.#foo;
+ /// }
+ /// #foo;
+ /// }
+ ///
+ /// However, we can't do the same for static fields:
+ ///
+ /// class Foo {
+ /// static #foo = 123
+ /// static bar = this.#foo
+ /// }
+ ///
+ /// Compiling these static fields to something like this would be invalid:
+ ///
+ /// class Foo {
+ /// static #foo;
+ /// }
+ /// Foo.#foo = 123;
+ /// Foo.bar = Foo.#foo;
+ ///
+ /// Thus "#foo" must be lowered even though it's supported. Another case is
+ /// when we're converting top-level class declarations to class expressions
+ /// to avoid the TDZ and the class shadowing symbol is referenced within the
+ /// class body:
+ ///
+ /// class Foo {
+ /// static #foo = Foo
+ /// }
+ ///
+ /// This cannot be converted into something like this:
+ ///
+ /// var Foo = class {
+ /// static #foo;
+ /// };
+ /// Foo.#foo = Foo;
+ ///
+ /// --- Not actually used yet -----------------------------------------------
private_symbol_must_be_lowered: bool = false,
+ remove_overwritten_function_declaration: bool = false,
+
+ /// In debug mode, sometimes its helpful to know what source file
+ /// A symbol came from. This is used for that.
+ ///
+ /// We don't want this in non-debug mode because it increases the size of
+ /// the symbol table.
+ debug_mode_source_index: if (Environment.allow_assert)
+ Index.Int
+ else
+ u0 = 0,
+
+ const invalid_chunk_index = std.math.maxInt(u32);
+ const invalid_nested_scope_slot = std.math.maxInt(u32);
+
+ pub const SlotNamespace = enum {
+ default,
+ label,
+ private_name,
+ mangled_prop,
+ must_not_be_renamed,
+ };
+
+ /// This is for generating cross-chunk imports and exports for code splitting.
+ pub inline fn chunkIndex(this: *const Symbol) ?u32 {
+ const i = this.chunk_index;
+ return if (i == invalid_chunk_index) null else i;
+ }
+
+ pub inline fn nestedScopeSlot(this: *const Symbol) ?u32 {
+ const i = this.nested_scope_slot;
+ return if (i == invalid_nested_scope_slot) null else i;
+ }
+
+ pub fn slotNamespace(this: *const Symbol) SlotNamespace {
+ const kind = this.kind;
+
+ if (kind == .unbound or this.must_not_be_renamed) {
+ return .must_not_be_renamed;
+ }
+
+ if (kind.isPrivate()) {
+ return .private_name;
+ }
+
+ return switch (kind) {
+ // .mangled_prop => .mangled_prop,
+ .label => .label,
+ else => .default,
+ };
+ }
+
pub inline fn hasLink(this: *const Symbol) bool {
- return !this.link.isNull();
+ return this.link.tag != .invalid;
}
pub const Kind = enum {
@@ -835,12 +926,55 @@ pub const Symbol = struct {
pub fn jsonStringify(self: @This(), opts: anytype, o: anytype) !void {
return try std.json.stringify(@tagName(self), opts, o);
}
+
+ pub inline fn isPrivate(kind: Symbol.Kind) bool {
+ return @enumToInt(kind) >= @enumToInt(Symbol.Kind.private_field) and @enumToInt(kind) <= @enumToInt(Symbol.Kind.private_static_get_set_pair);
+ }
+
+ pub inline fn isHoisted(kind: Symbol.Kind) bool {
+ return switch (kind) {
+ .hoisted, .hoisted_function => true,
+ else => false,
+ };
+ }
+
+ pub inline fn isHoistedOrFunction(kind: Symbol.Kind) bool {
+ return switch (kind) {
+ .hoisted, .hoisted_function, .generator_or_async_function => true,
+ else => false,
+ };
+ }
+
+ pub inline fn isFunction(kind: Symbol.Kind) bool {
+ return switch (kind) {
+ .hoisted_function, .generator_or_async_function => true,
+ else => false,
+ };
+ }
};
+ pub const isKindPrivate = Symbol.Kind.isPrivate;
+ pub const isKindHoisted = Symbol.Kind.isHoisted;
+ pub const isKindHoistedOrFunction = Symbol.Kind.isHoistedOrFunction;
+ pub const isKindFunction = Symbol.Kind.isFunction;
+
pub const Use = struct {
count_estimate: u32 = 0,
};
+ pub const List = BabyList(Symbol);
+ pub const NestedList = BabyList(List);
+
+ pub fn mergeContentsWith(this: *Symbol, old: *Symbol) void {
+ this.use_count_estimate += old.use_count_estimate;
+ if (old.must_not_be_renamed) {
+ this.original_name = old.original_name;
+ this.must_not_be_renamed = true;
+ }
+
+ // TODO: MustStartWithCapitalLetterForJSX
+ }
+
pub const Map = struct {
// This could be represented as a "map[Ref]Symbol" but a two-level array was
// more efficient in profiles. This appears to be because it doesn't involve
@@ -849,34 +983,94 @@ pub const Symbol = struct {
// single inner array, so you can join the maps together by just make a
// single outer array containing all of the inner arrays. See the comment on
// "Ref" for more detail.
- symbols_for_source: [][]Symbol,
+ symbols_for_source: NestedList = NestedList{},
- pub fn get(self: *Map, ref: Ref) ?*Symbol {
+ pub fn dump(this: Map) void {
+ defer Output.flush();
+ for (this.symbols_for_source.slice(), 0..) |symbols, i| {
+ Output.prettyln("\n\n-- Source ID: {d} ({d} symbols) --\n\n", .{ i, symbols.len });
+ for (symbols.slice(), 0..) |symbol, inner_index| {
+ Output.prettyln(
+ " name: {s}\n tag: {s}\n {any}\n",
+ .{
+ symbol.original_name, @tagName(symbol.kind),
+ if (symbol.hasLink()) symbol.link else Ref{
+ .source_index = @truncate(Ref.Int, i),
+ .inner_index = @truncate(Ref.Int, inner_index),
+ .tag = .symbol,
+ },
+ },
+ );
+ }
+ }
+ }
+
+ pub fn assignChunkIndex(this: *Map, decls_: DeclaredSymbol.List, chunk_index: u32) void {
+ const Iterator = struct {
+ map: *Map,
+ chunk_index: u32,
+
+ pub fn next(self: @This(), ref: Ref) void {
+ var symbol = self.map.get(ref).?;
+ symbol.chunk_index = self.chunk_index;
+ }
+ };
+ var decls = decls_;
+
+ DeclaredSymbol.forEachTopLevelSymbol(&decls, Iterator{ .map = this, .chunk_index = chunk_index }, Iterator.next);
+ }
+
+ pub fn merge(this: *Map, old: Ref, new: Ref) Ref {
+ if (old.eql(new)) {
+ return new;
+ }
+
+ var old_symbol = this.get(old).?;
+ if (old_symbol.hasLink()) {
+ const old_link = old_symbol.link;
+ old_symbol.link = this.merge(old_link, new);
+ return old_symbol.link;
+ }
+
+ var new_symbol = this.get(new).?;
+
+ if (new_symbol.hasLink()) {
+ const new_link = new_symbol.link;
+ new_symbol.link = this.merge(old, new_link);
+ return new_symbol.link;
+ }
+
+ old_symbol.link = new;
+ new_symbol.mergeContentsWith(old_symbol);
+ return new;
+ }
+
+ pub fn get(self: *const Map, ref: Ref) ?*Symbol {
if (Ref.isSourceIndexNull(ref.sourceIndex()) or ref.isSourceContentsSlice()) {
return null;
}
- return &self.symbols_for_source[ref.sourceIndex()][ref.innerIndex()];
+ return self.symbols_for_source.at(ref.sourceIndex()).mut(ref.innerIndex());
}
- pub fn getConst(self: *Map, ref: Ref) ?*const Symbol {
+ pub fn getConst(self: *const Map, ref: Ref) ?*const Symbol {
if (Ref.isSourceIndexNull(ref.sourceIndex()) or ref.isSourceContentsSlice()) {
return null;
}
- return &self.symbols_for_source[ref.sourceIndex()][ref.innerIndex()];
+ return self.symbols_for_source.at(ref.sourceIndex()).at(ref.innerIndex());
}
pub fn init(sourceCount: usize, allocator: std.mem.Allocator) !Map {
- var symbols_for_source: [][]Symbol = try allocator.alloc([]Symbol, sourceCount);
+ var symbols_for_source: NestedList = NestedList.init(try allocator.alloc([]Symbol, sourceCount));
return Map{ .symbols_for_source = symbols_for_source };
}
- pub fn initList(list: [][]Symbol) Map {
+ pub fn initList(list: NestedList) Map {
return Map{ .symbols_for_source = list };
}
- pub fn getWithLink(symbols: *Map, ref: Ref) ?*Symbol {
+ pub fn getWithLink(symbols: *const Map, ref: Ref) ?*Symbol {
var symbol: *Symbol = symbols.get(ref) orelse return null;
if (symbol.hasLink()) {
return symbols.get(symbol.link) orelse symbol;
@@ -892,52 +1086,35 @@ pub const Symbol = struct {
return symbol;
}
- pub fn follow(symbols: *Map, ref: Ref) Ref {
- if (symbols.get(ref)) |symbol| {
- const link = symbol.link;
- if (link.isNull())
- return ref;
-
- if (link.eql(ref)) {
- symbol.link = ref;
+ pub fn followAll(symbols: *Map) void {
+ for (symbols.symbols_for_source.slice()) |list| {
+ for (list.slice()) |*symbol| {
+ if (!symbol.hasLink()) continue;
+ symbol.link = follow(symbols, symbol.link);
}
+ }
+ }
- return symbol.link;
- } else {
+ pub fn follow(symbols: *const Map, ref: Ref) Ref {
+ var symbol = symbols.get(ref) orelse return ref;
+ if (!symbol.hasLink()) {
return ref;
}
- }
- };
- pub inline fn isKindPrivate(kind: Symbol.Kind) bool {
- return @enumToInt(kind) >= @enumToInt(Symbol.Kind.private_field) and @enumToInt(kind) <= @enumToInt(Symbol.Kind.private_static_get_set_pair);
- }
+ const link = follow(symbols, symbol.link);
- pub inline fn isKindHoisted(kind: Symbol.Kind) bool {
- return switch (kind) {
- .hoisted, .hoisted_function => true,
- else => false,
- };
- }
+ if (!symbol.link.eql(link)) {
+ symbol.link = link;
+ }
+
+ return link;
+ }
+ };
pub inline fn isHoisted(self: *const Symbol) bool {
return Symbol.isKindHoisted(self.kind);
}
- pub inline fn isKindHoistedOrFunction(kind: Symbol.Kind) bool {
- return switch (kind) {
- .hoisted, .hoisted_function, .generator_or_async_function => true,
- else => false,
- };
- }
-
- pub inline fn isKindFunction(kind: Symbol.Kind) bool {
- return switch (kind) {
- .hoisted_function, .generator_or_async_function => true,
- else => false,
- };
- }
-
pub fn isReactComponentishName(symbol: *const Symbol) bool {
switch (symbol.kind) {
.hoisted, .hoisted_function, .cconst, .class, .other => {
@@ -1095,13 +1272,13 @@ pub const E = struct {
target: ExprNodeIndex,
optional_chain: ?OptionalChain = null,
- pub fn hasSameFlagsAs(a: *Index, b: *Index) bool {
+ pub fn hasSameFlagsAs(a: *E.Index, b: *E.Index) bool {
return (a.optional_chain == b.optional_chain);
}
};
pub const Arrow = struct {
- args: []G.Arg,
+ args: []G.Arg = &[_]G.Arg{},
body: G.FnBody,
is_async: bool = false,
@@ -1140,34 +1317,38 @@ pub const E = struct {
}
};
- // This is similar to an EIdentifier but it represents a reference to an ES6
- // import item.
- //
- // Depending on how the code is linked, the file containing this EImportIdentifier
- // may or may not be in the same module group as the file it was imported from.
- //
- // If it's the same module group than we can just merge the import item symbol
- // with the corresponding symbol that was imported, effectively renaming them
- // to be the same thing and statically binding them together.
- //
- // But if it's a different module group, then the import must be dynamically
- // evaluated using a property access off the corresponding namespace symbol,
- // which represents the result of a require() call.
- //
- // It's stored as a separate type so it's not easy to confuse with a plain
- // identifier. For example, it'd be bad if code trying to convert "{x: x}" into
- // "{x}" shorthand syntax wasn't aware that the "x" in this case is actually
- // "{x: importedNamespace.x}". This separate type forces code to opt-in to
- // doing this instead of opt-out.
+ /// This is similar to an `Identifier` but it represents a reference to an ES6
+ /// import item.
+ ///
+ /// Depending on how the code is linked, the file containing this EImportIdentifier
+ /// may or may not be in the same module group as the file it was imported from.
+ ///
+ /// If it's the same module group than we can just merge the import item symbol
+ /// with the corresponding symbol that was imported, effectively renaming them
+ /// to be the same thing and statically binding them together.
+ ///
+ /// But if it's a different module group, then the import must be dynamically
+ /// evaluated using a property access off the corresponding namespace symbol,
+ /// which represents the result of a require() call.
+ ///
+ /// It's stored as a separate type so it's not easy to confuse with a plain
+ /// identifier. For example, it'd be bad if code trying to convert "{x: x}" into
+ /// "{x}" shorthand syntax wasn't aware that the "x" in this case is actually
+ /// "{x: importedNamespace.x}". This separate type forces code to opt-in to
+ /// doing this instead of opt-out.
pub const ImportIdentifier = struct {
ref: Ref = Ref.None,
- // If true, this was originally an identifier expression such as "foo". If
- // false, this could potentially have been a member access expression such
- // as "ns.foo" off of an imported namespace object.
+ /// If true, this was originally an identifier expression such as "foo". If
+ /// false, this could potentially have been a member access expression such
+ /// as "ns.foo" off of an imported namespace object.
was_originally_identifier: bool = false,
};
+ pub const CommonJSExportIdentifier = struct {
+ ref: Ref = Ref.None,
+ };
+
// This is similar to EIdentifier but it represents class-private fields and
// methods. It can be used where computed properties can be used, such as
// EIndex and Property.
@@ -1914,15 +2095,19 @@ pub const E = struct {
expr: ExprNodeIndex,
import_record_index: u32,
- // Comments inside "import()" expressions have special meaning for Webpack.
- // Preserving comments inside these expressions makes it possible to use
- // esbuild as a TypeScript-to-JavaScript frontend for Webpack to improve
- // performance. We intentionally do not interpret these comments in esbuild
- // because esbuild is not Webpack. But we do preserve them since doing so is
- // harmless, easy to maintain, and useful to people. See the Webpack docs for
- // more info: https://webpack.js.org/api/module-methods/#magic-comments.
- // TODO:
+ /// Comments inside "import()" expressions have special meaning for Webpack.
+ /// Preserving comments inside these expressions makes it possible to use
+ /// esbuild as a TypeScript-to-JavaScript frontend for Webpack to improve
+ /// performance. We intentionally do not interpret these comments in esbuild
+ /// because esbuild is not Webpack. But we do preserve them since doing so is
+ /// harmless, easy to maintain, and useful to people. See the Webpack docs for
+ /// more info: https://webpack.js.org/api/module-methods/#magic-comments.
+ /// TODO:
leading_interior_comments: []G.Comment = &([_]G.Comment{}),
+
+ pub fn isImportRecordNull(this: *const Import) bool {
+ return this.import_record_index == std.math.maxInt(u32);
+ }
};
};
@@ -1930,6 +2115,18 @@ pub const Stmt = struct {
loc: logger.Loc,
data: Data,
+ pub const Batcher = bun.Batcher(Stmt);
+
+ pub fn assign(a: Expr, b: Expr, allocator: std.mem.Allocator) Stmt {
+ return Stmt.alloc(
+ S.SExpr,
+ S.SExpr{
+ .value = Expr.assign(a, b, allocator),
+ },
+ a.loc,
+ );
+ }
+
const Serializable = struct {
type: Tag,
object: string,
@@ -1963,6 +2160,7 @@ pub const Stmt = struct {
pub var icount: usize = 0;
pub fn init(comptime StatementType: type, origData: *StatementType, loc: logger.Loc) Stmt {
icount += 1;
+
return switch (comptime StatementType) {
S.Empty => Stmt{ .loc = loc, .data = Data{ .s_empty = S.Empty{} } },
S.Block => Stmt.comptime_init("s_block", S.Block, origData, loc),
@@ -2002,7 +2200,24 @@ pub const Stmt = struct {
};
}
inline fn comptime_alloc(comptime tag_name: string, comptime typename: type, origData: anytype, loc: logger.Loc) Stmt {
- return Stmt{ .loc = loc, .data = @unionInit(Data, tag_name, Data.Store.append(typename, origData)) };
+ return Stmt{
+ .loc = loc,
+ .data = @unionInit(
+ Data,
+ tag_name,
+ Data.Store.append(
+ typename,
+ origData,
+ ),
+ ),
+ };
+ }
+
+ fn allocateData(allocator: std.mem.Allocator, comptime tag_name: string, comptime typename: type, origData: anytype, loc: logger.Loc) Stmt {
+ var value = allocator.create(@TypeOf(origData)) catch unreachable;
+ value.* = origData;
+
+ return comptime_init(tag_name, *typename, value, loc);
}
inline fn comptime_init(comptime tag_name: string, comptime TypeName: type, origData: TypeName, loc: logger.Loc) Stmt {
@@ -2010,6 +2225,8 @@ pub const Stmt = struct {
}
pub fn alloc(comptime StatementData: type, origData: StatementData, loc: logger.Loc) Stmt {
+ Stmt.Data.Store.assert();
+
icount += 1;
return switch (StatementData) {
S.Block => Stmt.comptime_alloc("s_block", S.Block, origData, loc),
@@ -2049,6 +2266,53 @@ pub const Stmt = struct {
};
}
+ pub const Disabler = bun.DebugOnlyDisabler(@This());
+
+ /// When the lifetime of an Stmt.Data's pointer must exist longer than reset() is called, use this function.
+ /// Be careful to free the memory (or use an allocator that does it for you)
+ /// Also, prefer Stmt.init or Stmt.alloc when possible. This will be slower.
+ pub fn allocate(allocator: std.mem.Allocator, comptime StatementData: type, origData: StatementData, loc: logger.Loc) Stmt {
+ Stmt.Data.Store.assert();
+
+ icount += 1;
+ return switch (StatementData) {
+ S.Block => Stmt.allocateData(allocator, "s_block", S.Block, origData, loc),
+ S.Break => Stmt.allocateData(allocator, "s_break", S.Break, origData, loc),
+ S.Class => Stmt.allocateData(allocator, "s_class", S.Class, origData, loc),
+ S.Comment => Stmt.allocateData(allocator, "s_comment", S.Comment, origData, loc),
+ S.Continue => Stmt.allocateData(allocator, "s_continue", S.Continue, origData, loc),
+ S.Debugger => Stmt{ .loc = loc, .data = .{ .s_debugger = origData } },
+ S.Directive => Stmt.allocateData(allocator, "s_directive", S.Directive, origData, loc),
+ S.DoWhile => Stmt.allocateData(allocator, "s_do_while", S.DoWhile, origData, loc),
+ S.Empty => Stmt{ .loc = loc, .data = Data{ .s_empty = S.Empty{} } },
+ S.Enum => Stmt.allocateData(allocator, "s_enum", S.Enum, origData, loc),
+ S.ExportClause => Stmt.allocateData(allocator, "s_export_clause", S.ExportClause, origData, loc),
+ S.ExportDefault => Stmt.allocateData(allocator, "s_export_default", S.ExportDefault, origData, loc),
+ S.ExportEquals => Stmt.allocateData(allocator, "s_export_equals", S.ExportEquals, origData, loc),
+ S.ExportFrom => Stmt.allocateData(allocator, "s_export_from", S.ExportFrom, origData, loc),
+ S.ExportStar => Stmt.allocateData(allocator, "s_export_star", S.ExportStar, origData, loc),
+ S.SExpr => Stmt.allocateData(allocator, "s_expr", S.SExpr, origData, loc),
+ S.ForIn => Stmt.allocateData(allocator, "s_for_in", S.ForIn, origData, loc),
+ S.ForOf => Stmt.allocateData(allocator, "s_for_of", S.ForOf, origData, loc),
+ S.For => Stmt.allocateData(allocator, "s_for", S.For, origData, loc),
+ S.Function => Stmt.allocateData(allocator, "s_function", S.Function, origData, loc),
+ S.If => Stmt.allocateData(allocator, "s_if", S.If, origData, loc),
+ S.Import => Stmt.allocateData(allocator, "s_import", S.Import, origData, loc),
+ S.Label => Stmt.allocateData(allocator, "s_label", S.Label, origData, loc),
+ S.LazyExport => Stmt.allocateData(allocator, "s_lazy_export", S.LazyExport, origData, loc),
+ S.Local => Stmt.allocateData(allocator, "s_local", S.Local, origData, loc),
+ S.Namespace => Stmt.allocateData(allocator, "s_namespace", S.Namespace, origData, loc),
+ S.Return => Stmt.allocateData(allocator, "s_return", S.Return, origData, loc),
+ S.Switch => Stmt.allocateData(allocator, "s_switch", S.Switch, origData, loc),
+ S.Throw => Stmt.allocateData(allocator, "s_throw", S.Throw, origData, loc),
+ S.Try => Stmt.allocateData(allocator, "s_try", S.Try, origData, loc),
+ S.TypeScript => Stmt{ .loc = loc, .data = Data{ .s_type_script = S.TypeScript{} } },
+ S.While => Stmt.allocateData(allocator, "s_while", S.While, origData, loc),
+ S.With => Stmt.allocateData(allocator, "s_with", S.With, origData, loc),
+ else => @compileError("Invalid type in Stmt.init"),
+ };
+ }
+
pub const Tag = enum(u6) {
s_block,
s_break,
@@ -2166,12 +2430,13 @@ pub const Stmt = struct {
S.While,
S.With,
};
- pub const All = NewBaseStore(Union, 128);
+ const All = NewBaseStore(Union, 128);
+ pub threadlocal var memory_allocator: ?*ASTMemoryAllocator = null;
threadlocal var has_inited = false;
pub threadlocal var disable_reset = false;
pub fn create(allocator: std.mem.Allocator) void {
- if (has_inited) {
+ if (has_inited or memory_allocator != null) {
return;
}
@@ -2180,25 +2445,29 @@ pub const Stmt = struct {
}
pub fn reset() void {
- if (disable_reset) return;
+ if (disable_reset or memory_allocator != null) return;
All.reset();
}
pub fn deinit() void {
- if (!has_inited) return;
+ if (!has_inited or memory_allocator != null) return;
All.deinit();
has_inited = false;
}
- pub fn assert() void {
+ pub inline fn assert() void {
if (comptime Environment.allow_assert) {
- if (!has_inited)
+ if (!has_inited and memory_allocator == null)
bun.unreachablePanic("Store must be init'd", .{});
}
}
pub fn append(comptime ValueType: type, value: anytype) *ValueType {
- return All.append(ValueType, value);
+ if (memory_allocator) |allocator| {
+ return allocator.append(ValueType, value);
+ }
+
+ return All.append(Disabler, ValueType, value);
}
pub fn toOwnedSlice() []*Store.All.Block {
@@ -2228,6 +2497,15 @@ pub const Expr = struct {
loc: logger.Loc,
data: Data,
+ pub fn isAnonymousNamed(expr: Expr) bool {
+ return switch (expr.data) {
+ .e_arrow => true,
+ .e_function => |func| func.func.name == null,
+ .e_class => |class| class.class_name == null,
+ else => false,
+ };
+ }
+
pub fn clone(this: Expr, allocator: std.mem.Allocator) !Expr {
return .{
.loc = this.loc,
@@ -2563,39 +2841,449 @@ pub const Expr = struct {
return [2]f64{ left.e_number.value, right.e_number.value };
}
- pub fn isAnonymousNamed(e: *Expr) bool {
- switch (e.data) {
- .e_arrow => {
- return true;
+ pub var icount: usize = 0;
+
+ // We don't need to dynamically allocate booleans
+ var true_bool = E.Boolean{ .value = true };
+ var false_bool = E.Boolean{ .value = false };
+ var bool_values = [_]*E.Boolean{ &false_bool, &true_bool };
+
+ /// When the lifetime of an Expr.Data's pointer must exist longer than reset() is called, use this function.
+ /// Be careful to free the memory (or use an allocator that does it for you)
+ /// Also, prefer Expr.init or Expr.alloc when possible. This will be slower.
+ pub fn allocate(allocator: std.mem.Allocator, comptime Type: type, st: Type, loc: logger.Loc) Expr {
+ icount += 1;
+ Data.Store.assert();
+
+ switch (Type) {
+ E.Array => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_array = brk: {
+ var item = allocator.create(Type) catch unreachable;
+ item.* = st;
+ break :brk item;
+ },
+ },
+ };
},
- .e_function => |func| {
- return func.func.name == null;
+ E.Class => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_class = brk: {
+ var item = allocator.create(Type) catch unreachable;
+ item.* = st;
+ break :brk item;
+ },
+ },
+ };
},
- .e_class => |class| {
- return class.class_name == null;
+ E.Unary => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_unary = brk: {
+ var item = allocator.create(Type) catch unreachable;
+ item.* = st;
+ break :brk item;
+ },
+ },
+ };
},
+ E.Binary => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_binary = brk: {
+ var item = allocator.create(Type) catch unreachable;
+ item.* = st;
+ break :brk item;
+ },
+ },
+ };
+ },
+ E.This => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_this = st,
+ },
+ };
+ },
+ E.Boolean => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_boolean = st,
+ },
+ };
+ },
+ E.Super => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_super = st,
+ },
+ };
+ },
+ E.Null => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_null = st,
+ },
+ };
+ },
+ E.Undefined => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_undefined = st,
+ },
+ };
+ },
+ E.New => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_new = brk: {
+ var item = allocator.create(Type) catch unreachable;
+ item.* = st;
+ break :brk item;
+ },
+ },
+ };
+ },
+ E.NewTarget => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_new_target = st,
+ },
+ };
+ },
+ E.Function => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_function = brk: {
+ var item = allocator.create(Type) catch unreachable;
+ item.* = st;
+ break :brk item;
+ },
+ },
+ };
+ },
+ E.ImportMeta => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_import_meta = st,
+ },
+ };
+ },
+ E.Call => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_call = brk: {
+ var item = allocator.create(Type) catch unreachable;
+ item.* = st;
+ break :brk item;
+ },
+ },
+ };
+ },
+ E.Dot => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_dot = brk: {
+ var item = allocator.create(Type) catch unreachable;
+ item.* = st;
+ break :brk item;
+ },
+ },
+ };
+ },
+ E.Index => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_index = brk: {
+ var item = allocator.create(Type) catch unreachable;
+ item.* = st;
+ break :brk item;
+ },
+ },
+ };
+ },
+ E.Arrow => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_arrow = brk: {
+ var item = allocator.create(Type) catch unreachable;
+ item.* = st;
+ break :brk item;
+ },
+ },
+ };
+ },
+ E.Identifier => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_identifier = E.Identifier{
+ .ref = st.ref,
+ .must_keep_due_to_with_stmt = st.must_keep_due_to_with_stmt,
+ .can_be_removed_if_unused = st.can_be_removed_if_unused,
+ .call_can_be_unwrapped_if_unused = st.call_can_be_unwrapped_if_unused,
+ },
+ },
+ };
+ },
+ E.ImportIdentifier => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_import_identifier = .{
+ .ref = st.ref,
+ .was_originally_identifier = st.was_originally_identifier,
+ },
+ },
+ };
+ },
+ E.CommonJSExportIdentifier => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_commonjs_export_identifier = .{
+ .ref = st.ref,
+ },
+ },
+ };
+ },
+
+ E.PrivateIdentifier => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_private_identifier = st,
+ },
+ };
+ },
+ E.JSXElement => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_jsx_element = brk: {
+ var item = allocator.create(Type) catch unreachable;
+ item.* = st;
+ break :brk item;
+ },
+ },
+ };
+ },
+ E.Missing => {
+ return Expr{ .loc = loc, .data = Data{ .e_missing = E.Missing{} } };
+ },
+ E.Number => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_number = st,
+ },
+ };
+ },
+ E.BigInt => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_big_int = brk: {
+ var item = allocator.create(Type) catch unreachable;
+ item.* = st;
+ break :brk item;
+ },
+ },
+ };
+ },
+ E.Object => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_object = brk: {
+ var item = allocator.create(Type) catch unreachable;
+ item.* = st;
+ break :brk item;
+ },
+ },
+ };
+ },
+ E.Spread => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_spread = brk: {
+ var item = allocator.create(Type) catch unreachable;
+ item.* = st;
+ break :brk item;
+ },
+ },
+ };
+ },
+ E.String => {
+ if (comptime Environment.isDebug) {
+ // Sanity check: assert string is not a null ptr
+ if (st.data.len > 0 and st.isUTF8()) {
+ std.debug.assert(@ptrToInt(st.data.ptr) > 0);
+ std.debug.assert(st.data[0] > 0);
+ }
+ }
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_string = brk: {
+ var item = allocator.create(Type) catch unreachable;
+ item.* = st;
+ break :brk item;
+ },
+ },
+ };
+ },
+ E.TemplatePart => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_template_part = brk: {
+ var item = allocator.create(Type) catch unreachable;
+ item.* = st;
+ break :brk item;
+ },
+ },
+ };
+ },
+ E.Template => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_template = brk: {
+ var item = allocator.create(Type) catch unreachable;
+ item.* = st;
+ break :brk item;
+ },
+ },
+ };
+ },
+ E.RegExp => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_reg_exp = brk: {
+ var item = allocator.create(Type) catch unreachable;
+ item.* = st;
+ break :brk item;
+ },
+ },
+ };
+ },
+ E.Await => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_await = brk: {
+ var item = allocator.create(Type) catch unreachable;
+ item.* = st;
+ break :brk item;
+ },
+ },
+ };
+ },
+ E.Yield => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_yield = brk: {
+ var item = allocator.create(Type) catch unreachable;
+ item.* = st;
+ break :brk item;
+ },
+ },
+ };
+ },
+ E.If => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_if = brk: {
+ var item = allocator.create(Type) catch unreachable;
+ item.* = st;
+ break :brk item;
+ },
+ },
+ };
+ },
+ E.RequireOrRequireResolve => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_require_or_require_resolve = st,
+ },
+ };
+ },
+ E.Import => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_import = brk: {
+ var item = allocator.create(Type) catch unreachable;
+ item.* = st;
+ break :brk item;
+ },
+ },
+ };
+ },
+ E.Require => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_require = st,
+ },
+ };
+ },
+ *E.String => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_string = brk: {
+ var item = allocator.create(Type) catch unreachable;
+ item.* = st.*;
+ break :brk item;
+ },
+ },
+ };
+ },
+
else => {
- return false;
+ @compileError("Invalid type passed to Expr.init: " ++ @typeName(Type));
},
}
}
- pub var icount: usize = 0;
-
- // We don't need to dynamically allocate booleans
- var true_bool = E.Boolean{ .value = true };
- var false_bool = E.Boolean{ .value = false };
- var bool_values = [_]*E.Boolean{ &false_bool, &true_bool };
+ pub const Disabler = bun.DebugOnlyDisabler(@This());
pub fn init(comptime Type: type, st: Type, loc: logger.Loc) Expr {
icount += 1;
+ Data.Store.assert();
switch (Type) {
E.Array => {
return Expr{
.loc = loc,
.data = Data{
- .e_array = Data.Store.All.append(Type, st),
+ .e_array = Data.Store.append(Type, st),
},
};
},
@@ -2603,7 +3291,7 @@ pub const Expr = struct {
return Expr{
.loc = loc,
.data = Data{
- .e_class = Data.Store.All.append(Type, st),
+ .e_class = Data.Store.append(Type, st),
},
};
},
@@ -2611,7 +3299,7 @@ pub const Expr = struct {
return Expr{
.loc = loc,
.data = Data{
- .e_unary = Data.Store.All.append(Type, st),
+ .e_unary = Data.Store.append(Type, st),
},
};
},
@@ -2619,7 +3307,7 @@ pub const Expr = struct {
return Expr{
.loc = loc,
.data = Data{
- .e_binary = Data.Store.All.append(Type, st),
+ .e_binary = Data.Store.append(Type, st),
},
};
},
@@ -2667,7 +3355,7 @@ pub const Expr = struct {
return Expr{
.loc = loc,
.data = Data{
- .e_new = Data.Store.All.append(Type, st),
+ .e_new = Data.Store.append(Type, st),
},
};
},
@@ -2683,7 +3371,7 @@ pub const Expr = struct {
return Expr{
.loc = loc,
.data = Data{
- .e_function = Data.Store.All.append(Type, st),
+ .e_function = Data.Store.append(Type, st),
},
};
},
@@ -2699,7 +3387,7 @@ pub const Expr = struct {
return Expr{
.loc = loc,
.data = Data{
- .e_call = Data.Store.All.append(Type, st),
+ .e_call = Data.Store.append(Type, st),
},
};
},
@@ -2707,7 +3395,7 @@ pub const Expr = struct {
return Expr{
.loc = loc,
.data = Data{
- .e_dot = Data.Store.All.append(Type, st),
+ .e_dot = Data.Store.append(Type, st),
},
};
},
@@ -2715,7 +3403,7 @@ pub const Expr = struct {
return Expr{
.loc = loc,
.data = Data{
- .e_index = Data.Store.All.append(Type, st),
+ .e_index = Data.Store.append(Type, st),
},
};
},
@@ -2723,7 +3411,7 @@ pub const Expr = struct {
return Expr{
.loc = loc,
.data = Data{
- .e_arrow = Data.Store.All.append(Type, st),
+ .e_arrow = Data.Store.append(Type, st),
},
};
},
@@ -2751,6 +3439,16 @@ pub const Expr = struct {
},
};
},
+ E.CommonJSExportIdentifier => {
+ return Expr{
+ .loc = loc,
+ .data = Data{
+ .e_commonjs_export_identifier = .{
+ .ref = st.ref,
+ },
+ },
+ };
+ },
E.PrivateIdentifier => {
return Expr{
.loc = loc,
@@ -2763,7 +3461,7 @@ pub const Expr = struct {
return Expr{
.loc = loc,
.data = Data{
- .e_jsx_element = Data.Store.All.append(Type, st),
+ .e_jsx_element = Data.Store.append(Type, st),
},
};
},
@@ -2782,7 +3480,7 @@ pub const Expr = struct {
return Expr{
.loc = loc,
.data = Data{
- .e_big_int = Data.Store.All.append(Type, st),
+ .e_big_int = Data.Store.append(Type, st),
},
};
},
@@ -2790,7 +3488,7 @@ pub const Expr = struct {
return Expr{
.loc = loc,
.data = Data{
- .e_object = Data.Store.All.append(Type, st),
+ .e_object = Data.Store.append(Type, st),
},
};
},
@@ -2798,7 +3496,7 @@ pub const Expr = struct {
return Expr{
.loc = loc,
.data = Data{
- .e_spread = Data.Store.All.append(Type, st),
+ .e_spread = Data.Store.append(Type, st),
},
};
},
@@ -2813,7 +3511,7 @@ pub const Expr = struct {
return Expr{
.loc = loc,
.data = Data{
- .e_string = Data.Store.All.append(Type, st),
+ .e_string = Data.Store.append(Type, st),
},
};
},
@@ -2821,7 +3519,7 @@ pub const Expr = struct {
return Expr{
.loc = loc,
.data = Data{
- .e_template_part = Data.Store.All.append(Type, st),
+ .e_template_part = Data.Store.append(Type, st),
},
};
},
@@ -2829,7 +3527,7 @@ pub const Expr = struct {
return Expr{
.loc = loc,
.data = Data{
- .e_template = Data.Store.All.append(Type, st),
+ .e_template = Data.Store.append(Type, st),
},
};
},
@@ -2837,7 +3535,7 @@ pub const Expr = struct {
return Expr{
.loc = loc,
.data = Data{
- .e_reg_exp = Data.Store.All.append(Type, st),
+ .e_reg_exp = Data.Store.append(Type, st),
},
};
},
@@ -2845,7 +3543,7 @@ pub const Expr = struct {
return Expr{
.loc = loc,
.data = Data{
- .e_await = Data.Store.All.append(Type, st),
+ .e_await = Data.Store.append(Type, st),
},
};
},
@@ -2853,7 +3551,7 @@ pub const Expr = struct {
return Expr{
.loc = loc,
.data = Data{
- .e_yield = Data.Store.All.append(Type, st),
+ .e_yield = Data.Store.append(Type, st),
},
};
},
@@ -2861,7 +3559,7 @@ pub const Expr = struct {
return Expr{
.loc = loc,
.data = Data{
- .e_if = Data.Store.All.append(Type, st),
+ .e_if = Data.Store.append(Type, st),
},
};
},
@@ -2877,7 +3575,7 @@ pub const Expr = struct {
return Expr{
.loc = loc,
.data = Data{
- .e_import = Data.Store.All.append(Type, st),
+ .e_import = Data.Store.append(Type, st),
},
};
},
@@ -2893,7 +3591,7 @@ pub const Expr = struct {
return Expr{
.loc = loc,
.data = Data{
- .e_string = Data.Store.All.append(@TypeOf(st.*), st.*),
+ .e_string = Data.Store.append(@TypeOf(st.*), st.*),
},
};
},
@@ -2942,6 +3640,8 @@ pub const Expr = struct {
e_class,
e_require,
+ e_commonjs_export_identifier,
+
// This should never make it to the printer
inline_identifier,
@@ -3504,16 +4204,6 @@ pub const Expr = struct {
return null;
}
- pub fn assignStmt(a: Expr, b: Expr, allocator: std.mem.Allocator) Stmt {
- return Stmt.alloc(
- S.SExpr,
- S.SExpr{
- .value = Expr.assign(a, b, allocator),
- },
- a.loc,
- );
- }
-
pub fn isOptionalChain(self: *const @This()) bool {
return switch (self.data) {
.e_dot => self.data.e_dot.optional_chain != null,
@@ -3594,6 +4284,7 @@ pub const Expr = struct {
e_identifier: E.Identifier,
e_import_identifier: E.ImportIdentifier,
e_private_identifier: E.PrivateIdentifier,
+ e_commonjs_export_identifier: E.CommonJSExportIdentifier,
e_boolean: E.Boolean,
e_number: E.Number,
@@ -3955,7 +4646,7 @@ pub const Expr = struct {
const medium = 256;
const rare = 24;
- pub const All = NewBaseStore(
+ const All = NewBaseStore(
&([_]type{
E.Array,
E.Unary,
@@ -3986,10 +4677,12 @@ pub const Expr = struct {
512,
);
+ pub threadlocal var memory_allocator: ?*ASTMemoryAllocator = null;
+
threadlocal var has_inited = false;
pub threadlocal var disable_reset = false;
pub fn create(allocator: std.mem.Allocator) void {
- if (has_inited) {
+ if (has_inited or memory_allocator != null) {
return;
}
@@ -3997,34 +4690,34 @@ pub const Expr = struct {
_ = All.init(allocator);
}
- pub fn assert() void {
- if (comptime Environment.allow_assert) {
- if (!has_inited)
- bun.unreachablePanic("Store must be init'd", .{});
- }
- }
-
pub fn reset() void {
- if (disable_reset) return;
+ if (disable_reset or memory_allocator != null) return;
All.reset();
}
pub fn deinit() void {
- if (!has_inited) return;
+ if (!has_inited or memory_allocator != null) return;
All.deinit();
has_inited = false;
}
+ pub inline fn assert() void {
+ if (comptime Environment.allow_assert) {
+ if (!has_inited and memory_allocator == null)
+ bun.unreachablePanic("Store must be init'd", .{});
+ }
+ }
+
pub fn append(comptime ValueType: type, value: anytype) *ValueType {
- if (ValueType == E.Identifier) {
- return E.Identifier.append(ValueType, value);
- } else {
- return All.append(ValueType, value);
+ if (memory_allocator) |allocator| {
+ return allocator.append(ValueType, value);
}
+
+ return All.append(Disabler, ValueType, value);
}
pub fn toOwnedSlice() []*Store.All.Block {
- if (!has_inited or Store.All._self.overflow.used == 0 or disable_reset) return &[_]*Store.All.Block{};
+ if (!has_inited or Store.All._self.overflow.used == 0 or disable_reset or memory_allocator != null) return &[_]*Store.All.Block{};
return Store.All.reclaim();
}
};
@@ -4534,9 +5227,11 @@ pub const ArrayBinding = struct {
};
pub const Ast = struct {
+ pub const TopLevelSymbolToParts = std.ArrayHashMapUnmanaged(Ref, BabyList(u32), Ref.ArrayHashCtx, false);
+
approximate_newline_count: usize = 0,
has_lazy_export: bool = false,
- runtime_imports: Runtime.Imports,
+ runtime_imports: Runtime.Imports = .{},
runtime_import_record_id: ?u32 = null,
needs_runtime: bool = false,
@@ -4554,22 +5249,23 @@ pub const Ast = struct {
// This is a list of ES6 features. They are ranges instead of booleans so
// that they can be used in log messages. Check to see if "Len > 0".
- import_keyword: ?logger.Range = null, // Does not include TypeScript-specific syntax or "import()"
- export_keyword: ?logger.Range = null, // Does not include TypeScript-specific syntax
- top_level_await_keyword: ?logger.Range = null,
+ import_keyword: logger.Range = logger.Range.None, // Does not include TypeScript-specific syntax or "import()"
+ export_keyword: logger.Range = logger.Range.None, // Does not include TypeScript-specific syntax
+ top_level_await_keyword: logger.Range = logger.Range.None,
// These are stored at the AST level instead of on individual AST nodes so
// they can be manipulated efficiently without a full AST traversal
- import_records: []ImportRecord = &([_]ImportRecord{}),
+ import_records: ImportRecord.List = .{},
hashbang: ?string = null,
directive: ?string = null,
url_for_css: ?string = null,
- parts: []Part,
- symbols: []Symbol = &([_]Symbol{}),
- module_scope: ?Scope = null,
+ parts: Part.List = Part.List{},
+ // This list may be mutated later, so we should store the capacity
+ symbols: Symbol.List = Symbol.List{},
+ module_scope: Scope = Scope{},
// char_freq: *CharFreq,
- exports_ref: ?Ref = null,
+ exports_ref: Ref = Ref.None,
module_ref: ?Ref = null,
wrapper_ref: ?Ref = null,
require_ref: Ref = Ref.None,
@@ -4583,21 +5279,38 @@ pub const Ast = struct {
// These are used when bundling. They are filled in during the parser pass
// since we already have to traverse the AST then anyway and the parser pass
// is conveniently fully parallelized.
- named_imports: NamedImports = undefined,
- named_exports: NamedExports = undefined,
+ named_imports: NamedImports = NamedImports.init(bun.failing_allocator),
+ named_exports: NamedExports = NamedExports.init(bun.failing_allocator),
export_star_import_records: []u32 = &([_]u32{}),
+ allocator: std.mem.Allocator,
+ top_level_symbols_to_parts: TopLevelSymbolToParts = .{},
+
+ commonjs_named_exports: CommonJSNamedExports = .{},
+
+ redirect_import_record_index: ?u32 = null,
+
+ /// Only populated when bundling
+ platform: bun.options.Platform = .browser,
+
+ pub const CommonJSNamedExport = struct {
+ loc_ref: LocRef,
+ needs_decl: bool = true,
+ };
+ pub const CommonJSNamedExports = bun.StringArrayHashMapUnmanaged(CommonJSNamedExport);
+
pub const NamedImports = std.ArrayHashMap(Ref, NamedImport, RefHashCtx, true);
pub const NamedExports = bun.StringArrayHashMap(NamedExport);
pub fn initTest(parts: []Part) Ast {
return Ast{
- .parts = parts,
+ .parts = Part.List.init(parts),
+ .allocator = bun.default_allocator,
.runtime_imports = .{},
};
}
- pub const empty = Ast{ .parts = &[_]Part{}, .runtime_imports = undefined };
+ pub const empty = Ast{ .parts = Part.List{}, .runtime_imports = .{}, .allocator = bun.default_allocator };
pub fn toJSON(self: *const Ast, _: std.mem.Allocator, stream: anytype) !void {
const opts = std.json.StringifyOptions{ .whitespace = std.json.StringifyOptions.Whitespace{
@@ -4609,16 +5322,16 @@ pub const Ast = struct {
/// Do not call this if it wasn't globally allocated!
pub fn deinit(this: *Ast) void {
// TODO: assert mimalloc-owned memory
- if (this.parts.len > 0) bun.default_allocator.free(this.parts);
+ if (this.parts.len > 0) this.parts.deinitWithAllocator(bun.default_allocator);
if (this.externals.len > 0) bun.default_allocator.free(this.externals);
- if (this.symbols.len > 0) bun.default_allocator.free(this.symbols);
- if (this.import_records.len > 0) bun.default_allocator.free(this.import_records);
+ if (this.symbols.len > 0) this.symbols.deinitWithAllocator(bun.default_allocator);
+ if (this.import_records.len > 0) this.import_records.deinitWithAllocator(bun.default_allocator);
}
};
pub const Span = struct {
- text: string,
- range: logger.Range,
+ text: string = "",
+ range: logger.Range = .{},
};
pub const ExportsKind = enum {
@@ -4646,7 +5359,11 @@ pub const ExportsKind = enum {
// "exports") with getters for the export names. All named imports to this
// module are allowed. Direct named imports reference the corresponding export
// directly. Other imports go through property accesses on "exports".
- esm_with_dyn,
+ esm_with_dynamic_fallback,
+
+ pub fn isDynamic(self: ExportsKind) bool {
+ return self == .esm_with_dynamic_fallback or self == .cjs;
+ }
pub fn jsonStringify(self: @This(), opts: anytype, o: anytype) !void {
return try std.json.stringify(@tagName(self), opts, o);
@@ -4656,11 +5373,103 @@ pub const ExportsKind = enum {
pub const DeclaredSymbol = struct {
ref: Ref,
is_top_level: bool = false,
+
+ pub const List = struct {
+ entries: bun.MultiArrayList(DeclaredSymbol) = .{},
+
+ pub fn refs(this: *const List) []Ref {
+ return this.entries.items(.ref);
+ }
+
+ pub fn toOwnedSlice(this: *List) List {
+ var new = this.*;
+
+ this.* = .{};
+ return new;
+ }
+
+ pub fn clone(this: *const List, allocator: std.mem.Allocator) !List {
+ return List{ .entries = try this.entries.clone(allocator) };
+ }
+
+ pub inline fn len(this: List) usize {
+ return this.entries.len;
+ }
+
+ pub fn append(this: *List, allocator: std.mem.Allocator, entry: DeclaredSymbol) !void {
+ try this.ensureUnusedCapacity(allocator, 1);
+ this.appendAssumeCapacity(entry);
+ }
+
+ pub fn appendList(this: *List, allocator: std.mem.Allocator, other: List) !void {
+ try this.ensureUnusedCapacity(allocator, other.len());
+ this.appendListAssumeCapacity(other);
+ }
+
+ pub fn appendListAssumeCapacity(this: *List, other: List) void {
+ this.entries.appendListAssumeCapacity(other.entries);
+ }
+
+ pub fn appendAssumeCapacity(this: *List, entry: DeclaredSymbol) void {
+ this.entries.appendAssumeCapacity(entry);
+ }
+
+ pub fn ensureTotalCapacity(this: *List, allocator: std.mem.Allocator, count: usize) !void {
+ try this.entries.ensureTotalCapacity(allocator, count);
+ }
+
+ pub fn ensureUnusedCapacity(this: *List, allocator: std.mem.Allocator, count: usize) !void {
+ try this.entries.ensureUnusedCapacity(allocator, count);
+ }
+
+ pub fn clearRetainingCapacity(this: *List) void {
+ this.entries.clearRetainingCapacity();
+ }
+
+ pub fn deinit(this: *List, allocator: std.mem.Allocator) void {
+ this.entries.deinit(allocator);
+ }
+
+ pub fn initCapacity(allocator: std.mem.Allocator, capacity: usize) !List {
+ var entries = bun.MultiArrayList(DeclaredSymbol){};
+ try entries.ensureUnusedCapacity(allocator, capacity);
+ return List{ .entries = entries };
+ }
+
+ pub fn fromSlice(allocator: std.mem.Allocator, entries: []const DeclaredSymbol) !List {
+ var this = try List.initCapacity(allocator, entries.len);
+ errdefer this.deinit(allocator);
+ for (entries) |entry| {
+ this.appendAssumeCapacity(entry);
+ }
+
+ return this;
+ }
+ };
+
+ fn forEachTopLevelSymbolWithType(decls: *List, comptime Ctx: type, ctx: Ctx, comptime Fn: fn (Ctx, Ref) void) void {
+ var entries = decls.entries.slice();
+ const is_top_level = entries.items(.is_top_level);
+ const refs = entries.items(.ref);
+
+ // TODO: SIMD
+ for (is_top_level, refs) |top, ref| {
+ if (top) {
+ @call(.always_inline, Fn, .{ ctx, ref });
+ }
+ }
+ }
+
+ pub fn forEachTopLevelSymbol(decls: *List, ctx: anytype, comptime Fn: anytype) void {
+ forEachTopLevelSymbolWithType(decls, @TypeOf(ctx), ctx, Fn);
+ }
};
-pub const Dependency = packed struct {
- source_index: u32 = 0,
- part_index: u32 = 0,
+pub const Dependency = struct {
+ source_index: Index = Index.invalid,
+ part_index: Index.Int = 0,
+
+ pub const List = BabyList(Dependency);
};
pub const ExprList = std.ArrayList(Expr);
@@ -4673,24 +5482,27 @@ pub const BindingList = std.ArrayList(Binding);
// shaking and can be assigned to separate chunks (i.e. output files) by code
// splitting.
pub const Part = struct {
- stmts: []Stmt,
+ pub const ImportRecordIndices = BabyList(u32);
+ pub const List = BabyList(Part);
+
+ stmts: []Stmt = &([_]Stmt{}),
scopes: []*Scope = &([_]*Scope{}),
// Each is an index into the file-level import record list
- import_record_indices: []u32 = &([_]u32{}),
+ import_record_indices: ImportRecordIndices = .{},
// All symbols that are declared in this part. Note that a given symbol may
// have multiple declarations, and so may end up being declared in multiple
// parts (e.g. multiple "var" declarations with the same name). Also note
// that this list isn't deduplicated and may contain duplicates.
- declared_symbols: []DeclaredSymbol = &([_]DeclaredSymbol{}),
+ declared_symbols: DeclaredSymbol.List = .{},
// An estimate of the number of uses of all symbols used within this part.
symbol_uses: SymbolUseMap = SymbolUseMap{},
// The indices of the other parts in this file that are needed if this part
// is needed.
- dependencies: []Dependency = &([_]Dependency{}),
+ dependencies: Dependency.List = .{},
// If true, this part can be removed if none of the declared symbols are
// used. If the file containing this part is imported, then all parts that
@@ -4708,6 +5520,8 @@ pub const Part = struct {
tag: Tag = Tag.none,
+ valid_in_development: if (bun.Environment.allow_assert) bool else void = bun.DebugOnlyDefault(true),
+
pub const Tag = enum {
none,
jsx_import,
@@ -4737,10 +5551,10 @@ pub const StmtOrExpr = union(enum) {
pub const NamedImport = struct {
// Parts within this file that use this import
- local_parts_with_uses: []u32 = &([_]u32{}),
+ local_parts_with_uses: BabyList(u32) = BabyList(u32){},
alias: ?string,
- alias_loc: ?logger.Loc,
+ alias_loc: ?logger.Loc = null,
namespace_ref: ?Ref,
import_record_index: u32,
@@ -4760,7 +5574,7 @@ pub const NamedExport = struct {
alias_loc: logger.Loc,
};
-pub const StrictModeKind = enum(u7) {
+pub const StrictModeKind = enum(u4) {
sloppy_mode,
explicit_strict_mode,
implicit_strict_mode_import,
@@ -4777,10 +5591,10 @@ pub const Scope = struct {
id: usize = 0,
kind: Kind = Kind.block,
- parent: ?*Scope,
- children: std.ArrayListUnmanaged(*Scope) = .{},
+ parent: ?*Scope = null,
+ children: BabyList(*Scope) = .{},
members: MemberHashMap = .{},
- generated: std.ArrayListUnmanaged(Ref) = .{},
+ generated: BabyList(Ref) = .{},
// This is used to store the ref of the label symbol for ScopeLabel scopes.
label_ref: ?Ref = null,
@@ -4798,6 +5612,8 @@ pub const Scope = struct {
is_after_const_local_prefix: bool = false,
+ pub const NestedScopeMap = std.AutoArrayHashMap(u32, bun.BabyList(*Scope));
+
pub fn getMemberHash(name: []const u8) u64 {
return bun.StringHashMapContext.hash(.{}, name);
}
@@ -4900,10 +5716,12 @@ pub const Scope = struct {
// "var foo; function foo() {}"
// "function foo() {} var foo;"
// "function *foo() {} function *foo() {}" but not "{ function *foo() {} function *foo() {} }"
- if (Symbol.isKindHoistedOrFunction(new) and Symbol.isKindHoistedOrFunction(existing) and (scope.kind == .entry or scope.kind == .function_body or
- (Symbol.isKindHoisted(new) and Symbol.isKindHoisted(existing))))
+ if (Symbol.isKindHoistedOrFunction(new) and
+ Symbol.isKindHoistedOrFunction(existing) and
+ (scope.kind == .entry or scope.kind == .function_body or scope.kind == .function_args or
+ (new == existing and Symbol.isKindHoisted(existing))))
{
- return .keep_existing;
+ return .replace_with_new;
}
// "get #foo() {} set #foo() {}"
@@ -4958,7 +5776,7 @@ pub const Scope = struct {
pub fn recursiveSetStrictMode(s: *Scope, kind: StrictModeKind) void {
if (s.strict_mode == .sloppy_mode) {
s.strict_mode = kind;
- for (s.children.items) |child| {
+ for (s.children.slice()) |child| {
child.recursiveSetStrictMode(kind);
}
}
@@ -8337,6 +9155,116 @@ pub const Macro = struct {
};
};
+pub const ASTMemoryAllocator = struct {
+ stack_allocator: std.heap.StackFallbackAllocator(8096) = undefined,
+ bump_allocator: std.mem.Allocator = undefined,
+ allocator: std.mem.Allocator,
+ previous: ?*ASTMemoryAllocator = null,
+
+ pub fn push(this: *ASTMemoryAllocator) void {
+ if (Stmt.Data.Store.memory_allocator == this) {
+ return;
+ }
+ this.stack_allocator.fallback_allocator = this.allocator;
+ this.bump_allocator = this.stack_allocator.get();
+ var prev = Stmt.Data.Store.memory_allocator;
+ if (this.previous) |other| {
+ other.previous = prev;
+ } else {
+ this.previous = prev;
+ }
+ Stmt.Data.Store.memory_allocator = this;
+ Expr.Data.Store.memory_allocator = this;
+ }
+
+ pub fn pop(this: *ASTMemoryAllocator) void {
+ var prev = this.previous;
+ std.debug.assert(prev != this);
+ Stmt.Data.Store.memory_allocator = prev;
+ Expr.Data.Store.memory_allocator = prev;
+ this.previous = null;
+ }
+
+ pub fn append(this: ASTMemoryAllocator, comptime ValueType: type, value: anytype) *ValueType {
+ const ptr = this.bump_allocator.create(ValueType) catch unreachable;
+ ptr.* = value;
+ return ptr;
+ }
+};
+
+pub const UseDirective = enum {
+ none,
+ @"use client",
+ @"use server",
+
+ pub const Flags = struct {
+ is_client: bool = false,
+ is_server: bool = false,
+ };
+
+ pub fn isBoundary(this: UseDirective, other: UseDirective) bool {
+ if (this == other or other == .none)
+ return false;
+
+ return true;
+ }
+
+ pub fn boundering(this: UseDirective, other: UseDirective) ?UseDirective {
+ if (this == other or other == .none)
+ return null;
+
+ return other;
+ }
+
+ pub const EntryPoint = struct {
+ source_index: Index.Int,
+ use_directive: UseDirective,
+ };
+
+ pub const List = std.MultiArrayList(UseDirective.EntryPoint);
+
+ // TODO: remove this, add an onModuleDirective() callback to the parser
+ pub fn parse(contents: []const u8) UseDirective {
+ const truncated = std.mem.trimLeft(u8, contents, " \t\n\r;");
+
+ if (truncated.len < "'use client';".len)
+ return .none;
+
+ const directive_string = truncated[0.."'use client';".len].*;
+
+ const first_quote = directive_string[0];
+ const last_quote = directive_string[directive_string.len - 2];
+ if (first_quote != last_quote or (first_quote != '"' and first_quote != '\'' and first_quote != '`'))
+ return .none;
+
+ const unquoted = directive_string[1 .. directive_string.len - 2];
+
+ if (strings.eqlComptime(
+ unquoted,
+ "use client",
+ )) {
+ return .@"use client";
+ }
+
+ if (strings.eqlComptime(
+ unquoted,
+ "use server",
+ )) {
+ return .@"use server";
+ }
+
+ return .none;
+ }
+
+ pub fn platform(this: UseDirective, default: bun.options.Platform) bun.options.Platform {
+ return switch (this) {
+ .none => default,
+ .@"use client" => .browser,
+ .@"use server" => .bun,
+ };
+ }
+};
+
// test "Binding.init" {
// var binding = Binding.alloc(
// std.heap.page_allocator,
@@ -8525,3 +9453,4 @@ pub const Macro = struct {
// Stmt | 192
// STry | 384
// -- ESBuild bit sizes
+