/// This is for TypeScript "enum" and "namespace" blocks. Each block can /// potentially be instantiated multiple times. The exported members of each /// block are merged into a single namespace while the non-exported code is /// still scoped to just within that block: /// /// let x = 1; /// namespace Foo { /// let x = 2; /// export let y = 3; /// } /// namespace Foo { /// console.log(x); // 1 /// console.log(y); // 3 /// } /// /// Doing this also works inside an enum: /// /// enum Foo { /// A = 3, /// B = A + 1, /// } /// enum Foo { /// C = A + 2, /// } /// console.log(Foo.B) // 4 /// console.log(Foo.C) // 5 /// /// This is a form of identifier lookup that works differently than the /// hierarchical scope-based identifier lookup in JavaScript. Lookup now needs /// to search sibling scopes in addition to parent scopes. This is accomplished /// by sharing the map of exported members between all matching sibling scopes. pub const TSNamespaceScope = struct { /// This is specific to this namespace block. It's the argument of the /// immediately-invoked function expression that the namespace block is /// compiled into: /// /// var ns; /// (function (ns2) { /// ns2.x = 123; /// })(ns || (ns = {})); /// /// This variable is "ns2" in the above example. It's the symbol to use when /// generating property accesses off of this namespace when it's in scope. arg_ref: Ref, /// This is shared between all sibling namespace blocks exported_members: *TSNamespaceMemberMap, /// This is a lazily-generated map of identifiers that actually represent /// property accesses to this namespace's properties. For example: /// /// namespace x { /// export let y = 123 /// } /// namespace x { /// export let z = y /// } /// /// This should be compiled into the following code: /// /// var x; /// (function(x2) { /// x2.y = 123; /// })(x || (x = {})); /// (function(x3) { /// x3.z = x3.y; /// })(x || (x = {})); /// /// When we try to find the symbol "y", we instead return one of these lazily /// generated proxy symbols that represent the property access "x3.y". This /// map is unique per namespace block because "x3" is the argument symbol that /// is specific to that particular namespace block. property_accesses: bun.StringArrayHashMapUnmanaged(Ref) = .{}, /// Even though enums are like namespaces and both enums and namespaces allow /// implicit references to properties of sibling scopes, they behave like /// separate, er, namespaces. Implicit references only work namespace-to- /// namespace and enum-to-enum. They do not work enum-to-namespace. And I'm /// not sure what's supposed to happen for the namespace-to-enum case because /// the compiler crashes: https://github.com/microsoft/TypeScript/issues/46891. /// So basically these both work: /// /// enum a { b = 1 } /// enum a { c = b } /// /// namespace x { export let y = 1 } /// namespace x { export let z = y } /// /// This doesn't work: /// /// enum a { b = 1 } /// namespace a { export let c = b } /// /// And this crashes the TypeScript compiler: /// /// namespace a { export let b = 1 } /// enum a { c = b } /// /// Therefore we only allow enum/enum and namespace/namespace interactions. is_enum_scope: bool, }; pub const TSNamespaceMemberMap = bun.StringArrayHashMapUnmanaged(TSNamespaceMember); pub const TSNamespaceMember = struct { loc: logger.Loc, data: Data, pub const Data = union(enum) { /// "namespace ns { export let it }" property, /// "namespace ns { export namespace it {} }" namespace: *TSNamespaceMemberMap, /// "enum ns { it }" enum_number: f64, /// "enum ns { it = 'it' }" enum_string: *E.String, /// "enum ns { it = something() }" enum_property: void, pub fn isEnum(data: Data) bool { return switch (data) { inline else => |_, tag| comptime std.mem.startsWith(u8, @tagName(tag), "enum_"), }; } }; }; pub const Class = G.Class; const std = @import("std"); const bun = @import("bun"); const logger = bun.logger; const js_ast = bun.ast; const E = js_ast.E; const G = js_ast.G; const Ref = js_ast.Ref;