pub const ContentsOrFd = union(enum) {
    fd: struct {
        dir: StoredFileDescriptorType,
        file: StoredFileDescriptorType,
    },
    contents: string,

    const Tag = @typeInfo(ContentsOrFd).@"union".tag_type.?;
};

pub const ParseTask = @This();

path: Fs.Path,
secondary_path_for_commonjs_interop: ?Fs.Path = null,
contents_or_fd: ContentsOrFd,
external_free_function: CacheEntry.ExternalFreeFunction = .none,
side_effects: _resolver.SideEffects,
loader: ?Loader = null,
jsx: options.JSX.Pragma,
source_index: Index = Index.invalid,
task: ThreadPoolLib.Task = .{ .callback = &taskCallback },

// Split this into a different task so that we don't accidentally run the
// tasks for io on the threads that are meant for parsing.
io_task: ThreadPoolLib.Task = .{ .callback = &ioTaskCallback },

// Used for splitting up the work between the io and parse steps.
stage: ParseTaskStage = .needs_source_code,

tree_shaking: bool = false,
known_target: options.Target,
module_type: options.ModuleType = .unknown,
emit_decorator_metadata: bool = false,
ctx: *BundleV2,
package_version: string = "",
is_entry_point: bool = false,
/// This is set when the file is an entrypoint, and it has an onLoad plugin.
/// In this case we want to defer adding this to additional_files until after
/// the onLoad plugin has finished.
defer_copy_for_bundling: bool = false,

const ParseTaskStage = union(enum) {
    needs_source_code: void,
    needs_parse: CacheEntry,
};

/// The information returned to the Bundler thread when a parse finishes.
pub const Result = struct {
    task: EventLoop.Task,
    ctx: *BundleV2,
    value: Value,
    watcher_data: WatcherData,
    /// This is used for native onBeforeParsePlugins to store
    /// a function pointer and context pointer to free the
    /// returned source code by the plugin.
    external: CacheEntry.ExternalFreeFunction = .none,

    pub const Value = union(enum) {
        success: Success,
        err: Error,
        empty: struct {
            source_index: Index,
        },
    };

    const WatcherData = struct {
        fd: bun.StoredFileDescriptorType,
        dir_fd: bun.StoredFileDescriptorType,

        /// When no files to watch, this encoding is used.
        pub const none: WatcherData = .{
            .fd = bun.invalid_fd,
            .dir_fd = bun.invalid_fd,
        };
    };

    pub const Success = struct {
        ast: JSAst,
        source: Logger.Source,
        log: Logger.Log,
        use_directive: UseDirective,
        side_effects: _resolver.SideEffects,

        /// Used by "file" loader files.
        unique_key_for_additional_file: []const u8 = "",
        /// Used by "file" loader files.
        content_hash_for_additional_file: u64 = 0,

        loader: Loader,
    };

    pub const Error = struct {
        err: anyerror,
        step: Step,
        log: Logger.Log,
        target: options.Target,
        source_index: Index,

        pub const Step = enum {
            pending,
            read_file,
            parse,
            resolve,
        };
    };
};

const debug = Output.scoped(.ParseTask, true);

pub fn init(resolve_result: *const _resolver.Result, source_index: Index, ctx: *BundleV2) ParseTask {
    return .{
        .ctx = ctx,
        .path = resolve_result.path_pair.primary,
        .contents_or_fd = .{
            .fd = .{
                .dir = resolve_result.dirname_fd,
                .file = resolve_result.file_fd,
            },
        },
        .side_effects = resolve_result.primary_side_effects_data,
        .jsx = resolve_result.jsx,
        .source_index = source_index,
        .module_type = resolve_result.module_type,
        .emit_decorator_metadata = resolve_result.emit_decorator_metadata,
        .package_version = if (resolve_result.package_json) |package_json| package_json.version else "",
        .known_target = ctx.transpiler.options.target,
    };
}

const RuntimeSource = struct {
    parse_task: ParseTask,
    source: Logger.Source,
};

