implement step 6

This commit is contained in:
Jarred Sumner
2022-04-21 23:19:06 -07:00
parent 83c83949ab
commit 636d2e486f
9 changed files with 629 additions and 32 deletions

View File

@@ -34,6 +34,15 @@ pub fn BabyList(comptime Type: type) type {
this.len += 1;
}
pub inline fn appendSliceAssumeCapacity(this: *@This(), values: []const Type) void {
var tail = this.ptr[this.len];
for (values) |value| {
tail.* = value;
tail += 1;
}
this.len += values.len;
}
pub inline fn init(items: []const Type) ListType {
@setRuntimeSafety(false);
return ListType{

View File

@@ -1282,6 +1282,25 @@ const LinkerGraph = struct {
// it is a 2 dimensional bitset
file_entry_bits: Bitmap,
pub fn generateRuntimeSymbolImportAndUse(
graph: *LinkerGraph,
source_index: Index.Int,
index: Index.Int,
entry_point_part_index: Index,
name: []const u8,
count: u32,
) !void {
const ref = graph.ast.items(.ast)[Index.runtime.get()].module_scope.members.get(name).?.ref;
try graph.generateSymbolImportAndUse(
index,
source_index,
entry_point_part_index.get(),
ref,
count,
Index.runtime,
);
}
pub fn addPartToFile(
graph: *LinkerGraph,
id: u32,
@@ -1761,12 +1780,441 @@ const LinkerContext = struct {
// Step 5: Create namespace exports for every file. This is always necessary
// for CommonJS files, and is also necessary for other files if they are
// imported using an import star statement.
// Note: `do` will wait for all to finish before moving forward
try this.parse_graph.pool.pool.do(this.allocator(), &this.wait_group, this, doStep5, this.graph.reachable_files);
// Step 6: Bind imports to exports. This adds non-local dependencies on the
// parts that declare the export to all parts that use the import. Also
// generate wrapper parts for wrapped files.
{
try this.parse_graph.pool.pool.do(this.allocator(), &this.wait_group, this, doStep5, this.graph.reachable_files);
const bufPrint = std.fmt.bufPrint;
var parts_list: []js_ast.Part.List = this.graph.ast.items(.parts);
var wrapper_refs = this.graph.meta.items(.wrapper_ref);
const needs_export_symbol_from_runtime: []const bool = this.graph.meta.items(.needs_export_symbol_from_runtime);
var imports_to_bind_list: []*RefImportData = this.graph.meta.items(.imports_to_bind);
var runtime_export_symbol_ref: Ref = Ref.None;
for (reachable) |source_index| {
const id = asts[source_index];
if (id > named_imports.len) {
continue;
}
const is_entry_point = entry_point_kinds[source_index].isEntryPoint();
const aliases = this.graph.meta.items(.sorted_and_filtered_export_aliases)[id];
const wrap = wraps[id];
const export_kind = export_kinds[id];
const source: *const Logger.Source = &this.parse_graph.input_files.items(.source)[source_index];
const exports_ref = exports_refs[id];
var exports_symbol: ?*js_ast.Symbol = if (exports_ref.isValid())
this.graph.symbols.get(exports_ref)
else
null;
const module_ref = module_refs[id];
var module_symbol: ?*js_ast.Symbol = if (module_ref.isValid())
this.graph.symbols.get(module_ref)
else
null;
// TODO: see if counting and batching into a single large allocation instead of per-file improves perf
const string_buffer_len: usize = brk: {
var count: usize = 0;
if (is_entry_point and this.output_format == .esm) {
for (aliases) |alias| {
count += std.fmt.count("{}", .{strings.fmtIdentifier(alias)});
}
count *= "export_".len;
}
var ident_fmt_len: usize = 0;
if (wrap == .esm or (wrap != .cjs and export_kind != .common_js)) {
ident_fmt_len += if (source.identifier_name.len > 0)
source.identifier_name.len
else
std.fmt.count("{}", .{source.fmtIdentifier()});
}
if (wrap == .esm) {
count += "init_".len + ident_fmt_len;
}
if (wrap != .cjs and export_kind != .common_js) {
count += "exports_".len + ident_fmt_len;
count += "module_".len + ident_fmt_len;
}
break :brk count;
};
var string_buffer = this.allocator.alloc(u8, string_buffer_len) catch unreachable;
var buf = string_buffer;
defer std.debug.assert(buf.len == 0); // ensure we used all of it
// Pre-generate symbols for re-exports CommonJS symbols in case they
// are necessary later. This is done now because the symbols map cannot be
// mutated later due to parallelism.
if (is_entry_point and this.output_format == .esm) {
var copies = this.allocator().alloc(Ref, aliases.len) catch unreachable;
for (aliases) |alias, i| {
const original_name = bufPrint(buf, "export_{}", .{strings.fmtIdentifier(alias)}) catch unreachable;
buf = buf[original_name.len..];
copies[i] = this.graph.generateNewSymbol(source_index, .other, original_name);
}
this.graph.meta.items(.cjs_export_copies)[id] = copies;
}
// Use "init_*" for ESM wrappers instead of "require_*"
if (wrap == .esm) {
const original_name = bufPrint(
buf,
"init_{}",
.{
strings.fmtIdentifier(source.fmtIdentifier()),
},
) catch unreachable;
buf = buf[original_name.len..];
this.graph.symbols.get(wrapper_refs[id]).original_name = original_name;
}
// If this isn't CommonJS, then rename the unused "exports" and "module"
// variables to avoid them causing the identically-named variables in
// actual CommonJS files from being renamed. This is purely about
// aesthetics and is not about correctness. This is done here because by
// this point, we know the CommonJS status will not change further.
if (wrap != .cjs and export_kind != .common_js) {
const exports_name = bufPrint(buf, "exports_{s}", .{strings.fmtIdentifier(source.fmtIdentifier())}) catch unreachable;
buf = buf[exports_name.len..];
const module_name = bufPrint(buf, "module_{s}", .{strings.fmtIdentifier(source.fmtIdentifier())}) catch unreachable;
buf = buf[module_name.len..];
exports_symbol.?.original_name = exports_name;
module_symbol.?.original_name = module_name;
}
// Include the "__export" symbol from the runtime if it was used in the
// previous step. The previous step can't do this because it's running in
// parallel and can't safely mutate the "importsToBind" map of another file.
if (needs_exports_variable[id]) {
if (!runtime_export_symbol_ref.isValid()) {
runtime_export_symbol_ref = this.graph.ast.items(.module_scope)[Index.runtime.get()].members.get("__export").?.ref;
}
std.debug.assert(runtime_export_symbol_ref.isValid());
this.graph.generateSymbolImportAndUse(
id,
source_index,
js_ast.namespace_export_part_index,
runtime_export_symbol_ref,
1,
Index.runtime.get(),
) catch unreachable;
}
var parts: []js_ast.Part = parts_list[id].slice();
var imports_to_bind = imports_to_bind_list[id];
var imports_to_bind_iter = imports_to_bind.iterator();
while (imports_to_bind_iter.next()) |import| {
const import_source_index = import.value_ptr.source_index;
const import_id = asts[import_source_index];
const import_ref = import.key_ptr.*;
var named_import = named_imports[import_id].getPtr(import_ref) orelse continue;
const parts_declaring_symbol = this.topLevelSymbolsToParts(import_id, import_ref);
for (named_import.local_parts_with_uses.slice()) |part_index| {
var part: *js_ast.Part = &parts[part_index];
part.dependencies.ensureUnusedCapacity(
this.allocator(),
parts_declaring_symbol.len + @as(usize, import.value_ptr.re_exports.len),
) catch unreachable;
// Depend on the file containing the imported symbol
for (parts_declaring_symbol) |resolved_part_index| {
part.dependencies.appendAssumeCapacity(
this.allocator(),
.{
.source_index = import_source_index,
.part_index = resolved_part_index,
},
);
}
// Also depend on any files that re-exported this symbol in between the
// file containing the import and the file containing the imported symbol
part.dependencies.appendSliceAssumeCapacity(import.value_ptr.re_exports.slice());
}
// Merge these symbols so they will share the same name
this.graph.symbols.merge(import_ref, import.value_ptr.ref);
}
// If this is an entry point, depend on all exports so they are included
if (is_entry_point) {
const force_include_exports = force_include_exports_for_entry_points[id];
const add_wrapper = wrap != .none;
var dependencies = std.ArrayList(js_ast.Dependency).initCapacity(
this.allocator(),
@as(usize, @boolToInt(force_include_exports)) + @as(usize, @boolToInt(add_wrapper)),
);
var resolved_exports_list: *RefExportData = this.graph.meta.items(.resolved_exports)[id];
for (aliases) |alias| {
var export_ = resolved_exports_list.get(alias).?;
var target_source_index = export_.source_index;
var target_id = asts[target_source_index];
var target_ref = export_.ref;
// If this is an import, then target what the import points to
if (imports_to_bind.get(target_ref)) |import_data| {
target_source_index = import_data.value_ptr.source_index;
target_id = asts[target_source_index];
target_ref = import_data.value_ptr.ref;
dependencies.appendSlice(import_data.re_exports.slice()) catch unreachable;
}
const top_to_parts = this.topLevelSymbolsToParts(target_id, target_ref);
dependencies.ensureUnusedCapacity(top_to_parts.len) catch unreachable;
// Pull in all declarations of this symbol
for (top_to_parts) |part_index| {
dependencies.appendAssumeCapacity(
.{
.source_index = target_source_index,
.part_index = part_index,
},
);
}
}
dependencies.ensureUnusedCapacity(@as(usize, @boolToInt(force_include_exports)) + @as(usize, @boolToInt(add_wrapper))) catch unreachable;
// Ensure "exports" is included if the current output format needs it
if (force_include_exports) {
dependencies.appendAssumeCapacity(
.{ .source_index = source_index, .part_index = js_ast.namespace_export_part_index },
);
}
if (add_wrapper) {
dependencies.appendAssumeCapacity(
.{
.source_index = source_index,
.part_index = this.graph.meta.items(.wrapper_part_index)[id].get(),
},
);
}
// Represent these constraints with a dummy part
const entry_point_part_index = this.graph.addPartToFile(
id,
.{
.dependencies = js_ast.Dependency.List.fromList(dependencies),
.can_be_removed_if_unused = false,
},
) catch unreachable;
this.graph.items(.meta)[id].entry_point_part_index = Index.init(entry_point_part_index);
// Pull in the "__toCommonJS" symbol if we need it due to being an entry point
if (force_include_exports) {
this.graph.generateRuntimeSymbolImportAndUse(
source_index,
entry_point_part_index,
"__toCommonJS",
1,
) catch unreachable;
}
}
// Encode import-specific constraints in the dependency graph
var import_records = import_records_list[id].slice();
for (parts) |*part, part_index| {
var to_esm_uses: u32 = 0;
var to_common_js_uses: u32 = 0;
var runtime_require_uses: u32 = 0;
for (part.import_record_indices.slice()) |import_record_index| {
var record = &import_records[import_record_index];
const kind = record.kind;
// Don't follow external imports (this includes import() expressions)
if (!record.source_index.isValid() or this.isExternalDynamicImport(record, source_index)) {
// This is an external import. Check if it will be a "require()" call.
if (kind == .require or !output_format.keepES6ImportExportSyntax() or
(kind == .dynamic))
{
// We should use "__require" instead of "require" if we're not
// generating a CommonJS output file, since it won't exist otherwise
if (this.shouldCallRuntimeRequire(output_format)) {
record.calls_runtime_require = true;
runtime_require_uses += 1;
}
// If this wasn't originally a "require()" call, then we may need
// to wrap this in a call to the "__toESM" wrapper to convert from
// CommonJS semantics to ESM semantics.
//
// Unfortunately this adds some additional code since the conversion
// is somewhat complex. As an optimization, we can avoid this if the
// following things are true:
//
// - The import is an ES module statement (e.g. not an "import()" expression)
// - The ES module namespace object must not be captured
// - The "default" and "__esModule" exports must not be accessed
//
if (kind != .require and
(kind != .stmt or
record.contains_import_star or
record.contains_default_alias or
record.contains_es_module_alias))
{
record.wrap_with_to_esm = true;
to_esm_uses += 1;
}
}
continue;
}
const other_source_index = record.source_index.get();
const other_id = asts[other_source_index];
std.debug.assert(@intCast(usize, other_id) < this.graph.meta.len);
const other_export_kind = export_kinds[other_id];
switch (wrap) {
else => {
// Depend on the automatically-generated require wrapper symbol
const wrapper_ref = wrapper_refs[other_id];
this.graph.generateSymbolImportAndUse(
id,
source_index,
@intCast(u32, part_index),
wrapper_ref,
1,
Index.init(other_source_index),
) catch unreachable;
// This is an ES6 import of a CommonJS module, so it needs the
// "__toESM" wrapper as long as it's not a bare "require()"
if (kind != .require and other_export_kind == .common_js) {
record.wrap_with_to_esm = true;
to_esm_uses += 1;
}
},
.none => {
if (kind == .stmt and other_export_kind == .esm_with_dynamic_fallback) {
// This is an import of a module that has a dynamic export fallback
// object. In that case we need to depend on that object in case
// something ends up needing to use it later. This could potentially
// be omitted in some cases with more advanced analysis if this
// dynamic export fallback object doesn't end up being needed.
this.graph.generateSymbolImportAndUse(
id,
source_index,
@intCast(u32, part_index),
this.graph.ast.items(.exports_ref)[other_id],
1,
Index.init(other_source_index),
) catch unreachable;
}
},
}
}
// If there's an ES6 import of a non-ES6 module, then we're going to need the
// "__toESM" symbol from the runtime to wrap the result of "require()"
this.graph.generateRuntimeSymbolImportAndUse(
id,
source_index,
Index.init(part_index),
"__toESM",
to_esm_uses,
) catch unreachable;
// If there's a CommonJS require of an ES6 module, then we're going to need the
// "__toCommonJS" symbol from the runtime to wrap the exports object
this.graph.generateRuntimeSymbolImportAndUse(
id,
source_index,
Index.init(part_index),
"__toCommonJS",
to_common_js_uses,
) catch unreachable;
// If there are unbundled calls to "require()" and we're not generating
// code for node, then substitute a "__require" wrapper for "require".
this.graph.generateRuntimeSymbolImportAndUse(
id,
source_index,
Index.init(part_index),
"__require",
runtime_require_uses,
) catch unreachable;
// If there's an ES6 export star statement of a non-ES6 module, then we're
// going to need the "__reExport" symbol from the runtime
var re_export_uses: u32 = 0;
for (export_star_import_records[id]) |import_record_index| {
var record = &import_records[import_record_index];
var happens_at_runtime = record.source_index.isInvalid() and (!is_entry_point or !output_format.keepES6ImportExportSyntax());
if (record.source_index.isValid()) {
var other_source_index = record.source_index.get();
const other_id = asts[other_source_index];
std.debug.assert(@intCast(usize, other_id) < this.graph.meta.len);
const other_export_kind = export_kinds[other_id];
if (other_source_index != source_index and other_export_kind.isDynamic()) {
happens_at_runtime = true;
}
if (other_export_kind == .esm_with_dynamic_fallback) {
// This looks like "__reExport(exports_a, exports_b)". Make sure to
// pull in the "exports_b" symbol into this export star. This matters
// in code splitting situations where the "export_b" symbol might live
// in a different chunk than this export star.
this.graph.generateSymbolImportAndUse(
id,
source_index,
@intCast(u32, part_index),
this.graph.ast.items(.exports_ref)[other_id],
1,
Index.init(other_source_index),
) catch unreachable;
}
}
if (happens_at_runtime) {
// Depend on this file's "exports" object for the first argument to "__reExport"
this.graph.generateSymbolImportAndUse(
id,
source_index,
@intCast(u32, part_index),
this.graph.ast.items(.exports_ref)[id],
1,
Index.init(source_index),
) catch unreachable;
this.graph.ast.items(.uses_export_ref)[id] = true;
record.calls_runtime_re_export_fn = true;
re_export_uses += 1;
}
}
this.graph.generateRuntimeSymbolImportAndUse(
source_index,
id,
Index.init(part_index),
"__reExport",
re_export_uses,
) catch unreachable;
}
}
}
}
pub fn createExportsForFile(c: *LinkerContext, allocator_: std.mem.Allocator, source_index: Index.Int, id: u32, ids: []u32, resolved_exports: *RefExportData, imports_to_bind: []*RefImportData, export_aliases: []const string, re_exports_count: usize) void {
pub fn createExportsForFile(c: *LinkerContext, allocator_: std.mem.Allocator, id: u32, ids: []u32, resolved_exports: *RefExportData, imports_to_bind: []*RefImportData, export_aliases: []const string, re_exports_count: usize) void {
////////////////////////////////////////////////////////////////////////////////
// WARNING: This method is run in parallel over all files. Do not mutate data
// for other files within this method or you will create a data race.
@@ -1793,7 +2241,7 @@ const LinkerContext = struct {
defer stmts.done();
const loc = Logger.Loc.Empty;
// todo: investigate if preallocating this array is faster
var ns_export_dependencies = std.ArrayList(js_ast.Dependency).init(allocator_);
var ns_export_dependencies = std.ArrayList(js_ast.Dependency).initCapacity(allocator_, re_exports_count) catch unreachable;
for (export_aliases) |alias| {
var export_ = resolved_exports.getPtr(alias).?;
@@ -1883,7 +2331,7 @@ const LinkerContext = struct {
var declared_symbols = js_ast.DeclaredSymbol.List{};
var exports_ref = c.graph.ast.items(.exports_ref)[id];
var export_stmts: []js_ast.Stmt = stmts.head;
std.debug.assert(stmts.head.len <= 2);
std.debug.assert(stmts.head.len <= 2); // assert we allocated exactly the right amount
stmts.head.len = 0;
// Prefix this part with "var exports = {}" if this isn't a CommonJS entry point
@@ -1976,6 +2424,9 @@ const LinkerContext = struct {
}
}
/// Step 5: Create namespace exports for every file. This is always necessary
/// for CommonJS files, and is also necessary for other files if they are
/// imported using an import star statement.
pub fn doStep5(c: *LinkerContext, source_index: Index.Int, _: usize) void {
const ids = c.parse_graph.input_files.items(.ast);
const id = ids[source_index];
@@ -2043,7 +2494,6 @@ const LinkerContext = struct {
// come second after we fill in that array
c.createExportsForFile(
allocator_,
source_index,
id,
ids,
resolved_exports,

View File

@@ -1075,6 +1075,25 @@ pub const PathName = struct {
ext: string,
filename: string,
pub fn nonUniqueNameStringBase(self: *const PathName) string {
// /bar/foo/index.js -> foo
if (self.dir.len > 0 and strings.eqlComptime(self.base, "index")) {
// "/index" -> "index"
return Fs.PathName.init(self.dir).base;
}
if (comptime Environment.allow_assert) {
std.debug.assert(!strings.includes(self.base, "/"));
}
// /bar/foo.js -> foo
return self.base;
}
pub fn fmtIdentifier(self: *const PathName) strings.FormatValidIdentifier {
return strings.fmtIdentifier(self.nonUniqueNameStringBase());
}
// For readability, the names of certain automatically-generated symbols are
// derived from the file name. For example, instead of the CommonJS wrapper for
// a file being called something like "require273" it can be called something
@@ -1087,13 +1106,7 @@ pub const PathName = struct {
// through the renaming logic that all other symbols go through to avoid name
// collisions.
pub fn nonUniqueNameString(self: *const PathName, allocator: std.mem.Allocator) !string {
if (strings.eqlComptime(self.base, "index")) {
if (self.dir.len > 0) {
return MutableString.ensureValidIdentifier(PathName.init(self.dir).base, allocator);
}
}
return MutableString.ensureValidIdentifier(self.base, allocator);
return MutableString.ensureValidIdentifier(self.nonUniqueNameStringBase(), allocator);
}
pub inline fn dirWithTrailingSlash(this: *const PathName) string {

View File

@@ -116,11 +116,17 @@ pub const ImportRecord = struct {
/// If true, this "export * from 'path'" statement is evaluated at run-time by
/// calling the "__reExport()" helper function
calls_run_time_re_export_fn: bool = false,
calls_runtime_re_export_fn: bool = false,
/// If true, this calls require() at runtime
calls_runtime_require: bool = false,
/// Tell the printer to wrap this call to "require()" in "__toModule(...)"
wrap_with_to_module: bool = false,
/// Tell the printer to wrap this call to "toESM()" in "__toESM(...)"
wrap_with_to_esm: bool = false,
/// True for require calls like this: "try { require() } catch {}". In this
/// case we shouldn't generate an error if the path could not be resolved.
is_inside_try_body: bool = false,

View File

@@ -2227,7 +2227,7 @@ pub const Parser = struct {
import_record.is_unused = import_record.is_unused or
(import_record.kind == .stmt and
!import_record.was_originally_bare_import and
!import_record.calls_run_time_re_export_fn);
!import_record.calls_runtime_re_export_fn);
}
var iter = scan_pass.used_symbols.iterator();
@@ -4125,20 +4125,13 @@ fn NewParser_(
var import_record: *ImportRecord = &p.import_records.items[import_record_i];
import_record.path.namespace = "runtime";
import_record.tag = .runtime;
var import_path_identifier = try import_record.path.name.nonUniqueNameString(allocator);
var namespace_identifier = try allocator.alloc(u8, import_path_identifier.len + suffix.len);
const import_path_identifier = comptime suffix ++ "bun_runtime";
var clause_items = try allocator.alloc(js_ast.ClauseItem, imports.len);
var stmts = try allocator.alloc(Stmt, 1 + if (additional_stmt != null) @as(usize, 1) else @as(usize, 0));
var declared_symbols = js_ast.DeclaredSymbol.List{};
try declared_symbols.ensureTotalCapacity(p.allocator, imports.len);
std.mem.copy(u8, namespace_identifier[0..suffix.len], suffix);
std.mem.copy(
u8,
namespace_identifier[suffix.len..namespace_identifier.len],
import_path_identifier[0..import_path_identifier.len],
);
const namespace_ref = try p.newSymbol(.other, namespace_identifier);
const namespace_ref = try p.newSymbol(.other, import_path_identifier);
try p.module_scope.generated.append(allocator, namespace_ref);
for (imports) |alias, i| {
const ref = symbols.get(alias) orelse unreachable;
@@ -5588,6 +5581,7 @@ fn NewParser_(
var stmt = stmt_;
if (is_macro) {
const id = p.addImportRecord(.stmt, path.loc, path.text);
p.import_records.items[id].tag = .macro;
p.import_records.items[id].path.namespace = js_ast.Macro.namespace;
p.import_records.items[id].is_unused = true;
@@ -6276,7 +6270,7 @@ fn NewParser_(
if (comptime track_symbol_usage_during_parse_pass) {
// In the scan pass, we need _some_ way of knowing *not* to mark as unused
p.import_records.items[import_record_index].calls_run_time_re_export_fn = true;
p.import_records.items[import_record_index].calls_runtime_re_export_fn = true;
}
try p.lexer.expectOrInsertSemicolon();
@@ -6315,7 +6309,7 @@ fn NewParser_(
if (comptime track_symbol_usage_during_parse_pass) {
// In the scan pass, we need _some_ way of knowing *not* to mark as unused
p.import_records.items[import_record_index].calls_run_time_re_export_fn = true;
p.import_records.items[import_record_index].calls_runtime_re_export_fn = true;
}
return p.s(S.ExportFrom{ .items = export_clause.clauses, .is_single_line = export_clause.is_single_line, .namespace_ref = namespace_ref, .import_record_index = import_record_index }, loc);
@@ -9016,7 +9010,7 @@ fn NewParser_(
}
pub fn addImportRecordByRange(p: *P, kind: ImportKind, range: logger.Range, name: string) u32 {
var index = p.import_records.items.len;
const index = p.import_records.items.len;
const record = ImportRecord{
.kind = kind,
.range = range,

View File

@@ -1021,8 +1021,27 @@ pub const Source = struct {
contents: string,
contents_is_recycled: bool = false,
/// Lazily-generated human-readable identifier name that is non-unique
/// Avoid accessing this directly most of the time
identifier_name: string = "",
index: Index = Index.invalid,
pub fn fmtIdentifier(this: *const Source) strings.FormatValidIdentifier {
return this.path.name.fmtIdentifier();
}
pub fn identifierName(this: *Source, allocator: std.mem.Allocator) !string {
if (this.identifier_name.len > 0) {
return this.identifier_name;
}
std.debug.assert(this.path.text.len > 0);
const name = try this.path.name.nonUniqueNameString(allocator);
this.identifier_name = name;
return name;
}
pub fn rangeOfIdentifier(this: *const Source, loc: Loc) Range {
const js_lexer = @import("./js_lexer.zig");
return js_lexer.rangeOfIdentifier(this.contents, loc);

View File

@@ -156,10 +156,10 @@ export var __exportDefault = (target, value) => {
});
};
export var __reExport = (target, module, desc) => {
export var __reExport = (target, module, copyDefault, desc) => {
if ((module && typeof module === "object") || typeof module === "function")
for (let key of __getOwnPropNames(module))
if (!__hasOwnProp.call(target, key) && key !== "default")
if (!__hasOwnProp.call(target, key) && (copyDefault || key !== "default"))
__defProp(target, key, {
get: () => module[key],
configurable: true,
@@ -168,3 +168,37 @@ export var __reExport = (target, module, desc) => {
});
return target;
};
// Converts the module from CommonJS to ESM
export var __toESM = (module, isNodeMode) => {
return __reExport(
__markAsModule(
__defProp(
module != null ? __create(__getProtoOf(module)) : {},
"default",
// If the importer is not in node compatibility mode and this is an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has been set), then forward
// "default" to the export named "default". Otherwise set "default" to
// "module.exports" for node compatibility.
!isNodeMode && module && module.__esModule
? { get: () => module.default, enumerable: true }
: { value: module, enumerable: true }
)
),
module
);
};
// Converts the module from ESM to CommonJS
export var __toCommonJS = /* @__PURE__ */ ((cache) => {
return (module, temp) => {
return (
(cache && cache.get(module)) ||
((temp = __reExport(__markAsModule({}), module, /* copyDefault */ 1)),
cache && cache.set(module, temp),
temp)
);
};
})(typeof WeakMap !== "undefined" ? new WeakMap() : 0);

View File

@@ -64,6 +64,79 @@ pub fn indexOfCharNeg(self: string, char: u8) i32 {
return -1;
}
/// Format a string to an ECMAScript identifier.
/// Unlike the string_mutable.zig version, this always allocate/copy
pub fn fmtIdentifier(name: string) FormatValidIdentifier {
return FormatValidIdentifier{ .name = name };
}
/// Format a string to an ECMAScript identifier.
/// Different implementation than string_mutable because string_mutable may avoid allocating
/// This will always allocate
pub const FormatValidIdentifier = struct {
name: string,
const js_lexer = @import("./js_lexer.zig");
pub fn format(self: FormatValidIdentifier, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
var iterator = strings.CodepointIterator.init(self.name);
var cursor = strings.CodepointIterator.Cursor{};
var has_needed_gap = false;
var needs_gap = false;
var start_i: usize = 0;
if (!iterator.next(&cursor)) {
try writer.writeAll("_");
return;
}
// Common case: no gap necessary. No allocation necessary.
needs_gap = !js_lexer.isIdentifierStart(cursor.c);
if (!needs_gap) {
// Are there any non-alphanumeric chars at all?
while (iterator.next(&cursor)) {
if (!js_lexer.isIdentifierContinue(cursor.c) or cursor.width > 1) {
needs_gap = true;
start_i = cursor.i;
break;
}
}
}
if (needs_gap) {
needs_gap = false;
var slice = self.name[start_i..];
iterator = strings.CodepointIterator.init(slice);
cursor = strings.CodepointIterator.Cursor{};
while (iterator.next(&cursor)) {
if (js_lexer.isIdentifierContinue(cursor.c) and cursor.width == 1) {
if (needs_gap) {
try writer.writeAll("_");
needs_gap = false;
has_needed_gap = true;
}
try writer.writeAll(slice[cursor.i .. cursor.i + @as(u32, cursor.width)]);
} else if (!needs_gap) {
needs_gap = true;
// skip the code point, replace it with a single _
}
}
// If it ends with an emoji
if (needs_gap) {
try writer.writeAll("_");
needs_gap = false;
has_needed_gap = true;
}
return;
}
try writer.writeAll(self.name);
}
};
pub fn indexOfSigned(self: string, str: string) i32 {
const i = std.mem.indexOf(u8, self, str) orelse return -1;
return @intCast(i32, i);

View File

@@ -62,10 +62,9 @@ pub const MutableString = struct {
return mutable;
}
// Convert it to an ASCII identifier. Note: If you change this to a non-ASCII
// identifier, you're going to potentially cause trouble with non-BMP code
// points in target environments that don't support bracketed Unicode escapes.
/// Convert it to an ASCII identifier. Note: If you change this to a non-ASCII
/// identifier, you're going to potentially cause trouble with non-BMP code
/// points in target environments that don't support bracketed Unicode escapes.
pub fn ensureValidIdentifier(str: string, allocator: std.mem.Allocator) !string {
if (str.len == 0) {
return "_";