mirror of
https://github.com/oven-sh/bun
synced 2026-02-11 19:38:58 +00:00
140 lines
4.4 KiB
Zig
140 lines
4.4 KiB
Zig
/// 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;
|