fn getRuntimeSourceComptime(comptime target: options.Target) RuntimeSource {
    // When the `require` identifier is visited, it is replaced with e_require_call_target
    // and then that is either replaced with the module itself, or an import to the
    // runtime here.
    const runtime_require = switch (target) {
        // Previously, Bun inlined `import.meta.require` at all usages. This broke
        // code that called `fn.toString()` and parsed the code outside a module
        // context.
        .bun, .bun_macro =>
        \\export var __require = import.meta.require;
        ,

        .node =>
        \\import { createRequire } from "node:module";
        \\export var __require = /* @__PURE__ */ createRequire(import.meta.url);
        \\
        ,

        // Copied from esbuild's runtime.go:
        //
        // > This fallback "require" function exists so that "typeof require" can
        // > naturally be "function" even in non-CommonJS environments since esbuild
        // > emulates a CommonJS environment (issue #1202). However, people want this
        // > shim to fall back to "globalThis.require" even if it's defined later
        // > (including property accesses such as "require.resolve") so we need to
        // > use a proxy (issue #1614).
        //
        // When bundling to node, esbuild picks this code path as well, but `globalThis.require`
        // is not always defined there. The `createRequire` call approach is more reliable.
        else =>
        \\export var __require = /* @__PURE__ */ (x =>
        \\  typeof require !== 'undefined' ? require :
        \\  typeof Proxy !== 'undefined' ? new Proxy(x, {
        \\    get: (a, b) => (typeof require !== 'undefined' ? require : a)[b]
        \\  }) : x
        \\)(function (x) {
        \\  if (typeof require !== 'undefined') return require.apply(this, arguments)
        \\  throw Error('Dynamic require of "' + x + '" is not supported')
        \\});
        \\
    };
    const runtime_using_symbols = switch (target) {
        // bun's webkit has Symbol.asyncDispose, Symbol.dispose, and SuppressedError, but not the syntax support
        .bun =>
        \\export var __using = (stack, value, async) => {
        \\  if (value != null) {
        \\    if (typeof value !== 'object' && typeof value !== 'function') throw TypeError('Object expected to be assigned to "using" declaration')
        \\    let dispose
        \\    if (async) dispose = value[Symbol.asyncDispose]
        \\    if (dispose === void 0) dispose = value[Symbol.dispose]
        \\    if (typeof dispose !== 'function') throw TypeError('Object not disposable')
        \\    stack.push([async, dispose, value])
        \\  } else if (async) {
        \\    stack.push([async])
        \\  }
        \\  return value
        \\}
        \\
        \\export var __callDispose = (stack, error, hasError) => {
        \\  let fail = e => error = hasError ? new SuppressedError(e, error, 'An error was suppressed during disposal') : (hasError = true, e)
        \\    , next = (it) => {
        \\      while (it = stack.pop()) {
        \\        try {
        \\          var result = it[1] && it[1].call(it[2])
        \\          if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()))
        \\        } catch (e) {
        \\          fail(e)
        \\        }
        \\      }
        \\      if (hasError) throw error
        \\    }
        \\  return next()
        \\}
        \\
        ,
        // Other platforms may or may not have the symbol or errors
        // The definitions of __dispose and __asyncDispose match what esbuild's __wellKnownSymbol() helper does
        else =>
        \\var __dispose = Symbol.dispose || /* @__PURE__ */ Symbol.for('Symbol.dispose');
        \\var __asyncDispose =  Symbol.asyncDispose || /* @__PURE__ */ Symbol.for('Symbol.asyncDispose');
        \\
        \\export var __using = (stack, value, async) => {
        \\  if (value != null) {
        \\    if (typeof value !== 'object' && typeof value !== 'function') throw TypeError('Object expected to be assigned to "using" declaration')
        \\    var dispose
        \\    if (async) dispose = value[__asyncDispose]
        \\    if (dispose === void 0) dispose = value[__dispose]
        \\    if (typeof dispose !== 'function') throw TypeError('Object not disposable')
        \\    stack.push([async, dispose, value])
        \\  } else if (async) {
        \\    stack.push([async])
        \\  }
        \\  return value
        \\}
        \\
        \\export var __callDispose = (stack, error, hasError) => {
        \\  var E = typeof SuppressedError === 'function' ? SuppressedError :
        \\    function (e, s, m, _) { return _ = Error(m), _.name = 'SuppressedError', _.error = e, _.suppressed = s, _ },
        \\    fail = e => error = hasError ? new E(e, error, 'An error was suppressed during disposal') : (hasError = true, e),
        \\    next = (it) => {
        \\      while (it = stack.pop()) {
        \\        try {
        \\          var result = it[1] && it[1].call(it[2])
        \\          if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()))
        \\        } catch (e) {
        \\          fail(e)
        \\        }
        \\      }
        \\      if (hasError) throw error
        \\    }
        \\  return next()
        \\}
        \\
    };
    const runtime_code = @embedFile("../runtime.js") ++ runtime_require ++ runtime_using_symbols;

    const parse_task = ParseTask{
        .ctx = undefined,
        .path = Fs.Path.initWithNamespace("runtime", "bun:runtime"),
        .side_effects = .no_side_effects__pure_data,
        .jsx = .{
            .parse = false,
        },
        .contents_or_fd = .{
            .contents = runtime_code,
        },
        .source_index = Index.runtime,
        .loader = .js,
        .known_target = target,
    };
    const source = Logger.Source{
        .path = parse_task.path,
        .contents = parse_task.contents_or_fd.contents,
        .index = Index.runtime,
    };
    return .{ .parse_task = parse_task, .source = source };
}

pub fn getRuntimeSource(target: options.Target) RuntimeSource {
    return switch (target) {
        inline else => |t| comptime getRuntimeSourceComptime(t),
    };
}

threadlocal var override_file_path_buf: bun.PathBuffer = undefined;

fn getEmptyCSSAST(
    log: *Logger.Log,
    transpiler: *Transpiler,
    opts: js_parser.Parser.Options,
    allocator: std.mem.Allocator,
    source: *const Logger.Source,
) !JSAst {
    const root = Expr.init(E.Object, E.Object{}, Logger.Loc{ .start = 0 });
    var ast = JSAst.init((try js_parser.newLazyExportAST(allocator, transpiler.options.define, opts, log, root, source, "")).?);
    ast.css = bun.create(allocator, bun.css.BundlerStyleSheet, bun.css.BundlerStyleSheet.empty(allocator));
    return ast;
}

fn getEmptyAST(log: *Logger.Log, transpiler: *Transpiler, opts: js_parser.Parser.Options, allocator: std.mem.Allocator, source: *const Logger.Source, comptime RootType: type) !JSAst {
    const root = Expr.init(RootType, RootType{}, Logger.Loc.Empty);
    return JSAst.init((try js_parser.newLazyExportAST(allocator, transpiler.options.define, opts, log, root, source, "")).?);
}

const FileLoaderHash = struct {
    key: []const u8,
    content_hash: u64,
};

fn getAST(
    log: *Logger.Log,
    transpiler: *Transpiler,
    opts: js_parser.Parser.Options,
    allocator: std.mem.Allocator,
    resolver: *Resolver,
    source: *const Logger.Source,
    loader: Loader,
    unique_key_prefix: u64,
    unique_key_for_additional_file: *FileLoaderHash,
    has_any_css_locals: *std.atomic.Value(u32),
) !JSAst {
    switch (loader) {
        .jsx, .tsx, .js, .ts => {
            const trace = bun.perf.trace("Bundler.ParseJS");
            defer trace.end();
            return if (try resolver.caches.js.parse(
                transpiler.allocator,
                opts,
                transpiler.options.define,
                log,
                source,
            )) |res|
                JSAst.init(res.ast)
            else switch (opts.module_type == .esm) {
                inline else => |as_undefined| try getEmptyAST(
                    log,
                    transpiler,
                    opts,
                    allocator,
                    source,
                    if (as_undefined) E.Undefined else E.Object,
                ),
            };
        },
        .json, .jsonc => |v| {
            const trace = bun.perf.trace("Bundler.ParseJSON");
            defer trace.end();
            const root = (try resolver.caches.json.parseJSON(log, source, allocator, if (v == .jsonc) .jsonc else .json, true)) orelse Expr.init(E.Object, E.Object{}, Logger.Loc.Empty);
            return JSAst.init((try js_parser.newLazyExportAST(allocator, transpiler.options.define, opts, log, root, source, "")).?);
        },
        .toml => {
            const trace = bun.perf.trace("Bundler.ParseTOML");
            defer trace.end();
            var temp_log = bun.logger.Log.init(allocator);
            defer {
                temp_log.cloneToWithRecycled(log, true) catch bun.outOfMemory();
                temp_log.msgs.clearAndFree();
            }
            const root = try TOML.parse(source, &temp_log, allocator, false);
            return JSAst.init((try js_parser.newLazyExportAST(allocator, transpiler.options.define, opts, &temp_log, root, source, "")).?);
        },
        .text => {
            const root = Expr.init(E.String, E.String{
                .data = source.contents,
            }, Logger.Loc{ .start = 0 });
            var ast = JSAst.init((try js_parser.newLazyExportAST(allocator, transpiler.options.define, opts, log, root, source, "")).?);
            ast.addUrlForCss(allocator, source, "text/plain", null);
            return ast;
        },

        .sqlite_embedded, .sqlite => {
            if (!transpiler.options.target.isBun()) {
                log.addError(
                    source,
                    Logger.Loc.Empty,
                    "To use the \"sqlite\" loader, set target to \"bun\"",
                ) catch bun.outOfMemory();
                return error.ParserError;
            }

            const path_to_use = brk: {
                // Implements embedded sqlite
                if (loader == .sqlite_embedded) {
                    const embedded_path = std.fmt.allocPrint(allocator, "{any}A{d:0>8}", .{ bun.fmt.hexIntLower(unique_key_prefix), source.index.get() }) catch unreachable;
                    unique_key_for_additional_file.* = .{
                        .key = embedded_path,
                        .content_hash = ContentHasher.run(source.contents),
                    };
                    break :brk embedded_path;
                }

                break :brk source.path.text;
            };

            // This injects the following code:
            //
            // import.meta.require(unique_key).db
            //
            const import_path = Expr.init(E.String, E.String{
                .data = path_to_use,
            }, Logger.Loc{ .start = 0 });

            const import_meta = Expr.init(E.ImportMeta, E.ImportMeta{}, Logger.Loc{ .start = 0 });
            const require_property = Expr.init(E.Dot, E.Dot{
                .target = import_meta,
                .name_loc = Logger.Loc.Empty,
                .name = "require",
            }, Logger.Loc{ .start = 0 });
            const require_args = allocator.alloc(Expr, 2) catch unreachable;
            require_args[0] = import_path;
            const object_properties = allocator.alloc(G.Property, 1) catch unreachable;
            object_properties[0] = G.Property{
                .key = Expr.init(E.String, E.String{
                    .data = "type",
                }, Logger.Loc{ .start = 0 }),
                .value = Expr.init(E.String, E.String{
                    .data = "sqlite",
                }, Logger.Loc{ .start = 0 }),
            };
            require_args[1] = Expr.init(E.Object, E.Object{
                .properties = G.Property.List.init(object_properties),
                .is_single_line = true,
            }, Logger.Loc{ .start = 0 });
            const require_call = Expr.init(E.Call, E.Call{
                .target = require_property,
                .args = BabyList(Expr).init(require_args),
            }, Logger.Loc{ .start = 0 });

            const root = Expr.init(E.Dot, E.Dot{
                .target = require_call,
                .name_loc = Logger.Loc.Empty,
                .name = "db",
            }, Logger.Loc{ .start = 0 });

            return JSAst.init((try js_parser.newLazyExportAST(allocator, transpiler.options.define, opts, log, root, source, "")).?);
        },
        .napi => {
            // (dap-eval-cb "source.contents.ptr")
            if (transpiler.options.target == .browser) {
                log.addError(
                    source,
                    Logger.Loc.Empty,
                    "Loading .node files won't work in the browser. Make sure to set target to \"bun\" or \"node\"",
                ) catch bun.outOfMemory();
                return error.ParserError;
            }

            const unique_key = std.fmt.allocPrint(allocator, "{any}A{d:0>8}", .{ bun.fmt.hexIntLower(unique_key_prefix), source.index.get() }) catch unreachable;
            // This injects the following code:
            //
            // require(unique_key)
            //
            const import_path = Expr.init(E.String, E.String{
                .data = unique_key,
            }, Logger.Loc{ .start = 0 });

            const require_args = allocator.alloc(Expr, 1) catch unreachable;
            require_args[0] = import_path;

            const root = Expr.init(E.Call, E.Call{
                .target = .{ .data = .{ .e_require_call_target = {} }, .loc = .{ .start = 0 } },
                .args = BabyList(Expr).init(require_args),
            }, Logger.Loc{ .start = 0 });

            unique_key_for_additional_file.* = .{
                .key = unique_key,
                .content_hash = ContentHasher.run(source.contents),
            };
            return JSAst.init((try js_parser.newLazyExportAST(allocator, transpiler.options.define, opts, log, root, source, "")).?);
        },
        .html => {
            var scanner = HTMLScanner.init(allocator, log, source);
            try scanner.scan(source.contents);

            // Reuse existing code for creating the AST
            // because it handles the various Ref and other structs we
            // need in order to print code later.
            var ast = (try js_parser.newLazyExportAST(
                allocator,
                transpiler.options.define,
                opts,
                log,
                Expr.init(E.Missing, E.Missing{}, Logger.Loc.Empty),
                source,
                "",
            )).?;
            ast.import_records = scanner.import_records;

            // We're banning import default of html loader files for now.
            //
            // TLDR: it kept including:
            //
            //   var name_default = ...;
            //
            // in the bundle because of the exports AST, and
            // gave up on figuring out how to fix it so that
            // this feature could ship.
            ast.has_lazy_export = false;
            ast.parts.ptr[1] = .{
                .stmts = &.{},
                .is_live = true,
                .import_record_indices = brk2: {
                    // Generate a single part that depends on all the import records.
                    // This is to ensure that we generate a JavaScript bundle containing all the user's code.
                    var import_record_indices = try Part.ImportRecordIndices.initCapacity(allocator, scanner.import_records.len);
                    import_record_indices.len = @truncate(scanner.import_records.len);
                    for (import_record_indices.slice(), 0..) |*import_record, index| {
                        import_record.* = @intCast(index);
                    }
                    break :brk2 import_record_indices;
                },
            };

            // Try to avoid generating unnecessary ESM <> CJS wrapper code.
            if (opts.output_format == .esm or opts.output_format == .iife) {
                ast.exports_kind = .esm;
            }

            return JSAst.init(ast);
        },
        .css => {
            // make css ast
            var import_records = BabyList(ImportRecord){};
            const source_code = source.contents;
            var temp_log = bun.logger.Log.init(allocator);
            defer {
                temp_log.appendToMaybeRecycled(log, source) catch bun.outOfMemory();
            }

            const css_module_suffix = ".module.css";
            const enable_css_modules = source.path.pretty.len > css_module_suffix.len and
                strings.eqlComptime(source.path.pretty[source.path.pretty.len - css_module_suffix.len ..], css_module_suffix);
            const parser_options = if (enable_css_modules) init: {
                var parseropts = bun.css.ParserOptions.default(allocator, &temp_log);
                parseropts.filename = bun.path.basename(source.path.pretty);
                parseropts.css_modules = bun.css.CssModuleConfig{};
                break :init parseropts;
            } else bun.css.ParserOptions.default(allocator, &temp_log);

            var css_ast, var extra = switch (bun.css.BundlerStyleSheet.parseBundler(
                allocator,
                source_code,
                parser_options,
                &import_records,
                source.index,
            )) {
                .result => |v| v,
                .err => |e| {
                    try e.addToLogger(&temp_log, source, allocator);
                    return error.SyntaxError;
                },
            };
            // Make sure the css modules local refs have a valid tag
            if (comptime bun.Environment.isDebug) {
                if (css_ast.local_scope.count() > 0) {
                    for (css_ast.local_scope.values()) |entry| {
                        const ref = entry.ref;
                        bun.assert(ref.innerIndex() < extra.symbols.len);
                    }
                }
            }
            if (css_ast.minify(allocator, bun.css.MinifyOptions{
                .targets = bun.css.Targets.forBundlerTarget(transpiler.options.target),
                .unused_symbols = .{},
            }, &extra).asErr()) |e| {
                try e.addToLogger(&temp_log, source, allocator);
                return error.MinifyError;
            }
            if (css_ast.local_scope.count() > 0) {
                _ = has_any_css_locals.fetchAdd(1, .monotonic);
            }
            // If this is a css module, the final exports object wil be set in `generateCodeForLazyExport`.
            const root = Expr.init(E.Object, E.Object{}, Logger.Loc{ .start = 0 });
            const css_ast_heap = bun.create(allocator, bun.css.BundlerStyleSheet, css_ast);
            var ast = JSAst.init((try js_parser.newLazyExportASTImpl(allocator, transpiler.options.define, opts, &temp_log, root, source, "", extra.symbols)).?);
            ast.css = css_ast_heap;
            ast.import_records = import_records;
            return ast;
        },
        // TODO:
        .dataurl, .base64, .bunsh => {
            return try getEmptyAST(log, transpiler, opts, allocator, source, E.String);
        },
        .file, .wasm => {
            bun.assert(loader.shouldCopyForBundling());

            // Put a unique key in the AST to implement the URL loader. At the end
            // of the bundle, the key is replaced with the actual URL.
            const content_hash = ContentHasher.run(source.contents);

            const unique_key: []const u8 = if (transpiler.options.dev_server != null)
                // With DevServer, the actual URL is added now, since it can be
                // known this far ahead of time, and it means the unique key code
                // does not have to perform an additional pass over files.
                //
                // To avoid a mutex, the actual insertion of the asset to DevServer
                // is done on the bundler thread.
                try std.fmt.allocPrint(
                    allocator,
                    bun.bake.DevServer.asset_prefix ++ "/{s}{s}",
                    .{
                        &std.fmt.bytesToHex(std.mem.asBytes(&content_hash), .lower),
                        std.fs.path.extension(source.path.text),
                    },
                )
            else
                try std.fmt.allocPrint(
                    allocator,
                    "{any}A{d:0>8}",
                    .{ bun.fmt.hexIntLower(unique_key_prefix), source.index.get() },
                );
            const root = Expr.init(E.String, .{ .data = unique_key }, .{ .start = 0 });
            unique_key_for_additional_file.* = .{
                .key = unique_key,
                .content_hash = content_hash,
            };
            var ast = JSAst.init((try js_parser.newLazyExportAST(allocator, transpiler.options.define, opts, log, root, source, "")).?);
            ast.addUrlForCss(allocator, source, null, unique_key);
            return ast;
        },
    }
}

fn getCodeForParseTaskWithoutPlugins(
    task: *ParseTask,
    log: *Logger.Log,
    transpiler: *Transpiler,
    resolver: *Resolver,
    allocator: std.mem.Allocator,
    file_path: *Fs.Path,
    loader: Loader,
) !CacheEntry {
    return switch (task.contents_or_fd) {
        .fd => |contents| brk: {
            const trace = bun.perf.trace("Bundler.readFile");
            defer trace.end();

            if (strings.eqlComptime(file_path.namespace, "node")) lookup_builtin: {
                if (task.ctx.framework) |f| {
                    if (f.built_in_modules.get(file_path.text)) |file| {
                        switch (file) {
                            .code => |code| break :brk .{ .contents = code, .fd = bun.invalid_fd },
                            .import => |path| {
                                file_path.* = Fs.Path.init(path);
                                break :lookup_builtin;
                            },
                        }
                    }
                }

                break :brk .{
                    .contents = NodeFallbackModules.contentsFromPath(file_path.text) orelse "",
                    .fd = bun.invalid_fd,
                };
            }

            break :brk resolver.caches.fs.readFileWithAllocator(
                // TODO: this allocator may be wrong for native plugins
                if (loader.shouldCopyForBundling())
                    // The OutputFile will own the memory for the contents
                    bun.default_allocator
                else
                    allocator,
                transpiler.fs,
                file_path.text,
                task.contents_or_fd.fd.dir,
                false,
                contents.file.unwrapValid(),
            ) catch |err| {
                const source = &Logger.Source.initEmptyFile(log.msgs.allocator.dupe(u8, file_path.text) catch unreachable);
                switch (err) {
                    error.ENOENT, error.FileNotFound => {
                        log.addErrorFmt(
                            source,
                            Logger.Loc.Empty,
                            allocator,
                            "File not found {}",
                            .{bun.fmt.quote(file_path.text)},
                        ) catch {};
                        return error.FileNotFound;
                    },
                    else => {
                        log.addErrorFmt(
                            source,
                            Logger.Loc.Empty,
                            allocator,
                            "{s} reading file: {}",
                            .{ @errorName(err), bun.fmt.quote(file_path.text) },
                        ) catch {};
                    },
                }
                return err;
            };
        },
        .contents => |contents| .{
            .contents = contents,
            .fd = bun.invalid_fd,
        },
    };
}

fn getCodeForParseTask(
    task: *ParseTask,
    log: *Logger.Log,
    transpiler: *Transpiler,
    resolver: *Resolver,
    allocator: std.mem.Allocator,
    file_path: *Fs.Path,
    loader: *Loader,
    from_plugin: *bool,
) !CacheEntry {
    const might_have_on_parse_plugins = brk: {
        if (task.source_index.isRuntime()) break :brk false;
        const plugin = task.ctx.plugins orelse break :brk false;
        if (!plugin.hasOnBeforeParsePlugins()) break :brk false;

        if (strings.eqlComptime(file_path.namespace, "node")) {
            break :brk false;
        }
        break :brk true;
    };

    if (!might_have_on_parse_plugins) {
        return getCodeForParseTaskWithoutPlugins(task, log, transpiler, resolver, allocator, file_path, loader.*);
    }

    var should_continue_running: i32 = 1;

    var ctx = OnBeforeParsePlugin{
        .task = task,
        .log = log,
        .transpiler = transpiler,
        .resolver = resolver,
        .allocator = allocator,
        .file_path = file_path,
        .loader = loader,
        .deferred_error = null,
        .should_continue_running = &should_continue_running,
    };

    return try ctx.run(task.ctx.plugins.?, from_plugin);
}

const OnBeforeParsePlugin = struct {
    task: *ParseTask,
    log: *Logger.Log,
    transpiler: *Transpiler,
    resolver: *Resolver,
    allocator: std.mem.Allocator,
    file_path: *Fs.Path,
    loader: *Loader,
    deferred_error: ?anyerror = null,
    should_continue_running: *i32,

    result: ?*OnBeforeParseResult = null,

    const headers = bun.c;

    comptime {
        bun.assert(@sizeOf(OnBeforeParseArguments) == @sizeOf(headers.OnBeforeParseArguments));
        bun.assert(@alignOf(OnBeforeParseArguments) == @alignOf(headers.OnBeforeParseArguments));

        bun.assert(@sizeOf(BunLogOptions) == @sizeOf(headers.BunLogOptions));
        bun.assert(@alignOf(BunLogOptions) == @alignOf(headers.BunLogOptions));

        bun.assert(@sizeOf(OnBeforeParseResult) == @sizeOf(headers.OnBeforeParseResult));
        bun.assert(@alignOf(OnBeforeParseResult) == @alignOf(headers.OnBeforeParseResult));

        bun.assert(@sizeOf(BunLogOptions) == @sizeOf(headers.BunLogOptions));
        bun.assert(@alignOf(BunLogOptions) == @alignOf(headers.BunLogOptions));
    }

    const OnBeforeParseArguments = extern struct {
        struct_size: usize = @sizeOf(OnBeforeParseArguments),
        context: *OnBeforeParsePlugin,
        path_ptr: ?[*]const u8 = "",
        path_len: usize = 0,
        namespace_ptr: ?[*]const u8 = "file",
        namespace_len: usize = "file".len,
        default_loader: Loader = .file,
        external: ?*anyopaque = null,
    };

    const BunLogOptions = extern struct {
        struct_size: usize = @sizeOf(BunLogOptions),
        message_ptr: ?[*]const u8 = null,
        message_len: usize = 0,
        path_ptr: ?[*]const u8 = null,
        path_len: usize = 0,
        source_line_text_ptr: ?[*]const u8 = null,
        source_line_text_len: usize = 0,
        level: Logger.Log.Level = .err,
        line: i32 = 0,
        column: i32 = 0,
        line_end: i32 = 0,
        column_end: i32 = 0,

        pub fn sourceLineText(this: *const BunLogOptions) string {
            if (this.source_line_text_ptr) |ptr| {
                if (this.source_line_text_len > 0) {
                    return ptr[0..this.source_line_text_len];
                }
            }
            return "";
        }

        pub fn path(this: *const BunLogOptions) string {
            if (this.path_ptr) |ptr| {
                if (this.path_len > 0) {
                    return ptr[0..this.path_len];
                }
            }
            return "";
        }

        pub fn message(this: *const BunLogOptions) string {
            if (this.message_ptr) |ptr| {
                if (this.message_len > 0) {
                    return ptr[0..this.message_len];
                }
            }
            return "";
        }

        pub fn append(this: *const BunLogOptions, log: *Logger.Log, namespace: string) void {
            const allocator = log.msgs.allocator;
            const source_line_text = this.sourceLineText();
            const location = Logger.Location.init(
                this.path(),
                namespace,
                @max(this.line, -1),
                @max(this.column, -1),
                @max(this.column_end - this.column, 0),
                if (source_line_text.len > 0) allocator.dupe(u8, source_line_text) catch bun.outOfMemory() else null,
                null,
            );
            var msg = Logger.Msg{ .data = .{ .location = location, .text = allocator.dupe(u8, this.message()) catch bun.outOfMemory() } };
            switch (this.level) {
                .err => msg.kind = .err,
                .warn => msg.kind = .warn,
                .verbose => msg.kind = .verbose,
                .debug => msg.kind = .debug,
                else => {},
            }
            if (msg.kind == .err) {
                log.errors += 1;
            } else if (msg.kind == .warn) {
                log.warnings += 1;
            }
            log.addMsg(msg) catch bun.outOfMemory();
        }

        pub fn logFn(
            args_: ?*OnBeforeParseArguments,
            log_options_: ?*BunLogOptions,
        ) callconv(.C) void {
            const args = args_ orelse return;
            const log_options = log_options_ orelse return;
            log_options.append(args.context.log, args.context.file_path.namespace);
        }
    };

    const OnBeforeParseResultWrapper = extern struct {
        original_source: ?[*]const u8 = null,
        original_source_len: usize = 0,
        original_source_fd: bun.FileDescriptor = bun.invalid_fd,
        loader: Loader,
        check: if (bun.Environment.isDebug) u32 else u0 = if (bun.Environment.isDebug) 42069 else 0, // Value to ensure OnBeforeParseResult is wrapped in this struct
        result: OnBeforeParseResult,
    };

    const OnBeforeParseResult = extern struct {
        struct_size: usize = @sizeOf(OnBeforeParseResult),
        source_ptr: ?[*]const u8 = null,
        source_len: usize = 0,
        loader: Loader,

        fetch_source_code_fn: *const fn (*OnBeforeParseArguments, *OnBeforeParseResult) callconv(.C) i32 = &fetchSourceCode,

        user_context: ?*anyopaque = null,
        free_user_context: ?*const fn (?*anyopaque) callconv(.C) void = null,

        log: *const fn (
            args_: ?*OnBeforeParseArguments,
            log_options_: ?*BunLogOptions,
        ) callconv(.C) void = &BunLogOptions.logFn,

        pub fn getWrapper(result: *OnBeforeParseResult) *OnBeforeParseResultWrapper {
            const wrapper: *OnBeforeParseResultWrapper = @fieldParentPtr("result", result);
            bun.debugAssert(wrapper.check == 42069);
            return wrapper;
        }
    };

    pub fn fetchSourceCode(args: *OnBeforeParseArguments, result: *OnBeforeParseResult) callconv(.C) i32 {
        debug("fetchSourceCode", .{});
        const this = args.context;
        if (this.log.errors > 0 or this.deferred_error != null or this.should_continue_running.* != 1) {
            return 1;
        }

        if (result.source_ptr != null) {
            return 0;
        }

        const entry = getCodeForParseTaskWithoutPlugins(
            this.task,
            this.log,
            this.transpiler,
            this.resolver,
            this.allocator,
            this.file_path,

            result.loader,
        ) catch |err| {
            this.deferred_error = err;
            this.should_continue_running.* = 0;
            return 1;
        };
        result.source_ptr = entry.contents.ptr;
        result.source_len = entry.contents.len;
        result.free_user_context = null;
        result.user_context = null;
        const wrapper: *OnBeforeParseResultWrapper = result.getWrapper();
        wrapper.original_source = entry.contents.ptr;
        wrapper.original_source_len = entry.contents.len;
        wrapper.original_source_fd = entry.fd;
        return 0;
    }

    pub export fn OnBeforeParseResult__reset(this: *OnBeforeParseResult) void {
        const wrapper = this.getWrapper();
        this.loader = wrapper.loader;
        if (wrapper.original_source) |src_ptr| {
            const src = src_ptr[0..wrapper.original_source_len];
            this.source_ptr = src.ptr;
            this.source_len = src.len;
        } else {
            this.source_ptr = null;
            this.source_len = 0;
        }
    }

    pub export fn OnBeforeParsePlugin__isDone(this: *OnBeforeParsePlugin) i32 {
        if (this.should_continue_running.* != 1) {
            return 1;
        }

        const result = this.result orelse return 1;
        // The first plugin to set the source wins.
        // But, we must check that they actually modified it
        // since fetching the source stores it inside `result.source_ptr`
        if (result.source_ptr != null) {
            const wrapper: *OnBeforeParseResultWrapper = result.getWrapper();
            return @intFromBool(result.source_ptr.? != wrapper.original_source.?);
        }

        return 0;
    }

    pub fn run(this: *OnBeforeParsePlugin, plugin: *JSC.API.JSBundler.Plugin, from_plugin: *bool) !CacheEntry {
        var args = OnBeforeParseArguments{
            .context = this,
            .path_ptr = this.file_path.text.ptr,
            .path_len = this.file_path.text.len,
            .default_loader = this.loader.*,
        };
        if (this.file_path.namespace.len > 0) {
            args.namespace_ptr = this.file_path.namespace.ptr;
            args.namespace_len = this.file_path.namespace.len;
        }
        var wrapper = OnBeforeParseResultWrapper{
            .loader = this.loader.*,
            .result = OnBeforeParseResult{
                .loader = this.loader.*,
            },
        };

        this.result = &wrapper.result;
        const count = plugin.callOnBeforeParsePlugins(
            this,
            if (bun.strings.eqlComptime(this.file_path.namespace, "file"))
                &bun.String.empty
            else
                &bun.String.init(this.file_path.namespace),

            &bun.String.init(this.file_path.text),
            &args,
            &wrapper.result,
            this.should_continue_running,
        );
        if (comptime Environment.enable_logs)
            debug("callOnBeforeParsePlugins({s}:{s}) = {d}", .{ this.file_path.namespace, this.file_path.text, count });
        if (count > 0) {
            if (this.deferred_error) |err| {
                if (wrapper.result.free_user_context) |free_user_context| {
                    free_user_context(wrapper.result.user_context);
                }

                return err;
            }

            // If the plugin sets the `free_user_context` function pointer, it _must_ set the `user_context` pointer.
            // Otherwise this is just invalid behavior.
            if (wrapper.result.user_context == null and wrapper.result.free_user_context != null) {
                var msg = Logger.Msg{ .data = .{ .location = null, .text = bun.default_allocator.dupe(
                    u8,
                    "Native plugin set the `free_plugin_source_code_context` field without setting the `plugin_source_code_context` field.",
                ) catch bun.outOfMemory() } };
                msg.kind = .err;
                args.context.log.errors += 1;
                args.context.log.addMsg(msg) catch bun.outOfMemory();
                return error.InvalidNativePlugin;
            }

            if (this.log.errors > 0) {
                if (wrapper.result.free_user_context) |free_user_context| {
                    free_user_context(wrapper.result.user_context);
                }

                return error.SyntaxError;
            }

            if (wrapper.result.source_ptr) |ptr| {
                if (wrapper.result.free_user_context != null) {
                    this.task.external_free_function = .{
                        .ctx = wrapper.result.user_context,
                        .function = wrapper.result.free_user_context,
                    };
                }
                from_plugin.* = true;
                this.loader.* = wrapper.result.loader;
                return .{
                    .contents = ptr[0..wrapper.result.source_len],
                    .external_free_function = .{
                        .ctx = wrapper.result.user_context,
                        .function = wrapper.result.free_user_context,
                    },
                    .fd = wrapper.original_source_fd,
                };
            }
        }

        return try getCodeForParseTaskWithoutPlugins(this.task, this.log, this.transpiler, this.resolver, this.allocator, this.file_path, this.loader.*);
    }
};

fn getSourceCode(
    task: *ParseTask,
    this: *ThreadPool.Worker,
    log: *Logger.Log,
) anyerror!CacheEntry {
    const allocator = this.allocator;

    var data = this.data;
    const transpiler = &data.transpiler;
    errdefer transpiler.resetStore();
    const resolver: *Resolver = &transpiler.resolver;
    var file_path = task.path;
    var loader = task.loader orelse file_path.loader(&transpiler.options.loaders) orelse options.Loader.file;

    var contents_came_from_plugin: bool = false;
    return try getCodeForParseTask(task, log, transpiler, resolver, allocator, &file_path, &loader, &contents_came_from_plugin);
}

fn runWithSourceCode(
    task: *ParseTask,
    this: *ThreadPool.Worker,
    step: *ParseTask.Result.Error.Step,
    log: *Logger.Log,
    entry: *CacheEntry,
) anyerror!Result.Success {
    const allocator = this.allocator;

    var transpiler = this.transpilerForTarget(task.known_target);
    errdefer transpiler.resetStore();
    var resolver: *Resolver = &transpiler.resolver;
    const file_path = &task.path;
    const loader = task.loader orelse file_path.loader(&transpiler.options.loaders) orelse options.Loader.file;

    // WARNING: Do not change the variant of `task.contents_or_fd` from
    // `.fd` to `.contents` (or back) after this point!
    //
    // When `task.contents_or_fd == .fd`, `entry.contents` is an owned string.
    // When `task.contents_or_fd == .contents`, `entry.contents` is NOT owned! Freeing it here will cause a double free!
    //
    // Changing from `.contents` to `.fd` will cause a double free.
    // This was the case in the situation where the ParseTask receives its `.contents` from an onLoad plugin, which caused it to be
    // allocated by `bun.default_allocator` and then freed in `BundleV2.deinit` (and also by `entry.deinit(allocator)` below).
    const debug_original_variant_check: if (bun.Environment.isDebug) ContentsOrFd.Tag else void =
        if (bun.Environment.isDebug) @as(ContentsOrFd.Tag, task.contents_or_fd);
    errdefer {
        if (comptime bun.Environment.isDebug) {
            if (@as(ContentsOrFd.Tag, task.contents_or_fd) != debug_original_variant_check) {
                std.debug.panic("BUG: `task.contents_or_fd` changed in a way that will cause a double free or memory to leak!\n\n    Original = {s}\n    New = {s}\n", .{
                    @tagName(debug_original_variant_check),
                    @tagName(task.contents_or_fd),
                });
            }
        }
        if (task.contents_or_fd == .fd) entry.deinit(allocator);
    }

    const will_close_file_descriptor = task.contents_or_fd == .fd and
        entry.fd.isValid() and
        entry.fd.stdioTag() == null and
        this.ctx.bun_watcher == null;
    if (will_close_file_descriptor) {
        _ = entry.closeFD();
        task.contents_or_fd = .{ .fd = .{
            .file = bun.invalid_fd,
            .dir = bun.invalid_fd,
        } };
    } else if (task.contents_or_fd == .fd) {
        task.contents_or_fd = .{ .fd = .{
            .file = entry.fd,
            .dir = bun.invalid_fd,
        } };
    }
    step.* = .parse;

    const is_empty = strings.isAllWhitespace(entry.contents);

    const use_directive: UseDirective = if (!is_empty and transpiler.options.server_components)
        if (UseDirective.parse(entry.contents)) |use|
            use
        else
            .none
    else
        .none;

    if (
    // separate_ssr_graph makes boundaries switch to client because the server file uses that generated file as input.
    // this is not done when there is one server graph because it is easier for plugins to deal with.
    (use_directive == .client and
        task.known_target != .bake_server_components_ssr and
        this.ctx.framework.?.server_components.?.separate_ssr_graph) or
        // set the target to the client when bundling client-side files
        ((transpiler.options.server_components or transpiler.options.dev_server != null) and
            task.known_target == .browser))
    {
        transpiler = this.ctx.client_transpiler.?;
        resolver = &transpiler.resolver;
        bun.assert(transpiler.options.target == .browser);
    }

    const source = &Logger.Source{
        .path = file_path.*,
        .index = task.source_index,
        .contents = entry.contents,
        .contents_is_recycled = false,
    };

    const target = (if (task.source_index.get() == 1) targetFromHashbang(entry.contents) else null) orelse
        if (task.known_target == .bake_server_components_ssr and transpiler.options.framework.?.server_components.?.separate_ssr_graph)
            .bake_server_components_ssr
        else
            transpiler.options.target;

    const output_format = transpiler.options.output_format;

    var opts = js_parser.Parser.Options.init(task.jsx, loader);
    opts.bundle = true;
    opts.warn_about_unbundled_modules = false;
    opts.macro_context = &this.data.macro_context;
    opts.package_version = task.package_version;

    opts.features.allow_runtime = !source.index.isRuntime();
    opts.features.unwrap_commonjs_to_esm = output_format == .esm and FeatureFlags.unwrap_commonjs_to_esm;
    opts.features.top_level_await = output_format == .esm or output_format == .internal_bake_dev;
    opts.features.auto_import_jsx = task.jsx.parse and transpiler.options.auto_import_jsx;
    opts.features.trim_unused_imports = loader.isTypeScript() or (transpiler.options.trim_unused_imports orelse false);
    opts.features.inlining = transpiler.options.minify_syntax;
    opts.output_format = output_format;
    opts.features.minify_syntax = transpiler.options.minify_syntax;
    opts.features.minify_identifiers = transpiler.options.minify_identifiers;
    opts.features.emit_decorator_metadata = transpiler.options.emit_decorator_metadata;
    opts.features.unwrap_commonjs_packages = transpiler.options.unwrap_commonjs_packages;
    opts.features.hot_module_reloading = output_format == .internal_bake_dev and !source.index.isRuntime();
    opts.features.auto_polyfill_require = output_format == .esm and !opts.features.hot_module_reloading;
    opts.features.react_fast_refresh = target == .browser and
        transpiler.options.react_fast_refresh and
        loader.isJSX() and
        !source.path.isNodeModule();

    opts.features.server_components = if (transpiler.options.server_components) switch (target) {
        .browser => .client_side,
        else => switch (use_directive) {
            .none => .wrap_anon_server_functions,
            .client => if (transpiler.options.framework.?.server_components.?.separate_ssr_graph)
                .client_side
            else
                .wrap_exports_for_client_reference,
            .server => .wrap_exports_for_server_reference,
        },
    } else .none;

    opts.framework = transpiler.options.framework;

    opts.ignore_dce_annotations = transpiler.options.ignore_dce_annotations and !source.index.isRuntime();

    // For files that are not user-specified entrypoints, set `import.meta.main` to `false`.
    // Entrypoints will have `import.meta.main` set as "unknown", unless we use `--compile`,
    // in which we inline `true`.
    if (transpiler.options.inline_entrypoint_import_meta_main or !task.is_entry_point) {
        opts.import_meta_main_value = task.is_entry_point and transpiler.options.dev_server == null;
    } else if (target == .node) {
        opts.lower_import_meta_main_for_node_js = true;
    }

    opts.tree_shaking = if (source.index.isRuntime()) true else transpiler.options.tree_shaking;
    opts.module_type = task.module_type;

    task.jsx.parse = loader.isJSX();

    var unique_key_for_additional_file: FileLoaderHash = .{
        .key = "",
        .content_hash = 0,
    };
    var ast: JSAst = if (!is_empty or loader.handlesEmptyFile())
        try getAST(log, transpiler, opts, allocator, resolver, source, loader, task.ctx.unique_key, &unique_key_for_additional_file, &task.ctx.linker.has_any_css_locals)
    else switch (opts.module_type == .esm) {
        inline else => |as_undefined| if (loader.isCSS()) try getEmptyCSSAST(
            log,
            transpiler,
            opts,
            allocator,
            source,
        ) else try getEmptyAST(
            log,
            transpiler,
            opts,
            allocator,
            source,
            if (as_undefined) E.Undefined else E.Object,
        ),
    };

    ast.target = target;
    if (ast.parts.len <= 1 and ast.css == null and (task.loader == null or task.loader.? != .html)) {
        task.side_effects = .no_side_effects__empty_ast;
    }

    // bun.debugAssert(ast.parts.len > 0); // when parts.len == 0, it is assumed to be pending/failed. empty ast has at least 1 part.

    step.* = .resolve;

    return .{
        .ast = ast,
        .source = source.*,
        .log = log.*,
        .use_directive = use_directive,
        .unique_key_for_additional_file = unique_key_for_additional_file.key,
        .side_effects = task.side_effects,
        .loader = loader,

        // Hash the files in here so that we do it in parallel.
        .content_hash_for_additional_file = if (loader.shouldCopyForBundling())
            unique_key_for_additional_file.content_hash
        else
            0,
    };
}

fn ioTaskCallback(task: *ThreadPoolLib.Task) void {
    runFromThreadPool(@fieldParentPtr("io_task", task));
}

fn taskCallback(task: *ThreadPoolLib.Task) void {
    runFromThreadPool(@fieldParentPtr("task", task));
}

pub fn runFromThreadPool(this: *ParseTask) void {
    var worker = ThreadPool.Worker.get(this.ctx);
    defer worker.unget();
    debug("ParseTask(0x{x}, {s}) callback", .{ @intFromPtr(this), this.path.text });

    var step: ParseTask.Result.Error.Step = .pending;
    var log = Logger.Log.init(worker.allocator);
    bun.assert(this.source_index.isValid()); // forgot to set source_index

    const value: ParseTask.Result.Value = value: {
        if (this.stage == .needs_source_code) {
            this.stage = .{
                .needs_parse = getSourceCode(this, worker, &log) catch |err| {
                    break :value .{ .err = .{
                        .err = err,
                        .step = step,
                        .log = log,
                        .source_index = this.source_index,
                        .target = this.known_target,
                    } };
                },
            };

            if (log.hasErrors()) {
                break :value .{ .err = .{
                    .err = error.SyntaxError,
                    .step = step,
                    .log = log,
                    .source_index = this.source_index,
                    .target = this.known_target,
                } };
            }

            if (this.ctx.graph.pool.usesIOPool()) {
                this.ctx.graph.pool.scheduleInsideThreadPool(this);
                return;
            }
        }

        if (runWithSourceCode(this, worker, &step, &log, &this.stage.needs_parse)) |ast| {
            // When using HMR, always flag asts with errors as parse failures.
            // Not done outside of the dev server out of fear of breaking existing code.
            if (this.ctx.transpiler.options.dev_server != null and ast.log.hasErrors()) {
                break :value .{
                    .err = .{
                        .err = error.SyntaxError,
                        .step = .parse,
                        .log = ast.log,
                        .source_index = this.source_index,
                        .target = this.known_target,
                    },
                };
            }

            break :value .{ .success = ast };
        } else |err| {
            if (err == error.EmptyAST) {
                log.deinit();
                break :value .{ .empty = .{
                    .source_index = this.source_index,
                } };
            }

            break :value .{ .err = .{
                .err = err,
                .step = step,
                .log = log,
                .source_index = this.source_index,
                .target = this.known_target,
            } };
        }
    };

    const result = bun.default_allocator.create(Result) catch bun.outOfMemory();

    result.* = .{
        .ctx = this.ctx,
        .task = .{},
        .value = value,
        .external = this.external_free_function,
        .watcher_data = switch (this.contents_or_fd) {
            .fd => |fd| .{ .fd = fd.file, .dir_fd = fd.dir },
            .contents => .none,
        },
    };

    switch (worker.ctx.loop().*) {
        .js => |jsc_event_loop| {
            jsc_event_loop.enqueueTaskConcurrent(JSC.ConcurrentTask.fromCallback(result, onComplete));
        },
        .mini => |*mini| {
            mini.enqueueTaskConcurrentWithExtraCtx(
                Result,
                BundleV2,
                result,
                BundleV2.onParseTaskComplete,
                .task,
            );
        },
    }
}

pub fn onComplete(result: *Result) void {
    BundleV2.onParseTaskComplete(result, result.ctx);
}

const Transpiler = bun.Transpiler;
const bun = @import("bun");
const string = bun.string;
const Output = bun.Output;
const Environment = bun.Environment;
const strings = bun.strings;
const default_allocator = bun.default_allocator;
const StoredFileDescriptorType = bun.StoredFileDescriptorType;
const FeatureFlags = bun.FeatureFlags;

const bundler = bun.bundle_v2;
const BundleV2 = bundler.BundleV2;

const ContentHasher = bundler.ContentHasher;
const UseDirective = bundler.UseDirective;
const targetFromHashbang = bundler.targetFromHashbang;

const std = @import("std");
const Logger = @import("../logger.zig");
const options = @import("../options.zig");
const js_parser = bun.js_parser;
const Part = js_ast.Part;
const js_ast = @import("../js_ast.zig");
const linker = @import("../linker.zig");
const base64 = bun.base64;
pub const Ref = @import("../ast/base.zig").Ref;
const ThreadPoolLib = @import("../thread_pool.zig");
const BabyList = @import("../baby_list.zig").BabyList;
const Fs = @import("../fs.zig");
const _resolver = @import("../resolver/resolver.zig");
const ImportRecord = bun.ImportRecord;
const runtime = @import("../runtime.zig");

const HTMLScanner = @import("../HTMLScanner.zig");
const NodeFallbackModules = @import("../node_fallbacks.zig");
const CacheEntry = @import("../cache.zig").Fs.Entry;
const URL = @import("../url.zig").URL;
const Resolver = _resolver.Resolver;
const TOML = @import("../toml/toml_parser.zig").TOML;
const JSAst = js_ast.BundledAst;
const Loader = options.Loader;
pub const Index = @import("../ast/base.zig").Index;
const Symbol = js_ast.Symbol;
const EventLoop = bun.JSC.AnyEventLoop;
const Expr = js_ast.Expr;
const E = js_ast.E;
const G = js_ast.G;
const JSC = bun.JSC;
const Loc = Logger.Loc;
const bake = bun.bake;

pub const DeferredBatchTask = bun.bundle_v2.DeferredBatchTask;
pub const ThreadPool = bun.bundle_v2.ThreadPool;
