mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
6864 lines
323 KiB
Zig
6864 lines
323 KiB
Zig
const ParserFeatures = struct {
|
||
typescript: bool = false,
|
||
jsx: JSXTransformType = .none,
|
||
scan_only: bool = false,
|
||
};
|
||
|
||
// workaround for https://github.com/ziglang/zig/issues/10903
|
||
pub fn NewParser(
|
||
comptime parser_features: ParserFeatures,
|
||
) type {
|
||
return NewParser_(
|
||
parser_features.typescript,
|
||
parser_features.jsx,
|
||
parser_features.scan_only,
|
||
);
|
||
}
|
||
pub fn NewParser_(
|
||
comptime parser_feature__typescript: bool,
|
||
comptime parser_feature__jsx: JSXTransformType,
|
||
comptime parser_feature__scan_only: bool,
|
||
) type {
|
||
const js_parser_features: ParserFeatures = .{
|
||
.typescript = parser_feature__typescript,
|
||
.jsx = parser_feature__jsx,
|
||
.scan_only = parser_feature__scan_only,
|
||
};
|
||
|
||
// P is for Parser!
|
||
return struct {
|
||
const js_parser_jsx = js_parser_features.jsx;
|
||
pub const is_typescript_enabled = js_parser_features.typescript;
|
||
pub const is_jsx_enabled = js_parser_jsx != .none;
|
||
pub const only_scan_imports_and_do_not_visit = js_parser_features.scan_only;
|
||
const ImportRecordList = if (only_scan_imports_and_do_not_visit) *std.array_list.Managed(ImportRecord) else std.array_list.Managed(ImportRecord);
|
||
const NamedImportsType = if (only_scan_imports_and_do_not_visit) *js_ast.Ast.NamedImports else js_ast.Ast.NamedImports;
|
||
const NeedsJSXType = if (only_scan_imports_and_do_not_visit) bool else void;
|
||
pub const track_symbol_usage_during_parse_pass = only_scan_imports_and_do_not_visit and is_typescript_enabled;
|
||
const ParsePassSymbolUsageType = if (track_symbol_usage_during_parse_pass) *ScanPassResult.ParsePassSymbolUsageMap else void;
|
||
|
||
pub const parser_features: ParserFeatures = js_parser_features;
|
||
const P = @This();
|
||
pub const jsx_transform_type: JSXTransformType = js_parser_jsx;
|
||
pub const allow_macros = FeatureFlags.is_macro_enabled;
|
||
const MacroCallCountType = if (allow_macros) u32 else u0;
|
||
|
||
const skipTypescript_zig = @import("./skipTypescript.zig").SkipTypescript(parser_feature__typescript, parser_feature__jsx, parser_feature__scan_only);
|
||
pub const skipTypescriptReturnType = skipTypescript_zig.skipTypescriptReturnType;
|
||
pub const skipTypescriptReturnTypeWithMetadata = skipTypescript_zig.skipTypescriptReturnTypeWithMetadata;
|
||
pub const skipTypeScriptType = skipTypescript_zig.skipTypeScriptType;
|
||
pub const skipTypeScriptTypeWithMetadata = skipTypescript_zig.skipTypeScriptTypeWithMetadata;
|
||
pub const skipTypeScriptBinding = skipTypescript_zig.skipTypeScriptBinding;
|
||
pub const skipTypescriptFnArgs = skipTypescript_zig.skipTypescriptFnArgs;
|
||
pub const skipTypeScriptParenOrFnType = skipTypescript_zig.skipTypeScriptParenOrFnType;
|
||
pub const skipTypeScriptTypeWithOpts = skipTypescript_zig.skipTypeScriptTypeWithOpts;
|
||
pub const skipTypeScriptObjectType = skipTypescript_zig.skipTypeScriptObjectType;
|
||
pub const skipTypeScriptTypeParameters = skipTypescript_zig.skipTypeScriptTypeParameters;
|
||
pub const skipTypeScriptTypeStmt = skipTypescript_zig.skipTypeScriptTypeStmt;
|
||
pub const skipTypeScriptInterfaceStmt = skipTypescript_zig.skipTypeScriptInterfaceStmt;
|
||
pub const skipTypeScriptTypeArguments = skipTypescript_zig.skipTypeScriptTypeArguments;
|
||
pub const trySkipTypeScriptTypeParametersThenOpenParenWithBacktracking = skipTypescript_zig.trySkipTypeScriptTypeParametersThenOpenParenWithBacktracking;
|
||
pub const trySkipTypeScriptTypeArgumentsWithBacktracking = skipTypescript_zig.trySkipTypeScriptTypeArgumentsWithBacktracking;
|
||
pub const trySkipTypeScriptArrowReturnTypeWithBacktracking = skipTypescript_zig.trySkipTypeScriptArrowReturnTypeWithBacktracking;
|
||
pub const trySkipTypeScriptArrowArgsWithBacktracking = skipTypescript_zig.trySkipTypeScriptArrowArgsWithBacktracking;
|
||
pub const trySkipTypeScriptConstraintOfInferTypeWithBacktracking = skipTypescript_zig.trySkipTypeScriptConstraintOfInferTypeWithBacktracking;
|
||
|
||
const parse_zig = @import("./parse.zig").Parse(parser_feature__typescript, parser_feature__jsx, parser_feature__scan_only);
|
||
pub const parsePrefix = parse_zig.parsePrefix;
|
||
pub const parseSuffix = parse_zig.parseSuffix;
|
||
pub const parseStmt = parse_zig.parseStmt;
|
||
pub const parseProperty = parse_zig.parseProperty;
|
||
pub const parseFn = parse_zig.parseFn;
|
||
pub const parseFnStmt = parse_zig.parseFnStmt;
|
||
pub const parseFnExpr = parse_zig.parseFnExpr;
|
||
pub const parseFnBody = parse_zig.parseFnBody;
|
||
pub const parseArrowBody = parse_zig.parseArrowBody;
|
||
pub const parseJSXElement = parse_zig.parseJSXElement;
|
||
pub const parseImportExpr = parse_zig.parseImportExpr;
|
||
pub const parseImportClause = parse_zig.parseImportClause;
|
||
pub const parseExportClause = parse_zig.parseExportClause;
|
||
pub const parseExprOrBindings = parse_zig.parseExprOrBindings;
|
||
pub const parseExpr = parse_zig.parseExpr;
|
||
pub const parseExprWithFlags = parse_zig.parseExprWithFlags;
|
||
pub const parseExprCommon = parse_zig.parseExprCommon;
|
||
pub const parseYieldExpr = parse_zig.parseYieldExpr;
|
||
pub const parseClass = parse_zig.parseClass;
|
||
pub const parseTemplateParts = parse_zig.parseTemplateParts;
|
||
pub const parseStringLiteral = parse_zig.parseStringLiteral;
|
||
pub const parseCallArgs = parse_zig.parseCallArgs;
|
||
pub const parseJSXPropValueIdentifier = parse_zig.parseJSXPropValueIdentifier;
|
||
pub const parseParenExpr = parse_zig.parseParenExpr;
|
||
pub const parseLabelName = parse_zig.parseLabelName;
|
||
pub const parseClassStmt = parse_zig.parseClassStmt;
|
||
pub const parseClauseAlias = parse_zig.parseClauseAlias;
|
||
pub const parseExprOrLetStmt = parse_zig.parseExprOrLetStmt;
|
||
pub const parseBinding = parse_zig.parseBinding;
|
||
pub const parsePropertyBinding = parse_zig.parsePropertyBinding;
|
||
pub const parseAndDeclareDecls = parse_zig.parseAndDeclareDecls;
|
||
pub const parsePath = parse_zig.parsePath;
|
||
pub const parseStmtsUpTo = parse_zig.parseStmtsUpTo;
|
||
pub const parseAsyncPrefixExpr = parse_zig.parseAsyncPrefixExpr;
|
||
pub const parseTypeScriptDecorators = parse_zig.parseTypeScriptDecorators;
|
||
pub const parseTypeScriptNamespaceStmt = parse_zig.parseTypeScriptNamespaceStmt;
|
||
pub const parseTypeScriptImportEqualsStmt = parse_zig.parseTypeScriptImportEqualsStmt;
|
||
pub const parseTypescriptEnumStmt = parse_zig.parseTypescriptEnumStmt;
|
||
|
||
const astVisit = @import("./visit.zig").Visit(parser_feature__typescript, parser_feature__jsx, parser_feature__scan_only);
|
||
pub const visitStmtsAndPrependTempRefs = astVisit.visitStmtsAndPrependTempRefs;
|
||
pub const recordDeclaredSymbol = astVisit.recordDeclaredSymbol;
|
||
pub const visitExpr = astVisit.visitExpr;
|
||
pub const visitExprInOut = astVisit.visitExprInOut;
|
||
pub const visitFunc = astVisit.visitFunc;
|
||
pub const visitArgs = astVisit.visitArgs;
|
||
pub const visitTSDecorators = astVisit.visitTSDecorators;
|
||
pub const visitDecls = astVisit.visitDecls;
|
||
pub const visitBindingAndExprForMacro = astVisit.visitBindingAndExprForMacro;
|
||
pub const visitDecl = astVisit.visitDecl;
|
||
pub const visitForLoopInit = astVisit.visitForLoopInit;
|
||
pub const visitBinding = astVisit.visitBinding;
|
||
pub const visitLoopBody = astVisit.visitLoopBody;
|
||
pub const visitSingleStmtBlock = astVisit.visitSingleStmtBlock;
|
||
pub const visitSingleStmt = astVisit.visitSingleStmt;
|
||
pub const visitClass = astVisit.visitClass;
|
||
pub const visitStmts = astVisit.visitStmts;
|
||
pub const visitAndAppendStmt = astVisit.visitAndAppendStmt;
|
||
|
||
pub const BinaryExpressionVisitor = @import("./visitBinaryExpression.zig").CreateBinaryExpressionVisitor(parser_feature__typescript, parser_feature__jsx, parser_feature__scan_only).BinaryExpressionVisitor;
|
||
|
||
const maybe = @import("./maybe.zig").AstMaybe(parser_feature__typescript, parser_feature__jsx, parser_feature__scan_only);
|
||
pub const maybeRelocateVarsToTopLevel = maybe.maybeRelocateVarsToTopLevel;
|
||
pub const maybeRewritePropertyAccess = maybe.maybeRewritePropertyAccess;
|
||
pub const maybeCommaSpreadError = maybe.maybeCommaSpreadError;
|
||
pub const maybeDefinedHelper = maybe.maybeDefinedHelper;
|
||
pub const checkIfDefinedHelper = maybe.checkIfDefinedHelper;
|
||
|
||
const symbols_zig = @import("./symbols.zig").Symbols(parser_feature__typescript, parser_feature__jsx, parser_feature__scan_only);
|
||
pub const findSymbol = symbols_zig.findSymbol;
|
||
pub const findSymbolWithRecordUsage = symbols_zig.findSymbolWithRecordUsage;
|
||
|
||
macro: MacroState = undefined,
|
||
allocator: Allocator,
|
||
options: Parser.Options,
|
||
log: *logger.Log,
|
||
define: *Define,
|
||
source: *const logger.Source,
|
||
lexer: js_lexer.Lexer,
|
||
allow_in: bool = false,
|
||
allow_private_identifiers: bool = false,
|
||
|
||
has_top_level_return: bool = false,
|
||
latest_return_had_semicolon: bool = false,
|
||
has_import_meta: bool = false,
|
||
has_es_module_syntax: bool = false,
|
||
top_level_await_keyword: logger.Range = logger.Range.None,
|
||
fn_or_arrow_data_parse: FnOrArrowDataParse = FnOrArrowDataParse{},
|
||
fn_or_arrow_data_visit: FnOrArrowDataVisit = FnOrArrowDataVisit{},
|
||
fn_only_data_visit: FnOnlyDataVisit = FnOnlyDataVisit{},
|
||
allocated_names: List(string) = .{},
|
||
// allocated_names: ListManaged(string) = ListManaged(string).init(bun.default_allocator),
|
||
// allocated_names_pool: ?*AllocatedNamesPool.Node = null,
|
||
latest_arrow_arg_loc: logger.Loc = logger.Loc.Empty,
|
||
forbid_suffix_after_as_loc: logger.Loc = logger.Loc.Empty,
|
||
current_scope: *js_ast.Scope = undefined,
|
||
scopes_for_current_part: List(*js_ast.Scope) = .{},
|
||
symbols: ListManaged(js_ast.Symbol) = undefined,
|
||
ts_use_counts: List(u32) = .{},
|
||
exports_ref: Ref = Ref.None,
|
||
require_ref: Ref = Ref.None,
|
||
module_ref: Ref = Ref.None,
|
||
filename_ref: Ref = Ref.None,
|
||
dirname_ref: Ref = Ref.None,
|
||
import_meta_ref: Ref = Ref.None,
|
||
hmr_api_ref: Ref = Ref.None,
|
||
|
||
/// If bake is enabled and this is a server-side file, we want to use
|
||
/// special `Response` class inside the `bun:app` built-in module to
|
||
/// support syntax like `return Response(<jsx />, {...})` or `return Response.render("/my-page")`
|
||
/// or `return Response.redirect("/other")`.
|
||
///
|
||
/// So we'll need to add a `import { Response } from 'bun:app'` to the
|
||
/// top of the file
|
||
///
|
||
/// We need to declare this `response_ref` upfront
|
||
response_ref: Ref = Ref.None,
|
||
/// We also need to declare the namespace ref for `bun:app` and attach
|
||
/// it to the symbol so the code generated `e_import_identifier`'s
|
||
bun_app_namespace_ref: Ref = Ref.None,
|
||
|
||
/// Used to track the `feature` function from `import { feature } from "bun:bundle"`.
|
||
/// When visiting e_call, if the target ref matches this, we replace the call with
|
||
/// a boolean based on whether the feature flag is enabled.
|
||
bundler_feature_flag_ref: Ref = Ref.None,
|
||
/// Set to true when visiting an if/ternary condition. feature() calls are only valid in this context.
|
||
in_branch_condition: bool = false,
|
||
|
||
scopes_in_order_visitor_index: usize = 0,
|
||
has_classic_runtime_warned: bool = false,
|
||
macro_call_count: MacroCallCountType = 0,
|
||
|
||
hoisted_ref_for_sloppy_mode_block_fn: RefRefMap = .{},
|
||
|
||
/// Used for transforming export default -> module.exports
|
||
has_export_default: bool = false,
|
||
has_export_keyword: bool = false,
|
||
|
||
// Used for forcing CommonJS
|
||
has_with_scope: bool = false,
|
||
|
||
is_file_considered_to_have_esm_exports: bool = false,
|
||
|
||
has_called_runtime: bool = false,
|
||
|
||
legacy_cjs_import_stmts: std.array_list.Managed(Stmt),
|
||
|
||
injected_define_symbols: List(Ref) = .{},
|
||
symbol_uses: SymbolUseMap = .{},
|
||
declared_symbols: DeclaredSymbol.List = .{},
|
||
declared_symbols_for_reuse: DeclaredSymbol.List = .{},
|
||
runtime_imports: RuntimeImports = RuntimeImports{},
|
||
|
||
/// Used with unwrap_commonjs_packages
|
||
imports_to_convert_from_require: List(DeferredImportNamespace) = .{},
|
||
unwrap_all_requires: bool = false,
|
||
|
||
commonjs_named_exports: js_ast.Ast.CommonJSNamedExports = .{},
|
||
commonjs_named_exports_deoptimized: bool = false,
|
||
commonjs_module_exports_assigned_deoptimized: bool = false,
|
||
commonjs_named_exports_needs_conversion: u32 = std.math.maxInt(u32),
|
||
had_commonjs_named_exports_this_visit: bool = false,
|
||
commonjs_replacement_stmts: StmtNodeList = &.{},
|
||
|
||
parse_pass_symbol_uses: ParsePassSymbolUsageType = undefined,
|
||
|
||
/// Used by commonjs_at_runtime
|
||
has_commonjs_export_names: bool = false,
|
||
|
||
stack_check: bun.StackCheck,
|
||
|
||
/// When this flag is enabled, we attempt to fold all expressions that
|
||
/// TypeScript would consider to be "constant expressions". This flag is
|
||
/// enabled inside each enum body block since TypeScript requires numeric
|
||
/// constant folding in enum definitions.
|
||
///
|
||
/// We also enable this flag in certain cases in JavaScript files such as when
|
||
/// parsing "const" declarations at the top of a non-ESM file, but we still
|
||
/// reuse TypeScript's notion of "constant expressions" for our own convenience.
|
||
///
|
||
/// As of TypeScript 5.0, a "constant expression" is defined as follows:
|
||
///
|
||
/// An expression is considered a constant expression if it is
|
||
///
|
||
/// * a number or string literal,
|
||
/// * a unary +, -, or ~ applied to a numeric constant expression,
|
||
/// * a binary +, -, *, /, %, **, <<, >>, >>>, |, &, ^ applied to two numeric constant expressions,
|
||
/// * a binary + applied to two constant expressions whereof at least one is a string,
|
||
/// * a template expression where each substitution expression is a constant expression,
|
||
/// * a parenthesized constant expression,
|
||
/// * a dotted name (e.g. x.y.z) that references a const variable with a constant expression initializer and no type annotation,
|
||
/// * a dotted name that references an enum member with an enum literal type, or
|
||
/// * a dotted name indexed by a string literal (e.g. x.y["z"]) that references an enum member with an enum literal type.
|
||
///
|
||
/// More detail: https://github.com/microsoft/TypeScript/pull/50528. Note that
|
||
/// we don't implement certain items in this list. For example, we don't do all
|
||
/// number-to-string conversions since ours might differ from how JavaScript
|
||
/// would do it, which would be a correctness issue.
|
||
///
|
||
/// This flag is also set globally when minify_syntax is enabled, in which this means
|
||
/// we always fold constant expressions.
|
||
should_fold_typescript_constant_expressions: bool = false,
|
||
|
||
emitted_namespace_vars: RefMap = RefMap{},
|
||
is_exported_inside_namespace: RefRefMap = .{},
|
||
local_type_names: StringBoolMap = StringBoolMap{},
|
||
|
||
// This is the reference to the generated function argument for the namespace,
|
||
// which is different than the reference to the namespace itself:
|
||
//
|
||
// namespace ns {
|
||
// }
|
||
//
|
||
// The code above is transformed into something like this:
|
||
//
|
||
// var ns1;
|
||
// (function(ns2) {
|
||
// })(ns1 or (ns1 = {}));
|
||
//
|
||
// This variable is "ns2" not "ns1". It is only used during the second
|
||
// "visit" pass.
|
||
enclosing_namespace_arg_ref: ?Ref = null,
|
||
|
||
jsx_imports: JSXImport.Symbols = .{},
|
||
|
||
/// only applicable when `.options.features.react_fast_refresh` is set.
|
||
/// populated before visit pass starts.
|
||
react_refresh: ReactRefresh = .{},
|
||
|
||
/// only applicable when `.options.features.server_components` is
|
||
/// configured to wrap exports. populated before visit pass starts.
|
||
server_components_wrap_ref: Ref = Ref.None,
|
||
|
||
jest: Jest = .{},
|
||
|
||
// Imports (both ES6 and CommonJS) are tracked at the top level
|
||
import_records: ImportRecordList,
|
||
import_records_for_current_part: List(u32) = .{},
|
||
export_star_import_records: List(u32) = .{},
|
||
import_symbol_property_uses: SymbolPropertyUseMap = .{},
|
||
|
||
// These are for handling ES6 imports and exports
|
||
esm_import_keyword: logger.Range = logger.Range.None,
|
||
esm_export_keyword: logger.Range = logger.Range.None,
|
||
enclosing_class_keyword: logger.Range = logger.Range.None,
|
||
import_items_for_namespace: std.AutoHashMapUnmanaged(Ref, ImportItemForNamespaceMap) = .{},
|
||
is_import_item: RefMap = .{},
|
||
named_imports: NamedImportsType,
|
||
named_exports: js_ast.Ast.NamedExports,
|
||
import_namespace_cc_map: Map(ImportNamespaceCallOrConstruct, bool) = .{},
|
||
|
||
// When we're only scanning the imports
|
||
// If they're using the automatic JSX runtime
|
||
// We won't know that we need to import JSX robustly because we don't track
|
||
// symbol counts. Instead, we ask:
|
||
// "Did we parse anything that looked like JSX"?
|
||
// If yes, then automatically add the JSX import.
|
||
needs_jsx_import: NeedsJSXType,
|
||
|
||
// The parser does two passes and we need to pass the scope tree information
|
||
// from the first pass to the second pass. That's done by tracking the calls
|
||
// to pushScopeForParsePass() and popScope() during the first pass in
|
||
// scopesInOrder.
|
||
//
|
||
// Then, when the second pass calls pushScopeForVisitPass() and popScope(),
|
||
// we consume entries from scopesInOrder and make sure they are in the same
|
||
// order. This way the second pass can efficiently use the same scope tree
|
||
// as the first pass without having to attach the scope tree to the AST.
|
||
//
|
||
// We need to split this into two passes because the pass that declares the
|
||
// symbols must be separate from the pass that binds identifiers to declared
|
||
// symbols to handle declaring a hoisted "var" symbol in a nested scope and
|
||
// binding a name to it in a parent or sibling scope.
|
||
scopes_in_order: ScopeOrderList = .{},
|
||
scope_order_to_visit: []ScopeOrder = &.{},
|
||
|
||
// These properties are for the visit pass, which runs after the parse pass.
|
||
// The visit pass binds identifiers to declared symbols, does constant
|
||
// folding, substitutes compile-time variable definitions, and lowers certain
|
||
// syntactic constructs as appropriate.
|
||
stmt_expr_value: Expr.Data,
|
||
call_target: Expr.Data,
|
||
delete_target: Expr.Data,
|
||
loop_body: Stmt.Data,
|
||
module_scope: *js_ast.Scope = undefined,
|
||
module_scope_directive_loc: logger.Loc = .{},
|
||
is_control_flow_dead: bool = false,
|
||
|
||
/// We must be careful to avoid revisiting nodes that have scopes.
|
||
is_revisit_for_substitution: bool = false,
|
||
|
||
method_call_must_be_replaced_with_undefined: bool = false,
|
||
|
||
// Inside a TypeScript namespace, an "export declare" statement can be used
|
||
// to cause a namespace to be emitted even though it has no other observable
|
||
// effect. This flag is used to implement this feature.
|
||
//
|
||
// Specifically, namespaces should be generated for all of the following
|
||
// namespaces below except for "f", which should not be generated:
|
||
//
|
||
// namespace a { export declare const a }
|
||
// namespace b { export declare let [[b]] }
|
||
// namespace c { export declare function c() }
|
||
// namespace d { export declare class d {} }
|
||
// namespace e { export declare enum e {} }
|
||
// namespace f { export declare namespace f {} }
|
||
//
|
||
// The TypeScript compiler compiles this into the following code (notice "f"
|
||
// is missing):
|
||
//
|
||
// var a; (function (a_1) {})(a or (a = {}));
|
||
// var b; (function (b_1) {})(b or (b = {}));
|
||
// var c; (function (c_1) {})(c or (c = {}));
|
||
// var d; (function (d_1) {})(d or (d = {}));
|
||
// var e; (function (e_1) {})(e or (e = {}));
|
||
//
|
||
// Note that this should not be implemented by declaring symbols for "export
|
||
// declare" statements because the TypeScript compiler doesn't generate any
|
||
// code for these statements, so these statements are actually references to
|
||
// global variables. There is one exception, which is that local variables
|
||
// *should* be declared as symbols because they are replaced with. This seems
|
||
// like very arbitrary behavior but it's what the TypeScript compiler does,
|
||
// so we try to match it.
|
||
//
|
||
// Specifically, in the following code below "a" and "b" should be declared
|
||
// and should be substituted with "ns.a" and "ns.b" but the other symbols
|
||
// shouldn't. References to the other symbols actually refer to global
|
||
// variables instead of to symbols that are exported from the namespace.
|
||
// This is the case as of TypeScript 4.3. I assume this is a TypeScript bug:
|
||
//
|
||
// namespace ns {
|
||
// export declare const a
|
||
// export declare let [[b]]
|
||
// export declare function c()
|
||
// export declare class d { }
|
||
// export declare enum e { }
|
||
// console.log(a, b, c, d, e)
|
||
// }
|
||
//
|
||
// The TypeScript compiler compiles this into the following code:
|
||
//
|
||
// var ns;
|
||
// (function (ns) {
|
||
// console.log(ns.a, ns.b, c, d, e);
|
||
// })(ns or (ns = {}));
|
||
//
|
||
// Relevant issue: https://github.com/evanw/esbuild/issues/1158
|
||
has_non_local_export_declare_inside_namespace: bool = false,
|
||
|
||
// This helps recognize the "await import()" pattern. When this is present,
|
||
// warnings about non-string import paths will be omitted inside try blocks.
|
||
await_target: ?js_ast.Expr.Data = null,
|
||
|
||
to_expr_wrapper_namespace: Binding2ExprWrapper.Namespace,
|
||
to_expr_wrapper_hoisted: Binding2ExprWrapper.Hoisted,
|
||
|
||
// This helps recognize the "import().catch()" pattern. We also try to avoid
|
||
// warning about this just like the "try { await import() }" pattern.
|
||
then_catch_chain: ThenCatchChain,
|
||
|
||
// Temporary variables used for lowering
|
||
temp_refs_to_declare: List(TempRef) = .{},
|
||
temp_ref_count: i32 = 0,
|
||
|
||
// When bundling, hoisted top-level local variables declared with "var" in
|
||
// nested scopes are moved up to be declared in the top-level scope instead.
|
||
// The old "var" statements are turned into regular assignments instead. This
|
||
// makes it easier to quickly scan the top-level statements for "var" locals
|
||
// with the guarantee that all will be found.
|
||
relocated_top_level_vars: List(js_ast.LocRef) = .{},
|
||
|
||
// ArrowFunction is a special case in the grammar. Although it appears to be
|
||
// a PrimaryExpression, it's actually an AssignmentExpression. This means if
|
||
// a AssignmentExpression ends up producing an ArrowFunction then nothing can
|
||
// come after it other than the comma operator, since the comma operator is
|
||
// the only thing above AssignmentExpression under the Expression rule:
|
||
//
|
||
// AssignmentExpression:
|
||
// ArrowFunction
|
||
// ConditionalExpression
|
||
// LeftHandSideExpression = AssignmentExpression
|
||
// LeftHandSideExpression AssignmentOperator AssignmentExpression
|
||
//
|
||
// Expression:
|
||
// AssignmentExpression
|
||
// Expression , AssignmentExpression
|
||
//
|
||
after_arrow_body_loc: logger.Loc = logger.Loc.Empty,
|
||
import_transposer: ImportTransposer,
|
||
require_transposer: RequireTransposer,
|
||
require_resolve_transposer: RequireResolveTransposer,
|
||
|
||
const_values: js_ast.Ast.ConstValuesMap = .{},
|
||
|
||
// These are backed by stack fallback allocators in _parse, and are uninitialized until then.
|
||
binary_expression_stack: ListManaged(BinaryExpressionVisitor) = undefined,
|
||
binary_expression_simplify_stack: ListManaged(SideEffects.BinaryExpressionSimplifyVisitor) = undefined,
|
||
|
||
/// We build up enough information about the TypeScript namespace hierarchy to
|
||
/// be able to resolve scope lookups and property accesses for TypeScript enum
|
||
/// and namespace features. Each JavaScript scope object inside a namespace
|
||
/// has a reference to a map of exported namespace members from sibling scopes.
|
||
///
|
||
/// In addition, there is a map from each relevant symbol reference to the data
|
||
/// associated with that namespace or namespace member: "ref_to_ts_namespace_member".
|
||
/// This gives enough info to be able to resolve queries into the namespace.
|
||
ref_to_ts_namespace_member: std.AutoHashMapUnmanaged(Ref, js_ast.TSNamespaceMember.Data) = .{},
|
||
/// When visiting expressions, namespace metadata is associated with the most
|
||
/// recently visited node. If namespace metadata is present, "tsNamespaceTarget"
|
||
/// will be set to the most recently visited node (as a way to mark that this
|
||
/// node has metadata) and "tsNamespaceMemberData" will be set to the metadata.
|
||
ts_namespace: RecentlyVisitedTSNamespace = .{},
|
||
top_level_enums: std.ArrayListUnmanaged(Ref) = .{},
|
||
|
||
scopes_in_order_for_enum: std.AutoArrayHashMapUnmanaged(logger.Loc, []ScopeOrder) = .{},
|
||
|
||
// If this is true, then all top-level statements are wrapped in a try/catch
|
||
will_wrap_module_in_try_catch_for_using: bool = false,
|
||
|
||
/// Used for react refresh, it must be able to insert `const _s = $RefreshSig$();`
|
||
nearest_stmt_list: ?*ListManaged(Stmt) = null,
|
||
|
||
const RecentlyVisitedTSNamespace = struct {
|
||
expr: Expr.Data = Expr.empty.data,
|
||
map: ?*js_ast.TSNamespaceMemberMap = null,
|
||
|
||
const ExpressionData = union(enum) {
|
||
ref: Ref,
|
||
ptr: *E.Dot,
|
||
};
|
||
};
|
||
|
||
/// use this instead of checking p.source.index
|
||
/// because when not bundling, p.source.index is `0`
|
||
pub inline fn isSourceRuntime(p: *const P) bool {
|
||
return p.options.bundle and p.source.index.isRuntime();
|
||
}
|
||
|
||
pub fn transposeImport(noalias p: *P, arg: Expr, state: *const TransposeState) Expr {
|
||
// The argument must be a string
|
||
if (arg.data.as(.e_string)) |str| {
|
||
// Ignore calls to import() if the control flow is provably dead here.
|
||
// We don't want to spend time scanning the required files if they will
|
||
// never be used.
|
||
if (p.is_control_flow_dead) {
|
||
return p.newExpr(E.Null{}, arg.loc);
|
||
}
|
||
|
||
const import_record_index = p.addImportRecord(.dynamic, arg.loc, str.slice(p.allocator));
|
||
|
||
if (state.import_record_tag) |tag| {
|
||
p.import_records.items[import_record_index].tag = tag;
|
||
}
|
||
|
||
p.import_records.items[import_record_index].flags.handles_import_errors = (state.is_await_target and p.fn_or_arrow_data_visit.try_body_count != 0) or state.is_then_catch_target;
|
||
p.import_records_for_current_part.append(p.allocator, import_record_index) catch unreachable;
|
||
|
||
return p.newExpr(E.Import{
|
||
.expr = arg,
|
||
.import_record_index = @intCast(import_record_index),
|
||
.options = state.import_options,
|
||
}, state.loc);
|
||
}
|
||
|
||
if (p.options.warn_about_unbundled_modules) {
|
||
// Use a debug log so people can see this if they want to
|
||
const r = js_lexer.rangeOfIdentifier(p.source, state.loc);
|
||
p.log.addRangeDebug(p.source, r, "This \"import\" expression cannot be bundled because the argument is not a string literal") catch unreachable;
|
||
}
|
||
|
||
return p.newExpr(E.Import{
|
||
.expr = arg,
|
||
.options = state.import_options,
|
||
.import_record_index = std.math.maxInt(u32),
|
||
}, state.loc);
|
||
}
|
||
|
||
pub fn transposeRequireResolve(noalias p: *P, arg: Expr, require_resolve_ref: Expr) Expr {
|
||
// The argument must be a string
|
||
if (arg.data == .e_string) {
|
||
return p.transposeRequireResolveKnownString(arg);
|
||
}
|
||
|
||
if (p.options.warn_about_unbundled_modules) {
|
||
// Use a debug log so people can see this if they want to
|
||
const r = js_lexer.rangeOfIdentifier(p.source, arg.loc);
|
||
p.log.addRangeDebug(p.source, r, "This \"require.resolve\" expression cannot be bundled because the argument is not a string literal") catch unreachable;
|
||
}
|
||
|
||
const args = p.allocator.alloc(Expr, 1) catch unreachable;
|
||
args[0] = arg;
|
||
|
||
return p.newExpr(E.Call{
|
||
.target = require_resolve_ref,
|
||
.args = ExprNodeList.fromOwnedSlice(args),
|
||
}, arg.loc);
|
||
}
|
||
|
||
pub inline fn transposeRequireResolveKnownString(noalias p: *P, arg: Expr) Expr {
|
||
bun.assert(arg.data == .e_string);
|
||
|
||
// Ignore calls to import() if the control flow is provably dead here.
|
||
// We don't want to spend time scanning the required files if they will
|
||
// never be used.
|
||
if (p.is_control_flow_dead) {
|
||
return p.newExpr(E.Null{}, arg.loc);
|
||
}
|
||
|
||
const import_record_index = p.addImportRecord(.require_resolve, arg.loc, arg.data.e_string.string(p.allocator) catch unreachable);
|
||
p.import_records.items[import_record_index].flags.handles_import_errors = p.fn_or_arrow_data_visit.try_body_count != 0;
|
||
p.import_records_for_current_part.append(p.allocator, import_record_index) catch unreachable;
|
||
|
||
return p.newExpr(
|
||
E.RequireResolveString{
|
||
.import_record_index = import_record_index,
|
||
// .leading_interior_comments = arg.getString().
|
||
},
|
||
arg.loc,
|
||
);
|
||
}
|
||
|
||
pub fn transposeRequire(noalias p: *P, arg: Expr, state: *const TransposeState) Expr {
|
||
if (!p.options.features.allow_runtime) {
|
||
const args = bun.handleOom(p.allocator.alloc(Expr, 1));
|
||
args[0] = arg;
|
||
return p.newExpr(
|
||
E.Call{
|
||
.target = p.valueForRequire(arg.loc),
|
||
.args = ExprNodeList.fromOwnedSlice(args),
|
||
},
|
||
arg.loc,
|
||
);
|
||
}
|
||
|
||
switch (arg.data) {
|
||
.e_string => |str| {
|
||
// Ignore calls to require() if the control flow is provably dead here.
|
||
// We don't want to spend time scanning the required files if they will
|
||
// never be used.
|
||
if (p.is_control_flow_dead) {
|
||
return Expr{ .data = nullExprData, .loc = arg.loc };
|
||
}
|
||
|
||
str.resolveRopeIfNeeded(p.allocator);
|
||
const pathname = str.string(p.allocator) catch unreachable;
|
||
const path = fs.Path.init(pathname);
|
||
|
||
const handles_import_errors = p.fn_or_arrow_data_visit.try_body_count != 0;
|
||
|
||
// For unwrapping CommonJS into ESM to fully work
|
||
// we must also unwrap requires into imports.
|
||
const should_unwrap_require = p.options.features.unwrap_commonjs_to_esm and
|
||
(p.unwrap_all_requires or
|
||
if (path.packageName()) |pkg| p.options.features.shouldUnwrapRequire(pkg) else false) and
|
||
// We cannot unwrap a require wrapped in a try/catch because
|
||
// import statements cannot be wrapped in a try/catch and
|
||
// require cannot return a promise.
|
||
!handles_import_errors;
|
||
|
||
if (should_unwrap_require) {
|
||
const import_record_index = p.addImportRecordByRangeAndPath(.stmt, p.source.rangeOfString(arg.loc), path);
|
||
p.import_records.items[import_record_index].flags.handles_import_errors = handles_import_errors;
|
||
|
||
// Note that this symbol may be completely removed later.
|
||
var path_name = fs.PathName.init(path.text);
|
||
const name = bun.handleOom(path_name.nonUniqueNameString(p.allocator));
|
||
const namespace_ref = bun.handleOom(p.newSymbol(.other, name));
|
||
|
||
p.imports_to_convert_from_require.append(p.allocator, .{
|
||
.namespace = .{
|
||
.ref = namespace_ref,
|
||
.loc = arg.loc,
|
||
},
|
||
.import_record_id = import_record_index,
|
||
}) catch |err| bun.handleOom(err);
|
||
bun.handleOom(p.import_items_for_namespace.put(p.allocator, namespace_ref, ImportItemForNamespaceMap.init(p.allocator)));
|
||
p.recordUsage(namespace_ref);
|
||
|
||
if (!state.is_require_immediately_assigned_to_decl) {
|
||
return p.newExpr(E.Identifier{
|
||
.ref = namespace_ref,
|
||
}, arg.loc);
|
||
}
|
||
|
||
return p.newExpr(
|
||
E.RequireString{
|
||
.import_record_index = import_record_index,
|
||
.unwrapped_id = @as(u32, @intCast(p.imports_to_convert_from_require.items.len - 1)),
|
||
},
|
||
arg.loc,
|
||
);
|
||
}
|
||
|
||
const import_record_index = p.addImportRecordByRangeAndPath(.require, p.source.rangeOfString(arg.loc), path);
|
||
p.import_records.items[import_record_index].flags.handles_import_errors = handles_import_errors;
|
||
p.import_records_for_current_part.append(p.allocator, import_record_index) catch unreachable;
|
||
|
||
return p.newExpr(E.RequireString{ .import_record_index = import_record_index }, arg.loc);
|
||
},
|
||
else => {
|
||
p.recordUsageOfRuntimeRequire();
|
||
const args = p.allocator.alloc(Expr, 1) catch unreachable;
|
||
args[0] = arg;
|
||
return p.newExpr(
|
||
E.Call{
|
||
.target = p.valueForRequire(arg.loc),
|
||
.args = ExprNodeList.fromOwnedSlice(args),
|
||
},
|
||
arg.loc,
|
||
);
|
||
},
|
||
}
|
||
}
|
||
|
||
pub inline fn shouldUnwrapCommonJSToESM(p: *const P) bool {
|
||
return p.options.features.unwrap_commonjs_to_esm;
|
||
}
|
||
|
||
fn isBindingUsed(noalias p: *P, binding: Binding, default_export_ref: Ref) bool {
|
||
switch (binding.data) {
|
||
.b_identifier => |ident| {
|
||
if (default_export_ref.eql(ident.ref)) return true;
|
||
if (p.named_imports.contains(ident.ref))
|
||
return true;
|
||
|
||
for (p.named_exports.values()) |named_export| {
|
||
if (named_export.ref.eql(ident.ref))
|
||
return true;
|
||
}
|
||
|
||
const symbol: *const Symbol = &p.symbols.items[ident.ref.innerIndex()];
|
||
return symbol.use_count_estimate > 0;
|
||
},
|
||
.b_array => |array| {
|
||
for (array.items) |item| {
|
||
if (isBindingUsed(p, item.binding, default_export_ref)) {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
},
|
||
.b_object => |obj| {
|
||
for (obj.properties) |prop| {
|
||
if (isBindingUsed(p, prop.value, default_export_ref)) {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
},
|
||
.b_missing => return false,
|
||
}
|
||
}
|
||
|
||
pub fn treeShake(noalias p: *P, parts: *[]js_ast.Part, merge: bool) void {
|
||
var parts_: []js_ast.Part = parts.*;
|
||
defer {
|
||
if (merge and parts_.len > 1) {
|
||
var first_none_part: usize = parts_.len;
|
||
var stmts_count: usize = 0;
|
||
for (parts_, 0..) |part, i| {
|
||
if (part.tag == .none) {
|
||
stmts_count += part.stmts.len;
|
||
first_none_part = @min(i, first_none_part);
|
||
}
|
||
}
|
||
|
||
if (first_none_part < parts_.len) {
|
||
const stmts_list = p.allocator.alloc(Stmt, stmts_count) catch unreachable;
|
||
var stmts_remain = stmts_list;
|
||
|
||
for (parts_) |part| {
|
||
if (part.tag == .none) {
|
||
bun.copy(Stmt, stmts_remain, part.stmts);
|
||
stmts_remain = stmts_remain[part.stmts.len..];
|
||
}
|
||
}
|
||
|
||
parts_[first_none_part].stmts = stmts_list;
|
||
|
||
parts_ = parts_[0 .. first_none_part + 1];
|
||
}
|
||
}
|
||
|
||
parts.* = parts_;
|
||
}
|
||
const default_export_ref =
|
||
if (p.named_exports.get("default")) |default_| default_.ref else Ref.None;
|
||
|
||
while (parts_.len > 1) {
|
||
var parts_end: usize = 0;
|
||
const last_end = parts_.len;
|
||
|
||
for (parts_) |part| {
|
||
const is_dead = part.can_be_removed_if_unused and can_remove_part: {
|
||
for (part.stmts) |stmt| {
|
||
switch (stmt.data) {
|
||
.s_local => |local| {
|
||
if (local.is_export) break :can_remove_part false;
|
||
for (local.decls.slice()) |decl| {
|
||
if (isBindingUsed(p, decl.binding, default_export_ref))
|
||
break :can_remove_part false;
|
||
}
|
||
},
|
||
.s_if => |if_statement| {
|
||
const result = SideEffects.toBoolean(p, if_statement.test_.data);
|
||
if (!(result.ok and result.side_effects == .no_side_effects and !result.value)) {
|
||
break :can_remove_part false;
|
||
}
|
||
},
|
||
.s_while => |while_statement| {
|
||
const result = SideEffects.toBoolean(p, while_statement.test_.data);
|
||
if (!(result.ok and result.side_effects == .no_side_effects and !result.value)) {
|
||
break :can_remove_part false;
|
||
}
|
||
},
|
||
.s_for => |for_statement| {
|
||
if (for_statement.test_) |expr| {
|
||
const result = SideEffects.toBoolean(p, expr.data);
|
||
if (!(result.ok and result.side_effects == .no_side_effects and !result.value)) {
|
||
break :can_remove_part false;
|
||
}
|
||
}
|
||
},
|
||
.s_function => |func| {
|
||
if (func.func.flags.contains(.is_export)) break :can_remove_part false;
|
||
if (func.func.name) |name| {
|
||
const symbol: *const Symbol = &p.symbols.items[name.ref.?.innerIndex()];
|
||
|
||
if (name.ref.?.eql(default_export_ref) or
|
||
symbol.use_count_estimate > 0 or
|
||
p.named_exports.contains(symbol.original_name) or
|
||
p.named_imports.contains(name.ref.?) or
|
||
p.is_import_item.get(name.ref.?) != null)
|
||
{
|
||
break :can_remove_part false;
|
||
}
|
||
}
|
||
},
|
||
.s_import,
|
||
.s_export_clause,
|
||
.s_export_from,
|
||
.s_export_default,
|
||
=> break :can_remove_part false,
|
||
|
||
.s_class => |class| {
|
||
if (class.is_export) break :can_remove_part false;
|
||
if (class.class.class_name) |name| {
|
||
const symbol: *const Symbol = &p.symbols.items[name.ref.?.innerIndex()];
|
||
|
||
if (name.ref.?.eql(default_export_ref) or
|
||
symbol.use_count_estimate > 0 or
|
||
p.named_exports.contains(symbol.original_name) or
|
||
p.named_imports.contains(name.ref.?) or
|
||
p.is_import_item.get(name.ref.?) != null)
|
||
{
|
||
break :can_remove_part false;
|
||
}
|
||
}
|
||
},
|
||
|
||
else => break :can_remove_part false,
|
||
}
|
||
}
|
||
break :can_remove_part true;
|
||
};
|
||
|
||
if (is_dead) {
|
||
p.clearSymbolUsagesFromDeadPart(&part);
|
||
|
||
continue;
|
||
}
|
||
|
||
parts_[parts_end] = part;
|
||
parts_end += 1;
|
||
}
|
||
|
||
parts_.len = parts_end;
|
||
if (last_end == parts_.len) {
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
const ImportTransposer = ExpressionTransposer(P, *const TransposeState, P.transposeImport);
|
||
const RequireTransposer = ExpressionTransposer(P, *const TransposeState, P.transposeRequire);
|
||
const RequireResolveTransposer = ExpressionTransposer(P, Expr, P.transposeRequireResolve);
|
||
|
||
const Binding2ExprWrapper = struct {
|
||
pub const Namespace = Binding.ToExpr(P, P.wrapIdentifierNamespace);
|
||
pub const Hoisted = Binding.ToExpr(P, P.wrapIdentifierHoisting);
|
||
};
|
||
|
||
fn clearSymbolUsagesFromDeadPart(noalias p: *P, part: *const js_ast.Part) void {
|
||
const symbol_use_refs = part.symbol_uses.keys();
|
||
const symbol_use_values = part.symbol_uses.values();
|
||
var symbols = p.symbols.items;
|
||
|
||
for (symbol_use_refs, symbol_use_values) |ref, prev| {
|
||
symbols[ref.innerIndex()].use_count_estimate -|= prev.count_estimate;
|
||
}
|
||
const declared_refs = part.declared_symbols.refs();
|
||
for (declared_refs) |declared| {
|
||
symbols[declared.innerIndex()].use_count_estimate = 0;
|
||
}
|
||
}
|
||
|
||
pub fn s(noalias _: *const P, t: anytype, loc: logger.Loc) Stmt {
|
||
const Type = @TypeOf(t);
|
||
if (!is_typescript_enabled and (Type == S.TypeScript or Type == *S.TypeScript)) {
|
||
@compileError("Attempted to use TypeScript syntax in a non-TypeScript environment");
|
||
}
|
||
|
||
// Output.print("\nStmt: {s} - {d}\n", .{ @typeName(@TypeOf(t)), loc.start });
|
||
if (@typeInfo(Type) == .pointer) {
|
||
// ExportFrom normally becomes import records during the visiting pass
|
||
// However, we skip the visiting pass in this mode
|
||
// So we must generate a minimum version of it here.
|
||
if (comptime only_scan_imports_and_do_not_visit) {
|
||
// if (@TypeOf(t) == *S.ExportFrom) {
|
||
// switch (call.target.data) {
|
||
// .e_identifier => |ident| {
|
||
// // is this a require("something")
|
||
// if (strings.eqlComptime(p.loadNameFromRef(ident.ref), "require") and call.args.len == 1 and std.meta.activeTag(call.args[0].data) == .e_string) {
|
||
// _ = p.addImportRecord(.require, loc, call.args[0].data.e_string.string(p.allocator) catch unreachable);
|
||
// }
|
||
// },
|
||
// else => {},
|
||
// }
|
||
// }
|
||
}
|
||
|
||
return Stmt.init(std.meta.Child(Type), t, loc);
|
||
} else {
|
||
return Stmt.alloc(Type, t, loc);
|
||
}
|
||
}
|
||
|
||
fn computeCharacterFrequency(p: *P) ?js_ast.CharFreq {
|
||
if (!p.options.features.minify_identifiers or p.isSourceRuntime()) {
|
||
return null;
|
||
}
|
||
|
||
// Add everything in the file to the histogram
|
||
var freq: js_ast.CharFreq = .{
|
||
.freqs = [_]i32{0} ** 64,
|
||
};
|
||
|
||
freq.scan(p.source.contents, 1);
|
||
|
||
// Subtract out all comments
|
||
for (p.lexer.all_comments.items) |comment_range| {
|
||
freq.scan(p.source.textForRange(comment_range), -1);
|
||
}
|
||
|
||
// Subtract out all import paths
|
||
for (p.import_records.items) |record| {
|
||
freq.scan(record.path.text, -1);
|
||
}
|
||
|
||
const ScopeVisitor = struct {
|
||
pub fn visit(symbols: []const js_ast.Symbol, char_freq: *js_ast.CharFreq, scope: *js_ast.Scope) void {
|
||
var iter = scope.members.iterator();
|
||
|
||
while (iter.next()) |entry| {
|
||
const symbol: *const Symbol = &symbols[entry.value_ptr.ref.innerIndex()];
|
||
|
||
if (symbol.slotNamespace() != .must_not_be_renamed) {
|
||
char_freq.scan(symbol.original_name, -@as(i32, @intCast(symbol.use_count_estimate)));
|
||
}
|
||
}
|
||
|
||
if (scope.label_ref) |ref| {
|
||
const symbol = &symbols[ref.innerIndex()];
|
||
|
||
if (symbol.slotNamespace() != .must_not_be_renamed) {
|
||
char_freq.scan(symbol.original_name, -@as(i32, @intCast(symbol.use_count_estimate)) - 1);
|
||
}
|
||
}
|
||
|
||
for (scope.children.slice()) |child| {
|
||
visit(symbols, char_freq, child);
|
||
}
|
||
}
|
||
};
|
||
ScopeVisitor.visit(p.symbols.items, &freq, p.module_scope);
|
||
|
||
// TODO: mangledProps
|
||
|
||
return freq;
|
||
}
|
||
|
||
pub fn newExpr(noalias p: *P, t: anytype, loc: logger.Loc) Expr {
|
||
const Type = @TypeOf(t);
|
||
|
||
comptime {
|
||
if (jsx_transform_type == .none) {
|
||
if (Type == E.JSXElement or Type == *E.JSXElement) {
|
||
@compileError("JSXElement is not supported in this environment");
|
||
}
|
||
}
|
||
}
|
||
|
||
// Output.print("\nExpr: {s} - {d}\n", .{ @typeName(@TypeOf(t)), loc.start });
|
||
if (@typeInfo(Type) == .pointer) {
|
||
if (comptime only_scan_imports_and_do_not_visit) {
|
||
if (Type == *E.Call) {
|
||
const call: *E.Call = t;
|
||
switch (call.target.data) {
|
||
.e_identifier => |ident| {
|
||
// is this a require("something")
|
||
if (strings.eqlComptime(p.loadNameFromRef(ident.ref), "require") and call.args.len == 1 and call.args.ptr[0].data == .e_string) {
|
||
_ = p.addImportRecord(.require, loc, call.args.at(0).data.e_string.string(p.allocator) catch unreachable);
|
||
}
|
||
},
|
||
else => {},
|
||
}
|
||
}
|
||
}
|
||
return Expr.init(std.meta.Child(Type), t.*, loc);
|
||
} else {
|
||
if (comptime only_scan_imports_and_do_not_visit) {
|
||
if (Type == E.Call) {
|
||
const call: E.Call = t;
|
||
switch (call.target.data) {
|
||
.e_identifier => |ident| {
|
||
// is this a require("something")
|
||
if (strings.eqlComptime(p.loadNameFromRef(ident.ref), "require") and call.args.len == 1 and call.args.ptr[0].data == .e_string) {
|
||
_ = p.addImportRecord(.require, loc, call.args.at(0).data.e_string.string(p.allocator) catch unreachable);
|
||
}
|
||
},
|
||
else => {},
|
||
}
|
||
}
|
||
}
|
||
return Expr.init(Type, t, loc);
|
||
}
|
||
}
|
||
|
||
pub fn b(p: *P, t: anytype, loc: logger.Loc) Binding {
|
||
if (@typeInfo(@TypeOf(t)) == .pointer) {
|
||
return Binding.init(t, loc);
|
||
} else {
|
||
return Binding.alloc(p.allocator, t, loc);
|
||
}
|
||
}
|
||
|
||
pub fn recordExportedBinding(noalias p: *P, binding: Binding) void {
|
||
switch (binding.data) {
|
||
.b_missing => {},
|
||
.b_identifier => |ident| {
|
||
p.recordExport(binding.loc, p.symbols.items[ident.ref.innerIndex()].original_name, ident.ref) catch unreachable;
|
||
},
|
||
.b_array => |array| {
|
||
for (array.items) |prop| {
|
||
p.recordExportedBinding(prop.binding);
|
||
}
|
||
},
|
||
.b_object => |obj| {
|
||
for (obj.properties) |prop| {
|
||
p.recordExportedBinding(prop.value);
|
||
}
|
||
},
|
||
}
|
||
}
|
||
|
||
pub fn recordExport(noalias p: *P, loc: logger.Loc, alias: string, ref: Ref) !void {
|
||
if (p.named_exports.get(alias)) |name| {
|
||
// Duplicate exports are an error
|
||
var notes = try p.allocator.alloc(logger.Data, 1);
|
||
notes[0] = logger.Data{
|
||
.text = try std.fmt.allocPrint(p.allocator, "\"{s}\" was originally exported here", .{alias}),
|
||
.location = logger.Location.initOrNull(p.source, js_lexer.rangeOfIdentifier(p.source, name.alias_loc)),
|
||
};
|
||
try p.log.addRangeErrorFmtWithNotes(
|
||
p.source,
|
||
js_lexer.rangeOfIdentifier(p.source, loc),
|
||
p.allocator,
|
||
notes,
|
||
"Multiple exports with the same name \"{s}\"",
|
||
.{std.mem.trim(u8, alias, "\"'")},
|
||
);
|
||
} else if (!p.isDeoptimizedCommonJS()) {
|
||
try p.named_exports.put(p.allocator, alias, js_ast.NamedExport{ .alias_loc = loc, .ref = ref });
|
||
}
|
||
}
|
||
|
||
pub fn isDeoptimizedCommonJS(noalias p: *P) bool {
|
||
return p.commonjs_named_exports_deoptimized and p.commonjs_named_exports.count() > 0;
|
||
}
|
||
|
||
pub fn recordUsage(noalias p: *P, ref: Ref) void {
|
||
if (p.is_revisit_for_substitution) return;
|
||
// The use count stored in the symbol is used for generating symbol names
|
||
// during minification. These counts shouldn't include references inside dead
|
||
// code regions since those will be culled.
|
||
if (!p.is_control_flow_dead) {
|
||
if (comptime Environment.allow_assert) assert(p.symbols.items.len > ref.innerIndex());
|
||
p.symbols.items[ref.innerIndex()].use_count_estimate += 1;
|
||
var result = p.symbol_uses.getOrPut(p.allocator, ref) catch unreachable;
|
||
if (!result.found_existing) {
|
||
result.value_ptr.* = Symbol.Use{ .count_estimate = 1 };
|
||
} else {
|
||
result.value_ptr.count_estimate += 1;
|
||
}
|
||
}
|
||
|
||
// The correctness of TypeScript-to-JavaScript conversion relies on accurate
|
||
// symbol use counts for the whole file, including dead code regions. This is
|
||
// tracked separately in a parser-only data structure.
|
||
if (is_typescript_enabled) {
|
||
p.ts_use_counts.items[ref.innerIndex()] += 1;
|
||
}
|
||
}
|
||
|
||
pub fn logArrowArgErrors(noalias p: *P, errors: *DeferredArrowArgErrors) void {
|
||
if (errors.invalid_expr_await.len > 0) {
|
||
const r = errors.invalid_expr_await;
|
||
p.log.addRangeError(p.source, r, "Cannot use an \"await\" expression here") catch unreachable;
|
||
}
|
||
|
||
if (errors.invalid_expr_yield.len > 0) {
|
||
const r = errors.invalid_expr_yield;
|
||
p.log.addRangeError(p.source, r, "Cannot use a \"yield\" expression here") catch unreachable;
|
||
}
|
||
}
|
||
|
||
pub fn keyNameForError(noalias p: *P, key: *const js_ast.Expr) string {
|
||
switch (key.data) {
|
||
.e_string => {
|
||
return key.data.e_string.string(p.allocator) catch unreachable;
|
||
},
|
||
.e_private_identifier => |private| {
|
||
return p.loadNameFromRef(private.ref);
|
||
},
|
||
else => {
|
||
return "property";
|
||
},
|
||
}
|
||
}
|
||
|
||
/// This function is very very hot.
|
||
pub fn handleIdentifier(noalias p: *P, loc: logger.Loc, ident: E.Identifier, original_name: ?string, opts: IdentifierOpts) Expr {
|
||
const ref = ident.ref;
|
||
|
||
if (p.options.features.inlining) {
|
||
if (p.const_values.get(ref)) |replacement| {
|
||
p.ignoreUsage(ref);
|
||
return replacement;
|
||
}
|
||
}
|
||
|
||
// Create an error for assigning to an import namespace
|
||
if ((opts.assign_target != .none or opts.is_delete_target) and p.symbols.items[ref.innerIndex()].kind == .import) {
|
||
const r = js_lexer.rangeOfIdentifier(p.source, loc);
|
||
p.log.addRangeErrorFmt(p.source, r, p.allocator, "Cannot assign to import \"{s}\"", .{
|
||
p.symbols.items[ref.innerIndex()].original_name,
|
||
}) catch unreachable;
|
||
}
|
||
|
||
// Substitute an EImportIdentifier now if this has a namespace alias
|
||
if (opts.assign_target == .none and !opts.is_delete_target) {
|
||
const symbol = &p.symbols.items[ref.inner_index];
|
||
if (symbol.namespace_alias) |ns_alias| {
|
||
if (p.ref_to_ts_namespace_member.get(ns_alias.namespace_ref)) |ts_member_data| {
|
||
if (ts_member_data == .namespace) {
|
||
if (ts_member_data.namespace.get(ns_alias.alias)) |member| {
|
||
switch (member.data) {
|
||
.enum_number => |num| return p.wrapInlinedEnum(
|
||
.{ .loc = loc, .data = .{ .e_number = .{ .value = num } } },
|
||
p.symbols.items[ref.inner_index].original_name,
|
||
),
|
||
|
||
.enum_string => |str| return p.wrapInlinedEnum(
|
||
.{ .loc = loc, .data = .{ .e_string = str } },
|
||
p.symbols.items[ref.inner_index].original_name,
|
||
),
|
||
|
||
.namespace => |map| {
|
||
const expr = p.newExpr(E.Dot{
|
||
.target = p.newExpr(E.Identifier.init(ns_alias.namespace_ref), loc),
|
||
.name = ns_alias.alias,
|
||
.name_loc = loc,
|
||
}, loc);
|
||
p.ts_namespace = .{
|
||
.expr = expr.data,
|
||
.map = map,
|
||
};
|
||
return expr;
|
||
},
|
||
|
||
else => {},
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return p.newExpr(E.ImportIdentifier{
|
||
.ref = ident.ref,
|
||
.was_originally_identifier = true,
|
||
}, loc);
|
||
}
|
||
}
|
||
|
||
// Substitute an EImportIdentifier now if this is an import item
|
||
if (p.is_import_item.contains(ref)) {
|
||
return p.newExpr(
|
||
E.ImportIdentifier{ .ref = ref, .was_originally_identifier = opts.was_originally_identifier },
|
||
loc,
|
||
);
|
||
}
|
||
|
||
if (is_typescript_enabled) {
|
||
if (p.ref_to_ts_namespace_member.get(ref)) |member_data| {
|
||
switch (member_data) {
|
||
.enum_number => |num| return p.wrapInlinedEnum(
|
||
.{ .loc = loc, .data = .{ .e_number = .{ .value = num } } },
|
||
p.symbols.items[ref.inner_index].original_name,
|
||
),
|
||
|
||
.enum_string => |str| return p.wrapInlinedEnum(
|
||
.{ .loc = loc, .data = .{ .e_string = str } },
|
||
p.symbols.items[ref.inner_index].original_name,
|
||
),
|
||
|
||
.namespace => |map| {
|
||
const expr: Expr = .{
|
||
.data = .{ .e_identifier = ident },
|
||
.loc = loc,
|
||
};
|
||
|
||
p.ts_namespace = .{
|
||
.expr = expr.data,
|
||
.map = map,
|
||
};
|
||
|
||
return expr;
|
||
},
|
||
|
||
else => {},
|
||
}
|
||
}
|
||
|
||
// Substitute a namespace export reference now if appropriate
|
||
if (p.is_exported_inside_namespace.get(ref)) |ns_ref| {
|
||
const name = p.symbols.items[ref.innerIndex()].original_name;
|
||
|
||
p.recordUsage(ns_ref);
|
||
const prop = p.newExpr(E.Dot{
|
||
.target = p.newExpr(E.Identifier.init(ns_ref), loc),
|
||
.name = name,
|
||
.name_loc = loc,
|
||
}, loc);
|
||
|
||
if (p.ts_namespace.expr == .e_identifier and
|
||
p.ts_namespace.expr.e_identifier.ref.eql(ident.ref))
|
||
{
|
||
p.ts_namespace.expr = prop.data;
|
||
}
|
||
|
||
return prop;
|
||
}
|
||
}
|
||
|
||
if (original_name) |name| {
|
||
const result = p.findSymbol(loc, name) catch unreachable;
|
||
var id_clone = ident;
|
||
id_clone.ref = result.ref;
|
||
return p.newExpr(id_clone, loc);
|
||
}
|
||
|
||
return .{
|
||
.data = .{ .e_identifier = ident },
|
||
.loc = loc,
|
||
};
|
||
}
|
||
|
||
pub fn generateImportStmtForBakeResponse(
|
||
noalias p: *P,
|
||
parts: *ListManaged(js_ast.Part),
|
||
) !void {
|
||
bun.assert(!p.response_ref.isNull());
|
||
bun.assert(!p.bun_app_namespace_ref.isNull());
|
||
const allocator = p.allocator;
|
||
|
||
const import_path = "bun:app";
|
||
|
||
const import_record_i = p.addImportRecordByRange(.stmt, logger.Range.None, import_path);
|
||
|
||
var declared_symbols = DeclaredSymbol.List{};
|
||
try declared_symbols.ensureTotalCapacity(allocator, 2);
|
||
|
||
var stmts = try allocator.alloc(Stmt, 1);
|
||
|
||
declared_symbols.appendAssumeCapacity(
|
||
DeclaredSymbol{ .ref = p.bun_app_namespace_ref, .is_top_level = true },
|
||
);
|
||
try p.module_scope.generated.append(allocator, p.bun_app_namespace_ref);
|
||
|
||
const clause_items = try allocator.dupe(js_ast.ClauseItem, &.{
|
||
js_ast.ClauseItem{
|
||
.alias = "Response",
|
||
.original_name = "Response",
|
||
.alias_loc = logger.Loc{},
|
||
.name = LocRef{ .ref = p.response_ref, .loc = logger.Loc{} },
|
||
},
|
||
});
|
||
|
||
declared_symbols.appendAssumeCapacity(DeclaredSymbol{
|
||
.ref = p.response_ref,
|
||
.is_top_level = true,
|
||
});
|
||
|
||
// ensure every e_import_identifier holds the namespace
|
||
if (p.options.features.hot_module_reloading) {
|
||
const symbol = &p.symbols.items[p.response_ref.inner_index];
|
||
bun.assert(symbol.namespace_alias != null);
|
||
symbol.namespace_alias.?.import_record_index = import_record_i;
|
||
}
|
||
|
||
try p.is_import_item.put(allocator, p.response_ref, {});
|
||
try p.named_imports.put(allocator, p.response_ref, js_ast.NamedImport{
|
||
.alias = "Response",
|
||
.alias_loc = logger.Loc{},
|
||
.namespace_ref = p.bun_app_namespace_ref,
|
||
.import_record_index = import_record_i,
|
||
});
|
||
|
||
stmts[0] = p.s(
|
||
S.Import{
|
||
.namespace_ref = p.bun_app_namespace_ref,
|
||
.items = clause_items,
|
||
.import_record_index = import_record_i,
|
||
.is_single_line = true,
|
||
},
|
||
logger.Loc{},
|
||
);
|
||
|
||
var import_records = try allocator.alloc(u32, 1);
|
||
import_records[0] = import_record_i;
|
||
|
||
// This import is placed in a part before the main code, however
|
||
// the bundler ends up re-ordering this to be after... The order
|
||
// does not matter as ESM imports are always hoisted.
|
||
parts.append(js_ast.Part{
|
||
.stmts = stmts,
|
||
.declared_symbols = declared_symbols,
|
||
.import_record_indices = bun.BabyList(u32).fromOwnedSlice(import_records),
|
||
.tag = .runtime,
|
||
}) catch unreachable;
|
||
}
|
||
|
||
pub fn generateImportStmt(
|
||
noalias p: *P,
|
||
import_path: string,
|
||
imports: anytype,
|
||
parts: *ListManaged(js_ast.Part),
|
||
symbols: anytype,
|
||
additional_stmt: ?Stmt,
|
||
comptime prefix: string,
|
||
comptime is_internal: bool,
|
||
) anyerror!void {
|
||
const allocator = p.allocator;
|
||
const import_record_i = p.addImportRecordByRange(.stmt, logger.Range.None, import_path);
|
||
var import_record: *ImportRecord = &p.import_records.items[import_record_i];
|
||
if (comptime is_internal)
|
||
import_record.path.namespace = "runtime";
|
||
import_record.flags.is_internal = is_internal;
|
||
const import_path_identifier = try import_record.path.name.nonUniqueNameString(allocator);
|
||
var namespace_identifier = try allocator.alloc(u8, import_path_identifier.len + prefix.len);
|
||
const 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 = DeclaredSymbol.List{};
|
||
try declared_symbols.ensureTotalCapacity(allocator, imports.len + 1);
|
||
bun.copy(u8, namespace_identifier, prefix);
|
||
bun.copy(u8, namespace_identifier[prefix.len..], import_path_identifier);
|
||
|
||
const namespace_ref = try p.newSymbol(.other, namespace_identifier);
|
||
declared_symbols.appendAssumeCapacity(.{
|
||
.ref = namespace_ref,
|
||
.is_top_level = true,
|
||
});
|
||
try p.module_scope.generated.append(allocator, namespace_ref);
|
||
for (imports, clause_items) |alias, *clause_item| {
|
||
const ref = symbols.get(alias) orelse unreachable;
|
||
const alias_name = if (@TypeOf(symbols) == RuntimeImports) RuntimeImports.all[alias] else alias;
|
||
clause_item.* = js_ast.ClauseItem{
|
||
.alias = alias_name,
|
||
.original_name = alias_name,
|
||
.alias_loc = logger.Loc{},
|
||
.name = LocRef{ .ref = ref, .loc = logger.Loc{} },
|
||
};
|
||
declared_symbols.appendAssumeCapacity(.{ .ref = ref, .is_top_level = true });
|
||
|
||
// ensure every e_import_identifier holds the namespace
|
||
if (p.options.features.hot_module_reloading) {
|
||
const symbol = &p.symbols.items[ref.inner_index];
|
||
if (symbol.namespace_alias == null) {
|
||
symbol.namespace_alias = .{
|
||
.namespace_ref = namespace_ref,
|
||
.alias = alias_name,
|
||
.import_record_index = import_record_i,
|
||
};
|
||
}
|
||
}
|
||
|
||
try p.is_import_item.put(allocator, ref, {});
|
||
try p.named_imports.put(allocator, ref, js_ast.NamedImport{
|
||
.alias = alias_name,
|
||
.alias_loc = logger.Loc{},
|
||
.namespace_ref = namespace_ref,
|
||
.import_record_index = import_record_i,
|
||
});
|
||
}
|
||
|
||
stmts[0] = p.s(
|
||
S.Import{
|
||
.namespace_ref = namespace_ref,
|
||
.items = clause_items,
|
||
.import_record_index = import_record_i,
|
||
.is_single_line = true,
|
||
},
|
||
logger.Loc{},
|
||
);
|
||
if (additional_stmt) |add| {
|
||
stmts[1] = add;
|
||
}
|
||
|
||
var import_records = try allocator.alloc(@TypeOf(import_record_i), 1);
|
||
import_records[0] = import_record_i;
|
||
|
||
// This import is placed in a part before the main code, however
|
||
// the bundler ends up re-ordering this to be after... The order
|
||
// does not matter as ESM imports are always hoisted.
|
||
parts.append(js_ast.Part{
|
||
.stmts = stmts,
|
||
.declared_symbols = declared_symbols,
|
||
.import_record_indices = bun.BabyList(u32).fromOwnedSlice(import_records),
|
||
.tag = .runtime,
|
||
}) catch unreachable;
|
||
}
|
||
|
||
pub fn generateReactRefreshImport(
|
||
noalias p: *P,
|
||
parts: *ListManaged(js_ast.Part),
|
||
import_path: []const u8,
|
||
clauses: []const ReactRefreshImportClause,
|
||
) !void {
|
||
switch (p.options.features.hot_module_reloading) {
|
||
inline else => |hmr| try p.generateReactRefreshImportHmr(parts, import_path, clauses, hmr),
|
||
}
|
||
}
|
||
|
||
const ReactRefreshImportClause = struct {
|
||
name: []const u8,
|
||
enabled: bool,
|
||
ref: Ref,
|
||
};
|
||
|
||
fn generateReactRefreshImportHmr(
|
||
noalias p: *P,
|
||
parts: *ListManaged(js_ast.Part),
|
||
import_path: []const u8,
|
||
clauses: []const ReactRefreshImportClause,
|
||
comptime hot_module_reloading: bool,
|
||
) !void {
|
||
// If `hot_module_reloading`, we are going to generate a require call:
|
||
//
|
||
// const { $RefreshSig$, $RefreshReg$ } = require("react-refresh/runtime")`
|
||
//
|
||
// Otherwise we are going to settle on an import statement. Using
|
||
// require is fine in HMR bundling because `react-refresh` itself is
|
||
// already a CommonJS module, and it will actually be more efficient
|
||
// at runtime this way.
|
||
const allocator = p.allocator;
|
||
const import_record_index = p.addImportRecordByRange(.stmt, logger.Range.None, import_path);
|
||
|
||
const Item = if (hot_module_reloading) B.Object.Property else js_ast.ClauseItem;
|
||
|
||
const len = 1 + @as(usize, @intFromBool(p.react_refresh.register_used)) +
|
||
@as(usize, @intFromBool(p.react_refresh.signature_used));
|
||
var items = try List(Item).initCapacity(allocator, len);
|
||
|
||
const stmts = try allocator.alloc(Stmt, 1);
|
||
var declared_symbols = DeclaredSymbol.List{};
|
||
try declared_symbols.ensureTotalCapacity(allocator, len);
|
||
|
||
const namespace_ref = try p.newSymbol(.other, "RefreshRuntime");
|
||
declared_symbols.appendAssumeCapacity(.{
|
||
.ref = namespace_ref,
|
||
.is_top_level = true,
|
||
});
|
||
try p.module_scope.generated.append(allocator, namespace_ref);
|
||
|
||
for (clauses) |entry| {
|
||
if (entry.enabled) {
|
||
items.appendAssumeCapacity(if (hot_module_reloading) .{
|
||
.key = p.newExpr(E.String{ .data = entry.name }, logger.Loc.Empty),
|
||
.value = p.b(B.Identifier{ .ref = entry.ref }, logger.Loc.Empty),
|
||
} else .{
|
||
.alias = entry.name,
|
||
.original_name = entry.name,
|
||
.alias_loc = logger.Loc{},
|
||
.name = LocRef{ .ref = entry.ref, .loc = logger.Loc{} },
|
||
});
|
||
declared_symbols.appendAssumeCapacity(.{ .ref = entry.ref, .is_top_level = true });
|
||
try p.module_scope.generated.append(allocator, entry.ref);
|
||
try p.is_import_item.put(allocator, entry.ref, {});
|
||
try p.named_imports.put(allocator, entry.ref, .{
|
||
.alias = entry.name,
|
||
.alias_loc = logger.Loc.Empty,
|
||
.namespace_ref = namespace_ref,
|
||
.import_record_index = import_record_index,
|
||
});
|
||
}
|
||
}
|
||
|
||
stmts[0] = p.s(if (hot_module_reloading)
|
||
S.Local{
|
||
.kind = .k_const,
|
||
.decls = try Decl.List.fromSlice(p.allocator, &.{.{
|
||
.binding = p.b(B.Object{
|
||
.properties = items.items,
|
||
}, logger.Loc.Empty),
|
||
.value = p.newExpr(E.RequireString{
|
||
.import_record_index = import_record_index,
|
||
}, logger.Loc.Empty),
|
||
}}),
|
||
}
|
||
else
|
||
S.Import{
|
||
.namespace_ref = namespace_ref,
|
||
.items = items.items,
|
||
.import_record_index = import_record_index,
|
||
.is_single_line = false,
|
||
}, logger.Loc.Empty);
|
||
|
||
try parts.append(.{
|
||
.stmts = stmts,
|
||
.declared_symbols = declared_symbols,
|
||
.import_record_indices = try bun.BabyList(u32).fromSlice(allocator, &.{import_record_index}),
|
||
.tag = .runtime,
|
||
});
|
||
}
|
||
|
||
pub fn substituteSingleUseSymbolInStmt(noalias p: *P, stmt: Stmt, ref: Ref, replacement: Expr) bool {
|
||
const expr: *Expr = brk: {
|
||
switch (stmt.data) {
|
||
.s_expr => |exp| {
|
||
break :brk &exp.value;
|
||
},
|
||
.s_throw => |throw| {
|
||
break :brk &throw.value;
|
||
},
|
||
.s_return => |ret| {
|
||
if (ret.value) |*value| {
|
||
break :brk value;
|
||
}
|
||
},
|
||
.s_if => |if_stmt| {
|
||
break :brk &if_stmt.test_;
|
||
},
|
||
.s_switch => |switch_stmt| {
|
||
break :brk &switch_stmt.test_;
|
||
},
|
||
.s_local => |local| {
|
||
if (local.decls.len > 0) {
|
||
var first: *Decl = &local.decls.ptr[0];
|
||
if (first.value) |*value| {
|
||
if (first.binding.data == .b_identifier) {
|
||
break :brk value;
|
||
}
|
||
}
|
||
}
|
||
},
|
||
else => {},
|
||
}
|
||
|
||
return false;
|
||
};
|
||
|
||
// Only continue trying to insert this replacement into sub-expressions
|
||
// after the first one if the replacement has no side effects:
|
||
//
|
||
// // Substitution is ok
|
||
// let replacement = 123;
|
||
// return x + replacement;
|
||
//
|
||
// // Substitution is not ok because "fn()" may change "x"
|
||
// let replacement = fn();
|
||
// return x + replacement;
|
||
//
|
||
// // Substitution is not ok because "x == x" may change "x" due to "valueOf()" evaluation
|
||
// let replacement = [x];
|
||
// return (x == x) + replacement;
|
||
//
|
||
const replacement_can_be_removed = p.exprCanBeRemovedIfUnused(&replacement);
|
||
switch (p.substituteSingleUseSymbolInExpr(expr.*, ref, replacement, replacement_can_be_removed)) {
|
||
.success => |result| {
|
||
if (result.data == .e_binary or result.data == .e_unary or result.data == .e_if) {
|
||
const prev_substituting = p.is_revisit_for_substitution;
|
||
p.is_revisit_for_substitution = true;
|
||
defer p.is_revisit_for_substitution = prev_substituting;
|
||
// O(n^2) and we will need to think more carefully about
|
||
// this once we implement syntax compression
|
||
expr.* = p.visitExpr(result);
|
||
} else {
|
||
expr.* = result;
|
||
}
|
||
|
||
return true;
|
||
},
|
||
else => {},
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
fn substituteSingleUseSymbolInExpr(
|
||
noalias p: *P,
|
||
expr: Expr,
|
||
ref: Ref,
|
||
replacement: Expr,
|
||
replacement_can_be_removed: bool,
|
||
) Substitution {
|
||
outer: {
|
||
switch (expr.data) {
|
||
.e_identifier => |ident| {
|
||
if (ident.ref.eql(ref) or p.symbols.items[ident.ref.innerIndex()].link.eql(ref)) {
|
||
p.ignoreUsage(ref);
|
||
return .{ .success = replacement };
|
||
}
|
||
},
|
||
.e_new => |new| {
|
||
switch (p.substituteSingleUseSymbolInExpr(new.target, ref, replacement, replacement_can_be_removed)) {
|
||
.continue_ => {},
|
||
.success => |result| {
|
||
new.target = result;
|
||
return .{ .success = expr };
|
||
},
|
||
.failure => |result| {
|
||
new.target = result;
|
||
return .{ .failure = expr };
|
||
},
|
||
}
|
||
|
||
if (replacement_can_be_removed) {
|
||
for (new.args.slice()) |*arg| {
|
||
switch (p.substituteSingleUseSymbolInExpr(arg.*, ref, replacement, replacement_can_be_removed)) {
|
||
.continue_ => {},
|
||
.success => |result| {
|
||
arg.* = result;
|
||
return .{ .success = expr };
|
||
},
|
||
.failure => |result| {
|
||
arg.* = result;
|
||
return .{ .failure = expr };
|
||
},
|
||
}
|
||
}
|
||
}
|
||
},
|
||
.e_spread => |spread| {
|
||
switch (p.substituteSingleUseSymbolInExpr(spread.value, ref, replacement, replacement_can_be_removed)) {
|
||
.continue_ => {},
|
||
.success => |result| {
|
||
spread.value = result;
|
||
return .{ .success = expr };
|
||
},
|
||
.failure => |result| {
|
||
spread.value = result;
|
||
return .{ .failure = expr };
|
||
},
|
||
}
|
||
},
|
||
.e_await => |await_expr| {
|
||
switch (p.substituteSingleUseSymbolInExpr(await_expr.value, ref, replacement, replacement_can_be_removed)) {
|
||
.continue_ => {},
|
||
.success => |result| {
|
||
await_expr.value = result;
|
||
return .{ .success = expr };
|
||
},
|
||
.failure => |result| {
|
||
await_expr.value = result;
|
||
return .{ .failure = expr };
|
||
},
|
||
}
|
||
},
|
||
.e_yield => |yield| {
|
||
switch (p.substituteSingleUseSymbolInExpr(yield.value orelse Expr{ .data = .{ .e_missing = .{} }, .loc = expr.loc }, ref, replacement, replacement_can_be_removed)) {
|
||
.continue_ => {},
|
||
.success => |result| {
|
||
yield.value = result;
|
||
return .{ .success = expr };
|
||
},
|
||
.failure => |result| {
|
||
yield.value = result;
|
||
return .{ .failure = expr };
|
||
},
|
||
}
|
||
},
|
||
.e_import => |import| {
|
||
switch (p.substituteSingleUseSymbolInExpr(import.expr, ref, replacement, replacement_can_be_removed)) {
|
||
.continue_ => {},
|
||
.success => |result| {
|
||
import.expr = result;
|
||
return .{ .success = expr };
|
||
},
|
||
.failure => |result| {
|
||
import.expr = result;
|
||
return .{ .failure = expr };
|
||
},
|
||
}
|
||
|
||
// The "import()" expression has side effects but the side effects are
|
||
// always asynchronous so there is no way for the side effects to modify
|
||
// the replacement value. So it's ok to reorder the replacement value
|
||
// past the "import()" expression assuming everything else checks out.
|
||
|
||
if (replacement_can_be_removed and p.exprCanBeRemovedIfUnused(&import.expr)) {
|
||
return .{ .continue_ = expr };
|
||
}
|
||
},
|
||
.e_unary => |e| {
|
||
switch (e.op) {
|
||
.un_pre_inc, .un_post_inc, .un_pre_dec, .un_post_dec, .un_delete => {
|
||
// Do not substitute into an assignment position
|
||
},
|
||
else => {
|
||
switch (p.substituteSingleUseSymbolInExpr(e.value, ref, replacement, replacement_can_be_removed)) {
|
||
.continue_ => {},
|
||
.success => |result| {
|
||
e.value = result;
|
||
return .{ .success = expr };
|
||
},
|
||
.failure => |result| {
|
||
e.value = result;
|
||
return .{ .failure = expr };
|
||
},
|
||
}
|
||
},
|
||
}
|
||
},
|
||
.e_dot => |e| {
|
||
switch (p.substituteSingleUseSymbolInExpr(e.target, ref, replacement, replacement_can_be_removed)) {
|
||
.continue_ => {},
|
||
.success => |result| {
|
||
e.target = result;
|
||
return .{ .success = expr };
|
||
},
|
||
.failure => |result| {
|
||
e.target = result;
|
||
return .{ .failure = expr };
|
||
},
|
||
}
|
||
},
|
||
.e_binary => |e| {
|
||
// Do not substitute into an assignment position
|
||
if (e.op.binaryAssignTarget() == .none) {
|
||
switch (p.substituteSingleUseSymbolInExpr(e.left, ref, replacement, replacement_can_be_removed)) {
|
||
.continue_ => {},
|
||
.success => |result| {
|
||
e.left = result;
|
||
|
||
return .{ .success = expr };
|
||
},
|
||
.failure => |result| {
|
||
e.left = result;
|
||
return .{ .failure = expr };
|
||
},
|
||
}
|
||
} else if (!p.exprCanBeRemovedIfUnused(&e.left)) {
|
||
// Do not reorder past a side effect in an assignment target, as that may
|
||
// change the replacement value. For example, "fn()" may change "a" here:
|
||
//
|
||
// let a = 1;
|
||
// foo[fn()] = a;
|
||
//
|
||
return .{ .failure = expr };
|
||
} else if (e.op.binaryAssignTarget() == .update and !replacement_can_be_removed) {
|
||
// If this is a read-modify-write assignment and the replacement has side
|
||
// effects, don't reorder it past the assignment target. The assignment
|
||
// target is being read so it may be changed by the side effect. For
|
||
// example, "fn()" may change "foo" here:
|
||
//
|
||
// let a = fn();
|
||
// foo += a;
|
||
//
|
||
return .{ .failure = expr };
|
||
}
|
||
|
||
// If we get here then it should be safe to attempt to substitute the
|
||
// replacement past the left operand into the right operand.
|
||
switch (p.substituteSingleUseSymbolInExpr(e.right, ref, replacement, replacement_can_be_removed)) {
|
||
.continue_ => {},
|
||
.success => |result| {
|
||
e.right = result;
|
||
return .{ .success = expr };
|
||
},
|
||
.failure => |result| {
|
||
e.right = result;
|
||
return .{ .failure = expr };
|
||
},
|
||
}
|
||
},
|
||
.e_if => |e| {
|
||
switch (p.substituteSingleUseSymbolInExpr(expr.data.e_if.test_, ref, replacement, replacement_can_be_removed)) {
|
||
.continue_ => {},
|
||
.success => |result| {
|
||
e.test_ = result;
|
||
return .{ .success = expr };
|
||
},
|
||
.failure => |result| {
|
||
e.test_ = result;
|
||
return .{ .failure = expr };
|
||
},
|
||
}
|
||
|
||
// Do not substitute our unconditionally-executed value into a branch
|
||
// unless the value itself has no side effects
|
||
if (replacement_can_be_removed) {
|
||
// Unlike other branches in this function such as "a && b" or "a?.[b]",
|
||
// the "a ? b : c" form has potential code evaluation along both control
|
||
// flow paths. Handle this by allowing substitution into either branch.
|
||
// Side effects in one branch should not prevent the substitution into
|
||
// the other branch.
|
||
|
||
const yes = p.substituteSingleUseSymbolInExpr(e.yes, ref, replacement, replacement_can_be_removed);
|
||
if (yes == .success) {
|
||
e.yes = yes.success;
|
||
return .{ .success = expr };
|
||
}
|
||
|
||
const no = p.substituteSingleUseSymbolInExpr(e.no, ref, replacement, replacement_can_be_removed);
|
||
if (no == .success) {
|
||
e.no = no.success;
|
||
return .{ .success = expr };
|
||
}
|
||
|
||
// Side effects in either branch should stop us from continuing to try to
|
||
// substitute the replacement after the control flow branches merge again.
|
||
if (yes != .continue_ or no != .continue_) {
|
||
return .{ .failure = expr };
|
||
}
|
||
}
|
||
},
|
||
.e_index => |index| {
|
||
switch (p.substituteSingleUseSymbolInExpr(index.target, ref, replacement, replacement_can_be_removed)) {
|
||
.continue_ => {},
|
||
.success => |result| {
|
||
index.target = result;
|
||
return .{ .success = expr };
|
||
},
|
||
.failure => |result| {
|
||
index.target = result;
|
||
return .{ .failure = expr };
|
||
},
|
||
}
|
||
|
||
// Do not substitute our unconditionally-executed value into a branch
|
||
// unless the value itself has no side effects
|
||
if (replacement_can_be_removed or index.optional_chain == null) {
|
||
switch (p.substituteSingleUseSymbolInExpr(index.index, ref, replacement, replacement_can_be_removed)) {
|
||
.continue_ => {},
|
||
.success => |result| {
|
||
index.index = result;
|
||
return .{ .success = expr };
|
||
},
|
||
.failure => |result| {
|
||
index.index = result;
|
||
return .{ .failure = expr };
|
||
},
|
||
}
|
||
}
|
||
},
|
||
|
||
.e_call => |e| {
|
||
// Don't substitute something into a call target that could change "this"
|
||
switch (replacement.data) {
|
||
.e_dot, .e_index => {
|
||
if (e.target.data == .e_identifier and e.target.data.e_identifier.ref.eql(ref)) {
|
||
break :outer;
|
||
}
|
||
},
|
||
else => {},
|
||
}
|
||
|
||
switch (p.substituteSingleUseSymbolInExpr(e.target, ref, replacement, replacement_can_be_removed)) {
|
||
.continue_ => {},
|
||
.success => |result| {
|
||
e.target = result;
|
||
return .{ .success = expr };
|
||
},
|
||
.failure => |result| {
|
||
e.target = result;
|
||
return .{ .failure = expr };
|
||
},
|
||
}
|
||
|
||
// Do not substitute our unconditionally-executed value into a branch
|
||
// unless the value itself has no side effects
|
||
if (replacement_can_be_removed or e.optional_chain == null) {
|
||
for (e.args.slice()) |*arg| {
|
||
switch (p.substituteSingleUseSymbolInExpr(arg.*, ref, replacement, replacement_can_be_removed)) {
|
||
.continue_ => {},
|
||
.success => |result| {
|
||
arg.* = result;
|
||
return .{ .success = expr };
|
||
},
|
||
.failure => |result| {
|
||
arg.* = result;
|
||
return .{ .failure = expr };
|
||
},
|
||
}
|
||
}
|
||
}
|
||
},
|
||
|
||
.e_array => |e| {
|
||
for (e.items.slice()) |*item| {
|
||
switch (p.substituteSingleUseSymbolInExpr(item.*, ref, replacement, replacement_can_be_removed)) {
|
||
.continue_ => {},
|
||
.success => |result| {
|
||
item.* = result;
|
||
return .{ .success = expr };
|
||
},
|
||
.failure => |result| {
|
||
item.* = result;
|
||
return .{ .failure = expr };
|
||
},
|
||
}
|
||
}
|
||
},
|
||
|
||
.e_object => |e| {
|
||
for (e.properties.slice()) |*property| {
|
||
// Check the key
|
||
|
||
if (property.flags.contains(.is_computed)) {
|
||
switch (p.substituteSingleUseSymbolInExpr(property.key.?, ref, replacement, replacement_can_be_removed)) {
|
||
.continue_ => {},
|
||
.success => |result| {
|
||
property.key = result;
|
||
return .{ .success = expr };
|
||
},
|
||
.failure => |result| {
|
||
property.key = result;
|
||
return .{ .failure = expr };
|
||
},
|
||
}
|
||
|
||
// Stop now because both computed keys and property spread have side effects
|
||
return .{ .failure = expr };
|
||
}
|
||
|
||
// Check the value
|
||
if (property.value) |value| {
|
||
switch (p.substituteSingleUseSymbolInExpr(value, ref, replacement, replacement_can_be_removed)) {
|
||
.continue_ => {},
|
||
.success => |result| {
|
||
if (result.data == .e_missing) {
|
||
property.value = null;
|
||
} else {
|
||
property.value = result;
|
||
}
|
||
return .{ .success = expr };
|
||
},
|
||
.failure => |result| {
|
||
if (result.data == .e_missing) {
|
||
property.value = null;
|
||
} else {
|
||
property.value = result;
|
||
}
|
||
return .{ .failure = expr };
|
||
},
|
||
}
|
||
}
|
||
}
|
||
},
|
||
|
||
.e_template => |e| {
|
||
if (e.tag) |*tag| {
|
||
switch (p.substituteSingleUseSymbolInExpr(tag.*, ref, replacement, replacement_can_be_removed)) {
|
||
.continue_ => {},
|
||
.success => |result| {
|
||
tag.* = result;
|
||
return .{ .success = expr };
|
||
},
|
||
.failure => |result| {
|
||
tag.* = result;
|
||
return .{ .failure = expr };
|
||
},
|
||
}
|
||
}
|
||
|
||
for (e.parts) |*part| {
|
||
switch (p.substituteSingleUseSymbolInExpr(part.value, ref, replacement, replacement_can_be_removed)) {
|
||
.continue_ => {},
|
||
.success => |result| {
|
||
part.value = result;
|
||
|
||
// todo: mangle template parts
|
||
|
||
return .{ .success = expr };
|
||
},
|
||
.failure => |result| {
|
||
part.value = result;
|
||
return .{ .failure = expr };
|
||
},
|
||
}
|
||
}
|
||
},
|
||
else => {},
|
||
}
|
||
}
|
||
|
||
// If both the replacement and this expression have no observable side
|
||
// effects, then we can reorder the replacement past this expression
|
||
if (replacement_can_be_removed and p.exprCanBeRemovedIfUnused(&expr)) {
|
||
return .{ .continue_ = expr };
|
||
}
|
||
|
||
const tag: Expr.Tag = @as(Expr.Tag, expr.data);
|
||
|
||
// We can always reorder past primitive values
|
||
if (tag.isPrimitiveLiteral()) {
|
||
return .{ .continue_ = expr };
|
||
}
|
||
|
||
// Otherwise we should stop trying to substitute past this point
|
||
return .{ .failure = expr };
|
||
}
|
||
|
||
pub fn prepareForVisitPass(noalias p: *P) anyerror!void {
|
||
{
|
||
var i: usize = 0;
|
||
p.scope_order_to_visit = try p.allocator.alloc(ScopeOrder, p.scopes_in_order.items.len);
|
||
for (p.scopes_in_order.items) |item| {
|
||
if (item) |_item| {
|
||
p.scope_order_to_visit[i] = _item;
|
||
i += 1;
|
||
}
|
||
}
|
||
p.scope_order_to_visit.len = i;
|
||
}
|
||
|
||
p.is_file_considered_to_have_esm_exports =
|
||
!p.top_level_await_keyword.isEmpty() or !p.esm_export_keyword.isEmpty() or
|
||
p.options.module_type == .esm;
|
||
|
||
try p.pushScopeForVisitPass(js_ast.Scope.Kind.entry, locModuleScope);
|
||
p.fn_or_arrow_data_visit.is_outside_fn_or_arrow = true;
|
||
p.module_scope = p.current_scope;
|
||
p.has_es_module_syntax = p.has_es_module_syntax or p.esm_import_keyword.len > 0 or p.esm_export_keyword.len > 0 or p.top_level_await_keyword.len > 0;
|
||
|
||
if (p.lexer.jsx_pragma.jsx()) |factory| {
|
||
p.options.jsx.factory = options.JSX.Pragma.memberListToComponentsIfDifferent(p.allocator, p.options.jsx.factory, factory.text) catch unreachable;
|
||
}
|
||
|
||
if (p.lexer.jsx_pragma.jsxFrag()) |fragment| {
|
||
p.options.jsx.fragment = options.JSX.Pragma.memberListToComponentsIfDifferent(p.allocator, p.options.jsx.fragment, fragment.text) catch unreachable;
|
||
}
|
||
|
||
if (p.lexer.jsx_pragma.jsxImportSource()) |import_source| {
|
||
p.options.jsx.classic_import_source = import_source.text;
|
||
p.options.jsx.package_name = p.options.jsx.classic_import_source;
|
||
p.options.jsx.setImportSource(p.allocator);
|
||
}
|
||
|
||
if (p.lexer.jsx_pragma.jsxRuntime()) |runtime| {
|
||
if (options.JSX.RuntimeMap.get(runtime.text)) |jsx_runtime| {
|
||
p.options.jsx.runtime = jsx_runtime.runtime;
|
||
if (jsx_runtime.development) |dev| {
|
||
p.options.jsx.development = dev;
|
||
}
|
||
} else {
|
||
// make this a warning instead of an error because we don't support "preserve" right now
|
||
try p.log.addRangeWarningFmt(p.source, runtime.range, p.allocator, "Unsupported JSX runtime: \"{s}\"", .{runtime.text});
|
||
}
|
||
}
|
||
|
||
// ECMAScript modules are always interpreted as strict mode. This has to be
|
||
// done before "hoistSymbols" because strict mode can alter hoisting (!).
|
||
if (p.esm_import_keyword.len > 0) {
|
||
p.module_scope.recursiveSetStrictMode(js_ast.StrictModeKind.implicit_strict_mode_import);
|
||
} else if (p.esm_export_keyword.len > 0) {
|
||
p.module_scope.recursiveSetStrictMode(js_ast.StrictModeKind.implicit_strict_mode_export);
|
||
} else if (p.top_level_await_keyword.len > 0) {
|
||
p.module_scope.recursiveSetStrictMode(js_ast.StrictModeKind.implicit_strict_mode_top_level_await);
|
||
}
|
||
|
||
p.hoistSymbols(p.module_scope);
|
||
|
||
var generated_symbols_count: u32 = 3;
|
||
|
||
if (p.options.features.react_fast_refresh) {
|
||
generated_symbols_count += 3;
|
||
}
|
||
|
||
if (is_jsx_enabled) {
|
||
generated_symbols_count += 7;
|
||
|
||
if (p.options.jsx.development) generated_symbols_count += 1;
|
||
}
|
||
|
||
try p.module_scope.generated.ensureUnusedCapacity(p.allocator, generated_symbols_count * 3);
|
||
try p.module_scope.members.ensureUnusedCapacity(p.allocator, generated_symbols_count * 3 + p.module_scope.members.count());
|
||
|
||
p.exports_ref = try p.declareCommonJSSymbol(.hoisted, "exports");
|
||
p.module_ref = try p.declareCommonJSSymbol(.hoisted, "module");
|
||
|
||
p.require_ref = try p.declareCommonJSSymbol(.unbound, "require");
|
||
p.dirname_ref = try p.declareCommonJSSymbol(.unbound, "__dirname");
|
||
p.filename_ref = try p.declareCommonJSSymbol(.unbound, "__filename");
|
||
|
||
if (p.options.features.inject_jest_globals) {
|
||
p.jest.@"test" = try p.declareCommonJSSymbol(.unbound, "test");
|
||
p.jest.it = try p.declareCommonJSSymbol(.unbound, "it");
|
||
p.jest.describe = try p.declareCommonJSSymbol(.unbound, "describe");
|
||
p.jest.expect = try p.declareCommonJSSymbol(.unbound, "expect");
|
||
p.jest.expectTypeOf = try p.declareCommonJSSymbol(.unbound, "expectTypeOf");
|
||
p.jest.beforeAll = try p.declareCommonJSSymbol(.unbound, "beforeAll");
|
||
p.jest.beforeEach = try p.declareCommonJSSymbol(.unbound, "beforeEach");
|
||
p.jest.afterEach = try p.declareCommonJSSymbol(.unbound, "afterEach");
|
||
p.jest.afterAll = try p.declareCommonJSSymbol(.unbound, "afterAll");
|
||
p.jest.jest = try p.declareCommonJSSymbol(.unbound, "jest");
|
||
p.jest.vi = try p.declareCommonJSSymbol(.unbound, "vi");
|
||
p.jest.xit = try p.declareCommonJSSymbol(.unbound, "xit");
|
||
p.jest.xtest = try p.declareCommonJSSymbol(.unbound, "xtest");
|
||
p.jest.xdescribe = try p.declareCommonJSSymbol(.unbound, "xdescribe");
|
||
}
|
||
|
||
if (p.options.features.react_fast_refresh) {
|
||
p.react_refresh.create_signature_ref = try p.declareGeneratedSymbol(.other, "$RefreshSig$");
|
||
p.react_refresh.register_ref = try p.declareGeneratedSymbol(.other, "$RefreshReg$");
|
||
}
|
||
|
||
switch (p.options.features.server_components) {
|
||
.none, .client_side => {},
|
||
.wrap_exports_for_client_reference => {
|
||
p.server_components_wrap_ref = try p.declareGeneratedSymbol(.other, "registerClientReference");
|
||
},
|
||
// TODO: these wrapping modes.
|
||
.wrap_anon_server_functions => {},
|
||
.wrap_exports_for_server_reference => {},
|
||
}
|
||
|
||
// Server-side components:
|
||
// Declare upfront the symbols for "Response" and "bun:app"
|
||
switch (p.options.features.server_components) {
|
||
.none, .client_side => {},
|
||
else => {
|
||
p.response_ref = try p.declareGeneratedSymbol(.import, "Response");
|
||
p.bun_app_namespace_ref = try p.newSymbol(
|
||
.other,
|
||
"import_bun_app",
|
||
);
|
||
const symbol = &p.symbols.items[p.response_ref.inner_index];
|
||
symbol.namespace_alias = .{
|
||
.namespace_ref = p.bun_app_namespace_ref,
|
||
.alias = "Response",
|
||
.import_record_index = std.math.maxInt(u32),
|
||
};
|
||
},
|
||
}
|
||
|
||
if (p.options.features.hot_module_reloading) {
|
||
p.hmr_api_ref = try p.declareCommonJSSymbol(.unbound, "hmr");
|
||
}
|
||
}
|
||
|
||
fn ensureRequireSymbol(p: *P) void {
|
||
if (p.runtime_imports.__require != null) return;
|
||
const static_symbol = generatedSymbolName("__require");
|
||
p.runtime_imports.__require = bun.handleOom(declareSymbolMaybeGenerated(p, .other, logger.Loc.Empty, static_symbol, true));
|
||
p.runtime_imports.put("__require", p.runtime_imports.__require.?);
|
||
}
|
||
|
||
pub fn resolveCommonJSSymbols(p: *P) void {
|
||
if (!p.options.features.allow_runtime)
|
||
return;
|
||
|
||
p.ensureRequireSymbol();
|
||
}
|
||
|
||
fn willUseRenamer(p: *P) bool {
|
||
return p.options.bundle or p.options.features.minify_identifiers;
|
||
}
|
||
|
||
fn hoistSymbols(noalias p: *P, scope: *js_ast.Scope) void {
|
||
if (!scope.kindStopsHoisting()) {
|
||
var iter = scope.members.iterator();
|
||
const allocator = p.allocator;
|
||
var symbols = p.symbols.items;
|
||
|
||
defer {
|
||
if (comptime Environment.allow_assert) {
|
||
// we call `.newSymbol` in this function
|
||
// we need to avoid using a potentially re-sized array
|
||
// so we assert that the array is in sync
|
||
assert(symbols.ptr == p.symbols.items.ptr);
|
||
assert(symbols.len == p.symbols.items.len);
|
||
}
|
||
}
|
||
|
||
// Check for collisions that would prevent to hoisting "var" symbols up to the enclosing function scope
|
||
if (scope.parent) |parent_scope| {
|
||
nextMember: while (iter.next()) |res| {
|
||
var value = res.value_ptr.*;
|
||
var symbol: *Symbol = &symbols[value.ref.innerIndex()];
|
||
|
||
const name = symbol.original_name;
|
||
var hash: ?u64 = null;
|
||
|
||
if (parent_scope.kind == .catch_binding and symbol.kind != .hoisted) {
|
||
hash = Scope.getMemberHash(name);
|
||
if (parent_scope.getMemberWithHash(name, hash.?)) |existing_member| {
|
||
p.log.addSymbolAlreadyDeclaredError(
|
||
p.allocator,
|
||
p.source,
|
||
symbol.original_name,
|
||
value.loc,
|
||
existing_member.loc,
|
||
) catch unreachable;
|
||
continue;
|
||
}
|
||
}
|
||
|
||
if (!symbol.isHoisted()) {
|
||
continue;
|
||
}
|
||
|
||
var __scope = scope.parent;
|
||
if (comptime Environment.allow_assert)
|
||
assert(__scope != null);
|
||
|
||
var is_sloppy_mode_block_level_fn_stmt = false;
|
||
const original_member_ref = value.ref;
|
||
|
||
if (p.willUseRenamer() and symbol.kind == .hoisted_function) {
|
||
// Block-level function declarations behave like "let" in strict mode
|
||
if (scope.strict_mode != .sloppy_mode) {
|
||
continue;
|
||
}
|
||
|
||
// In sloppy mode, block level functions behave like "let" except with
|
||
// an assignment to "var", sort of. This code:
|
||
//
|
||
// if (x) {
|
||
// f();
|
||
// function f() {}
|
||
// }
|
||
// f();
|
||
//
|
||
// behaves like this code:
|
||
//
|
||
// if (x) {
|
||
// let f2 = function() {}
|
||
// var f = f2;
|
||
// f2();
|
||
// }
|
||
// f();
|
||
//
|
||
const hoisted_ref = p.newSymbol(.hoisted, symbol.original_name) catch unreachable;
|
||
symbols = p.symbols.items;
|
||
bun.handleOom(scope.generated.append(p.allocator, hoisted_ref));
|
||
p.hoisted_ref_for_sloppy_mode_block_fn.put(p.allocator, value.ref, hoisted_ref) catch unreachable;
|
||
value.ref = hoisted_ref;
|
||
symbol = &symbols[hoisted_ref.innerIndex()];
|
||
is_sloppy_mode_block_level_fn_stmt = true;
|
||
}
|
||
|
||
if (hash == null) hash = Scope.getMemberHash(name);
|
||
|
||
while (__scope) |_scope| {
|
||
const scope_kind = _scope.kind;
|
||
|
||
// Variable declarations hoisted past a "with" statement may actually end
|
||
// up overwriting a property on the target of the "with" statement instead
|
||
// of initializing the variable. We must not rename them or we risk
|
||
// causing a behavior change.
|
||
//
|
||
// var obj = { foo: 1 }
|
||
// with (obj) { var foo = 2 }
|
||
// assert(foo === undefined)
|
||
// assert(obj.foo === 2)
|
||
//
|
||
if (scope_kind == .with) {
|
||
symbol.must_not_be_renamed = true;
|
||
}
|
||
|
||
if (_scope.getMemberWithHash(name, hash.?)) |member_in_scope| {
|
||
var existing_symbol: *Symbol = &symbols[member_in_scope.ref.innerIndex()];
|
||
const existing_kind = existing_symbol.kind;
|
||
|
||
// We can hoist the symbol from the child scope into the symbol in
|
||
// this scope if:
|
||
//
|
||
// - The symbol is unbound (i.e. a global variable access)
|
||
// - The symbol is also another hoisted variable
|
||
// - The symbol is a function of any kind and we're in a function or module scope
|
||
//
|
||
// Is this unbound (i.e. a global access) or also hoisted?
|
||
if (existing_kind == .unbound or existing_kind == .hoisted or
|
||
(Symbol.isKindFunction(existing_kind) and (scope_kind == .entry or scope_kind == .function_body)))
|
||
{
|
||
// Silently merge this symbol into the existing symbol
|
||
symbol.link = member_in_scope.ref;
|
||
const entry = _scope.getOrPutMemberWithHash(p.allocator, name, hash.?) catch unreachable;
|
||
entry.value_ptr.* = member_in_scope;
|
||
entry.key_ptr.* = name;
|
||
continue :nextMember;
|
||
}
|
||
|
||
// Otherwise if this isn't a catch identifier, it's a collision
|
||
if (existing_kind != .catch_identifier and existing_kind != .arguments) {
|
||
|
||
// An identifier binding from a catch statement and a function
|
||
// declaration can both silently shadow another hoisted symbol
|
||
if (symbol.kind != .catch_identifier and symbol.kind != .hoisted_function) {
|
||
if (!is_sloppy_mode_block_level_fn_stmt) {
|
||
const r = js_lexer.rangeOfIdentifier(p.source, value.loc);
|
||
var notes = allocator.alloc(logger.Data, 1) catch unreachable;
|
||
notes[0] =
|
||
logger.rangeData(
|
||
p.source,
|
||
r,
|
||
std.fmt.allocPrint(
|
||
allocator,
|
||
"{s} was originally declared here",
|
||
.{name},
|
||
) catch unreachable,
|
||
);
|
||
|
||
p.log.addRangeErrorFmtWithNotes(p.source, js_lexer.rangeOfIdentifier(p.source, member_in_scope.loc), allocator, notes, "{s} has already been declared", .{name}) catch unreachable;
|
||
} else if (_scope == scope.parent) {
|
||
// Never mind about this, turns out it's not needed after all
|
||
_ = p.hoisted_ref_for_sloppy_mode_block_fn.remove(original_member_ref);
|
||
}
|
||
}
|
||
continue :nextMember;
|
||
}
|
||
|
||
// If this is a catch identifier, silently merge the existing symbol
|
||
// into this symbol but continue hoisting past this catch scope
|
||
existing_symbol.link = value.ref;
|
||
const entry = _scope.getOrPutMemberWithHash(p.allocator, name, hash.?) catch unreachable;
|
||
entry.value_ptr.* = value;
|
||
entry.key_ptr.* = name;
|
||
}
|
||
|
||
if (_scope.kindStopsHoisting()) {
|
||
const entry = _scope.getOrPutMemberWithHash(allocator, name, hash.?) catch unreachable;
|
||
entry.value_ptr.* = value;
|
||
entry.key_ptr.* = name;
|
||
break;
|
||
}
|
||
|
||
__scope = _scope.parent;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
{
|
||
const children = scope.children.slice();
|
||
for (children) |child| {
|
||
p.hoistSymbols(child);
|
||
}
|
||
}
|
||
}
|
||
|
||
inline fn nextScopeInOrderForVisitPass(p: *P) ScopeOrder {
|
||
const head = p.scope_order_to_visit[0];
|
||
p.scope_order_to_visit = p.scope_order_to_visit[1..p.scope_order_to_visit.len];
|
||
return head;
|
||
}
|
||
|
||
pub fn pushScopeForVisitPass(noalias p: *P, kind: js_ast.Scope.Kind, loc: logger.Loc) anyerror!void {
|
||
const order = p.nextScopeInOrderForVisitPass();
|
||
|
||
// Sanity-check that the scopes generated by the first and second passes match
|
||
if (bun.Environment.allow_assert and
|
||
order.loc.start != loc.start or order.scope.kind != kind)
|
||
{
|
||
p.log.level = .verbose;
|
||
|
||
bun.handleOom(p.log.addDebugFmt(p.source, loc, p.allocator, "Expected this scope (.{s})", .{@tagName(kind)}));
|
||
bun.handleOom(p.log.addDebugFmt(p.source, order.loc, p.allocator, "Found this scope (.{s})", .{@tagName(order.scope.kind)}));
|
||
|
||
p.panic("Scope mismatch while visiting", .{});
|
||
}
|
||
|
||
p.current_scope = order.scope;
|
||
|
||
try p.scopes_for_current_part.append(p.allocator, order.scope);
|
||
}
|
||
|
||
pub fn pushScopeForParsePass(noalias p: *P, comptime kind: js_ast.Scope.Kind, loc: logger.Loc) !usize {
|
||
var parent: *Scope = p.current_scope;
|
||
const allocator = p.allocator;
|
||
var scope = try allocator.create(Scope);
|
||
|
||
scope.* = Scope{
|
||
.kind = kind,
|
||
.label_ref = null,
|
||
.parent = parent,
|
||
.generated = .{},
|
||
};
|
||
|
||
try parent.children.append(allocator, scope);
|
||
scope.strict_mode = parent.strict_mode;
|
||
|
||
p.current_scope = scope;
|
||
|
||
if (comptime kind == .with) {
|
||
// "with" statements change the default from ESModule to CommonJS at runtime.
|
||
// "with" statements are not allowed in strict mode.
|
||
if (p.options.features.commonjs_at_runtime) {
|
||
p.has_with_scope = true;
|
||
}
|
||
}
|
||
|
||
if (comptime Environment.isDebug) {
|
||
// Enforce that scope locations are strictly increasing to help catch bugs
|
||
// where the pushed scopes are mismatched between the first and second passes
|
||
if (p.scopes_in_order.items.len > 0) {
|
||
var last_i = p.scopes_in_order.items.len - 1;
|
||
while (p.scopes_in_order.items[last_i] == null and last_i > 0) {
|
||
last_i -= 1;
|
||
}
|
||
|
||
if (p.scopes_in_order.items[last_i]) |prev_scope| {
|
||
if (prev_scope.loc.start >= loc.start) {
|
||
p.log.level = .verbose;
|
||
bun.handleOom(p.log.addDebugFmt(p.source, prev_scope.loc, p.allocator, "Previous Scope", .{}));
|
||
bun.handleOom(p.log.addDebugFmt(p.source, loc, p.allocator, "Next Scope", .{}));
|
||
p.panic("Scope location {d} must be greater than {d}", .{ loc.start, prev_scope.loc.start });
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Copy down function arguments into the function body scope. That way we get
|
||
// errors if a statement in the function body tries to re-declare any of the
|
||
// arguments.
|
||
if (comptime kind == js_ast.Scope.Kind.function_body) {
|
||
if (comptime Environment.allow_assert)
|
||
assert(parent.kind == js_ast.Scope.Kind.function_args);
|
||
|
||
var iter = scope.parent.?.members.iterator();
|
||
while (iter.next()) |entry| {
|
||
// Don't copy down the optional function expression name. Re-declaring
|
||
// the name of a function expression is allowed.
|
||
const value = entry.value_ptr.*;
|
||
const adjacent_kind = p.symbols.items[value.ref.innerIndex()].kind;
|
||
if (adjacent_kind != .hoisted_function) {
|
||
try scope.members.put(allocator, entry.key_ptr.*, value);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Remember the length in case we call popAndDiscardScope() later
|
||
const scope_index = p.scopes_in_order.items.len;
|
||
try p.scopes_in_order.append(allocator, ScopeOrder{ .loc = loc, .scope = scope });
|
||
// Output.print("\nLoc: {d}\n", .{loc.start});
|
||
return scope_index;
|
||
}
|
||
|
||
// Note: do not write to "p.log" in this function. Any errors due to conversion
|
||
// from expression to binding should be written to "invalidLog" instead. That
|
||
// way we can potentially keep this as an expression if it turns out it's not
|
||
// needed as a binding after all.
|
||
fn convertExprToBinding(noalias p: *P, expr: ExprNodeIndex, invalid_loc: *LocList) ?Binding {
|
||
switch (expr.data) {
|
||
.e_missing => {
|
||
return null;
|
||
},
|
||
.e_identifier => |ex| {
|
||
return p.b(B.Identifier{ .ref = ex.ref }, expr.loc);
|
||
},
|
||
.e_array => |ex| {
|
||
if (ex.comma_after_spread) |spread| {
|
||
invalid_loc.append(.{
|
||
.loc = spread,
|
||
.kind = .spread,
|
||
}) catch unreachable;
|
||
}
|
||
|
||
if (ex.is_parenthesized) {
|
||
invalid_loc.append(.{
|
||
.loc = p.source.rangeOfOperatorBefore(expr.loc, "(").loc,
|
||
.kind = .parentheses,
|
||
}) catch unreachable;
|
||
}
|
||
|
||
// p.markSyntaxFeature(Destructing)
|
||
var items = List(js_ast.ArrayBinding).initCapacity(p.allocator, ex.items.len) catch unreachable;
|
||
var is_spread = false;
|
||
for (ex.items.slice(), 0..) |_, i| {
|
||
var item = ex.items.ptr[i];
|
||
if (item.data == .e_spread) {
|
||
is_spread = true;
|
||
item = item.data.e_spread.value;
|
||
}
|
||
const res = p.convertExprToBindingAndInitializer(&item, invalid_loc, is_spread);
|
||
|
||
items.appendAssumeCapacity(js_ast.ArrayBinding{
|
||
// It's valid for it to be missing
|
||
// An example:
|
||
// Promise.all(promises).then(([, len]) => true);
|
||
// ^ Binding is missing there
|
||
.binding = res.binding orelse p.b(B.Missing{}, item.loc),
|
||
.default_value = res.expr,
|
||
});
|
||
}
|
||
|
||
return p.b(B.Array{
|
||
.items = items.items,
|
||
.has_spread = is_spread,
|
||
.is_single_line = ex.is_single_line,
|
||
}, expr.loc);
|
||
},
|
||
.e_object => |ex| {
|
||
if (ex.comma_after_spread) |sp| {
|
||
invalid_loc.append(.{ .loc = sp, .kind = .spread }) catch unreachable;
|
||
}
|
||
|
||
if (ex.is_parenthesized) {
|
||
invalid_loc.append(.{ .loc = p.source.rangeOfOperatorBefore(expr.loc, "(").loc, .kind = .parentheses }) catch unreachable;
|
||
}
|
||
// p.markSyntaxFeature(compat.Destructuring, p.source.RangeOfOperatorAfter(expr.Loc, "{"))
|
||
|
||
var properties = List(B.Property).initCapacity(p.allocator, ex.properties.len) catch unreachable;
|
||
for (ex.properties.slice()) |*item| {
|
||
if (item.flags.contains(.is_method) or item.kind == .get or item.kind == .set) {
|
||
invalid_loc.append(.{
|
||
.loc = item.key.?.loc,
|
||
.kind = if (item.flags.contains(.is_method))
|
||
InvalidLoc.Tag.method
|
||
else if (item.kind == .get)
|
||
InvalidLoc.Tag.getter
|
||
else
|
||
InvalidLoc.Tag.setter,
|
||
}) catch unreachable;
|
||
continue;
|
||
}
|
||
const value = &item.value.?;
|
||
const tup = p.convertExprToBindingAndInitializer(value, invalid_loc, false);
|
||
const initializer = tup.expr orelse item.initializer;
|
||
const is_spread = item.kind == .spread or item.flags.contains(.is_spread);
|
||
properties.appendAssumeCapacity(B.Property{
|
||
.flags = Flags.Property.init(.{
|
||
.is_spread = is_spread,
|
||
.is_computed = item.flags.contains(.is_computed),
|
||
}),
|
||
.key = item.key orelse p.newExpr(E.Missing{}, expr.loc),
|
||
.value = tup.binding orelse p.b(B.Missing{}, expr.loc),
|
||
.default_value = initializer,
|
||
});
|
||
}
|
||
|
||
return p.b(B.Object{
|
||
.properties = properties.items,
|
||
.is_single_line = ex.is_single_line,
|
||
}, expr.loc);
|
||
},
|
||
else => {
|
||
invalid_loc.append(.{ .loc = expr.loc, .kind = .unknown }) catch unreachable;
|
||
return null;
|
||
},
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
pub fn convertExprToBindingAndInitializer(noalias p: *P, _expr: *ExprNodeIndex, invalid_log: *LocList, is_spread: bool) ExprBindingTuple {
|
||
var initializer: ?ExprNodeIndex = null;
|
||
var expr = _expr;
|
||
// zig syntax is sometimes painful
|
||
switch (expr.*.data) {
|
||
.e_binary => |bin| {
|
||
if (bin.op == .bin_assign) {
|
||
initializer = bin.right;
|
||
expr = &bin.left;
|
||
}
|
||
},
|
||
else => {},
|
||
}
|
||
|
||
const bind = p.convertExprToBinding(expr.*, invalid_log);
|
||
if (initializer) |initial| {
|
||
const equalsRange = p.source.rangeOfOperatorBefore(initial.loc, "=");
|
||
if (is_spread) {
|
||
p.log.addRangeError(p.source, equalsRange, "A rest argument cannot have a default initializer") catch unreachable;
|
||
} else {
|
||
// p.markSyntaxFeature();
|
||
}
|
||
}
|
||
return ExprBindingTuple{ .binding = bind, .expr = initializer };
|
||
}
|
||
|
||
pub fn forbidLexicalDecl(noalias p: *const P, loc: logger.Loc) anyerror!void {
|
||
try p.log.addError(p.source, loc, "Cannot use a declaration in a single-statement context");
|
||
}
|
||
|
||
/// If we attempt to parse TypeScript syntax outside of a TypeScript file
|
||
/// make it a compile error
|
||
pub inline fn markTypeScriptOnly(noalias _: *const P) void {
|
||
if (comptime !is_typescript_enabled) {
|
||
@compileError("This function can only be used in TypeScript");
|
||
}
|
||
|
||
// explicitly mark it as unreachable in the hopes that the function doesn't exist at all
|
||
if (!is_typescript_enabled) {
|
||
unreachable;
|
||
}
|
||
}
|
||
|
||
pub fn logExprErrors(noalias p: *P, noalias errors: *DeferredErrors) void {
|
||
if (errors.invalid_expr_default_value) |r| {
|
||
p.log.addRangeError(
|
||
p.source,
|
||
r,
|
||
"Unexpected \"=\"",
|
||
) catch unreachable;
|
||
}
|
||
|
||
if (errors.invalid_expr_after_question) |r| {
|
||
p.log.addRangeErrorFmt(p.source, r, p.allocator, "Unexpected {s}", .{p.source.contents[r.loc.i()..r.endI()]}) catch unreachable;
|
||
}
|
||
|
||
// if (errors.array_spread_feature) |err| {
|
||
// p.markSyntaxFeature(compat.ArraySpread, errors.arraySpreadFeature)
|
||
// }
|
||
}
|
||
|
||
pub fn popAndDiscardScope(noalias p: *P, scope_index: usize) void {
|
||
// Move up to the parent scope
|
||
const to_discard = p.current_scope;
|
||
const parent = to_discard.parent orelse unreachable;
|
||
|
||
p.current_scope = parent;
|
||
|
||
// Truncate the scope order where we started to pretend we never saw this scope
|
||
p.scopes_in_order.shrinkRetainingCapacity(scope_index);
|
||
|
||
var children = parent.children;
|
||
// Remove the last child from the parent scope
|
||
const last = children.len - 1;
|
||
if (children.slice()[last] != to_discard) {
|
||
p.panic("Internal error", .{});
|
||
}
|
||
|
||
_ = children.pop();
|
||
}
|
||
|
||
pub fn processImportStatement(p: *P, stmt_: S.Import, path: ParsedPath, loc: logger.Loc, was_originally_bare_import: bool) anyerror!Stmt {
|
||
const is_macro = FeatureFlags.is_macro_enabled and (path.is_macro or js_ast.Macro.isMacroPath(path.text));
|
||
var stmt = stmt_;
|
||
if (is_macro) {
|
||
const id = p.addImportRecord(.stmt, path.loc, path.text);
|
||
p.import_records.items[id].path.namespace = js_ast.Macro.namespace;
|
||
p.import_records.items[id].flags.is_unused = true;
|
||
|
||
if (stmt.default_name) |name_loc| {
|
||
const name = p.loadNameFromRef(name_loc.ref.?);
|
||
const ref = try p.declareSymbol(.other, name_loc.loc, name);
|
||
try p.is_import_item.put(p.allocator, ref, {});
|
||
try p.macro.refs.put(ref, .{
|
||
.import_record_id = id,
|
||
.name = "default",
|
||
});
|
||
}
|
||
|
||
if (stmt.star_name_loc) |star| {
|
||
const name = p.loadNameFromRef(stmt.namespace_ref);
|
||
const ref = try p.declareSymbol(.other, star, name);
|
||
stmt.namespace_ref = ref;
|
||
try p.macro.refs.put(ref, .{ .import_record_id = id });
|
||
}
|
||
|
||
for (stmt.items) |item| {
|
||
const name = p.loadNameFromRef(item.name.ref.?);
|
||
const ref = try p.declareSymbol(.other, item.name.loc, name);
|
||
try p.is_import_item.put(p.allocator, ref, {});
|
||
try p.macro.refs.put(ref, .{
|
||
.import_record_id = id,
|
||
.name = item.alias,
|
||
});
|
||
}
|
||
|
||
return p.s(S.Empty{}, loc);
|
||
}
|
||
|
||
// Handle `import { feature } from "bun:bundle"` - this is a special import
|
||
// that provides static feature flag checking at bundle time.
|
||
// We handle it here at parse time (similar to macros) rather than at visit time.
|
||
if (strings.eqlComptime(path.text, "bun:bundle")) {
|
||
// Look for the "feature" import and validate specifiers
|
||
for (stmt.items) |*item| {
|
||
// In ClauseItem from parseImportClause:
|
||
// - alias is the name from the source module ("feature")
|
||
// - original_name is the local binding name
|
||
// - name.ref is the ref for the local binding
|
||
if (strings.eqlComptime(item.alias, "feature")) {
|
||
// Check for duplicate imports of feature
|
||
if (p.bundler_feature_flag_ref.isValid()) {
|
||
try p.log.addError(p.source, item.alias_loc, "`feature` from \"bun:bundle\" may only be imported once");
|
||
continue;
|
||
}
|
||
// Declare the symbol and store the ref
|
||
const name = p.loadNameFromRef(item.name.ref.?);
|
||
const ref = try p.declareSymbol(.other, item.name.loc, name);
|
||
p.bundler_feature_flag_ref = ref;
|
||
} else {
|
||
try p.log.addErrorFmt(p.source, item.alias_loc, p.allocator, "\"bun:bundle\" has no export named \"{s}\"", .{item.alias});
|
||
}
|
||
}
|
||
// Return empty statement - the import is completely removed
|
||
return p.s(S.Empty{}, loc);
|
||
}
|
||
|
||
const macro_remap = if (comptime allow_macros)
|
||
p.options.macro_context.getRemap(path.text)
|
||
else
|
||
null;
|
||
|
||
stmt.import_record_index = p.addImportRecord(.stmt, path.loc, path.text);
|
||
p.import_records.items[stmt.import_record_index].flags.was_originally_bare_import = was_originally_bare_import;
|
||
|
||
if (stmt.star_name_loc) |star| {
|
||
const name = p.loadNameFromRef(stmt.namespace_ref);
|
||
|
||
stmt.namespace_ref = try p.declareSymbol(.import, star, name);
|
||
|
||
if (comptime track_symbol_usage_during_parse_pass) {
|
||
p.parse_pass_symbol_uses.put(name, .{
|
||
.ref = stmt.namespace_ref,
|
||
.import_record_index = stmt.import_record_index,
|
||
}) catch unreachable;
|
||
}
|
||
|
||
// TODO: not sure how to handle macro remappings for namespace imports
|
||
} else {
|
||
var path_name = fs.PathName.init(path.text);
|
||
const name = try strings.append(p.allocator, "import_", try path_name.nonUniqueNameString(p.allocator));
|
||
stmt.namespace_ref = try p.newSymbol(.other, name);
|
||
var scope: *Scope = p.current_scope;
|
||
try scope.generated.append(p.allocator, stmt.namespace_ref);
|
||
}
|
||
|
||
var item_refs = ImportItemForNamespaceMap.init(p.allocator);
|
||
const count_excluding_namespace = @as(u16, @intCast(stmt.items.len)) +
|
||
@as(u16, @intCast(@intFromBool(stmt.default_name != null)));
|
||
|
||
try item_refs.ensureUnusedCapacity(count_excluding_namespace);
|
||
// Even though we allocate ahead of time here
|
||
// we cannot use putAssumeCapacity because a symbol can have existing links
|
||
// those may write to this hash table, so this estimate may be innaccurate
|
||
try p.is_import_item.ensureUnusedCapacity(p.allocator, count_excluding_namespace);
|
||
var remap_count: u32 = 0;
|
||
// Link the default item to the namespace
|
||
if (stmt.default_name) |*name_loc| outer: {
|
||
const name = p.loadNameFromRef(name_loc.ref.?);
|
||
const ref = try p.declareSymbol(.import, name_loc.loc, name);
|
||
name_loc.ref = ref;
|
||
try p.is_import_item.put(p.allocator, ref, {});
|
||
|
||
// ensure every e_import_identifier holds the namespace
|
||
if (p.options.features.hot_module_reloading) {
|
||
const symbol = &p.symbols.items[ref.inner_index];
|
||
if (symbol.namespace_alias == null) {
|
||
symbol.namespace_alias = .{
|
||
.namespace_ref = stmt.namespace_ref,
|
||
.alias = "default",
|
||
.import_record_index = stmt.import_record_index,
|
||
};
|
||
}
|
||
}
|
||
|
||
if (macro_remap) |*remap| {
|
||
if (remap.get("default")) |remapped_path| {
|
||
const new_import_id = p.addImportRecord(.stmt, path.loc, remapped_path);
|
||
try p.macro.refs.put(ref, .{
|
||
.import_record_id = new_import_id,
|
||
.name = "default",
|
||
});
|
||
|
||
p.import_records.items[new_import_id].path.namespace = js_ast.Macro.namespace;
|
||
p.import_records.items[new_import_id].flags.is_unused = true;
|
||
if (comptime only_scan_imports_and_do_not_visit) {
|
||
p.import_records.items[new_import_id].flags.is_internal = true;
|
||
p.import_records.items[new_import_id].path.is_disabled = true;
|
||
}
|
||
stmt.default_name = null;
|
||
remap_count += 1;
|
||
break :outer;
|
||
}
|
||
}
|
||
|
||
if (comptime track_symbol_usage_during_parse_pass) {
|
||
p.parse_pass_symbol_uses.put(name, .{
|
||
.ref = ref,
|
||
.import_record_index = stmt.import_record_index,
|
||
}) catch unreachable;
|
||
}
|
||
|
||
if (comptime ParsePassSymbolUsageType != void) {
|
||
p.parse_pass_symbol_uses.put(name, .{
|
||
.ref = ref,
|
||
.import_record_index = stmt.import_record_index,
|
||
}) catch unreachable;
|
||
}
|
||
|
||
// No need to add the `default_name` to `item_refs` because
|
||
// `.scanImportsAndExports(...)` special cases and handles
|
||
// `default_name` separately
|
||
}
|
||
var end: usize = 0;
|
||
|
||
for (stmt.items) |item_| {
|
||
var item = item_;
|
||
const name = p.loadNameFromRef(item.name.ref orelse unreachable);
|
||
const ref = try p.declareSymbol(.import, item.name.loc, name);
|
||
item.name.ref = ref;
|
||
|
||
try p.is_import_item.put(p.allocator, ref, {});
|
||
p.checkForNonBMPCodePoint(item.alias_loc, item.alias);
|
||
|
||
// ensure every e_import_identifier holds the namespace
|
||
if (p.options.features.hot_module_reloading) {
|
||
const symbol = &p.symbols.items[ref.inner_index];
|
||
if (symbol.namespace_alias == null) {
|
||
symbol.namespace_alias = .{
|
||
.namespace_ref = stmt.namespace_ref,
|
||
.alias = item.alias,
|
||
.import_record_index = stmt.import_record_index,
|
||
};
|
||
}
|
||
}
|
||
|
||
if (macro_remap) |*remap| {
|
||
if (remap.get(item.alias)) |remapped_path| {
|
||
const new_import_id = p.addImportRecord(.stmt, path.loc, remapped_path);
|
||
try p.macro.refs.put(ref, .{
|
||
.import_record_id = new_import_id,
|
||
.name = item.alias,
|
||
});
|
||
|
||
p.import_records.items[new_import_id].path.namespace = js_ast.Macro.namespace;
|
||
p.import_records.items[new_import_id].flags.is_unused = true;
|
||
if (comptime only_scan_imports_and_do_not_visit) {
|
||
p.import_records.items[new_import_id].flags.is_internal = true;
|
||
p.import_records.items[new_import_id].path.is_disabled = true;
|
||
}
|
||
remap_count += 1;
|
||
continue;
|
||
}
|
||
}
|
||
|
||
if (comptime track_symbol_usage_during_parse_pass) {
|
||
p.parse_pass_symbol_uses.put(name, .{
|
||
.ref = ref,
|
||
.import_record_index = stmt.import_record_index,
|
||
}) catch unreachable;
|
||
}
|
||
|
||
item_refs.putAssumeCapacity(item.alias, item.name);
|
||
stmt.items[end] = item;
|
||
end += 1;
|
||
}
|
||
stmt.items = stmt.items[0..end];
|
||
|
||
// If we remapped the entire import away
|
||
// i.e. import {graphql} "react-relay"
|
||
|
||
if (remap_count > 0 and stmt.items.len == 0 and stmt.default_name == null) {
|
||
p.import_records.items[stmt.import_record_index].path.namespace = js_ast.Macro.namespace;
|
||
p.import_records.items[stmt.import_record_index].flags.is_unused = true;
|
||
|
||
if (comptime only_scan_imports_and_do_not_visit) {
|
||
p.import_records.items[stmt.import_record_index].path.is_disabled = true;
|
||
p.import_records.items[stmt.import_record_index].flags.is_internal = true;
|
||
}
|
||
|
||
return p.s(S.Empty{}, loc);
|
||
} else if (remap_count > 0) {
|
||
item_refs.shrinkAndFree(stmt.items.len + @as(usize, @intFromBool(stmt.default_name != null)));
|
||
}
|
||
|
||
if (path.import_tag != .none or path.loader != null) {
|
||
try p.validateAndSetImportType(&path, &stmt);
|
||
}
|
||
|
||
// Track the items for this namespace
|
||
try p.import_items_for_namespace.put(p.allocator, stmt.namespace_ref, item_refs);
|
||
return p.s(stmt, loc);
|
||
}
|
||
|
||
fn validateAndSetImportType(p: *P, path: *const ParsedPath, stmt: *S.Import) !void {
|
||
@branchHint(.cold);
|
||
|
||
if (path.loader) |loader| {
|
||
p.import_records.items[stmt.import_record_index].loader = loader;
|
||
|
||
if (loader == .sqlite or loader == .sqlite_embedded) {
|
||
for (stmt.items) |*item| {
|
||
if (!(strings.eqlComptime(item.alias, "default") or strings.eqlComptime(item.alias, "db"))) {
|
||
try p.log.addError(
|
||
p.source,
|
||
item.name.loc,
|
||
"sqlite imports only support the \"default\" or \"db\" imports",
|
||
);
|
||
break;
|
||
}
|
||
}
|
||
} else if (loader == .file or loader == .text) {
|
||
for (stmt.items) |*item| {
|
||
if (!(strings.eqlComptime(item.alias, "default"))) {
|
||
try p.log.addError(
|
||
p.source,
|
||
item.name.loc,
|
||
"This loader type only supports the \"default\" import",
|
||
);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
} else if (path.import_tag == .bake_resolve_to_ssr_graph) {
|
||
p.import_records.items[stmt.import_record_index].tag = path.import_tag;
|
||
}
|
||
}
|
||
|
||
pub fn createDefaultName(p: *P, loc: logger.Loc) !js_ast.LocRef {
|
||
const identifier = try std.fmt.allocPrint(p.allocator, "{s}_default", .{try p.source.path.name.nonUniqueNameString(p.allocator)});
|
||
|
||
const name = js_ast.LocRef{ .loc = loc, .ref = try p.newSymbol(Symbol.Kind.other, identifier) };
|
||
|
||
var scope = p.current_scope;
|
||
|
||
try scope.generated.append(p.allocator, name.ref.?);
|
||
|
||
return name;
|
||
}
|
||
|
||
pub fn newSymbol(p: *P, kind: Symbol.Kind, identifier: string) !Ref {
|
||
const inner_index: Ref.Int = @truncate(p.symbols.items.len);
|
||
try p.symbols.append(Symbol{
|
||
.kind = kind,
|
||
.original_name = identifier,
|
||
});
|
||
|
||
if (is_typescript_enabled) {
|
||
try p.ts_use_counts.append(p.allocator, 0);
|
||
}
|
||
|
||
return Ref{
|
||
.inner_index = inner_index,
|
||
.source_index = @intCast(p.source.index.get()),
|
||
.tag = .symbol,
|
||
};
|
||
}
|
||
|
||
pub fn defaultNameForExpr(p: *P, expr: Expr, loc: logger.Loc) LocRef {
|
||
switch (expr.data) {
|
||
.e_function => |func_container| {
|
||
if (func_container.func.name) |_name| {
|
||
if (_name.ref) |ref| {
|
||
return LocRef{ .loc = loc, .ref = ref };
|
||
}
|
||
}
|
||
},
|
||
.e_identifier => |ident| {
|
||
return LocRef{ .loc = loc, .ref = ident.ref };
|
||
},
|
||
.e_import_identifier => |ident| {
|
||
if (!allow_macros or (allow_macros and !p.macro.refs.contains(ident.ref))) {
|
||
return LocRef{ .loc = loc, .ref = ident.ref };
|
||
}
|
||
},
|
||
.e_class => |class| {
|
||
if (class.class_name) |_name| {
|
||
if (_name.ref) |ref| {
|
||
return LocRef{ .loc = loc, .ref = ref };
|
||
}
|
||
}
|
||
},
|
||
else => {},
|
||
}
|
||
|
||
return createDefaultName(p, loc) catch unreachable;
|
||
}
|
||
|
||
pub fn discardScopesUpTo(p: *P, scope_index: usize) void {
|
||
// Remove any direct children from their parent
|
||
const scope = p.current_scope;
|
||
var children = scope.children;
|
||
defer scope.children = children;
|
||
|
||
for (p.scopes_in_order.items[scope_index..]) |_child| {
|
||
const child = _child orelse continue;
|
||
|
||
if (child.scope.parent == p.current_scope) {
|
||
var i: usize = children.len - 1;
|
||
while (i >= 0) {
|
||
if (children.mut(i).* == child.scope) {
|
||
_ = children.orderedRemove(i);
|
||
break;
|
||
}
|
||
i -= 1;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Truncate the scope order where we started to pretend we never saw this scope
|
||
p.scopes_in_order.shrinkRetainingCapacity(scope_index);
|
||
}
|
||
|
||
pub fn defineExportedNamespaceBinding(
|
||
p: *P,
|
||
exported_members: *js_ast.TSNamespaceMemberMap,
|
||
binding: Binding,
|
||
) !void {
|
||
switch (binding.data) {
|
||
.b_missing => {},
|
||
.b_identifier => |id| {
|
||
const name = p.symbols.items[id.ref.inner_index].original_name;
|
||
try exported_members.put(p.allocator, name, .{
|
||
.loc = binding.loc,
|
||
.data = .property,
|
||
});
|
||
try p.ref_to_ts_namespace_member.put(
|
||
p.allocator,
|
||
id.ref,
|
||
.property,
|
||
);
|
||
},
|
||
.b_object => |obj| {
|
||
for (obj.properties) |prop| {
|
||
try p.defineExportedNamespaceBinding(exported_members, prop.value);
|
||
}
|
||
},
|
||
.b_array => |obj| {
|
||
for (obj.items) |prop| {
|
||
try p.defineExportedNamespaceBinding(exported_members, prop.binding);
|
||
}
|
||
},
|
||
}
|
||
}
|
||
|
||
pub fn forbidInitializers(p: *P, decls: []G.Decl, comptime loop_type: string, is_var: bool) anyerror!void {
|
||
switch (decls.len) {
|
||
0 => {},
|
||
1 => {
|
||
if (decls[0].value) |value| {
|
||
if (is_var) {
|
||
|
||
// This is a weird special case. Initializers are allowed in "var"
|
||
// statements with identifier bindings.
|
||
return;
|
||
}
|
||
|
||
try p.log.addError(p.source, value.loc, comptime std.fmt.comptimePrint("for-{s} loop variables cannot have an initializer", .{loop_type}));
|
||
}
|
||
},
|
||
else => {
|
||
try p.log.addError(p.source, decls[0].binding.loc, comptime std.fmt.comptimePrint("for-{s} loops must have a single declaration", .{loop_type}));
|
||
},
|
||
}
|
||
}
|
||
|
||
pub fn requireInitializers(noalias p: *P, comptime kind: S.Local.Kind, decls: []G.Decl) anyerror!void {
|
||
const what = switch (kind) {
|
||
.k_await_using, .k_using => "declaration",
|
||
.k_const => "constant",
|
||
else => @compileError("unreachable"),
|
||
};
|
||
|
||
for (decls) |*decl| {
|
||
if (decl.value == null) {
|
||
switch (decl.binding.data) {
|
||
.b_identifier => |ident| {
|
||
const r = js_lexer.rangeOfIdentifier(p.source, decl.binding.loc);
|
||
try p.log.addRangeErrorFmt(p.source, r, p.allocator, "The " ++ what ++ " \"{s}\" must be initialized", .{
|
||
p.symbols.items[ident.ref.innerIndex()].original_name,
|
||
});
|
||
// return;/
|
||
},
|
||
else => {
|
||
try p.log.addError(p.source, decl.binding.loc, "This " ++ what ++ " must be initialized");
|
||
},
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Generate a TypeScript namespace object for this namespace's scope. If this
|
||
// namespace is another block that is to be merged with an existing namespace,
|
||
// use that earlier namespace's object instead.
|
||
pub fn getOrCreateExportedNamespaceMembers(p: *P, name: []const u8, is_export: bool, is_enum_scope: bool) *js_ast.TSNamespaceScope {
|
||
const map = brk: {
|
||
|
||
// Merge with a sibling namespace from the same scope
|
||
if (p.current_scope.members.get(name)) |existing_member| {
|
||
if (p.ref_to_ts_namespace_member.get(existing_member.ref)) |member_data| {
|
||
if (member_data == .namespace)
|
||
break :brk member_data.namespace;
|
||
}
|
||
}
|
||
|
||
// Merge with a sibling namespace from a different scope
|
||
if (is_export) {
|
||
if (p.current_scope.ts_namespace) |ns| {
|
||
if (ns.exported_members.get(name)) |member| {
|
||
if (member.data == .namespace)
|
||
break :brk member.data.namespace;
|
||
}
|
||
}
|
||
}
|
||
|
||
break :brk null;
|
||
};
|
||
|
||
if (map) |existing| {
|
||
return bun.create(p.allocator, js_ast.TSNamespaceScope, .{
|
||
.exported_members = existing,
|
||
.is_enum_scope = is_enum_scope,
|
||
.arg_ref = Ref.None,
|
||
});
|
||
}
|
||
|
||
// Otherwise, generate a new namespace object
|
||
// Batch the allocation of the namespace object and the map into a single allocation.
|
||
const Pair = struct {
|
||
map: js_ast.TSNamespaceMemberMap,
|
||
scope: js_ast.TSNamespaceScope,
|
||
};
|
||
|
||
var pair = bun.handleOom(p.allocator.create(Pair));
|
||
pair.map = .{};
|
||
pair.scope = .{
|
||
.exported_members = &pair.map,
|
||
.is_enum_scope = is_enum_scope,
|
||
.arg_ref = Ref.None,
|
||
};
|
||
|
||
return &pair.scope;
|
||
}
|
||
// TODO:
|
||
pub fn checkForNonBMPCodePoint(_: *P, _: logger.Loc, _: string) void {}
|
||
|
||
pub fn markStrictModeFeature(p: *P, feature: StrictModeFeature, r: logger.Range, detail: string) anyerror!void {
|
||
const can_be_transformed = feature == StrictModeFeature.for_in_var_init;
|
||
const text = switch (feature) {
|
||
.with_statement => "With statements",
|
||
.delete_bare_name => "\"delete\" of a bare identifier",
|
||
.for_in_var_init => "Variable initializers within for-in loops",
|
||
.eval_or_arguments => try std.fmt.allocPrint(p.allocator, "Declarations with the name \"{s}\"", .{detail}),
|
||
.reserved_word => try std.fmt.allocPrint(p.allocator, "\"{s}\" is a reserved word and", .{detail}),
|
||
.legacy_octal_literal => "Legacy octal literals",
|
||
.legacy_octal_escape => "Legacy octal escape sequences",
|
||
.if_else_function_stmt => "Function declarations inside if statements",
|
||
// else => {
|
||
// text = "This feature";
|
||
// },
|
||
};
|
||
|
||
const scope = p.current_scope;
|
||
if (p.isStrictMode()) {
|
||
var why: string = "";
|
||
var where: logger.Range = logger.Range.None;
|
||
switch (scope.strict_mode) {
|
||
.implicit_strict_mode_import => {
|
||
where = p.esm_import_keyword;
|
||
},
|
||
.implicit_strict_mode_export => {
|
||
where = p.esm_export_keyword;
|
||
},
|
||
.implicit_strict_mode_top_level_await => {
|
||
where = p.top_level_await_keyword;
|
||
},
|
||
.implicit_strict_mode_class => {
|
||
why = "All code inside a class is implicitly in strict mode";
|
||
where = p.enclosing_class_keyword;
|
||
},
|
||
else => {},
|
||
}
|
||
if (why.len == 0) {
|
||
why = try std.fmt.allocPrint(p.allocator, "This file is implicitly in strict mode because of the \"{s}\" keyword here", .{p.source.textForRange(where)});
|
||
}
|
||
var notes = try p.allocator.alloc(logger.Data, 1);
|
||
notes[0] = logger.rangeData(p.source, where, why);
|
||
try p.log.addRangeErrorWithNotes(p.source, r, try std.fmt.allocPrint(p.allocator, "{s} cannot be used in strict mode", .{text}), notes);
|
||
} else if (!can_be_transformed and p.isStrictModeOutputFormat()) {
|
||
try p.log.addRangeError(p.source, r, try std.fmt.allocPrint(p.allocator, "{s} cannot be used with the ESM output format due to strict mode", .{text}));
|
||
}
|
||
}
|
||
|
||
pub inline fn isStrictMode(p: *P) bool {
|
||
return p.current_scope.strict_mode != .sloppy_mode;
|
||
}
|
||
|
||
pub inline fn isStrictModeOutputFormat(p: *P) bool {
|
||
return p.options.bundle and p.options.output_format.isESM();
|
||
}
|
||
|
||
pub fn declareCommonJSSymbol(p: *P, comptime kind: Symbol.Kind, comptime name: string) !Ref {
|
||
const name_hash = comptime Scope.getMemberHash(name);
|
||
const member = p.module_scope.getMemberWithHash(name, name_hash);
|
||
|
||
// If the code declared this symbol using "var name", then this is actually
|
||
// not a collision. For example, node will let you do this:
|
||
//
|
||
// var exports;
|
||
// module.exports.foo = 123;
|
||
// console.log(exports.foo);
|
||
//
|
||
// This works because node's implementation of CommonJS wraps the entire
|
||
// source file like this:
|
||
//
|
||
// (function(require, exports, module, __filename, __dirname) {
|
||
// var exports;
|
||
// module.exports.foo = 123;
|
||
// console.log(exports.foo);
|
||
// })
|
||
//
|
||
// Both the "exports" argument and "var exports" are hoisted variables, so
|
||
// they don't collide.
|
||
if (member) |_member| {
|
||
if (p.symbols.items[_member.ref.innerIndex()].kind == .hoisted and kind == .hoisted and !p.has_es_module_syntax) {
|
||
return _member.ref;
|
||
}
|
||
}
|
||
|
||
// Create a new symbol if we didn't merge with an existing one above
|
||
const ref = try p.newSymbol(kind, name);
|
||
|
||
if (member == null) {
|
||
try p.module_scope.members.put(p.allocator, name, Scope.Member{ .ref = ref, .loc = logger.Loc.Empty });
|
||
return ref;
|
||
}
|
||
|
||
// If the variable was declared, then it shadows this symbol. The code in
|
||
// this module will be unable to reference this symbol. However, we must
|
||
// still add the symbol to the scope so it gets minified (automatically-
|
||
// generated code may still reference the symbol).
|
||
try p.module_scope.generated.append(p.allocator, ref);
|
||
return ref;
|
||
}
|
||
|
||
pub fn declareGeneratedSymbol(p: *P, kind: Symbol.Kind, comptime name: string) !Ref {
|
||
// The bundler runs the renamer, so it is ok to not append a hash
|
||
if (p.options.bundle) {
|
||
return try declareSymbolMaybeGenerated(p, kind, logger.Loc.Empty, name, true);
|
||
}
|
||
|
||
return try declareSymbolMaybeGenerated(p, kind, logger.Loc.Empty, generatedSymbolName(name), true);
|
||
}
|
||
|
||
pub fn declareSymbol(p: *P, kind: Symbol.Kind, loc: logger.Loc, name: string) !Ref {
|
||
return try @call(bun.callmod_inline, declareSymbolMaybeGenerated, .{ p, kind, loc, name, false });
|
||
}
|
||
|
||
pub fn declareSymbolMaybeGenerated(p: *P, kind: Symbol.Kind, loc: logger.Loc, name: string, comptime is_generated: bool) !Ref {
|
||
// p.checkForNonBMPCodePoint(loc, name)
|
||
if (comptime !is_generated) {
|
||
// Forbid declaring a symbol with a reserved word in strict mode
|
||
if (p.isStrictMode() and name.ptr != arguments_str.ptr and js_lexer.StrictModeReservedWords.has(name)) {
|
||
try p.markStrictModeFeature(.reserved_word, js_lexer.rangeOfIdentifier(p.source, loc), name);
|
||
}
|
||
}
|
||
|
||
// Allocate a new symbol
|
||
var ref = try p.newSymbol(kind, name);
|
||
|
||
const scope = p.current_scope;
|
||
const entry = try scope.members.getOrPut(p.allocator, name);
|
||
if (entry.found_existing) {
|
||
const existing = entry.value_ptr.*;
|
||
var symbol: *Symbol = &p.symbols.items[existing.ref.innerIndex()];
|
||
|
||
if (comptime !is_generated) {
|
||
switch (scope.canMergeSymbols(symbol.kind, kind, is_typescript_enabled)) {
|
||
.forbidden => {
|
||
try p.log.addSymbolAlreadyDeclaredError(p.allocator, p.source, symbol.original_name, loc, existing.loc);
|
||
return existing.ref;
|
||
},
|
||
|
||
.keep_existing => {
|
||
ref = existing.ref;
|
||
},
|
||
|
||
.replace_with_new => {
|
||
symbol.link = ref;
|
||
|
||
// If these are both functions, remove the overwritten declaration
|
||
if (kind.isFunction() and symbol.kind.isFunction()) {
|
||
symbol.remove_overwritten_function_declaration = true;
|
||
}
|
||
},
|
||
|
||
.become_private_get_set_pair => {
|
||
ref = existing.ref;
|
||
symbol.kind = .private_get_set_pair;
|
||
},
|
||
|
||
.become_private_static_get_set_pair => {
|
||
ref = existing.ref;
|
||
symbol.kind = .private_static_get_set_pair;
|
||
},
|
||
|
||
.overwrite_with_new => {},
|
||
}
|
||
} else {
|
||
p.symbols.items[ref.innerIndex()].link = existing.ref;
|
||
}
|
||
}
|
||
entry.key_ptr.* = name;
|
||
entry.value_ptr.* = js_ast.Scope.Member{ .ref = ref, .loc = loc };
|
||
if (comptime is_generated) {
|
||
try p.module_scope.generated.append(p.allocator, ref);
|
||
}
|
||
return ref;
|
||
}
|
||
|
||
pub fn validateFunctionName(p: *P, func: G.Fn, kind: FunctionKind) void {
|
||
if (func.name) |name| {
|
||
const original_name = p.symbols.items[name.ref.?.innerIndex()].original_name;
|
||
|
||
if (func.flags.contains(.is_async) and strings.eqlComptime(original_name, "await")) {
|
||
p.log.addRangeError(
|
||
p.source,
|
||
js_lexer.rangeOfIdentifier(p.source, name.loc),
|
||
"An async function cannot be named \"await\"",
|
||
) catch unreachable;
|
||
} else if (kind == .expr and func.flags.contains(.is_generator) and strings.eqlComptime(original_name, "yield")) {
|
||
p.log.addRangeError(
|
||
p.source,
|
||
js_lexer.rangeOfIdentifier(p.source, name.loc),
|
||
"An generator function expression cannot be named \"yield\"",
|
||
) catch unreachable;
|
||
}
|
||
}
|
||
}
|
||
|
||
pub fn declareBinding(p: *P, kind: Symbol.Kind, binding: *BindingNodeIndex, opts: *ParseStatementOptions) anyerror!void {
|
||
switch (binding.data) {
|
||
.b_missing => {},
|
||
.b_identifier => |bind| {
|
||
if (!opts.is_typescript_declare or (opts.is_namespace_scope and opts.is_export)) {
|
||
bind.ref = try p.declareSymbol(kind, binding.loc, p.loadNameFromRef(bind.ref));
|
||
}
|
||
},
|
||
.b_array => |bind| {
|
||
for (bind.items) |*item| {
|
||
p.declareBinding(kind, &item.binding, opts) catch unreachable;
|
||
}
|
||
},
|
||
.b_object => |bind| {
|
||
for (bind.properties) |*prop| {
|
||
p.declareBinding(kind, &prop.value, opts) catch unreachable;
|
||
}
|
||
},
|
||
}
|
||
}
|
||
|
||
pub fn storeNameInRef(p: *P, name: string) !Ref {
|
||
if (comptime track_symbol_usage_during_parse_pass) {
|
||
if (p.parse_pass_symbol_uses.getPtr(name)) |res| {
|
||
res.used = true;
|
||
}
|
||
}
|
||
|
||
if (@intFromPtr(p.source.contents.ptr) <= @intFromPtr(name.ptr) and (@intFromPtr(name.ptr) + name.len) <= (@intFromPtr(p.source.contents.ptr) + p.source.contents.len)) {
|
||
return Ref.initSourceEnd(.{
|
||
.source_index = @intCast(@intFromPtr(name.ptr) - @intFromPtr(p.source.contents.ptr)),
|
||
.inner_index = @intCast(name.len),
|
||
.tag = .source_contents_slice,
|
||
});
|
||
} else {
|
||
const inner_index: u31 = @intCast(p.allocated_names.items.len);
|
||
try p.allocated_names.append(p.allocator, name);
|
||
return Ref.init(
|
||
inner_index,
|
||
p.source.index.get(),
|
||
false,
|
||
);
|
||
}
|
||
}
|
||
|
||
pub fn loadNameFromRef(p: *P, ref: Ref) string {
|
||
return switch (ref.tag) {
|
||
.symbol => p.symbols.items[ref.innerIndex()].original_name,
|
||
.source_contents_slice => p.source.contents[ref.sourceIndex() .. ref.sourceIndex() + ref.innerIndex()],
|
||
.allocated_name => p.allocated_names.items[ref.innerIndex()],
|
||
else => @panic("Internal error: JS parser tried to load an invalid name from a Ref"),
|
||
};
|
||
}
|
||
|
||
pub inline fn addImportRecord(p: *P, kind: ImportKind, loc: logger.Loc, name: string) u32 {
|
||
return p.addImportRecordByRange(kind, p.source.rangeOfString(loc), name);
|
||
}
|
||
|
||
pub fn addImportRecordByRange(p: *P, kind: ImportKind, range: logger.Range, name: string) u32 {
|
||
return p.addImportRecordByRangeAndPath(kind, range, fs.Path.init(name));
|
||
}
|
||
|
||
pub fn addImportRecordByRangeAndPath(p: *P, kind: ImportKind, range: logger.Range, path: fs.Path) u32 {
|
||
const index = p.import_records.items.len;
|
||
const record = ImportRecord{
|
||
.kind = kind,
|
||
.range = range,
|
||
.path = path,
|
||
};
|
||
p.import_records.append(record) catch unreachable;
|
||
return @as(u32, @intCast(index));
|
||
}
|
||
|
||
pub fn popScope(noalias p: *P) void {
|
||
const current_scope = p.current_scope;
|
||
// We cannot rename anything inside a scope containing a direct eval() call
|
||
if (current_scope.contains_direct_eval) {
|
||
var iter = current_scope.members.iterator();
|
||
while (iter.next()) |member| {
|
||
|
||
// Using direct eval when bundling is not a good idea in general because
|
||
// esbuild must assume that it can potentially reach anything in any of
|
||
// the containing scopes. We try to make it work but this isn't possible
|
||
// in some cases.
|
||
//
|
||
// For example, symbols imported using an ESM import are a live binding
|
||
// to the underlying symbol in another file. This is emulated during
|
||
// scope hoisting by erasing the ESM import and just referencing the
|
||
// underlying symbol in the flattened bundle directly. However, that
|
||
// symbol may have a different name which could break uses of direct
|
||
// eval:
|
||
//
|
||
// // Before bundling
|
||
// import { foo as bar } from './foo.js'
|
||
// console.log(eval('bar'))
|
||
//
|
||
// // After bundling
|
||
// let foo = 123 // The contents of "foo.js"
|
||
// console.log(eval('bar'))
|
||
//
|
||
// There really isn't any way to fix this. You can't just rename "foo" to
|
||
// "bar" in the example above because there may be a third bundled file
|
||
// that also contains direct eval and imports the same symbol with a
|
||
// different conflicting import alias. And there is no way to store a
|
||
// live binding to the underlying symbol in a variable with the import's
|
||
// name so that direct eval can access it:
|
||
//
|
||
// // After bundling
|
||
// let foo = 123 // The contents of "foo.js"
|
||
// const bar = /* cannot express a live binding to "foo" here */
|
||
// console.log(eval('bar'))
|
||
//
|
||
// Technically a "with" statement could potentially make this work (with
|
||
// a big hit to performance), but they are deprecated and are unavailable
|
||
// in strict mode. This is a non-starter since all ESM code is strict mode.
|
||
//
|
||
// So while we still try to obey the requirement that all symbol names are
|
||
// pinned when direct eval is present, we make an exception for top-level
|
||
// symbols in an ESM file when bundling is enabled. We make no guarantee
|
||
// that "eval" will be able to reach these symbols and we allow them to be
|
||
// renamed or removed by tree shaking.
|
||
// if (p.currentScope.parent == null and p.has_es_module_syntax) {
|
||
// continue;
|
||
// }
|
||
|
||
p.symbols.items[member.value_ptr.ref.innerIndex()].must_not_be_renamed = true;
|
||
}
|
||
}
|
||
|
||
p.current_scope = current_scope.parent orelse p.panic("Internal error: attempted to call popScope() on the topmost scope", .{});
|
||
}
|
||
|
||
pub fn markExprAsParenthesized(noalias _: *P, expr: *Expr) void {
|
||
switch (expr.data) {
|
||
.e_array => |ex| {
|
||
ex.is_parenthesized = true;
|
||
},
|
||
.e_object => |ex| {
|
||
ex.is_parenthesized = true;
|
||
},
|
||
else => {
|
||
return;
|
||
},
|
||
}
|
||
}
|
||
|
||
pub fn panic(p: *P, comptime fmt: string, args: anytype) noreturn {
|
||
@branchHint(.cold);
|
||
p.panicLoc(fmt, args, null);
|
||
}
|
||
|
||
pub fn panicLoc(p: *P, comptime fmt: string, args: anytype, loc: ?logger.Loc) noreturn {
|
||
const panic_buffer = p.allocator.alloc(u8, 32 * 1024) catch unreachable;
|
||
var panic_stream = std.Io.Writer.fixed(panic_buffer);
|
||
|
||
// panic during visit pass leaves the lexer at the end, which
|
||
// would make this location absolutely useless.
|
||
const location = loc orelse p.lexer.loc();
|
||
if (location.start < p.lexer.source.contents.len and !location.isEmpty()) {
|
||
p.log.addRangeErrorFmt(
|
||
p.source,
|
||
.{ .loc = location },
|
||
p.allocator,
|
||
"panic here",
|
||
.{},
|
||
) catch |err| bun.handleOom(err);
|
||
}
|
||
|
||
p.log.level = .verbose;
|
||
p.log.print(&panic_stream) catch unreachable;
|
||
|
||
Output.panic(fmt ++ "\n{s}", args ++ .{panic_stream.buffered()});
|
||
}
|
||
|
||
pub fn jsxStringsToMemberExpression(p: *P, loc: logger.Loc, parts: []const []const u8) !Expr {
|
||
const result = try p.findSymbol(loc, parts[0]);
|
||
|
||
const value = p.handleIdentifier(
|
||
loc,
|
||
E.Identifier{
|
||
.ref = result.ref,
|
||
.must_keep_due_to_with_stmt = result.is_inside_with_scope,
|
||
.can_be_removed_if_unused = true,
|
||
},
|
||
parts[0],
|
||
.{
|
||
.was_originally_identifier = true,
|
||
},
|
||
);
|
||
if (parts.len > 1) {
|
||
return p.memberExpression(loc, value, parts[1..]);
|
||
}
|
||
|
||
return value;
|
||
}
|
||
|
||
fn memberExpression(p: *P, loc: logger.Loc, initial_value: Expr, parts: []const []const u8) Expr {
|
||
var value = initial_value;
|
||
|
||
for (parts) |part| {
|
||
if (p.maybeRewritePropertyAccess(
|
||
loc,
|
||
value,
|
||
part,
|
||
loc,
|
||
.{
|
||
.is_call_target = false,
|
||
.assign_target = .none,
|
||
// .is_template_tag = false,
|
||
.is_delete_target = false,
|
||
},
|
||
)) |rewrote| {
|
||
value = rewrote;
|
||
} else {
|
||
value = p.newExpr(
|
||
E.Dot{
|
||
.target = value,
|
||
.name = part,
|
||
.name_loc = loc,
|
||
|
||
.can_be_removed_if_unused = p.options.features.dead_code_elimination,
|
||
},
|
||
loc,
|
||
);
|
||
}
|
||
}
|
||
|
||
return value;
|
||
}
|
||
|
||
pub fn willNeedBindingPattern(noalias p: *const P) bool {
|
||
return switch (p.lexer.token) {
|
||
// "[a] = b;"
|
||
.t_equals => true,
|
||
// "for ([a] in b) {}"
|
||
.t_in => !p.allow_in,
|
||
// "for ([a] of b) {}"
|
||
.t_identifier => !p.allow_in and p.lexer.isContextualKeyword("of"),
|
||
else => false,
|
||
};
|
||
}
|
||
|
||
pub fn appendPart(noalias p: *P, parts: *ListManaged(js_ast.Part), stmts: []Stmt) anyerror!void {
|
||
// Reuse the memory if possible
|
||
// This is reusable if the last part turned out to be dead
|
||
p.symbol_uses.clearRetainingCapacity();
|
||
p.declared_symbols.clearRetainingCapacity();
|
||
p.scopes_for_current_part.clearRetainingCapacity();
|
||
p.import_records_for_current_part.clearRetainingCapacity();
|
||
p.import_symbol_property_uses.clearRetainingCapacity();
|
||
|
||
p.had_commonjs_named_exports_this_visit = false;
|
||
|
||
const allocator = p.allocator;
|
||
var opts = PrependTempRefsOpts{};
|
||
var partStmts = ListManaged(Stmt).fromOwnedSlice(allocator, stmts);
|
||
|
||
try p.visitStmtsAndPrependTempRefs(&partStmts, &opts);
|
||
|
||
// Insert any relocated variable statements now
|
||
if (p.relocated_top_level_vars.items.len > 0) {
|
||
var already_declared = RefMap{};
|
||
var already_declared_allocator_stack = std.heap.stackFallback(1024, allocator);
|
||
const already_declared_allocator = already_declared_allocator_stack.get();
|
||
defer if (already_declared_allocator_stack.fixed_buffer_allocator.end_index >= 1023) already_declared.deinit(already_declared_allocator);
|
||
|
||
for (p.relocated_top_level_vars.items) |*local| {
|
||
// Follow links because "var" declarations may be merged due to hoisting
|
||
while (local.ref != null) {
|
||
var symbol = &p.symbols.items[local.ref.?.innerIndex()];
|
||
if (!symbol.hasLink()) {
|
||
break;
|
||
}
|
||
local.ref = symbol.link;
|
||
}
|
||
const ref = local.ref orelse continue;
|
||
const declaration_entry = try already_declared.getOrPut(already_declared_allocator, ref);
|
||
if (!declaration_entry.found_existing) {
|
||
const decls = try allocator.alloc(G.Decl, 1);
|
||
decls[0] = Decl{
|
||
.binding = p.b(B.Identifier{ .ref = ref }, local.loc),
|
||
};
|
||
try partStmts.append(p.s(
|
||
S.Local{ .decls = G.Decl.List.fromOwnedSlice(decls) },
|
||
local.loc,
|
||
));
|
||
try p.declared_symbols.append(p.allocator, .{ .ref = ref, .is_top_level = true });
|
||
}
|
||
}
|
||
p.relocated_top_level_vars.clearRetainingCapacity();
|
||
}
|
||
|
||
if (partStmts.items.len > 0) {
|
||
const final_stmts = partStmts.items;
|
||
|
||
try parts.append(js_ast.Part{
|
||
.stmts = final_stmts,
|
||
.symbol_uses = p.symbol_uses,
|
||
.import_symbol_property_uses = p.import_symbol_property_uses,
|
||
.declared_symbols = p.declared_symbols.toOwnedSlice(),
|
||
.import_record_indices = bun.BabyList(u32).fromOwnedSlice(
|
||
p.import_records_for_current_part.toOwnedSlice(
|
||
p.allocator,
|
||
) catch unreachable,
|
||
),
|
||
.scopes = try p.scopes_for_current_part.toOwnedSlice(p.allocator),
|
||
.can_be_removed_if_unused = p.stmtsCanBeRemovedIfUnused(final_stmts),
|
||
.tag = if (p.had_commonjs_named_exports_this_visit) js_ast.Part.Tag.commonjs_named_export else .none,
|
||
});
|
||
p.symbol_uses = .{};
|
||
p.import_symbol_property_uses = .{};
|
||
p.had_commonjs_named_exports_this_visit = false;
|
||
} else if (p.declared_symbols.len() > 0 or p.symbol_uses.count() > 0) {
|
||
// if the part is dead, invalidate all the usage counts
|
||
p.clearSymbolUsagesFromDeadPart(&.{ .stmts = undefined, .declared_symbols = p.declared_symbols, .symbol_uses = p.symbol_uses });
|
||
p.declared_symbols.clearRetainingCapacity();
|
||
p.import_records_for_current_part.clearRetainingCapacity();
|
||
}
|
||
}
|
||
|
||
fn bindingCanBeRemovedIfUnused(p: *P, binding: Binding) bool {
|
||
if (!p.options.features.dead_code_elimination) return false;
|
||
return bindingCanBeRemovedIfUnusedWithoutDCECheck(p, binding);
|
||
}
|
||
|
||
fn bindingCanBeRemovedIfUnusedWithoutDCECheck(p: *P, binding: Binding) bool {
|
||
switch (binding.data) {
|
||
.b_array => |bi| {
|
||
for (bi.items) |*item| {
|
||
if (!p.bindingCanBeRemovedIfUnusedWithoutDCECheck(item.binding)) {
|
||
return false;
|
||
}
|
||
|
||
if (item.default_value) |*default| {
|
||
if (!p.exprCanBeRemovedIfUnusedWithoutDCECheck(default)) {
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
},
|
||
.b_object => |bi| {
|
||
for (bi.properties) |*property| {
|
||
if (!property.flags.contains(.is_spread) and !p.exprCanBeRemovedIfUnusedWithoutDCECheck(&property.key)) {
|
||
return false;
|
||
}
|
||
|
||
if (!p.bindingCanBeRemovedIfUnusedWithoutDCECheck(property.value)) {
|
||
return false;
|
||
}
|
||
|
||
if (property.default_value) |*default| {
|
||
if (!p.exprCanBeRemovedIfUnusedWithoutDCECheck(default)) {
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
},
|
||
else => {},
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
fn stmtsCanBeRemovedIfUnused(p: *P, stmts: []Stmt) bool {
|
||
if (!p.options.features.dead_code_elimination) return false;
|
||
return stmtsCanBeRemovedifUnusedWithoutDCECheck(p, stmts);
|
||
}
|
||
|
||
fn stmtsCanBeRemovedifUnusedWithoutDCECheck(p: *P, stmts: []Stmt) bool {
|
||
for (stmts) |stmt| {
|
||
switch (stmt.data) {
|
||
// These never have side effects
|
||
.s_function, .s_empty => {},
|
||
|
||
// Let these be removed if they are unused. Note that we also need to
|
||
// check if the imported file is marked as "sideEffects: false" before we
|
||
// can remove a SImport statement. Otherwise the import must be kept for
|
||
// its side effects.
|
||
.s_import => {},
|
||
|
||
.s_class => |st| {
|
||
if (!p.classCanBeRemovedIfUnused(&st.class)) {
|
||
return false;
|
||
}
|
||
},
|
||
|
||
.s_expr => |st| {
|
||
if (st.does_not_affect_tree_shaking) {
|
||
// Expressions marked with this are automatically generated and have
|
||
// no side effects by construction.
|
||
continue;
|
||
}
|
||
|
||
if (!p.exprCanBeRemovedIfUnusedWithoutDCECheck(&st.value)) {
|
||
return false;
|
||
}
|
||
},
|
||
|
||
.s_local => |st| {
|
||
// "await" is a side effect because it affects code timing
|
||
if (st.kind == .k_await_using) return false;
|
||
|
||
for (st.decls.slice()) |*decl| {
|
||
if (!p.bindingCanBeRemovedIfUnusedWithoutDCECheck(decl.binding)) {
|
||
return false;
|
||
}
|
||
|
||
if (decl.value) |*decl_value| {
|
||
if (!p.exprCanBeRemovedIfUnusedWithoutDCECheck(decl_value)) {
|
||
return false;
|
||
} else if (st.kind == .k_using) {
|
||
// "using" declarations are only side-effect free if they are initialized to null or undefined
|
||
if (decl_value.data != .e_null and decl_value.data != .e_undefined) {
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
|
||
.s_try => |try_| {
|
||
if (!p.stmtsCanBeRemovedifUnusedWithoutDCECheck(try_.body) or (try_.finally != null and !p.stmtsCanBeRemovedifUnusedWithoutDCECheck(try_.finally.?.stmts))) {
|
||
return false;
|
||
}
|
||
},
|
||
|
||
// Exports are tracked separately, so this isn't necessary
|
||
.s_export_clause, .s_export_from => {},
|
||
|
||
.s_export_default => |st| {
|
||
switch (st.value) {
|
||
.stmt => |s2| {
|
||
switch (s2.data) {
|
||
.s_expr => |s_expr| {
|
||
if (!p.exprCanBeRemovedIfUnusedWithoutDCECheck(&s_expr.value)) {
|
||
return false;
|
||
}
|
||
},
|
||
|
||
// These never have side effects
|
||
.s_function => {},
|
||
|
||
.s_class => {
|
||
if (!p.classCanBeRemovedIfUnused(&s2.data.s_class.class)) {
|
||
return false;
|
||
}
|
||
},
|
||
else => {
|
||
Output.panic("Unexpected type in export default", .{});
|
||
},
|
||
}
|
||
},
|
||
.expr => |*exp| {
|
||
if (!p.exprCanBeRemovedIfUnusedWithoutDCECheck(exp)) {
|
||
return false;
|
||
}
|
||
},
|
||
}
|
||
},
|
||
|
||
else => {
|
||
// Assume that all statements not explicitly special-cased here have side
|
||
// effects, and cannot be removed even if unused
|
||
return false;
|
||
},
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
pub fn deoptimizeCommonJSNamedExports(p: *P) void {
|
||
// exists for debugging
|
||
p.commonjs_named_exports_deoptimized = true;
|
||
}
|
||
|
||
pub fn maybeKeepExprSymbolName(p: *P, expr: Expr, original_name: string, was_anonymous_named_expr: bool) Expr {
|
||
return if (was_anonymous_named_expr) p.keepExprSymbolName(expr, original_name) else expr;
|
||
}
|
||
|
||
pub fn valueForThis(p: *P, loc: logger.Loc) ?Expr {
|
||
// Substitute "this" if we're inside a static class property initializer
|
||
if (p.fn_only_data_visit.should_replace_this_with_class_name_ref) {
|
||
if (p.fn_only_data_visit.class_name_ref) |ref| {
|
||
p.recordUsage(ref.*);
|
||
return p.newExpr(E.Identifier{ .ref = ref.* }, loc);
|
||
}
|
||
}
|
||
|
||
// oroigianlly was !=- modepassthrough
|
||
if (!p.fn_only_data_visit.is_this_nested) {
|
||
if (p.has_es_module_syntax and p.commonjs_named_exports.count() == 0) {
|
||
// In an ES6 module, "this" is supposed to be undefined. Instead of
|
||
// doing this at runtime using "fn.call(undefined)", we do it at
|
||
// compile time using expression substitution here.
|
||
return Expr{ .loc = loc, .data = nullValueExpr };
|
||
} else {
|
||
// In a CommonJS module, "this" is supposed to be the same as "exports".
|
||
// Instead of doing this at runtime using "fn.call(module.exports)", we
|
||
// do it at compile time using expression substitution here.
|
||
p.recordUsage(p.exports_ref);
|
||
p.deoptimizeCommonJSNamedExports();
|
||
return p.newExpr(E.Identifier{ .ref = p.exports_ref }, loc);
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
pub fn isValidAssignmentTarget(p: *P, expr: Expr) bool {
|
||
return switch (expr.data) {
|
||
.e_identifier => |ident| !isEvalOrArguments(p.loadNameFromRef(ident.ref)),
|
||
.e_dot => |e| e.optional_chain == null,
|
||
.e_index => |e| e.optional_chain == null,
|
||
.e_array => |e| !e.is_parenthesized,
|
||
.e_object => |e| !e.is_parenthesized,
|
||
else => false,
|
||
};
|
||
}
|
||
|
||
/// This is only allowed to be called if allow_runtime is true
|
||
/// If --target=bun, this does nothing.
|
||
pub fn recordUsageOfRuntimeRequire(p: *P) void {
|
||
// target bun does not have __require
|
||
if (p.options.features.auto_polyfill_require) {
|
||
bun.assert(p.options.features.allow_runtime);
|
||
|
||
p.ensureRequireSymbol();
|
||
p.recordUsage(p.runtimeIdentifierRef(logger.Loc.Empty, "__require"));
|
||
}
|
||
}
|
||
|
||
pub fn ignoreUsageOfRuntimeRequire(p: *P) void {
|
||
if (p.options.features.auto_polyfill_require) {
|
||
bun.assert(p.runtime_imports.__require != null);
|
||
p.ignoreUsage(p.runtimeIdentifierRef(logger.Loc.Empty, "__require"));
|
||
p.symbols.items[p.require_ref.innerIndex()].use_count_estimate -|= 1;
|
||
}
|
||
}
|
||
|
||
pub inline fn valueForRequire(p: *P, loc: logger.Loc) Expr {
|
||
bun.assert(!p.isSourceRuntime());
|
||
return Expr{
|
||
.data = .{
|
||
.e_require_call_target = {},
|
||
},
|
||
.loc = loc,
|
||
};
|
||
}
|
||
|
||
pub inline fn valueForImportMetaMain(p: *P, inverted: bool, loc: logger.Loc) Expr {
|
||
if (p.options.import_meta_main_value) |known| {
|
||
return .{ .loc = loc, .data = .{ .e_boolean = .{ .value = if (inverted) !known else known } } };
|
||
} else {
|
||
// Node.js does not have import.meta.main, so we end up lowering
|
||
// this to `require.main === module`, but with the ESM format,
|
||
// both `require` and `module` are not present, so the code
|
||
// generation we need is:
|
||
//
|
||
// import { createRequire } from "node:module";
|
||
// var __require = createRequire(import.meta.url);
|
||
// var import_meta_main = __require.main === __require.module;
|
||
//
|
||
// The printer can handle this for us, but we need to reference
|
||
// a handle to the `__require` function.
|
||
if (p.options.lower_import_meta_main_for_node_js) {
|
||
p.recordUsageOfRuntimeRequire();
|
||
}
|
||
|
||
return .{
|
||
.loc = loc,
|
||
.data = .{ .e_import_meta_main = .{ .inverted = inverted } },
|
||
};
|
||
}
|
||
}
|
||
|
||
pub fn keepExprSymbolName(_: *P, _value: Expr, _: string) Expr {
|
||
return _value;
|
||
// var start = p.expr_list.items.len;
|
||
// p.expr_list.ensureUnusedCapacity(2) catch unreachable;
|
||
// p.expr_list.appendAssumeCapacity(_value);
|
||
// p.expr_list.appendAssumeCapacity(p.newExpr(E.String{
|
||
// .utf8 = name,
|
||
// }, _value.loc));
|
||
|
||
// var value = p.callRuntime(_value.loc, "ℹ", p.expr_list.items[start..p.expr_list.items.len]);
|
||
// // Make sure tree shaking removes this if the function is never used
|
||
// value.getCall().can_be_unwrapped_if_unused = true;
|
||
// return value;
|
||
}
|
||
|
||
pub fn isSimpleParameterList(args: []G.Arg, has_rest_arg: bool) bool {
|
||
if (has_rest_arg) {
|
||
return false;
|
||
}
|
||
|
||
for (args) |arg| {
|
||
if (@as(Binding.Tag, arg.binding.data) != .b_identifier or arg.default != null) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
// This one is never called in places that haven't already checked if DCE is enabled.
|
||
pub fn classCanBeRemovedIfUnused(p: *P, class: *G.Class) bool {
|
||
if (class.extends) |*extends| {
|
||
if (!p.exprCanBeRemovedIfUnusedWithoutDCECheck(extends)) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
for (class.properties) |*property| {
|
||
if (property.kind == .class_static_block) {
|
||
if (!p.stmtsCanBeRemovedifUnusedWithoutDCECheck(property.class_static_block.?.stmts.slice())) {
|
||
return false;
|
||
}
|
||
continue;
|
||
}
|
||
|
||
if (!p.exprCanBeRemovedIfUnusedWithoutDCECheck(&(property.key orelse unreachable))) {
|
||
return false;
|
||
}
|
||
|
||
if (property.value) |*val| {
|
||
if (!p.exprCanBeRemovedIfUnusedWithoutDCECheck(val)) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
if (property.initializer) |*val| {
|
||
if (!p.exprCanBeRemovedIfUnusedWithoutDCECheck(val)) {
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
// TODO:
|
||
// When React Fast Refresh is enabled, anything that's a JSX component should not be removable
|
||
// This is to improve the reliability of fast refresh between page loads.
|
||
pub fn exprCanBeRemovedIfUnused(p: *P, expr: *const Expr) bool {
|
||
if (!p.options.features.dead_code_elimination) return false;
|
||
|
||
return exprCanBeRemovedIfUnusedWithoutDCECheck(p, expr);
|
||
}
|
||
|
||
fn exprCanBeRemovedIfUnusedWithoutDCECheck(p: *P, expr: *const Expr) bool {
|
||
switch (expr.data) {
|
||
.e_null,
|
||
.e_undefined,
|
||
.e_missing,
|
||
.e_boolean,
|
||
.e_branch_boolean,
|
||
.e_number,
|
||
.e_big_int,
|
||
.e_string,
|
||
.e_this,
|
||
.e_reg_exp,
|
||
.e_function,
|
||
.e_arrow,
|
||
.e_import_meta,
|
||
=> {
|
||
return true;
|
||
},
|
||
|
||
.e_inlined_enum => |e| return p.exprCanBeRemovedIfUnusedWithoutDCECheck(&e.value),
|
||
|
||
.e_dot => |ex| {
|
||
return ex.can_be_removed_if_unused;
|
||
},
|
||
.e_class => |ex| {
|
||
return p.classCanBeRemovedIfUnused(ex);
|
||
},
|
||
.e_identifier => |ex| {
|
||
bun.assert(!ex.ref.isSourceContentsSlice()); // was not visited
|
||
|
||
if (ex.must_keep_due_to_with_stmt) {
|
||
return false;
|
||
}
|
||
|
||
// Unbound identifiers cannot be removed because they can have side effects.
|
||
// One possible side effect is throwing a ReferenceError if they don't exist.
|
||
// Another one is a getter with side effects on the global object:
|
||
//
|
||
// Object.defineProperty(globalThis, 'x', {
|
||
// get() {
|
||
// sideEffect();
|
||
// },
|
||
// });
|
||
//
|
||
// Be very careful about this possibility. It's tempting to treat all
|
||
// identifier expressions as not having side effects but that's wrong. We
|
||
// must make sure they have been declared by the code we are currently
|
||
// compiling before we can tell that they have no side effects.
|
||
//
|
||
// Note that we currently ignore ReferenceErrors due to TDZ access. This is
|
||
// incorrect but proper TDZ analysis is very complicated and would have to
|
||
// be very conservative, which would inhibit a lot of optimizations of code
|
||
// inside closures. This may need to be revisited if it proves problematic.
|
||
if (ex.can_be_removed_if_unused or p.symbols.items[ex.ref.innerIndex()].kind != .unbound) {
|
||
return true;
|
||
}
|
||
},
|
||
.e_commonjs_export_identifier, .e_import_identifier => {
|
||
|
||
// References to an ES6 import item are always side-effect free in an
|
||
// ECMAScript environment.
|
||
//
|
||
// They could technically have side effects if the imported module is a
|
||
// CommonJS module and the import item was translated to a property access
|
||
// (which esbuild's bundler does) and the property has a getter with side
|
||
// effects.
|
||
//
|
||
// But this is very unlikely and respecting this edge case would mean
|
||
// disabling tree shaking of all code that references an export from a
|
||
// CommonJS module. It would also likely violate the expectations of some
|
||
// developers because the code *looks* like it should be able to be tree
|
||
// shaken.
|
||
//
|
||
// So we deliberately ignore this edge case and always treat import item
|
||
// references as being side-effect free.
|
||
return true;
|
||
},
|
||
.e_if => |ex| {
|
||
return p.exprCanBeRemovedIfUnusedWithoutDCECheck(&ex.test_) and
|
||
(p.isSideEffectFreeUnboundIdentifierRef(
|
||
ex.yes,
|
||
ex.test_,
|
||
true,
|
||
) or
|
||
p.exprCanBeRemovedIfUnusedWithoutDCECheck(&ex.yes)) and
|
||
(p.isSideEffectFreeUnboundIdentifierRef(
|
||
ex.no,
|
||
ex.test_,
|
||
false,
|
||
) or p.exprCanBeRemovedIfUnusedWithoutDCECheck(
|
||
&ex.no,
|
||
));
|
||
},
|
||
.e_array => |ex| {
|
||
for (ex.items.slice()) |*item| {
|
||
if (!p.exprCanBeRemovedIfUnusedWithoutDCECheck(item)) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
},
|
||
.e_object => |ex| {
|
||
for (ex.properties.slice()) |*property| {
|
||
|
||
// The key must still be evaluated if it's computed or a spread
|
||
if (property.kind == .spread or (property.flags.contains(.is_computed) and !property.key.?.isPrimitiveLiteral()) or property.flags.contains(.is_spread)) {
|
||
return false;
|
||
}
|
||
|
||
if (property.value) |*val| {
|
||
if (!p.exprCanBeRemovedIfUnusedWithoutDCECheck(val)) {
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
return true;
|
||
},
|
||
.e_call => |ex| {
|
||
// A call that has been marked "__PURE__" can be removed if all arguments
|
||
// can be removed. The annotation causes us to ignore the target.
|
||
if (ex.can_be_unwrapped_if_unused != .never) {
|
||
for (ex.args.slice()) |*arg| {
|
||
if (!(p.exprCanBeRemovedIfUnusedWithoutDCECheck(arg) or (ex.can_be_unwrapped_if_unused == .if_unused_and_toString_safe and arg.data.isSafeToString()))) {
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
},
|
||
.e_new => |ex| {
|
||
|
||
// A call that has been marked "__PURE__" can be removed if all arguments
|
||
// can be removed. The annotation causes us to ignore the target.
|
||
if (ex.can_be_unwrapped_if_unused != .never) {
|
||
for (ex.args.slice()) |*arg| {
|
||
if (!(p.exprCanBeRemovedIfUnusedWithoutDCECheck(arg) or (ex.can_be_unwrapped_if_unused == .if_unused_and_toString_safe and arg.data.isSafeToString()))) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
},
|
||
.e_unary => |ex| {
|
||
switch (ex.op) {
|
||
// These operators must not have any type conversions that can execute code
|
||
// such as "toString" or "valueOf". They must also never throw any exceptions.
|
||
.un_void, .un_not => {
|
||
return p.exprCanBeRemovedIfUnusedWithoutDCECheck(&ex.value);
|
||
},
|
||
|
||
// The "typeof" operator doesn't do any type conversions so it can be removed
|
||
// if the result is unused and the operand has no side effects. However, it
|
||
// has a special case where if the operand is an identifier expression such
|
||
// as "typeof x" and "x" doesn't exist, no reference error is thrown so the
|
||
// operation has no side effects.
|
||
//
|
||
// Note that there *is* actually a case where "typeof x" can throw an error:
|
||
// when "x" is being referenced inside of its TDZ (temporal dead zone). TDZ
|
||
// checks are not yet handled correctly by bun or esbuild, so this possibility is
|
||
// currently ignored.
|
||
.un_typeof => {
|
||
if (ex.value.data == .e_identifier and ex.flags.was_originally_typeof_identifier) {
|
||
return true;
|
||
}
|
||
|
||
return p.exprCanBeRemovedIfUnusedWithoutDCECheck(&ex.value);
|
||
},
|
||
|
||
else => {},
|
||
}
|
||
},
|
||
.e_binary => |ex| {
|
||
switch (ex.op) {
|
||
// These operators must not have any type conversions that can execute code
|
||
// such as "toString" or "valueOf". They must also never throw any exceptions.
|
||
.bin_strict_eq,
|
||
.bin_strict_ne,
|
||
.bin_comma,
|
||
.bin_nullish_coalescing,
|
||
=> return p.exprCanBeRemovedIfUnusedWithoutDCECheck(&ex.left) and p.exprCanBeRemovedIfUnusedWithoutDCECheck(&ex.right),
|
||
|
||
// Special-case "||" to make sure "typeof x === 'undefined' || x" can be removed
|
||
.bin_logical_or => return p.exprCanBeRemovedIfUnusedWithoutDCECheck(&ex.left) and
|
||
(p.isSideEffectFreeUnboundIdentifierRef(ex.right, ex.left, false) or p.exprCanBeRemovedIfUnusedWithoutDCECheck(&ex.right)),
|
||
|
||
// Special-case "&&" to make sure "typeof x !== 'undefined' && x" can be removed
|
||
.bin_logical_and => return p.exprCanBeRemovedIfUnusedWithoutDCECheck(&ex.left) and
|
||
(p.isSideEffectFreeUnboundIdentifierRef(ex.right, ex.left, true) or p.exprCanBeRemovedIfUnusedWithoutDCECheck(&ex.right)),
|
||
|
||
// For "==" and "!=", pretend the operator was actually "===" or "!==". If
|
||
// we know that we can convert it to "==" or "!=", then we can consider the
|
||
// operator itself to have no side effects. This matters because our mangle
|
||
// logic will convert "typeof x === 'object'" into "typeof x == 'object'"
|
||
// and since "typeof x === 'object'" is considered to be side-effect free,
|
||
// we must also consider "typeof x == 'object'" to be side-effect free.
|
||
.bin_loose_eq, .bin_loose_ne => return SideEffects.canChangeStrictToLoose(
|
||
ex.left.data,
|
||
ex.right.data,
|
||
) and
|
||
p.exprCanBeRemovedIfUnusedWithoutDCECheck(&ex.left) and p.exprCanBeRemovedIfUnusedWithoutDCECheck(&ex.right),
|
||
|
||
// Special-case "<" and ">" with string, number, or bigint arguments
|
||
.bin_lt, .bin_gt, .bin_le, .bin_ge => {
|
||
const left = ex.left.knownPrimitive();
|
||
const right = ex.right.knownPrimitive();
|
||
switch (left) {
|
||
.string, .number, .bigint => {
|
||
return right == left and p.exprCanBeRemovedIfUnusedWithoutDCECheck(&ex.left) and p.exprCanBeRemovedIfUnusedWithoutDCECheck(&ex.right);
|
||
},
|
||
else => {},
|
||
}
|
||
},
|
||
else => {},
|
||
}
|
||
},
|
||
.e_template => |templ| {
|
||
if (templ.tag == null) {
|
||
for (templ.parts) |part| {
|
||
if (!p.exprCanBeRemovedIfUnusedWithoutDCECheck(&part.value) or part.value.knownPrimitive() == .unknown) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
},
|
||
else => {},
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
// // This is based on exprCanBeRemoved
|
||
// // The main difference: identifiers, functions, arrow functions cause it to return false
|
||
// pub fn exprCanBeHoistedForJSX(p: *P, expr: *const Expr) bool {
|
||
// if (comptime jsx_transform_type != .react) {
|
||
// unreachable;
|
||
// }
|
||
|
||
// switch (expr.data) {
|
||
// .e_null,
|
||
// .e_undefined,
|
||
// .e_missing,
|
||
// .e_boolean,
|
||
// .e_number,
|
||
// .e_big_int,
|
||
// .e_string,
|
||
// .e_reg_exp,
|
||
// => {
|
||
// return true;
|
||
// },
|
||
|
||
// .e_dot => |ex| {
|
||
// return ex.can_be_removed_if_unused;
|
||
// },
|
||
// .e_import_identifier => {
|
||
|
||
// // References to an ES6 import item are always side-effect free in an
|
||
// // ECMAScript environment.
|
||
// //
|
||
// // They could technically have side effects if the imported module is a
|
||
// // CommonJS module and the import item was translated to a property access
|
||
// // (which esbuild's bundler does) and the property has a getter with side
|
||
// // effects.
|
||
// //
|
||
// // But this is very unlikely and respecting this edge case would mean
|
||
// // disabling tree shaking of all code that references an export from a
|
||
// // CommonJS module. It would also likely violate the expectations of some
|
||
// // developers because the code *looks* like it should be able to be tree
|
||
// // shaken.
|
||
// //
|
||
// // So we deliberately ignore this edge case and always treat import item
|
||
// // references as being side-effect free.
|
||
// return true;
|
||
// },
|
||
// .e_if => |ex| {
|
||
// return p.exprCanBeHoistedForJSX(&ex.test_) and
|
||
// (p.isSideEffectFreeUnboundIdentifierRef(
|
||
// ex.yes,
|
||
// ex.test_,
|
||
// true,
|
||
// ) or
|
||
// p.exprCanBeHoistedForJSX(&ex.yes)) and
|
||
// (p.isSideEffectFreeUnboundIdentifierRef(
|
||
// ex.no,
|
||
// ex.test_,
|
||
// false,
|
||
// ) or p.exprCanBeHoistedForJSX(
|
||
// &ex.no,
|
||
// ));
|
||
// },
|
||
// .e_array => |ex| {
|
||
// for (ex.items.slice()) |*item| {
|
||
// if (!p.exprCanBeHoistedForJSX(item)) {
|
||
// return false;
|
||
// }
|
||
// }
|
||
|
||
// return true;
|
||
// },
|
||
// .e_object => |ex| {
|
||
// // macros disable this because macros get inlined
|
||
// // so it's sort of the opposite of the purpose of this function
|
||
// if (ex.was_originally_macro)
|
||
// return false;
|
||
|
||
// for (ex.properties.slice()) |*property| {
|
||
|
||
// // The key must still be evaluated if it's computed or a spread
|
||
// if (property.kind == .spread or property.flags.contains(.is_computed) or property.flags.contains(.is_spread)) {
|
||
// return false;
|
||
// }
|
||
|
||
// if (property.value) |*val| {
|
||
// if (!p.exprCanBeHoistedForJSX(val)) {
|
||
// return false;
|
||
// }
|
||
// }
|
||
// }
|
||
// return true;
|
||
// },
|
||
// .e_call => |ex| {
|
||
|
||
// // A call that has been marked "__PURE__" can be removed if all arguments
|
||
// // can be removed. The annotation causes us to ignore the target.
|
||
// if (ex.can_be_unwrapped_if_unused) {
|
||
// for (ex.args.slice()) |*arg| {
|
||
// if (!p.exprCanBeHoistedForJSX(arg)) {
|
||
// return false;
|
||
// }
|
||
// }
|
||
// return true;
|
||
// }
|
||
// },
|
||
// .e_new => |ex| {
|
||
|
||
// // A call that has been marked "__PURE__" can be removed if all arguments
|
||
// // can be removed. The annotation causes us to ignore the target.
|
||
// if (ex.can_be_unwrapped_if_unused) {
|
||
// for (ex.args.slice()) |*arg| {
|
||
// if (!p.exprCanBeHoistedForJSX(arg)) {
|
||
// return false;
|
||
// }
|
||
// }
|
||
|
||
// return true;
|
||
// }
|
||
// },
|
||
// .e_unary => |ex| {
|
||
// switch (ex.op) {
|
||
// // These operators must not have any type conversions that can execute code
|
||
// // such as "toString" or "valueOf". They must also never throw any exceptions.
|
||
// .un_void, .un_not => {
|
||
// return p.exprCanBeHoistedForJSX(&ex.value);
|
||
// },
|
||
|
||
// // The "typeof" operator doesn't do any type conversions so it can be removed
|
||
// // if the result is unused and the operand has no side effects. However, it
|
||
// // has a special case where if the operand is an identifier expression such
|
||
// // as "typeof x" and "x" doesn't exist, no reference error is thrown so the
|
||
// // operation has no side effects.
|
||
// //
|
||
// // Note that there *is* actually a case where "typeof x" can throw an error:
|
||
// // when "x" is being referenced inside of its TDZ (temporal dead zone). TDZ
|
||
// // checks are not yet handled correctly by bun or esbuild, so this possibility is
|
||
// // currently ignored.
|
||
// .un_typeof => {
|
||
// if (ex.value.data == .e_identifier) {
|
||
// return true;
|
||
// }
|
||
|
||
// return p.exprCanBeHoistedForJSX(&ex.value);
|
||
// },
|
||
|
||
// else => {},
|
||
// }
|
||
// },
|
||
// .e_binary => |ex| {
|
||
// switch (ex.op) {
|
||
// // These operators must not have any type conversions that can execute code
|
||
// // such as "toString" or "valueOf". They must also never throw any exceptions.
|
||
// .bin_strict_eq,
|
||
// .bin_strict_ne,
|
||
// .bin_comma,
|
||
// .bin_nullish_coalescing,
|
||
// => return p.exprCanBeHoistedForJSX(&ex.left) and p.exprCanBeHoistedForJSX(&ex.right),
|
||
|
||
// // Special-case "||" to make sure "typeof x === 'undefined' || x" can be removed
|
||
// .bin_logical_or => return p.exprCanBeHoistedForJSX(&ex.left) and
|
||
// (p.isSideEffectFreeUnboundIdentifierRef(ex.right, ex.left, false) or p.exprCanBeHoistedForJSX(&ex.right)),
|
||
|
||
// // Special-case "&&" to make sure "typeof x !== 'undefined' && x" can be removed
|
||
// .bin_logical_and => return p.exprCanBeHoistedForJSX(&ex.left) and
|
||
// (p.isSideEffectFreeUnboundIdentifierRef(ex.right, ex.left, true) or p.exprCanBeHoistedForJSX(&ex.right)),
|
||
|
||
// // For "==" and "!=", pretend the operator was actually "===" or "!==". If
|
||
// // we know that we can convert it to "==" or "!=", then we can consider the
|
||
// // operator itself to have no side effects. This matters because our mangle
|
||
// // logic will convert "typeof x === 'object'" into "typeof x == 'object'"
|
||
// // and since "typeof x === 'object'" is considered to be side-effect free,
|
||
// // we must also consider "typeof x == 'object'" to be side-effect free.
|
||
// .bin_loose_eq, .bin_loose_ne => return SideEffects.canChangeStrictToLoose(
|
||
// ex.left.data,
|
||
// ex.right.data,
|
||
// ) and
|
||
// p.exprCanBeHoistedForJSX(&ex.left) and p.exprCanBeHoistedForJSX(&ex.right),
|
||
// else => {},
|
||
// }
|
||
// },
|
||
// .e_template => |templ| {
|
||
// if (templ.tag == null) {
|
||
// for (templ.parts) |part| {
|
||
// if (!p.exprCanBeHoistedForJSX(&part.value) or part.value.knownPrimitive() == .unknown) {
|
||
// return false;
|
||
// }
|
||
// }
|
||
// }
|
||
|
||
// return true;
|
||
// },
|
||
// else => {},
|
||
|
||
// // These may reference variables from an upper scope
|
||
// // it's possible to detect that, but we are cutting scope for now
|
||
// // .e_function,
|
||
// // .e_arrow,
|
||
// // .e_this,
|
||
// }
|
||
|
||
// return false;
|
||
// }
|
||
|
||
fn isSideEffectFreeUnboundIdentifierRef(p: *P, value: Expr, guard_condition: Expr, is_yes_branch_: bool) bool {
|
||
if (value.data != .e_identifier or
|
||
p.symbols.items[value.data.e_identifier.ref.innerIndex()].kind != .unbound or
|
||
guard_condition.data != .e_binary)
|
||
return false;
|
||
|
||
const binary = guard_condition.data.e_binary.*;
|
||
var is_yes_branch = is_yes_branch_;
|
||
|
||
switch (binary.op) {
|
||
.bin_strict_eq, .bin_strict_ne, .bin_loose_eq, .bin_loose_ne => {
|
||
// typeof x !== 'undefined'
|
||
var typeof: Expr.Data = binary.left.data;
|
||
var compare: Expr.Data = binary.right.data;
|
||
// typeof 'undefined' !== x
|
||
if (typeof == .e_string) {
|
||
typeof = binary.right.data;
|
||
compare = binary.left.data;
|
||
}
|
||
|
||
// this order because Expr.Data Tag is not a pointer
|
||
// so it should be slightly faster to compare
|
||
if (compare != .e_string or
|
||
typeof != .e_unary)
|
||
return false;
|
||
const unary = typeof.e_unary.*;
|
||
|
||
if (unary.op != .un_typeof or unary.value.data != .e_identifier)
|
||
return false;
|
||
|
||
const id = value.data.e_identifier.ref;
|
||
const id2 = unary.value.data.e_identifier.ref;
|
||
return ((compare.e_string.eqlComptime("undefined") == is_yes_branch) ==
|
||
(binary.op == .bin_strict_ne or binary.op == .bin_loose_ne)) and
|
||
id.eql(id2);
|
||
},
|
||
.bin_lt, .bin_gt, .bin_le, .bin_ge => {
|
||
// Pattern match for "typeof x < <string>"
|
||
var typeof: Expr.Data = binary.left.data;
|
||
var str: Expr.Data = binary.right.data;
|
||
|
||
// Check if order is flipped: 'u' >= typeof x
|
||
if (typeof == .e_string) {
|
||
typeof = binary.right.data;
|
||
str = binary.left.data;
|
||
is_yes_branch = !is_yes_branch;
|
||
}
|
||
|
||
if (typeof == .e_unary and str == .e_string) {
|
||
const unary = typeof.e_unary.*;
|
||
if (unary.op == .un_typeof and
|
||
unary.value.data == .e_identifier and
|
||
unary.flags.was_originally_typeof_identifier and
|
||
str.e_string.eqlComptime("u"))
|
||
{
|
||
// In "typeof x < 'u' ? x : null", the reference to "x" is side-effect free
|
||
// In "typeof x > 'u' ? x : null", the reference to "x" is side-effect free
|
||
if (is_yes_branch == (binary.op == .bin_lt or binary.op == .bin_le)) {
|
||
const id = value.data.e_identifier.ref;
|
||
const id2 = unary.value.data.e_identifier.ref;
|
||
if (id.eql(id2)) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return false;
|
||
},
|
||
else => return false,
|
||
}
|
||
}
|
||
|
||
pub fn jsxImportAutomatic(p: *P, loc: logger.Loc, is_static: bool) Expr {
|
||
return p.jsxImport(
|
||
if (is_static and !p.options.jsx.development and FeatureFlags.support_jsxs_in_jsx_transform)
|
||
.jsxs
|
||
else if (p.options.jsx.development)
|
||
.jsxDEV
|
||
else
|
||
.jsx,
|
||
loc,
|
||
);
|
||
}
|
||
|
||
pub fn jsxImport(p: *P, kind: JSXImport, loc: logger.Loc) Expr {
|
||
switch (kind) {
|
||
inline else => |field| {
|
||
const ref: Ref = brk: {
|
||
if (p.jsx_imports.getWithTag(kind) == null) {
|
||
const symbol_name = @tagName(field);
|
||
|
||
const loc_ref = LocRef{
|
||
.loc = loc,
|
||
.ref = (p.declareGeneratedSymbol(.other, symbol_name) catch unreachable),
|
||
};
|
||
|
||
bun.handleOom(p.module_scope.generated.append(p.allocator, loc_ref.ref.?));
|
||
p.is_import_item.put(p.allocator, loc_ref.ref.?, {}) catch unreachable;
|
||
@field(p.jsx_imports, @tagName(field)) = loc_ref;
|
||
break :brk loc_ref.ref.?;
|
||
}
|
||
|
||
break :brk p.jsx_imports.getWithTag(kind).?;
|
||
};
|
||
|
||
p.recordUsage(ref);
|
||
return p.handleIdentifier(
|
||
loc,
|
||
E.Identifier{
|
||
.ref = ref,
|
||
.can_be_removed_if_unused = true,
|
||
.call_can_be_unwrapped_if_unused = true,
|
||
},
|
||
null,
|
||
.{
|
||
.was_originally_identifier = true,
|
||
},
|
||
);
|
||
},
|
||
}
|
||
}
|
||
|
||
pub fn selectLocalKind(p: *P, kind: S.Local.Kind) S.Local.Kind {
|
||
// Use "var" instead of "let" and "const" if the variable declaration may
|
||
// need to be separated from the initializer. This allows us to safely move
|
||
// this declaration into a nested scope.
|
||
if ((p.options.bundle or p.will_wrap_module_in_try_catch_for_using) and
|
||
p.current_scope.parent == null and !kind.isUsing())
|
||
{
|
||
return .k_var;
|
||
}
|
||
|
||
// Optimization: use "let" instead of "const" because it's shorter. This is
|
||
// only done when bundling because assigning to "const" is only an error when bundling.
|
||
if (p.options.bundle and kind == .k_const and p.options.features.minify_syntax) {
|
||
return .k_let;
|
||
}
|
||
|
||
return kind;
|
||
}
|
||
|
||
pub fn ignoreUsage(p: *P, ref: Ref) void {
|
||
if (!p.is_control_flow_dead and !p.is_revisit_for_substitution) {
|
||
if (comptime Environment.allow_assert) assert(@as(usize, ref.innerIndex()) < p.symbols.items.len);
|
||
p.symbols.items[ref.innerIndex()].use_count_estimate -|= 1;
|
||
var use = p.symbol_uses.get(ref) orelse return;
|
||
use.count_estimate -|= 1;
|
||
if (use.count_estimate == 0) {
|
||
_ = p.symbol_uses.swapRemove(ref);
|
||
} else {
|
||
p.symbol_uses.putAssumeCapacity(ref, use);
|
||
}
|
||
}
|
||
|
||
// Don't roll back the "tsUseCounts" increment. This must be counted even if
|
||
// the value is ignored because that's what the TypeScript compiler does.
|
||
}
|
||
|
||
pub fn ignoreUsageOfIdentifierInDotChain(p: *P, expr: Expr) void {
|
||
var current = expr;
|
||
while (true) {
|
||
switch (current.data) {
|
||
.e_identifier => |id| {
|
||
p.ignoreUsage(id.ref);
|
||
},
|
||
.e_dot => |dot| {
|
||
current = dot.target;
|
||
continue;
|
||
},
|
||
.e_index => |index| {
|
||
if (index.index.isString()) {
|
||
current = index.target;
|
||
continue;
|
||
}
|
||
},
|
||
else => return,
|
||
}
|
||
|
||
return;
|
||
}
|
||
}
|
||
|
||
pub fn isExportToEliminate(p: *P, ref: Ref) bool {
|
||
const symbol_name = p.loadNameFromRef(ref);
|
||
return p.options.features.replace_exports.contains(symbol_name);
|
||
}
|
||
|
||
pub fn injectReplacementExport(p: *P, stmts: *StmtList, name_ref: Ref, loc: logger.Loc, replacement: *const RuntimeFeatures.ReplaceableExport) bool {
|
||
switch (replacement.*) {
|
||
.delete => return false,
|
||
.replace => |value| {
|
||
const count = stmts.items.len;
|
||
var decls = p.allocator.alloc(G.Decl, 1) catch unreachable;
|
||
|
||
decls[0] = .{ .binding = p.b(B.Identifier{ .ref = name_ref }, loc), .value = value };
|
||
var local = p.s(
|
||
S.Local{
|
||
.is_export = true,
|
||
.decls = Decl.List.fromOwnedSlice(decls),
|
||
},
|
||
loc,
|
||
);
|
||
p.visitAndAppendStmt(stmts, &local) catch unreachable;
|
||
return count != stmts.items.len;
|
||
},
|
||
.inject => |with| {
|
||
const count = stmts.items.len;
|
||
var decls = p.allocator.alloc(G.Decl, 1) catch unreachable;
|
||
decls[0] = .{
|
||
.binding = p.b(
|
||
B.Identifier{ .ref = p.declareSymbol(.other, loc, with.name) catch unreachable },
|
||
loc,
|
||
),
|
||
.value = with.value,
|
||
};
|
||
|
||
var local = p.s(
|
||
S.Local{
|
||
.is_export = true,
|
||
.decls = Decl.List.fromOwnedSlice(decls),
|
||
},
|
||
loc,
|
||
);
|
||
p.visitAndAppendStmt(stmts, &local) catch unreachable;
|
||
return count != stmts.items.len;
|
||
},
|
||
}
|
||
}
|
||
|
||
pub fn replaceDeclAndPossiblyRemove(p: *P, decl: *G.Decl, replacement: *const RuntimeFeatures.ReplaceableExport) bool {
|
||
switch (replacement.*) {
|
||
.delete => return false,
|
||
.replace => |value| {
|
||
decl.*.value = p.visitExpr(value);
|
||
return true;
|
||
},
|
||
.inject => |with| {
|
||
decl.* = .{
|
||
.binding = p.b(
|
||
B.Identifier{ .ref = p.declareSymbol(.other, decl.binding.loc, with.name) catch unreachable },
|
||
decl.binding.loc,
|
||
),
|
||
.value = p.visitExpr(Expr{ .data = with.value.data, .loc = if (decl.value != null) decl.value.?.loc else decl.binding.loc }),
|
||
};
|
||
return true;
|
||
},
|
||
}
|
||
}
|
||
|
||
pub fn markExportedDeclsInsideNamespace(p: *P, ns_ref: Ref, decls: []G.Decl) void {
|
||
for (decls) |decl| {
|
||
p.markExportedBindingInsideNamespace(ns_ref, decl.binding);
|
||
}
|
||
}
|
||
|
||
pub fn appendIfBodyPreservingScope(noalias p: *P, stmts: *ListManaged(Stmt), body: Stmt) anyerror!void {
|
||
switch (body.data) {
|
||
.s_block => |block| {
|
||
var keep_block = false;
|
||
for (block.stmts) |stmt| {
|
||
if (statementCaresAboutScope(stmt)) {
|
||
keep_block = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!keep_block and block.stmts.len > 0) {
|
||
try stmts.appendSlice(block.stmts);
|
||
return;
|
||
}
|
||
},
|
||
else => {},
|
||
}
|
||
|
||
if (statementCaresAboutScope(body)) {
|
||
var block_stmts = try p.allocator.alloc(Stmt, 1);
|
||
block_stmts[0] = body;
|
||
try stmts.append(p.s(S.Block{ .stmts = block_stmts }, body.loc));
|
||
return;
|
||
}
|
||
|
||
try stmts.append(body);
|
||
return;
|
||
}
|
||
|
||
fn markExportedBindingInsideNamespace(p: *P, ref: Ref, binding: BindingNodeIndex) void {
|
||
switch (binding.data) {
|
||
.b_missing => {},
|
||
.b_identifier => |ident| {
|
||
p.is_exported_inside_namespace.put(p.allocator, ident.ref, ref) catch unreachable;
|
||
},
|
||
.b_array => |array| {
|
||
for (array.items) |item| {
|
||
p.markExportedBindingInsideNamespace(ref, item.binding);
|
||
}
|
||
},
|
||
.b_object => |obj| {
|
||
for (obj.properties) |item| {
|
||
p.markExportedBindingInsideNamespace(ref, item.value);
|
||
}
|
||
},
|
||
}
|
||
}
|
||
|
||
pub fn generateClosureForTypeScriptNamespaceOrEnum(
|
||
noalias p: *P,
|
||
noalias stmts: *ListManaged(Stmt),
|
||
stmt_loc: logger.Loc,
|
||
is_export: bool,
|
||
name_loc: logger.Loc,
|
||
original_name_ref: Ref,
|
||
arg_ref: Ref,
|
||
stmts_inside_closure: []Stmt,
|
||
all_values_are_pure: bool,
|
||
) anyerror!void {
|
||
var name_ref = original_name_ref;
|
||
|
||
// Follow the link chain in case symbols were merged
|
||
var symbol: Symbol = p.symbols.items[name_ref.innerIndex()];
|
||
while (symbol.hasLink()) {
|
||
const link = symbol.link;
|
||
name_ref = link;
|
||
symbol = p.symbols.items[name_ref.innerIndex()];
|
||
}
|
||
const allocator = p.allocator;
|
||
|
||
// Make sure to only emit a variable once for a given namespace, since there
|
||
// can be multiple namespace blocks for the same namespace
|
||
if ((symbol.kind == .ts_namespace or symbol.kind == .ts_enum) and
|
||
!p.emitted_namespace_vars.contains(name_ref))
|
||
{
|
||
bun.handleOom(p.emitted_namespace_vars.putNoClobber(allocator, name_ref, {}));
|
||
|
||
var decls = bun.handleOom(allocator.alloc(G.Decl, 1));
|
||
decls[0] = G.Decl{ .binding = p.b(B.Identifier{ .ref = name_ref }, name_loc) };
|
||
|
||
if (p.enclosing_namespace_arg_ref == null) {
|
||
// Top-level namespace: "var"
|
||
stmts.append(
|
||
p.s(S.Local{
|
||
.kind = .k_var,
|
||
.decls = G.Decl.List.fromOwnedSlice(decls),
|
||
.is_export = is_export,
|
||
}, stmt_loc),
|
||
) catch |err| bun.handleOom(err);
|
||
} else {
|
||
// Nested namespace: "let"
|
||
stmts.append(
|
||
p.s(S.Local{
|
||
.kind = .k_let,
|
||
.decls = G.Decl.List.fromOwnedSlice(decls),
|
||
}, stmt_loc),
|
||
) catch |err| bun.handleOom(err);
|
||
}
|
||
}
|
||
|
||
const arg_expr: Expr = arg_expr: {
|
||
// TODO: unsupportedJSFeatures.has(.logical_assignment)
|
||
// If the "||=" operator is supported, our minified output can be slightly smaller
|
||
if (is_export) if (p.enclosing_namespace_arg_ref) |namespace| {
|
||
const name = p.symbols.items[name_ref.innerIndex()].original_name;
|
||
|
||
// "name = (enclosing.name ||= {})"
|
||
p.recordUsage(namespace);
|
||
p.recordUsage(name_ref);
|
||
break :arg_expr Expr.assign(
|
||
Expr.initIdentifier(name_ref, name_loc),
|
||
p.newExpr(E.Binary{
|
||
.op = .bin_logical_or_assign,
|
||
.left = p.newExpr(
|
||
E.Dot{
|
||
.target = Expr.initIdentifier(namespace, name_loc),
|
||
.name = name,
|
||
.name_loc = name_loc,
|
||
},
|
||
name_loc,
|
||
),
|
||
.right = p.newExpr(E.Object{}, name_loc),
|
||
}, name_loc),
|
||
);
|
||
};
|
||
|
||
// "name ||= {}"
|
||
p.recordUsage(name_ref);
|
||
break :arg_expr p.newExpr(E.Binary{
|
||
.op = .bin_logical_or_assign,
|
||
.left = Expr.initIdentifier(name_ref, name_loc),
|
||
.right = p.newExpr(E.Object{}, name_loc),
|
||
}, name_loc);
|
||
};
|
||
|
||
var func_args = bun.handleOom(allocator.alloc(G.Arg, 1));
|
||
func_args[0] = .{ .binding = p.b(B.Identifier{ .ref = arg_ref }, name_loc) };
|
||
|
||
var args_list = bun.handleOom(allocator.alloc(ExprNodeIndex, 1));
|
||
args_list[0] = arg_expr;
|
||
|
||
// TODO: if unsupported features includes arrow functions
|
||
// const target = p.newExpr(
|
||
// E.Function{ .func = .{
|
||
// .args = func_args,
|
||
// .name = null,
|
||
// .open_parens_loc = stmt_loc,
|
||
// .body = G.FnBody{
|
||
// .loc = stmt_loc,
|
||
// .stmts = try allocator.dupe(StmtNodeIndex, stmts_inside_closure),
|
||
// },
|
||
// } },
|
||
// stmt_loc,
|
||
// );
|
||
|
||
const target = target: {
|
||
// "(() => { foo() })()" => "(() => foo())()"
|
||
if (p.options.features.minify_syntax and stmts_inside_closure.len == 1) {
|
||
if (stmts_inside_closure[0].data == .s_expr) {
|
||
stmts_inside_closure[0] = p.s(S.Return{
|
||
.value = stmts_inside_closure[0].data.s_expr.value,
|
||
}, stmts_inside_closure[0].loc);
|
||
}
|
||
}
|
||
|
||
break :target p.newExpr(E.Arrow{
|
||
.args = func_args,
|
||
.body = .{
|
||
.loc = stmt_loc,
|
||
.stmts = try allocator.dupe(StmtNodeIndex, stmts_inside_closure),
|
||
},
|
||
.prefer_expr = true,
|
||
}, stmt_loc);
|
||
};
|
||
|
||
// Call the closure with the name object
|
||
const call = p.newExpr(
|
||
E.Call{
|
||
.target = target,
|
||
.args = ExprNodeList.fromOwnedSlice(args_list),
|
||
// TODO: make these fully tree-shakable. this annotation
|
||
// as-is is incorrect. This would be done by changing all
|
||
// enum wrappers into `var Enum = ...` instead of two
|
||
// separate statements. This way, the @__PURE__ annotation
|
||
// is attached to the variable binding.
|
||
//
|
||
// .can_be_unwrapped_if_unused = all_values_are_pure,
|
||
},
|
||
stmt_loc,
|
||
);
|
||
|
||
const closure = p.s(S.SExpr{
|
||
.value = call,
|
||
.does_not_affect_tree_shaking = all_values_are_pure,
|
||
}, stmt_loc);
|
||
|
||
stmts.append(closure) catch unreachable;
|
||
}
|
||
|
||
pub fn lowerClass(
|
||
noalias p: *P,
|
||
stmtorexpr: js_ast.StmtOrExpr,
|
||
) []Stmt {
|
||
switch (stmtorexpr) {
|
||
.stmt => |stmt| {
|
||
if (comptime !is_typescript_enabled) {
|
||
if (!stmt.data.s_class.class.has_decorators) {
|
||
var stmts = p.allocator.alloc(Stmt, 1) catch unreachable;
|
||
stmts[0] = stmt;
|
||
return stmts;
|
||
}
|
||
}
|
||
var class = &stmt.data.s_class.class;
|
||
var constructor_function: ?*E.Function = null;
|
||
|
||
var static_decorators = ListManaged(Stmt).init(p.allocator);
|
||
var instance_decorators = ListManaged(Stmt).init(p.allocator);
|
||
var instance_members = ListManaged(Stmt).init(p.allocator);
|
||
var static_members = ListManaged(Stmt).init(p.allocator);
|
||
var class_properties = ListManaged(Property).init(p.allocator);
|
||
|
||
for (class.properties) |*prop| {
|
||
// merge parameter decorators with method decorators
|
||
if (prop.flags.contains(.is_method)) {
|
||
if (prop.value) |prop_value| {
|
||
switch (prop_value.data) {
|
||
.e_function => |func| {
|
||
const is_constructor = (prop.key.?.data == .e_string and prop.key.?.data.e_string.eqlComptime("constructor"));
|
||
|
||
if (is_constructor) constructor_function = func;
|
||
|
||
for (func.func.args, 0..) |arg, i| {
|
||
for (arg.ts_decorators.ptr[0..arg.ts_decorators.len]) |arg_decorator| {
|
||
var decorators = if (is_constructor)
|
||
&class.ts_decorators
|
||
else
|
||
&prop.ts_decorators;
|
||
const args = p.allocator.alloc(Expr, 2) catch unreachable;
|
||
args[0] = p.newExpr(E.Number{ .value = @as(f64, @floatFromInt(i)) }, arg_decorator.loc);
|
||
args[1] = arg_decorator;
|
||
decorators.append(
|
||
p.allocator,
|
||
p.callRuntime(arg_decorator.loc, "__legacyDecorateParamTS", args),
|
||
) catch |err| bun.handleOom(err);
|
||
}
|
||
}
|
||
},
|
||
else => unreachable,
|
||
}
|
||
}
|
||
}
|
||
|
||
// TODO: prop.kind == .declare and prop.value == null
|
||
|
||
if (prop.ts_decorators.len > 0) {
|
||
const descriptor_key = prop.key.?;
|
||
const loc = descriptor_key.loc;
|
||
|
||
// TODO: when we have the `accessor` modifier, add `and !prop.flags.contains(.has_accessor_modifier)` to
|
||
// the if statement.
|
||
const descriptor_kind: Expr = if (!prop.flags.contains(.is_method))
|
||
p.newExpr(E.Undefined{}, loc)
|
||
else
|
||
p.newExpr(E.Null{}, loc);
|
||
|
||
var target: Expr = undefined;
|
||
if (prop.flags.contains(.is_static)) {
|
||
p.recordUsage(class.class_name.?.ref.?);
|
||
target = p.newExpr(E.Identifier{ .ref = class.class_name.?.ref.? }, class.class_name.?.loc);
|
||
} else {
|
||
target = p.newExpr(E.Dot{ .target = p.newExpr(E.Identifier{ .ref = class.class_name.?.ref.? }, class.class_name.?.loc), .name = "prototype", .name_loc = loc }, loc);
|
||
}
|
||
|
||
var array: std.array_list.Managed(Expr) = .init(p.allocator);
|
||
|
||
if (p.options.features.emit_decorator_metadata) {
|
||
switch (prop.kind) {
|
||
.normal, .abstract => {
|
||
{
|
||
// design:type
|
||
var args = p.allocator.alloc(Expr, 2) catch unreachable;
|
||
args[0] = p.newExpr(E.String{ .data = "design:type" }, logger.Loc.Empty);
|
||
args[1] = p.serializeMetadata(prop.ts_metadata) catch unreachable;
|
||
array.append(p.callRuntime(loc, "__legacyMetadataTS", args)) catch unreachable;
|
||
}
|
||
// design:paramtypes and design:returntype if method
|
||
if (prop.flags.contains(.is_method)) {
|
||
if (prop.value) |prop_value| {
|
||
{
|
||
var args = p.allocator.alloc(Expr, 2) catch unreachable;
|
||
args[0] = p.newExpr(E.String{ .data = "design:paramtypes" }, logger.Loc.Empty);
|
||
|
||
const method_args = prop_value.data.e_function.func.args;
|
||
const args_array = p.allocator.alloc(Expr, method_args.len) catch unreachable;
|
||
for (args_array, method_args) |*entry, method_arg| {
|
||
entry.* = p.serializeMetadata(method_arg.ts_metadata) catch unreachable;
|
||
}
|
||
|
||
args[1] = p.newExpr(E.Array{ .items = ExprNodeList.fromOwnedSlice(args_array) }, logger.Loc.Empty);
|
||
|
||
array.append(p.callRuntime(loc, "__legacyMetadataTS", args)) catch unreachable;
|
||
}
|
||
{
|
||
var args = p.allocator.alloc(Expr, 2) catch unreachable;
|
||
args[0] = p.newExpr(E.String{ .data = "design:returntype" }, logger.Loc.Empty);
|
||
args[1] = p.serializeMetadata(prop_value.data.e_function.func.return_ts_metadata) catch unreachable;
|
||
array.append(p.callRuntime(loc, "__legacyMetadataTS", args)) catch unreachable;
|
||
}
|
||
}
|
||
}
|
||
},
|
||
.get => if (prop.flags.contains(.is_method)) {
|
||
// typescript sets design:type to the return value & design:paramtypes to [].
|
||
if (prop.value) |prop_value| {
|
||
{
|
||
var args = p.allocator.alloc(Expr, 2) catch unreachable;
|
||
args[0] = p.newExpr(E.String{ .data = "design:type" }, logger.Loc.Empty);
|
||
args[1] = p.serializeMetadata(prop_value.data.e_function.func.return_ts_metadata) catch unreachable;
|
||
array.append(p.callRuntime(loc, "__legacyMetadataTS", args)) catch unreachable;
|
||
}
|
||
{
|
||
var args = p.allocator.alloc(Expr, 2) catch unreachable;
|
||
args[0] = p.newExpr(E.String{ .data = "design:paramtypes" }, logger.Loc.Empty);
|
||
args[1] = p.newExpr(E.Array{ .items = ExprNodeList.empty }, logger.Loc.Empty);
|
||
array.append(p.callRuntime(loc, "__legacyMetadataTS", args)) catch unreachable;
|
||
}
|
||
}
|
||
},
|
||
.set => if (prop.flags.contains(.is_method)) {
|
||
// typescript sets design:type to the return value & design:paramtypes to [arg].
|
||
// note that typescript does not allow you to put a decorator on both the getter and the setter.
|
||
// if you do anyway, bun will set design:type and design:paramtypes twice, so it's fine.
|
||
if (prop.value) |prop_value| {
|
||
const method_args = prop_value.data.e_function.func.args;
|
||
{
|
||
var args = p.allocator.alloc(Expr, 2) catch unreachable;
|
||
args[0] = p.newExpr(E.String{ .data = "design:paramtypes" }, logger.Loc.Empty);
|
||
|
||
const args_array = p.allocator.alloc(Expr, method_args.len) catch unreachable;
|
||
for (args_array, method_args) |*entry, method_arg| {
|
||
entry.* = p.serializeMetadata(method_arg.ts_metadata) catch unreachable;
|
||
}
|
||
|
||
args[1] = p.newExpr(E.Array{ .items = ExprNodeList.fromOwnedSlice(args_array) }, logger.Loc.Empty);
|
||
|
||
array.append(p.callRuntime(loc, "__legacyMetadataTS", args)) catch unreachable;
|
||
}
|
||
if (method_args.len >= 1) {
|
||
var args = p.allocator.alloc(Expr, 2) catch unreachable;
|
||
args[0] = p.newExpr(E.String{ .data = "design:type" }, logger.Loc.Empty);
|
||
args[1] = p.serializeMetadata(method_args[0].ts_metadata) catch unreachable;
|
||
array.append(p.callRuntime(loc, "__legacyMetadataTS", args)) catch unreachable;
|
||
}
|
||
}
|
||
},
|
||
.spread, .declare => {}, // not allowed in a class
|
||
.class_static_block => {}, // not allowed to decorate this
|
||
}
|
||
}
|
||
|
||
bun.handleOom(array.insertSlice(0, prop.ts_decorators.slice()));
|
||
const args = p.allocator.alloc(Expr, 4) catch unreachable;
|
||
args[0] = p.newExpr(E.Array{ .items = ExprNodeList.moveFromList(&array) }, loc);
|
||
args[1] = target;
|
||
args[2] = descriptor_key;
|
||
args[3] = descriptor_kind;
|
||
|
||
const decorator = p.callRuntime(prop.key.?.loc, "__legacyDecorateClassTS", args);
|
||
const decorator_stmt = p.s(S.SExpr{ .value = decorator }, decorator.loc);
|
||
|
||
if (prop.flags.contains(.is_static)) {
|
||
static_decorators.append(decorator_stmt) catch unreachable;
|
||
} else {
|
||
instance_decorators.append(decorator_stmt) catch unreachable;
|
||
}
|
||
}
|
||
|
||
if (prop.kind != .class_static_block and !prop.flags.contains(.is_method) and prop.key.?.data != .e_private_identifier and prop.ts_decorators.len > 0) {
|
||
// remove decorated fields without initializers to avoid assigning undefined.
|
||
const initializer = if (prop.initializer) |initializer_value| initializer_value else continue;
|
||
|
||
var target: Expr = undefined;
|
||
if (prop.flags.contains(.is_static)) {
|
||
p.recordUsage(class.class_name.?.ref.?);
|
||
target = p.newExpr(E.Identifier{ .ref = class.class_name.?.ref.? }, class.class_name.?.loc);
|
||
} else {
|
||
target = p.newExpr(E.This{}, prop.key.?.loc);
|
||
}
|
||
|
||
if (prop.flags.contains(.is_computed) or prop.key.?.data == .e_number) {
|
||
target = p.newExpr(E.Index{
|
||
.target = target,
|
||
.index = prop.key.?,
|
||
}, prop.key.?.loc);
|
||
} else {
|
||
target = p.newExpr(E.Dot{
|
||
.target = target,
|
||
.name = prop.key.?.data.e_string.data,
|
||
.name_loc = prop.key.?.loc,
|
||
}, prop.key.?.loc);
|
||
}
|
||
|
||
// remove fields with decorators from class body. Move static members outside of class.
|
||
if (prop.flags.contains(.is_static)) {
|
||
static_members.append(Stmt.assign(target, initializer)) catch unreachable;
|
||
} else {
|
||
instance_members.append(Stmt.assign(target, initializer)) catch unreachable;
|
||
}
|
||
continue;
|
||
}
|
||
|
||
class_properties.append(prop.*) catch unreachable;
|
||
}
|
||
|
||
class.properties = class_properties.items;
|
||
|
||
if (instance_members.items.len > 0) {
|
||
if (constructor_function == null) {
|
||
var properties = ListManaged(Property).fromOwnedSlice(p.allocator, class.properties);
|
||
var constructor_stmts = ListManaged(Stmt).init(p.allocator);
|
||
|
||
if (class.extends != null) {
|
||
const target = p.newExpr(E.Super{}, stmt.loc);
|
||
const arguments_ref = p.newSymbol(.unbound, arguments_str) catch unreachable;
|
||
bun.handleOom(p.current_scope.generated.append(p.allocator, arguments_ref));
|
||
|
||
const super = p.newExpr(E.Spread{ .value = p.newExpr(E.Identifier{ .ref = arguments_ref }, stmt.loc) }, stmt.loc);
|
||
const args = bun.handleOom(ExprNodeList.initOne(p.allocator, super));
|
||
|
||
constructor_stmts.append(p.s(S.SExpr{ .value = p.newExpr(E.Call{ .target = target, .args = args }, stmt.loc) }, stmt.loc)) catch unreachable;
|
||
}
|
||
|
||
constructor_stmts.appendSlice(instance_members.items) catch unreachable;
|
||
|
||
properties.insert(0, G.Property{
|
||
.flags = Flags.Property.init(.{ .is_method = true }),
|
||
.key = p.newExpr(E.String{ .data = "constructor" }, stmt.loc),
|
||
.value = p.newExpr(E.Function{ .func = G.Fn{
|
||
.name = null,
|
||
.open_parens_loc = logger.Loc.Empty,
|
||
.args = &[_]Arg{},
|
||
.body = .{ .loc = stmt.loc, .stmts = constructor_stmts.items },
|
||
.flags = Flags.Function.init(.{}),
|
||
} }, stmt.loc),
|
||
}) catch unreachable;
|
||
|
||
class.properties = properties.items;
|
||
} else {
|
||
var constructor_stmts = ListManaged(Stmt).fromOwnedSlice(p.allocator, constructor_function.?.func.body.stmts);
|
||
// statements coming from class body inserted after super call or beginning of constructor.
|
||
var super_index: ?usize = null;
|
||
for (constructor_stmts.items, 0..) |item, index| {
|
||
if (item.data != .s_expr or item.data.s_expr.value.data != .e_call or item.data.s_expr.value.data.e_call.target.data != .e_super) continue;
|
||
super_index = index;
|
||
break;
|
||
}
|
||
|
||
const i = if (super_index) |j| j + 1 else 0;
|
||
constructor_stmts.insertSlice(i, instance_members.items) catch unreachable;
|
||
|
||
constructor_function.?.func.body.stmts = constructor_stmts.items;
|
||
}
|
||
|
||
// TODO: make sure "super()" comes before instance field initializers
|
||
// https://github.com/evanw/esbuild/blob/e9413cc4f7ab87263ea244a999c6fa1f1e34dc65/internal/js_parser/js_parser_lower.go#L2742
|
||
}
|
||
|
||
var stmts_count: usize = 1 + static_members.items.len + instance_decorators.items.len + static_decorators.items.len;
|
||
if (class.ts_decorators.len > 0) stmts_count += 1;
|
||
var stmts = ListManaged(Stmt).initCapacity(p.allocator, stmts_count) catch unreachable;
|
||
stmts.appendAssumeCapacity(stmt);
|
||
stmts.appendSliceAssumeCapacity(static_members.items);
|
||
stmts.appendSliceAssumeCapacity(instance_decorators.items);
|
||
stmts.appendSliceAssumeCapacity(static_decorators.items);
|
||
if (class.ts_decorators.len > 0) {
|
||
var array = class.ts_decorators.moveToListManaged(p.allocator);
|
||
|
||
if (p.options.features.emit_decorator_metadata) {
|
||
if (constructor_function != null) {
|
||
// design:paramtypes
|
||
var args = p.allocator.alloc(Expr, 2) catch unreachable;
|
||
args[0] = p.newExpr(E.String{ .data = "design:paramtypes" }, logger.Loc.Empty);
|
||
|
||
const constructor_args = constructor_function.?.func.args;
|
||
if (constructor_args.len > 0) {
|
||
var param_array = p.allocator.alloc(Expr, constructor_args.len) catch unreachable;
|
||
|
||
for (constructor_args, 0..) |constructor_arg, i| {
|
||
param_array[i] = p.serializeMetadata(constructor_arg.ts_metadata) catch unreachable;
|
||
}
|
||
|
||
args[1] = p.newExpr(E.Array{ .items = ExprNodeList.fromOwnedSlice(param_array) }, logger.Loc.Empty);
|
||
} else {
|
||
args[1] = p.newExpr(E.Array{ .items = ExprNodeList.empty }, logger.Loc.Empty);
|
||
}
|
||
|
||
array.append(p.callRuntime(stmt.loc, "__legacyMetadataTS", args)) catch unreachable;
|
||
}
|
||
}
|
||
|
||
const args = p.allocator.alloc(Expr, 2) catch unreachable;
|
||
args[0] = p.newExpr(E.Array{ .items = ExprNodeList.fromOwnedSlice(array.items) }, stmt.loc);
|
||
args[1] = p.newExpr(E.Identifier{ .ref = class.class_name.?.ref.? }, class.class_name.?.loc);
|
||
|
||
stmts.appendAssumeCapacity(Stmt.assign(
|
||
p.newExpr(E.Identifier{ .ref = class.class_name.?.ref.? }, class.class_name.?.loc),
|
||
p.callRuntime(stmt.loc, "__legacyDecorateClassTS", args),
|
||
));
|
||
|
||
p.recordUsage(class.class_name.?.ref.?);
|
||
p.recordUsage(class.class_name.?.ref.?);
|
||
}
|
||
return stmts.items;
|
||
},
|
||
.expr => |expr| {
|
||
var stmts = p.allocator.alloc(Stmt, 1) catch unreachable;
|
||
stmts[0] = p.s(S.SExpr{ .value = expr }, expr.loc);
|
||
return stmts;
|
||
},
|
||
}
|
||
}
|
||
|
||
fn serializeMetadata(noalias p: *P, ts_metadata: TypeScript.Metadata) !Expr {
|
||
return switch (ts_metadata) {
|
||
.m_none,
|
||
.m_any,
|
||
.m_unknown,
|
||
.m_object,
|
||
=> p.newExpr(
|
||
E.Identifier{
|
||
.ref = (p.findSymbol(logger.Loc.Empty, "Object") catch unreachable).ref,
|
||
},
|
||
logger.Loc.Empty,
|
||
),
|
||
|
||
.m_never,
|
||
.m_undefined,
|
||
.m_null,
|
||
.m_void,
|
||
=> p.newExpr(
|
||
E.Undefined{},
|
||
logger.Loc.Empty,
|
||
),
|
||
|
||
.m_string => p.newExpr(
|
||
E.Identifier{
|
||
.ref = (p.findSymbol(logger.Loc.Empty, "String") catch unreachable).ref,
|
||
},
|
||
logger.Loc.Empty,
|
||
),
|
||
.m_number => p.newExpr(
|
||
E.Identifier{
|
||
.ref = (p.findSymbol(logger.Loc.Empty, "Number") catch unreachable).ref,
|
||
},
|
||
logger.Loc.Empty,
|
||
),
|
||
.m_function => p.newExpr(
|
||
E.Identifier{
|
||
.ref = (p.findSymbol(logger.Loc.Empty, "Function") catch unreachable).ref,
|
||
},
|
||
logger.Loc.Empty,
|
||
),
|
||
.m_boolean => p.newExpr(
|
||
E.Identifier{
|
||
.ref = (p.findSymbol(logger.Loc.Empty, "Boolean") catch unreachable).ref,
|
||
},
|
||
logger.Loc.Empty,
|
||
),
|
||
.m_array => p.newExpr(
|
||
E.Identifier{
|
||
.ref = (p.findSymbol(logger.Loc.Empty, "Array") catch unreachable).ref,
|
||
},
|
||
logger.Loc.Empty,
|
||
),
|
||
|
||
.m_bigint => p.maybeDefinedHelper(
|
||
p.newExpr(
|
||
E.Identifier{
|
||
.ref = (p.findSymbol(logger.Loc.Empty, "BigInt") catch unreachable).ref,
|
||
},
|
||
logger.Loc.Empty,
|
||
),
|
||
),
|
||
|
||
.m_symbol => p.maybeDefinedHelper(
|
||
p.newExpr(
|
||
E.Identifier{
|
||
.ref = (p.findSymbol(logger.Loc.Empty, "Symbol") catch unreachable).ref,
|
||
},
|
||
logger.Loc.Empty,
|
||
),
|
||
),
|
||
|
||
.m_promise => p.newExpr(
|
||
E.Identifier{
|
||
.ref = (p.findSymbol(logger.Loc.Empty, "Promise") catch unreachable).ref,
|
||
},
|
||
logger.Loc.Empty,
|
||
),
|
||
|
||
.m_identifier => |ref| {
|
||
p.recordUsage(ref);
|
||
if (p.is_import_item.contains(ref)) {
|
||
return p.maybeDefinedHelper(p.newExpr(
|
||
E.ImportIdentifier{
|
||
.ref = ref,
|
||
},
|
||
logger.Loc.Empty,
|
||
));
|
||
}
|
||
|
||
return p.maybeDefinedHelper(p.newExpr(
|
||
E.Identifier{ .ref = ref },
|
||
logger.Loc.Empty,
|
||
));
|
||
},
|
||
|
||
.m_dot => |_refs| {
|
||
var refs = _refs;
|
||
bun.assert(refs.items.len >= 2);
|
||
defer refs.deinit(p.allocator);
|
||
|
||
var dots = p.newExpr(
|
||
E.Dot{
|
||
.name = p.loadNameFromRef(refs.items[refs.items.len - 1]),
|
||
.name_loc = logger.Loc.Empty,
|
||
.target = undefined,
|
||
},
|
||
logger.Loc.Empty,
|
||
);
|
||
|
||
var current_expr = &dots.data.e_dot.target;
|
||
var i: usize = refs.items.len - 2;
|
||
while (i > 0) {
|
||
current_expr.* = p.newExpr(E.Dot{
|
||
.name = p.loadNameFromRef(refs.items[i]),
|
||
.name_loc = logger.Loc.Empty,
|
||
.target = undefined,
|
||
}, logger.Loc.Empty);
|
||
current_expr = ¤t_expr.data.e_dot.target;
|
||
i -= 1;
|
||
}
|
||
|
||
if (p.is_import_item.contains(refs.items[0])) {
|
||
current_expr.* = p.newExpr(
|
||
E.ImportIdentifier{
|
||
.ref = refs.items[0],
|
||
},
|
||
logger.Loc.Empty,
|
||
);
|
||
} else {
|
||
current_expr.* = p.newExpr(
|
||
E.Identifier{
|
||
.ref = refs.items[0],
|
||
},
|
||
logger.Loc.Empty,
|
||
);
|
||
}
|
||
|
||
const dot_identifier = current_expr.*;
|
||
var current_dot = dots;
|
||
|
||
var maybe_defined_dots = p.newExpr(
|
||
E.Binary{
|
||
.op = .bin_logical_or,
|
||
.right = try p.checkIfDefinedHelper(current_dot),
|
||
.left = undefined,
|
||
},
|
||
logger.Loc.Empty,
|
||
);
|
||
|
||
if (i < refs.items.len - 2) {
|
||
current_dot = current_dot.data.e_dot.target;
|
||
}
|
||
current_expr = &maybe_defined_dots.data.e_binary.left;
|
||
|
||
while (i < refs.items.len - 2) {
|
||
current_expr.* = p.newExpr(
|
||
E.Binary{
|
||
.op = .bin_logical_or,
|
||
.right = try p.checkIfDefinedHelper(current_dot),
|
||
.left = undefined,
|
||
},
|
||
logger.Loc.Empty,
|
||
);
|
||
|
||
current_expr = ¤t_expr.data.e_binary.left;
|
||
i += 1;
|
||
if (i < refs.items.len - 2) {
|
||
current_dot = current_dot.data.e_dot.target;
|
||
}
|
||
}
|
||
|
||
current_expr.* = try p.checkIfDefinedHelper(dot_identifier);
|
||
|
||
const root = p.newExpr(
|
||
E.If{
|
||
.yes = p.newExpr(
|
||
E.Identifier{
|
||
.ref = (p.findSymbol(logger.Loc.Empty, "Object") catch unreachable).ref,
|
||
},
|
||
logger.Loc.Empty,
|
||
),
|
||
.no = dots,
|
||
.test_ = maybe_defined_dots,
|
||
},
|
||
logger.Loc.Empty,
|
||
);
|
||
|
||
return root;
|
||
},
|
||
};
|
||
}
|
||
|
||
fn wrapIdentifierNamespace(
|
||
p: *P,
|
||
loc: logger.Loc,
|
||
ref: Ref,
|
||
) Expr {
|
||
const enclosing_ref = p.enclosing_namespace_arg_ref.?;
|
||
p.recordUsage(enclosing_ref);
|
||
|
||
return p.newExpr(E.Dot{
|
||
.target = Expr.initIdentifier(enclosing_ref, loc),
|
||
.name = p.symbols.items[ref.innerIndex()].original_name,
|
||
.name_loc = loc,
|
||
}, loc);
|
||
}
|
||
|
||
fn wrapIdentifierHoisting(
|
||
p: *P,
|
||
loc: logger.Loc,
|
||
ref: Ref,
|
||
) Expr {
|
||
// There was a Zig stage1 bug here we had to copy `ref` into a local
|
||
// const variable or else the result would be wrong
|
||
// I remember that bug in particular took hours, possibly days to uncover.
|
||
|
||
p.relocated_top_level_vars.append(p.allocator, LocRef{ .loc = loc, .ref = ref }) catch unreachable;
|
||
p.recordUsage(ref);
|
||
return Expr.initIdentifier(ref, loc);
|
||
}
|
||
|
||
pub fn wrapInlinedEnum(noalias p: *P, value: Expr, comment: string) Expr {
|
||
if (bun.strings.containsComptime(comment, "*/")) {
|
||
// Don't wrap with a comment
|
||
return value;
|
||
}
|
||
|
||
// Wrap with a comment
|
||
return p.newExpr(E.InlinedEnum{
|
||
.value = value,
|
||
.comment = comment,
|
||
}, value.loc);
|
||
}
|
||
|
||
pub fn valueForDefine(noalias p: *P, loc: logger.Loc, assign_target: js_ast.AssignTarget, is_delete_target: bool, define_data: *const DefineData) Expr {
|
||
switch (define_data.value) {
|
||
.e_identifier => {
|
||
return p.handleIdentifier(
|
||
loc,
|
||
define_data.value.e_identifier,
|
||
define_data.original_name().?,
|
||
IdentifierOpts{
|
||
.assign_target = assign_target,
|
||
.is_delete_target = is_delete_target,
|
||
.was_originally_identifier = true,
|
||
},
|
||
);
|
||
},
|
||
.e_string => |str| {
|
||
return p.newExpr(str, loc);
|
||
},
|
||
else => {},
|
||
}
|
||
|
||
return Expr{
|
||
.data = define_data.value,
|
||
.loc = loc,
|
||
};
|
||
}
|
||
|
||
pub fn isDotDefineMatch(noalias p: *P, expr: Expr, parts: []const string) bool {
|
||
switch (expr.data) {
|
||
.e_dot => |ex| {
|
||
if (parts.len > 1) {
|
||
if (ex.optional_chain != null) {
|
||
return false;
|
||
}
|
||
|
||
// Intermediates must be dot expressions
|
||
const last = parts.len - 1;
|
||
const is_tail_match = strings.eql(parts[last], ex.name);
|
||
return is_tail_match and p.isDotDefineMatch(ex.target, parts[0..last]);
|
||
}
|
||
},
|
||
.e_import_meta => {
|
||
return (parts.len == 2 and strings.eqlComptime(parts[0], "import") and strings.eqlComptime(parts[1], "meta"));
|
||
},
|
||
// Note: this behavior differs from esbuild
|
||
// esbuild does not try to match index accessors
|
||
// we do, but only if it's a UTF8 string
|
||
// the intent is to handle people using this form instead of E.Dot. So we really only want to do this if the accessor can also be an identifier
|
||
.e_index => |index| {
|
||
if (parts.len > 1 and index.index.data == .e_string and index.index.data.e_string.isUTF8()) {
|
||
if (index.optional_chain != null) {
|
||
return false;
|
||
}
|
||
|
||
const last = parts.len - 1;
|
||
const is_tail_match = strings.eql(parts[last], index.index.data.e_string.slice(p.allocator));
|
||
return is_tail_match and p.isDotDefineMatch(index.target, parts[0..last]);
|
||
}
|
||
},
|
||
.e_identifier => |ex| {
|
||
|
||
// The last expression must be an identifier
|
||
if (parts.len == 1) {
|
||
const name = p.loadNameFromRef(ex.ref);
|
||
if (!strings.eql(name, parts[0])) {
|
||
return false;
|
||
}
|
||
|
||
const result = p.findSymbolWithRecordUsage(expr.loc, name, false) catch return false;
|
||
|
||
// We must not be in a "with" statement scope
|
||
if (result.is_inside_with_scope) {
|
||
return false;
|
||
}
|
||
|
||
// when there's actually no symbol by that name, we return Ref.None
|
||
// If a symbol had already existed by that name, we return .unbound
|
||
return (result.ref.isNull() or p.symbols.items[result.ref.innerIndex()].kind == .unbound);
|
||
}
|
||
},
|
||
else => {},
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
// One statement could potentially expand to several statements
|
||
pub fn stmtsToSingleStmt(noalias p: *P, loc: logger.Loc, stmts: []Stmt) Stmt {
|
||
if (stmts.len == 0) {
|
||
return Stmt{ .data = Prefill.Data.SEmpty, .loc = loc };
|
||
}
|
||
|
||
if (stmts.len == 1 and !statementCaresAboutScope(stmts[0])) {
|
||
// "let" and "const" must be put in a block when in a single-statement context
|
||
return stmts[0];
|
||
}
|
||
|
||
return p.s(S.Block{ .stmts = stmts }, loc);
|
||
}
|
||
|
||
pub fn findLabelSymbol(noalias p: *P, loc: logger.Loc, name: string) FindLabelSymbolResult {
|
||
var res = FindLabelSymbolResult{ .ref = Ref.None, .is_loop = false };
|
||
|
||
var _scope: ?*Scope = p.current_scope;
|
||
|
||
while (_scope != null and !_scope.?.kindStopsHoisting()) : (_scope = _scope.?.parent.?) {
|
||
const scope = _scope orelse unreachable;
|
||
const label_ref = scope.label_ref orelse continue;
|
||
if (scope.kind == .label and strings.eql(name, p.symbols.items[label_ref.innerIndex()].original_name)) {
|
||
// Track how many times we've referenced this symbol
|
||
p.recordUsage(label_ref);
|
||
res.ref = label_ref;
|
||
res.is_loop = scope.label_stmt_is_loop;
|
||
res.found = true;
|
||
return res;
|
||
}
|
||
}
|
||
|
||
const r = js_lexer.rangeOfIdentifier(p.source, loc);
|
||
p.log.addRangeErrorFmt(p.source, r, p.allocator, "There is no containing label named \"{s}\"", .{name}) catch unreachable;
|
||
|
||
// Allocate an "unbound" symbol
|
||
const ref = p.newSymbol(.unbound, name) catch unreachable;
|
||
|
||
// Track how many times we've referenced this symbol
|
||
p.recordUsage(ref);
|
||
|
||
return res;
|
||
}
|
||
|
||
fn keepStmtSymbolName(p: *P, loc: logger.Loc, ref: Ref, name: string) Stmt {
|
||
_ = p;
|
||
_ = loc;
|
||
_ = ref;
|
||
_ = name;
|
||
// TODO:
|
||
@compileError("not implemented");
|
||
}
|
||
|
||
fn runtimeIdentifierRef(p: *P, loc: logger.Loc, comptime name: string) Ref {
|
||
p.has_called_runtime = true;
|
||
|
||
if (!p.runtime_imports.contains(name)) {
|
||
if (!p.options.bundle) {
|
||
const generated_symbol = p.declareGeneratedSymbol(.other, name) catch unreachable;
|
||
p.runtime_imports.put(name, generated_symbol);
|
||
return generated_symbol;
|
||
} else {
|
||
const loc_ref = js_ast.LocRef{
|
||
.loc = loc,
|
||
.ref = p.newSymbol(.other, name) catch unreachable,
|
||
};
|
||
p.runtime_imports.put(
|
||
name,
|
||
loc_ref.ref.?,
|
||
);
|
||
bun.handleOom(p.module_scope.generated.append(p.allocator, loc_ref.ref.?));
|
||
return loc_ref.ref.?;
|
||
}
|
||
} else {
|
||
return p.runtime_imports.at(name).?;
|
||
}
|
||
}
|
||
|
||
fn runtimeIdentifier(p: *P, loc: logger.Loc, comptime name: string) Expr {
|
||
const ref = p.runtimeIdentifierRef(loc, name);
|
||
p.recordUsage(ref);
|
||
return p.newExpr(
|
||
E.ImportIdentifier{
|
||
.ref = ref,
|
||
.was_originally_identifier = false,
|
||
},
|
||
loc,
|
||
);
|
||
}
|
||
|
||
pub fn callRuntime(p: *P, loc: logger.Loc, comptime name: string, args: []Expr) Expr {
|
||
return p.newExpr(
|
||
E.Call{
|
||
.target = p.runtimeIdentifier(loc, name),
|
||
.args = ExprNodeList.fromOwnedSlice(args),
|
||
},
|
||
loc,
|
||
);
|
||
}
|
||
|
||
pub fn extractDeclsForBinding(binding: Binding, decls: *ListManaged(G.Decl)) anyerror!void {
|
||
switch (binding.data) {
|
||
.b_missing => {},
|
||
.b_identifier => {
|
||
try decls.append(G.Decl{ .binding = binding });
|
||
},
|
||
.b_array => |arr| {
|
||
for (arr.items) |item| {
|
||
extractDeclsForBinding(item.binding, decls) catch unreachable;
|
||
}
|
||
},
|
||
.b_object => |obj| {
|
||
for (obj.properties) |prop| {
|
||
extractDeclsForBinding(prop.value, decls) catch unreachable;
|
||
}
|
||
},
|
||
}
|
||
}
|
||
|
||
pub inline fn @"module.exports"(p: *P, loc: logger.Loc) Expr {
|
||
return p.newExpr(E.Dot{ .name = exports_string_name, .name_loc = loc, .target = p.newExpr(E.Identifier{ .ref = p.module_ref }, loc) }, loc);
|
||
}
|
||
|
||
// This code is tricky.
|
||
// - Doing it incorrectly will cause segfaults.
|
||
// - Doing it correctly drastically affects runtime performance while parsing larger files
|
||
// The key is in how we remove scopes from the list
|
||
// If we do an orderedRemove, it gets very slow.
|
||
// swapRemove is fast. But a little more dangerous.
|
||
// Instead, we just tombstone it.
|
||
pub fn popAndFlattenScope(p: *P, scope_index: usize) void {
|
||
// Move up to the parent scope
|
||
var to_flatten = p.current_scope;
|
||
var parent = to_flatten.parent.?;
|
||
p.current_scope = parent;
|
||
|
||
// Erase this scope from the order. This will shift over the indices of all
|
||
// the scopes that were created after us. However, we shouldn't have to
|
||
// worry about other code with outstanding scope indices for these scopes.
|
||
// These scopes were all created in between this scope's push and pop
|
||
// operations, so they should all be child scopes and should all be popped
|
||
// by the time we get here.
|
||
p.scopes_in_order.items[scope_index] = null;
|
||
// Decrement the length so that in code with lots of scopes, we use
|
||
// less memory and do less work
|
||
p.scopes_in_order.items.len -= @as(usize, @intFromBool(p.scopes_in_order.items.len == scope_index + 1));
|
||
|
||
// Remove the last child from the parent scope
|
||
const last = parent.children.len - 1;
|
||
if (comptime Environment.allow_assert) assert(parent.children.ptr[last] == to_flatten);
|
||
parent.children.len -|= 1;
|
||
|
||
for (to_flatten.children.slice()) |item| {
|
||
item.parent = parent;
|
||
bun.handleOom(parent.children.append(p.allocator, item));
|
||
}
|
||
}
|
||
|
||
/// When not transpiling we dont use the renamer, so our solution is to generate really
|
||
/// hard to collide with variables, instead of actually making things collision free
|
||
pub fn generateTempRef(p: *P, default_name: ?string) Ref {
|
||
return p.generateTempRefWithScope(default_name, p.current_scope);
|
||
}
|
||
|
||
pub fn generateTempRefWithScope(p: *P, default_name: ?string, scope: *Scope) Ref {
|
||
const name = (if (p.willUseRenamer()) default_name else null) orelse brk: {
|
||
p.temp_ref_count += 1;
|
||
break :brk bun.handleOom(std.fmt.allocPrint(p.allocator, "__bun_temp_ref_{x}$", .{p.temp_ref_count}));
|
||
};
|
||
const ref = bun.handleOom(p.newSymbol(.other, name));
|
||
|
||
p.temp_refs_to_declare.append(p.allocator, .{
|
||
.ref = ref,
|
||
}) catch |err| bun.handleOom(err);
|
||
|
||
bun.handleOom(scope.generated.append(p.allocator, ref));
|
||
|
||
return ref;
|
||
}
|
||
|
||
pub fn computeTsEnumsMap(p: *const P, allocator: Allocator) !js_ast.Ast.TsEnumsMap {
|
||
// When hot module reloading is enabled, we disable enum inlining
|
||
// to avoid making the HMR graph more complicated.
|
||
if (p.options.features.hot_module_reloading)
|
||
return .{};
|
||
|
||
const InlinedEnumValue = js_ast.InlinedEnumValue;
|
||
var map: js_ast.Ast.TsEnumsMap = .{};
|
||
try map.ensureTotalCapacity(allocator, @intCast(p.top_level_enums.items.len));
|
||
for (p.top_level_enums.items) |ref| {
|
||
const entry = p.ref_to_ts_namespace_member.getEntry(ref).?;
|
||
const namespace = entry.value_ptr.namespace;
|
||
var inner_map: bun.StringHashMapUnmanaged(InlinedEnumValue) = .{};
|
||
try inner_map.ensureTotalCapacity(allocator, @intCast(namespace.count()));
|
||
for (namespace.keys(), namespace.values()) |key, val| {
|
||
switch (val.data) {
|
||
.enum_number => |num| inner_map.putAssumeCapacityNoClobber(
|
||
key,
|
||
InlinedEnumValue.encode(.{ .number = num }),
|
||
),
|
||
.enum_string => |str| inner_map.putAssumeCapacityNoClobber(
|
||
key,
|
||
InlinedEnumValue.encode(.{ .string = str }),
|
||
),
|
||
else => continue,
|
||
}
|
||
}
|
||
map.putAssumeCapacity(entry.key_ptr.*, inner_map);
|
||
}
|
||
return map;
|
||
}
|
||
|
||
pub fn shouldLowerUsingDeclarations(p: *const P, stmts: []Stmt) bool {
|
||
// TODO: We do not support lowering await, but when we do this needs to point to that var
|
||
const lower_await = false;
|
||
|
||
// Check feature flags first, then iterate statements.
|
||
if (!p.options.features.lower_using and !lower_await) return false;
|
||
|
||
for (stmts) |stmt| {
|
||
if (stmt.data == .s_local and
|
||
// Need to re-check lower_using for the k_using case in case lower_await is true
|
||
((stmt.data.s_local.kind == .k_using and p.options.features.lower_using) or
|
||
(stmt.data.s_local.kind == .k_await_using)))
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
pub const LowerUsingDeclarationsContext = struct {
|
||
first_using_loc: logger.Loc,
|
||
stack_ref: Ref,
|
||
has_await_using: bool,
|
||
|
||
pub fn init(p: *P) !LowerUsingDeclarationsContext {
|
||
return LowerUsingDeclarationsContext{
|
||
.first_using_loc = logger.Loc.Empty,
|
||
.stack_ref = p.generateTempRef("__stack"),
|
||
.has_await_using = false,
|
||
};
|
||
}
|
||
|
||
pub fn scanStmts(ctx: *LowerUsingDeclarationsContext, p: *P, stmts: []Stmt) void {
|
||
for (stmts) |stmt| {
|
||
switch (stmt.data) {
|
||
.s_local => |local| {
|
||
if (!local.kind.isUsing()) continue;
|
||
|
||
if (ctx.first_using_loc.isEmpty()) {
|
||
ctx.first_using_loc = stmt.loc;
|
||
}
|
||
if (local.kind == .k_await_using) {
|
||
ctx.has_await_using = true;
|
||
}
|
||
for (local.decls.slice()) |*decl| {
|
||
if (decl.value) |*decl_value| {
|
||
const value_loc = decl_value.loc;
|
||
p.recordUsage(ctx.stack_ref);
|
||
const args = bun.handleOom(p.allocator.alloc(Expr, 3));
|
||
args[0] = Expr{
|
||
.data = .{ .e_identifier = .{ .ref = ctx.stack_ref } },
|
||
.loc = stmt.loc,
|
||
};
|
||
args[1] = decl_value.*;
|
||
// 1. always pass this param for hopefully better jit performance
|
||
// 2. pass 1 or 0 to be shorter than `true` or `false`
|
||
args[2] = Expr{
|
||
.data = .{ .e_number = .{ .value = if (local.kind == .k_await_using) 1 else 0 } },
|
||
.loc = stmt.loc,
|
||
};
|
||
decl.value = p.callRuntime(value_loc, "__using", args);
|
||
}
|
||
}
|
||
if (p.will_wrap_module_in_try_catch_for_using and p.current_scope.kind == .entry) {
|
||
local.kind = .k_var;
|
||
} else {
|
||
local.kind = .k_const;
|
||
}
|
||
},
|
||
else => {},
|
||
}
|
||
}
|
||
}
|
||
|
||
pub fn finalize(ctx: *LowerUsingDeclarationsContext, p: *P, stmts: []Stmt, should_hoist_fns: bool) ListManaged(Stmt) {
|
||
var result = ListManaged(Stmt).init(p.allocator);
|
||
var exports = ListManaged(js_ast.ClauseItem).init(p.allocator);
|
||
var end: u32 = 0;
|
||
for (stmts) |stmt| {
|
||
switch (stmt.data) {
|
||
.s_directive, .s_import, .s_export_from, .s_export_star => {
|
||
// These can't go in a try/catch block
|
||
bun.handleOom(result.append(stmt));
|
||
continue;
|
||
},
|
||
|
||
.s_class => {
|
||
if (stmt.data.s_class.is_export) {
|
||
// can't go in try/catch; hoist out
|
||
bun.handleOom(result.append(stmt));
|
||
continue;
|
||
}
|
||
},
|
||
|
||
.s_export_default => {
|
||
continue; // this prevents re-exporting default since we already have it as an .s_export_clause
|
||
},
|
||
|
||
.s_export_clause => |data| {
|
||
// Merge export clauses together
|
||
bun.handleOom(exports.appendSlice(data.items));
|
||
continue;
|
||
},
|
||
|
||
.s_function => {
|
||
if (should_hoist_fns) {
|
||
// Hoist function declarations for cross-file ESM references
|
||
bun.handleOom(result.append(stmt));
|
||
continue;
|
||
}
|
||
},
|
||
|
||
.s_local => |local| {
|
||
// If any of these are exported, turn it into a "var" and add export clauses
|
||
if (local.is_export) {
|
||
local.is_export = false;
|
||
for (local.decls.slice()) |decl| {
|
||
if (decl.binding.data == .b_identifier) {
|
||
const identifier = decl.binding.data.b_identifier;
|
||
exports.append(js_ast.ClauseItem{
|
||
.name = .{
|
||
.loc = decl.binding.loc,
|
||
.ref = identifier.ref,
|
||
},
|
||
.alias = p.symbols.items[identifier.ref.inner_index].original_name,
|
||
.alias_loc = decl.binding.loc,
|
||
}) catch |err| bun.handleOom(err);
|
||
local.kind = .k_var;
|
||
}
|
||
}
|
||
}
|
||
},
|
||
|
||
else => {},
|
||
}
|
||
|
||
stmts[end] = stmt;
|
||
end += 1;
|
||
}
|
||
|
||
const non_exported_statements = stmts[0..end];
|
||
|
||
const caught_ref = p.generateTempRef("_catch");
|
||
const err_ref = p.generateTempRef("_err");
|
||
const has_err_ref = p.generateTempRef("_hasErr");
|
||
|
||
var scope = p.current_scope;
|
||
while (!scope.kindStopsHoisting()) {
|
||
scope = scope.parent.?;
|
||
}
|
||
|
||
const is_top_level = scope == p.module_scope;
|
||
scope.generated.appendSlice(p.allocator, &.{
|
||
ctx.stack_ref,
|
||
caught_ref,
|
||
err_ref,
|
||
has_err_ref,
|
||
}) catch |err| bun.handleOom(err);
|
||
p.declared_symbols.ensureUnusedCapacity(
|
||
p.allocator,
|
||
// 5 to include the _promise decl later on:
|
||
if (ctx.has_await_using) 5 else 4,
|
||
) catch |err| bun.handleOom(err);
|
||
p.declared_symbols.appendAssumeCapacity(.{ .is_top_level = is_top_level, .ref = ctx.stack_ref });
|
||
p.declared_symbols.appendAssumeCapacity(.{ .is_top_level = is_top_level, .ref = caught_ref });
|
||
p.declared_symbols.appendAssumeCapacity(.{ .is_top_level = is_top_level, .ref = err_ref });
|
||
p.declared_symbols.appendAssumeCapacity(.{ .is_top_level = is_top_level, .ref = has_err_ref });
|
||
|
||
const loc = ctx.first_using_loc;
|
||
const call_dispose = call_dispose: {
|
||
p.recordUsage(ctx.stack_ref);
|
||
p.recordUsage(err_ref);
|
||
p.recordUsage(has_err_ref);
|
||
const args = bun.handleOom(p.allocator.alloc(Expr, 3));
|
||
args[0] = Expr{
|
||
.data = .{ .e_identifier = .{ .ref = ctx.stack_ref } },
|
||
.loc = loc,
|
||
};
|
||
args[1] = Expr{
|
||
.data = .{ .e_identifier = .{ .ref = err_ref } },
|
||
.loc = loc,
|
||
};
|
||
args[2] = Expr{
|
||
.data = .{ .e_identifier = .{ .ref = has_err_ref } },
|
||
.loc = loc,
|
||
};
|
||
break :call_dispose p.callRuntime(loc, "__callDispose", args);
|
||
};
|
||
|
||
const finally_stmts = finally: {
|
||
if (ctx.has_await_using) {
|
||
const promise_ref = p.generateTempRef("_promise");
|
||
bun.handleOom(scope.generated.append(p.allocator, promise_ref));
|
||
p.declared_symbols.appendAssumeCapacity(.{ .is_top_level = is_top_level, .ref = promise_ref });
|
||
|
||
const promise_ref_expr = p.newExpr(E.Identifier{ .ref = promise_ref }, loc);
|
||
|
||
const await_expr = p.newExpr(E.Await{
|
||
.value = promise_ref_expr,
|
||
}, loc);
|
||
p.recordUsage(promise_ref);
|
||
|
||
const statements = bun.handleOom(p.allocator.alloc(Stmt, 2));
|
||
statements[0] = p.s(S.Local{
|
||
.decls = decls: {
|
||
const decls = bun.handleOom(p.allocator.alloc(Decl, 1));
|
||
decls[0] = .{
|
||
.binding = p.b(B.Identifier{ .ref = promise_ref }, loc),
|
||
.value = call_dispose,
|
||
};
|
||
break :decls G.Decl.List.fromOwnedSlice(decls);
|
||
},
|
||
}, loc);
|
||
|
||
// The "await" must not happen if an error was thrown before the
|
||
// "await using", so we conditionally await here:
|
||
//
|
||
// var promise = __callDispose(stack, error, hasError);
|
||
// promise && await promise;
|
||
//
|
||
statements[1] = p.s(S.SExpr{
|
||
.value = p.newExpr(E.Binary{
|
||
.op = .bin_logical_and,
|
||
.left = promise_ref_expr,
|
||
.right = await_expr,
|
||
}, loc),
|
||
}, loc);
|
||
|
||
break :finally statements;
|
||
} else {
|
||
const single = bun.handleOom(p.allocator.alloc(Stmt, 1));
|
||
single[0] = p.s(S.SExpr{ .value = call_dispose }, call_dispose.loc);
|
||
break :finally single;
|
||
}
|
||
};
|
||
|
||
// Wrap everything in a try/catch/finally block
|
||
p.recordUsage(caught_ref);
|
||
bun.handleOom(result.ensureUnusedCapacity(2 + @as(usize, @intFromBool(exports.items.len > 0))));
|
||
result.appendAssumeCapacity(p.s(S.Local{
|
||
.decls = decls: {
|
||
const decls = bun.handleOom(p.allocator.alloc(Decl, 1));
|
||
decls[0] = .{
|
||
.binding = p.b(B.Identifier{ .ref = ctx.stack_ref }, loc),
|
||
.value = p.newExpr(E.Array{}, loc),
|
||
};
|
||
break :decls G.Decl.List.fromOwnedSlice(decls);
|
||
},
|
||
.kind = .k_let,
|
||
}, loc));
|
||
result.appendAssumeCapacity(p.s(S.Try{
|
||
.body = non_exported_statements,
|
||
.body_loc = loc,
|
||
.catch_ = .{
|
||
.binding = p.b(B.Identifier{ .ref = caught_ref }, loc),
|
||
.body = catch_body: {
|
||
const statements = bun.handleOom(p.allocator.alloc(Stmt, 1));
|
||
statements[0] = p.s(S.Local{
|
||
.decls = decls: {
|
||
const decls = bun.handleOom(p.allocator.alloc(Decl, 2));
|
||
decls[0] = .{
|
||
.binding = p.b(B.Identifier{ .ref = err_ref }, loc),
|
||
.value = p.newExpr(E.Identifier{ .ref = caught_ref }, loc),
|
||
};
|
||
decls[1] = .{
|
||
.binding = p.b(B.Identifier{ .ref = has_err_ref }, loc),
|
||
.value = p.newExpr(E.Number{ .value = 1 }, loc),
|
||
};
|
||
break :decls G.Decl.List.fromOwnedSlice(decls);
|
||
},
|
||
}, loc);
|
||
break :catch_body statements;
|
||
},
|
||
.body_loc = loc,
|
||
.loc = loc,
|
||
},
|
||
.finally = .{
|
||
.loc = loc,
|
||
.stmts = finally_stmts,
|
||
},
|
||
}, loc));
|
||
|
||
if (exports.items.len > 0) {
|
||
result.appendAssumeCapacity(p.s(S.ExportClause{
|
||
.items = exports.items,
|
||
.is_single_line = false,
|
||
}, loc));
|
||
}
|
||
|
||
return result;
|
||
}
|
||
};
|
||
|
||
const import_meta_hot_accept_err = "Dependencies to `import.meta.hot.accept` must be statically analyzable module specifiers matching direct imports.";
|
||
|
||
/// The signatures for `import.meta.hot.accept` are:
|
||
/// `accept()` - self accept
|
||
/// `accept(Function)` - self accept
|
||
/// `accept(string, Function)` - accepting another module
|
||
/// `accept(string[], Function)` - accepting multiple modules
|
||
///
|
||
/// The strings that can be passed in the first argument must be module
|
||
/// specifiers that were imported. We enforce that they line up exactly
|
||
/// with ones that were imported, so that it can share an import record.
|
||
///
|
||
/// This function replaces all specifier strings with `e_require_resolve_string`
|
||
pub fn handleImportMetaHotAcceptCall(p: *@This(), call: *E.Call) void {
|
||
if (call.args.len == 0) return;
|
||
switch (call.args.at(0).data) {
|
||
.e_string => |str| {
|
||
call.args.mut(0).data = p.rewriteImportMetaHotAcceptString(str, call.args.at(0).loc) orelse
|
||
return;
|
||
},
|
||
.e_array => |arr| for (arr.items.slice()) |*item| {
|
||
if (item.data != .e_string) {
|
||
bun.handleOom(p.log.addError(p.source, item.loc, import_meta_hot_accept_err));
|
||
continue;
|
||
}
|
||
item.data = p.rewriteImportMetaHotAcceptString(item.data.e_string, item.loc) orelse
|
||
return;
|
||
},
|
||
else => return,
|
||
}
|
||
|
||
call.target.data.e_special = .hot_accept_visited;
|
||
}
|
||
|
||
fn rewriteImportMetaHotAcceptString(p: *P, str: *E.String, loc: logger.Loc) ?Expr.Data {
|
||
bun.handleOom(str.toUTF8(p.allocator));
|
||
const specifier = str.data;
|
||
|
||
const import_record_index = for (p.import_records.items, 0..) |import_record, i| {
|
||
if (bun.strings.eql(specifier, import_record.path.text)) {
|
||
break i;
|
||
}
|
||
} else {
|
||
bun.handleOom(p.log.addError(p.source, loc, import_meta_hot_accept_err));
|
||
return null;
|
||
};
|
||
|
||
return .{ .e_special = .{
|
||
.resolved_specifier_string = .init(@intCast(import_record_index)),
|
||
} };
|
||
}
|
||
|
||
const ReactRefreshExportKind = enum { named, default };
|
||
|
||
pub fn handleReactRefreshRegister(p: *P, stmts: *ListManaged(Stmt), original_name: []const u8, ref: Ref, export_kind: ReactRefreshExportKind) !void {
|
||
bun.assert(p.options.features.react_fast_refresh);
|
||
bun.assert(p.current_scope == p.module_scope);
|
||
|
||
if (ReactRefresh.isComponentishName(original_name)) {
|
||
try p.emitReactRefreshRegister(stmts, original_name, ref, export_kind);
|
||
}
|
||
}
|
||
|
||
pub fn emitReactRefreshRegister(p: *P, stmts: *ListManaged(Stmt), original_name: []const u8, ref: Ref, export_kind: ReactRefreshExportKind) !void {
|
||
bun.assert(p.options.features.react_fast_refresh);
|
||
bun.assert(p.current_scope == p.module_scope);
|
||
|
||
// $RefreshReg$(component, "file.ts:Original Name")
|
||
const loc = logger.Loc.Empty;
|
||
try stmts.append(p.s(S.SExpr{ .value = p.newExpr(E.Call{
|
||
.target = Expr.initIdentifier(p.react_refresh.register_ref, loc),
|
||
.args = try ExprNodeList.fromSlice(p.allocator, &.{
|
||
Expr.initIdentifier(ref, loc),
|
||
p.newExpr(E.String{
|
||
.data = try bun.strings.concat(p.allocator, &.{
|
||
p.source.path.pretty,
|
||
":",
|
||
switch (export_kind) {
|
||
.named => original_name,
|
||
.default => "default",
|
||
},
|
||
}),
|
||
}, loc),
|
||
}),
|
||
}, loc) }, loc));
|
||
|
||
p.recordUsage(ref);
|
||
p.react_refresh.register_used = true;
|
||
}
|
||
|
||
pub fn wrapValueForServerComponentReference(p: *P, val: Expr, original_name: []const u8) Expr {
|
||
bun.assert(p.options.features.server_components.wrapsExports());
|
||
bun.assert(p.current_scope == p.module_scope);
|
||
|
||
if (p.options.features.server_components == .wrap_exports_for_server_reference)
|
||
bun.todoPanic(@src(), "registerServerReference", .{});
|
||
|
||
const module_path = p.newExpr(E.String{
|
||
.data = if (p.options.jsx.development)
|
||
p.source.path.pretty
|
||
else
|
||
bun.todoPanic(@src(), "TODO: unique_key here", .{}),
|
||
}, logger.Loc.Empty);
|
||
|
||
// registerClientReference(
|
||
// Comp,
|
||
// "src/filepath.tsx",
|
||
// "Comp"
|
||
// );
|
||
return p.newExpr(E.Call{
|
||
.target = Expr.initIdentifier(p.server_components_wrap_ref, logger.Loc.Empty),
|
||
.args = js_ast.ExprNodeList.fromSlice(p.allocator, &.{
|
||
val,
|
||
module_path,
|
||
p.newExpr(E.String{ .data = original_name }, logger.Loc.Empty),
|
||
}) catch |err| bun.handleOom(err),
|
||
}, logger.Loc.Empty);
|
||
}
|
||
|
||
pub fn handleReactRefreshHookCall(p: *P, hook_call: *E.Call, original_name: []const u8) void {
|
||
bun.assert(p.options.features.react_fast_refresh);
|
||
bun.assert(ReactRefresh.isHookName(original_name));
|
||
const ctx_storage = p.react_refresh.hook_ctx_storage orelse
|
||
return; // not in a function, ignore this hook call.
|
||
|
||
// if this function has no hooks recorded, initialize a hook context
|
||
// every function visit provides stack storage, which it will inspect at visit finish.
|
||
const ctx: *ReactRefresh.HookContext = if (ctx_storage.*) |*ctx| ctx else init: {
|
||
p.react_refresh.signature_used = true;
|
||
|
||
var scope = p.current_scope;
|
||
while (scope.kind != .function_body and scope.kind != .block and scope.kind != .entry) {
|
||
scope = scope.parent orelse break;
|
||
}
|
||
|
||
ctx_storage.* = .{
|
||
.hasher = std.hash.Wyhash.init(0),
|
||
.signature_cb = p.generateTempRefWithScope("_s", scope),
|
||
.user_hooks = .{},
|
||
};
|
||
|
||
// TODO(paperclover): fix the renamer bug. this bug
|
||
// theoretically affects all usages of temp refs, but i cannot
|
||
// find another example of it breaking (like with `using`)
|
||
p.declared_symbols.append(p.allocator, .{
|
||
.is_top_level = true,
|
||
.ref = ctx_storage.*.?.signature_cb,
|
||
}) catch |err| bun.handleOom(err);
|
||
|
||
break :init &(ctx_storage.*.?);
|
||
};
|
||
|
||
ctx.hasher.update(original_name);
|
||
|
||
if (ReactRefresh.built_in_hooks.get(original_name)) |built_in_hook| hash_arg: {
|
||
const arg_index: usize = switch (built_in_hook) {
|
||
// useState first argument is initial state.
|
||
.useState => 0,
|
||
// useReducer second argument is initial state.
|
||
.useReducer => 1,
|
||
else => break :hash_arg,
|
||
};
|
||
if (hook_call.args.len <= arg_index) break :hash_arg;
|
||
const arg = hook_call.args.slice()[arg_index];
|
||
arg.data.writeToHasher(&ctx.hasher, p.symbols.items);
|
||
} else switch (hook_call.target.data) {
|
||
inline .e_identifier,
|
||
.e_import_identifier,
|
||
.e_commonjs_export_identifier,
|
||
=> |id, tag| {
|
||
const gop = bun.handleOom(ctx.user_hooks.getOrPut(p.allocator, id.ref));
|
||
if (!gop.found_existing) {
|
||
gop.value_ptr.* = .{
|
||
.data = @unionInit(Expr.Data, @tagName(tag), id),
|
||
.loc = .Empty,
|
||
};
|
||
}
|
||
},
|
||
else => {},
|
||
}
|
||
|
||
ctx.hasher.update("\x00");
|
||
}
|
||
|
||
pub fn handleReactRefreshPostVisitFunctionBody(p: *P, stmts: *ListManaged(Stmt), hook: *ReactRefresh.HookContext) void {
|
||
bun.assert(p.options.features.react_fast_refresh);
|
||
|
||
// We need to prepend `_s();` as a statement.
|
||
if (stmts.items.len == stmts.capacity) {
|
||
// If the ArrayList does not have enough capacity, it is
|
||
// re-allocated entirely to fit. Only one slot of new capacity
|
||
// is used since we know this statement list is not going to be
|
||
// appended to afterwards; This function is a post-visit handler.
|
||
const new_stmts = bun.handleOom(p.allocator.alloc(Stmt, stmts.items.len + 1));
|
||
@memcpy(new_stmts[1..], stmts.items);
|
||
stmts.deinit();
|
||
stmts.* = ListManaged(Stmt).fromOwnedSlice(p.allocator, new_stmts);
|
||
} else {
|
||
// The array has enough capacity, so there is no possibility of
|
||
// allocation failure. We just move all of the statements over
|
||
// by one, and increase the length using `addOneAssumeCapacity`
|
||
_ = stmts.addOneAssumeCapacity();
|
||
bun.copy(Stmt, stmts.items[1..], stmts.items[0 .. stmts.items.len - 1]);
|
||
}
|
||
|
||
const loc = logger.Loc.Empty;
|
||
const prepended_stmt = p.s(S.SExpr{ .value = p.newExpr(E.Call{
|
||
.target = Expr.initIdentifier(hook.signature_cb, loc),
|
||
}, loc) }, loc);
|
||
stmts.items[0] = prepended_stmt;
|
||
}
|
||
|
||
pub fn getReactRefreshHookSignalDecl(p: *P, signal_cb_ref: Ref) Stmt {
|
||
const loc = logger.Loc.Empty;
|
||
p.react_refresh.latest_signature_ref = signal_cb_ref;
|
||
// var s_ = $RefreshSig$();
|
||
return p.s(S.Local{ .decls = G.Decl.List.fromSlice(p.allocator, &.{.{
|
||
.binding = p.b(B.Identifier{ .ref = signal_cb_ref }, loc),
|
||
.value = p.newExpr(E.Call{
|
||
.target = Expr.initIdentifier(p.react_refresh.create_signature_ref, loc),
|
||
}, loc),
|
||
}}) catch |err| bun.handleOom(err) }, loc);
|
||
}
|
||
|
||
pub fn getReactRefreshHookSignalInit(p: *P, ctx: *ReactRefresh.HookContext, function_with_hook_calls: Expr) Expr {
|
||
const loc = logger.Loc.Empty;
|
||
|
||
const final = ctx.hasher.final();
|
||
const hash_data = bun.handleOom(p.allocator.alloc(u8, comptime bun.base64.encodeLenFromSize(@sizeOf(@TypeOf(final)))));
|
||
bun.assert(bun.base64.encode(hash_data, std.mem.asBytes(&final)) == hash_data.len);
|
||
|
||
const have_custom_hooks = ctx.user_hooks.count() > 0;
|
||
const have_force_arg = have_custom_hooks or p.react_refresh.force_reset;
|
||
|
||
const args = p.allocator.alloc(
|
||
Expr,
|
||
2 +
|
||
@as(usize, @intFromBool(have_force_arg)) +
|
||
@as(usize, @intFromBool(have_custom_hooks)),
|
||
) catch |err| bun.handleOom(err);
|
||
|
||
args[0] = function_with_hook_calls;
|
||
args[1] = p.newExpr(E.String{ .data = hash_data }, loc);
|
||
|
||
if (have_force_arg) args[2] = p.newExpr(E.Boolean{ .value = p.react_refresh.force_reset }, loc);
|
||
|
||
if (have_custom_hooks) {
|
||
// () => [useCustom1, useCustom2]
|
||
args[3] = p.newExpr(E.Arrow{
|
||
.body = .{
|
||
.stmts = p.allocator.dupe(Stmt, &.{
|
||
p.s(S.Return{ .value = p.newExpr(E.Array{
|
||
.items = ExprNodeList.fromBorrowedSliceDangerous(ctx.user_hooks.values()),
|
||
}, loc) }, loc),
|
||
}) catch |err| bun.handleOom(err),
|
||
.loc = loc,
|
||
},
|
||
.prefer_expr = true,
|
||
}, loc);
|
||
}
|
||
|
||
// _s(func, "<hash>", force, () => [useCustom])
|
||
return p.newExpr(E.Call{
|
||
.target = Expr.initIdentifier(ctx.signature_cb, loc),
|
||
.args = ExprNodeList.fromOwnedSlice(args),
|
||
}, loc);
|
||
}
|
||
|
||
pub fn toAST(
|
||
p: *P,
|
||
parts: *ListManaged(js_ast.Part),
|
||
exports_kind: js_ast.ExportsKind,
|
||
wrap_mode: WrapMode,
|
||
hashbang: []const u8,
|
||
) !js_ast.Ast {
|
||
const allocator = p.allocator;
|
||
|
||
// if (p.options.tree_shaking and p.options.features.trim_unused_imports) {
|
||
// p.treeShake(&parts, false);
|
||
// }
|
||
|
||
const bundling = p.options.bundle;
|
||
var parts_end: usize = @as(usize, @intFromBool(bundling));
|
||
|
||
// When bundling with HMR, we need every module to be just a
|
||
// single part, as we later wrap each module into a function,
|
||
// which requires a single part. Otherwise, you'll end up with
|
||
// multiple instances of a module, each with different parts of
|
||
// the file. That is also why tree-shaking is disabled.
|
||
if (p.options.features.hot_module_reloading) {
|
||
bun.assert(!p.options.tree_shaking);
|
||
bun.assert(p.options.features.hot_module_reloading);
|
||
|
||
var hmr_transform_ctx = ConvertESMExportsForHmr{
|
||
.last_part = &parts.items[parts.items.len - 1],
|
||
.is_in_node_modules = p.source.path.isNodeModule(),
|
||
};
|
||
try hmr_transform_ctx.stmts.ensureTotalCapacity(p.allocator, prealloc_count: {
|
||
// get a estimate on how many statements there are going to be
|
||
var count: usize = 0;
|
||
for (parts.items) |part| count += part.stmts.len;
|
||
break :prealloc_count count + 2;
|
||
});
|
||
|
||
for (parts.items) |part| {
|
||
// Bake does not care about 'import =', as it handles it on it's own
|
||
_ = try ImportScanner.scan(P, p, part.stmts, wrap_mode != .none, true, &hmr_transform_ctx);
|
||
}
|
||
|
||
try hmr_transform_ctx.finalize(p, parts.items);
|
||
} else {
|
||
// Handle import paths after the whole file has been visited because we need
|
||
// symbol usage counts to be able to remove unused type-only imports in
|
||
// TypeScript code.
|
||
while (true) {
|
||
var kept_import_equals = false;
|
||
var removed_import_equals = false;
|
||
|
||
const begin = parts_end;
|
||
// Potentially remove some statements, then filter out parts to remove any
|
||
// with no statements
|
||
for (parts.items[begin..]) |part_| {
|
||
var part = part_;
|
||
p.import_records_for_current_part.clearRetainingCapacity();
|
||
p.declared_symbols.clearRetainingCapacity();
|
||
|
||
const result = try ImportScanner.scan(P, p, part.stmts, wrap_mode != .none, false, {});
|
||
kept_import_equals = kept_import_equals or result.kept_import_equals;
|
||
removed_import_equals = removed_import_equals or result.removed_import_equals;
|
||
|
||
part.stmts = result.stmts;
|
||
if (part.stmts.len > 0) {
|
||
if (p.module_scope.contains_direct_eval and part.declared_symbols.len() > 0) {
|
||
// If this file contains a direct call to "eval()", all parts that
|
||
// declare top-level symbols must be kept since the eval'd code may
|
||
// reference those symbols.
|
||
part.can_be_removed_if_unused = false;
|
||
}
|
||
if (part.declared_symbols.len() == 0) {
|
||
part.declared_symbols = p.declared_symbols.clone(p.allocator) catch unreachable;
|
||
} else {
|
||
part.declared_symbols.appendList(p.allocator, p.declared_symbols) catch unreachable;
|
||
}
|
||
|
||
if (part.import_record_indices.len == 0) {
|
||
part.import_record_indices = .fromOwnedSlice(bun.handleOom(
|
||
p.allocator.dupe(u32, p.import_records_for_current_part.items),
|
||
));
|
||
} else {
|
||
part.import_record_indices.appendSlice(
|
||
p.allocator,
|
||
p.import_records_for_current_part.items,
|
||
) catch |err| bun.handleOom(err);
|
||
}
|
||
|
||
parts.items[parts_end] = part;
|
||
parts_end += 1;
|
||
}
|
||
}
|
||
|
||
// We need to iterate multiple times if an import-equals statement was
|
||
// removed and there are more import-equals statements that may be removed
|
||
if (!kept_import_equals or !removed_import_equals) {
|
||
break;
|
||
}
|
||
}
|
||
|
||
// leave the first part in there for namespace export when bundling
|
||
parts.items.len = parts_end;
|
||
|
||
// Do a second pass for exported items now that imported items are filled out.
|
||
// This isn't done for HMR because it already deletes all `.s_export_clause`s
|
||
for (parts.items) |part| {
|
||
for (part.stmts) |stmt| {
|
||
switch (stmt.data) {
|
||
.s_export_clause => |clause| {
|
||
for (clause.items) |item| {
|
||
if (p.named_imports.getEntry(item.name.ref.?)) |_import| {
|
||
_import.value_ptr.is_exported = true;
|
||
}
|
||
}
|
||
},
|
||
else => {},
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (wrap_mode == .bun_commonjs and !p.options.features.remove_cjs_module_wrapper) {
|
||
// This transforms the user's code into.
|
||
//
|
||
// (function (exports, require, module, __filename, __dirname) {
|
||
// ...
|
||
// })
|
||
//
|
||
// which is then called in `evaluateCommonJSModuleOnce`
|
||
var args = bun.handleOom(allocator.alloc(Arg, 5 + @as(usize, @intFromBool(p.has_import_meta))));
|
||
args[0..5].* = .{
|
||
Arg{ .binding = p.b(B.Identifier{ .ref = p.exports_ref }, logger.Loc.Empty) },
|
||
Arg{ .binding = p.b(B.Identifier{ .ref = p.require_ref }, logger.Loc.Empty) },
|
||
Arg{ .binding = p.b(B.Identifier{ .ref = p.module_ref }, logger.Loc.Empty) },
|
||
Arg{ .binding = p.b(B.Identifier{ .ref = p.filename_ref }, logger.Loc.Empty) },
|
||
Arg{ .binding = p.b(B.Identifier{ .ref = p.dirname_ref }, logger.Loc.Empty) },
|
||
};
|
||
if (p.has_import_meta) {
|
||
p.import_meta_ref = bun.handleOom(p.newSymbol(.other, "$Bun_import_meta"));
|
||
args[5] = Arg{ .binding = p.b(B.Identifier{ .ref = p.import_meta_ref }, logger.Loc.Empty) };
|
||
}
|
||
|
||
var total_stmts_count: usize = 0;
|
||
for (parts.items) |part| {
|
||
total_stmts_count += part.stmts.len;
|
||
}
|
||
|
||
const preserve_strict_mode = p.module_scope.strict_mode == .explicit_strict_mode and
|
||
!(parts.items.len > 0 and
|
||
parts.items[0].stmts.len > 0 and
|
||
parts.items[0].stmts[0].data == .s_directive);
|
||
|
||
total_stmts_count += @as(usize, @intCast(@intFromBool(preserve_strict_mode)));
|
||
|
||
const stmts_to_copy = bun.handleOom(allocator.alloc(Stmt, total_stmts_count));
|
||
{
|
||
var remaining_stmts = stmts_to_copy;
|
||
if (preserve_strict_mode) {
|
||
remaining_stmts[0] = p.s(
|
||
S.Directive{
|
||
.value = "use strict",
|
||
},
|
||
p.module_scope_directive_loc,
|
||
);
|
||
remaining_stmts = remaining_stmts[1..];
|
||
}
|
||
|
||
for (parts.items) |part| {
|
||
for (part.stmts, remaining_stmts[0..part.stmts.len]) |src, *dest| {
|
||
dest.* = src;
|
||
}
|
||
remaining_stmts = remaining_stmts[part.stmts.len..];
|
||
}
|
||
}
|
||
|
||
const wrapper = p.newExpr(
|
||
E.Function{
|
||
.func = G.Fn{
|
||
.name = null,
|
||
.open_parens_loc = logger.Loc.Empty,
|
||
.args = args,
|
||
.body = .{ .loc = logger.Loc.Empty, .stmts = stmts_to_copy },
|
||
.flags = Flags.Function.init(.{ .is_export = false }),
|
||
},
|
||
},
|
||
logger.Loc.Empty,
|
||
);
|
||
|
||
var top_level_stmts = bun.handleOom(p.allocator.alloc(Stmt, 1));
|
||
top_level_stmts[0] = p.s(
|
||
S.SExpr{
|
||
.value = wrapper,
|
||
},
|
||
logger.Loc.Empty,
|
||
);
|
||
|
||
try parts.ensureUnusedCapacity(1);
|
||
parts.items.len = 1;
|
||
parts.items[0].stmts = top_level_stmts;
|
||
}
|
||
|
||
var top_level_symbols_to_parts = js_ast.Ast.TopLevelSymbolToParts{};
|
||
var top_level = &top_level_symbols_to_parts;
|
||
|
||
if (p.options.bundle) {
|
||
const Ctx = struct {
|
||
allocator: std.mem.Allocator,
|
||
top_level_symbols_to_parts: *js_ast.Ast.TopLevelSymbolToParts,
|
||
symbols: []const js_ast.Symbol,
|
||
part_index: u32,
|
||
|
||
pub fn next(ctx: @This(), input: Ref) void {
|
||
// If this symbol was merged, use the symbol at the end of the
|
||
// linked list in the map. This is the case for multiple "var"
|
||
// declarations with the same name, for example.
|
||
var ref = input;
|
||
var symbol_ref = &ctx.symbols[ref.innerIndex()];
|
||
while (symbol_ref.hasLink()) : (symbol_ref = &ctx.symbols[ref.innerIndex()]) {
|
||
ref = symbol_ref.link;
|
||
}
|
||
|
||
var entry = ctx.top_level_symbols_to_parts.getOrPut(ctx.allocator, ref) catch unreachable;
|
||
if (!entry.found_existing) {
|
||
entry.value_ptr.* = .{};
|
||
}
|
||
|
||
bun.handleOom(entry.value_ptr.append(ctx.allocator, @as(u32, @truncate(ctx.part_index))));
|
||
}
|
||
};
|
||
|
||
// Each part tracks the other parts it depends on within this file
|
||
for (parts.items, 0..) |*part, part_index| {
|
||
const decls = &part.declared_symbols;
|
||
const ctx = Ctx{
|
||
.allocator = p.allocator,
|
||
.top_level_symbols_to_parts = top_level,
|
||
.symbols = p.symbols.items,
|
||
.part_index = @as(u32, @truncate(part_index)),
|
||
};
|
||
|
||
DeclaredSymbol.forEachTopLevelSymbol(decls, ctx, Ctx.next);
|
||
}
|
||
|
||
// Pulling in the exports of this module always pulls in the export part
|
||
|
||
{
|
||
var entry = top_level.getOrPut(p.allocator, p.exports_ref) catch unreachable;
|
||
|
||
if (!entry.found_existing) {
|
||
entry.value_ptr.* = .{};
|
||
}
|
||
|
||
bun.handleOom(entry.value_ptr.append(p.allocator, js_ast.namespace_export_part_index));
|
||
}
|
||
}
|
||
|
||
const wrapper_ref: Ref = brk: {
|
||
if (p.options.features.hot_module_reloading) {
|
||
break :brk p.hmr_api_ref;
|
||
}
|
||
|
||
if (p.options.bundle and p.needsWrapperRef(parts.items)) {
|
||
break :brk p.newSymbol(
|
||
.other,
|
||
std.fmt.allocPrint(
|
||
p.allocator,
|
||
"require_{f}",
|
||
.{p.source.fmtIdentifier()},
|
||
) catch |err| bun.handleOom(err),
|
||
) catch |err| bun.handleOom(err);
|
||
}
|
||
|
||
break :brk Ref.None;
|
||
};
|
||
|
||
return .{
|
||
.runtime_imports = p.runtime_imports,
|
||
.module_scope = p.module_scope.*,
|
||
.exports_ref = p.exports_ref,
|
||
.wrapper_ref = wrapper_ref,
|
||
.module_ref = p.module_ref,
|
||
.export_star_import_records = p.export_star_import_records.items,
|
||
.approximate_newline_count = p.lexer.approximate_newline_count,
|
||
.exports_kind = exports_kind,
|
||
.named_imports = p.named_imports,
|
||
.named_exports = p.named_exports,
|
||
.import_keyword = p.esm_import_keyword,
|
||
.export_keyword = p.esm_export_keyword,
|
||
.top_level_symbols_to_parts = top_level_symbols_to_parts,
|
||
.char_freq = p.computeCharacterFrequency(),
|
||
.directive = if (p.module_scope.strict_mode == .explicit_strict_mode) "use strict" else null,
|
||
|
||
// Assign slots to symbols in nested scopes. This is some precomputation for
|
||
// the symbol renaming pass that will happen later in the linker. It's done
|
||
// now in the parser because we want it to be done in parallel per file and
|
||
// we're already executing code in parallel here
|
||
.nested_scope_slot_counts = if (p.options.features.minify_identifiers)
|
||
renamer.assignNestedScopeSlots(p.allocator, p.module_scope, p.symbols.items)
|
||
else
|
||
js_ast.SlotCounts{},
|
||
|
||
.require_ref = if (p.runtime_imports.__require != null)
|
||
p.runtime_imports.__require.?
|
||
else
|
||
p.require_ref,
|
||
|
||
.force_cjs_to_esm = p.unwrap_all_requires or exports_kind == .esm_with_dynamic_fallback_from_cjs,
|
||
.uses_module_ref = p.symbols.items[p.module_ref.inner_index].use_count_estimate > 0,
|
||
.uses_exports_ref = p.symbols.items[p.exports_ref.inner_index].use_count_estimate > 0,
|
||
.uses_require_ref = if (p.options.bundle)
|
||
p.runtime_imports.__require != null and
|
||
p.symbols.items[p.runtime_imports.__require.?.inner_index].use_count_estimate > 0
|
||
else
|
||
p.symbols.items[p.require_ref.inner_index].use_count_estimate > 0,
|
||
.commonjs_module_exports_assigned_deoptimized = p.commonjs_module_exports_assigned_deoptimized,
|
||
.top_level_await_keyword = p.top_level_await_keyword,
|
||
.commonjs_named_exports = p.commonjs_named_exports,
|
||
.has_commonjs_export_names = p.has_commonjs_export_names,
|
||
|
||
.hashbang = hashbang,
|
||
// TODO: cross-module constant inlining
|
||
// .const_values = p.const_values,
|
||
.ts_enums = try p.computeTsEnumsMap(allocator),
|
||
.import_meta_ref = p.import_meta_ref,
|
||
|
||
.symbols = js_ast.Symbol.List.moveFromList(&p.symbols),
|
||
.parts = bun.BabyList(js_ast.Part).moveFromList(parts),
|
||
.import_records = ImportRecord.List.moveFromList(&p.import_records),
|
||
};
|
||
}
|
||
|
||
/// The bundler will generate wrappers to contain top-level side effects using
|
||
/// the '__esm' helper. Example:
|
||
///
|
||
/// var init_foo = __esm(() => {
|
||
/// someExport = Math.random();
|
||
/// });
|
||
///
|
||
/// This wrapper can be removed if all of the constructs get moved
|
||
/// outside of the file. Due to paralleization, we can't retroactively
|
||
/// delete the `init_foo` symbol, but instead it must be known far in
|
||
/// advance if the symbol is needed or not.
|
||
///
|
||
/// The logic in this function must be in sync with the hoisting
|
||
/// logic in `LinkerContext.generateCodeForFileInChunkJS`
|
||
fn needsWrapperRef(p: *const P, parts: []const js_ast.Part) bool {
|
||
bun.assert(p.options.bundle);
|
||
for (parts) |part| {
|
||
for (part.stmts) |stmt| {
|
||
switch (stmt.data) {
|
||
.s_function => {},
|
||
.s_class => |class| if (!class.class.canBeMoved()) return true,
|
||
.s_local => |local| {
|
||
if (local.was_commonjs_export or p.commonjs_named_exports.count() == 0) {
|
||
for (local.decls.slice()) |decl| {
|
||
if (decl.value) |value|
|
||
if (value.data != .e_missing and !value.canBeMoved())
|
||
return true;
|
||
}
|
||
continue;
|
||
}
|
||
return true;
|
||
},
|
||
.s_export_default => |ed| {
|
||
if (!ed.canBeMoved())
|
||
return true;
|
||
},
|
||
.s_export_equals => |e| {
|
||
if (!e.value.canBeMoved())
|
||
return true;
|
||
},
|
||
else => return true,
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
pub fn init(
|
||
allocator: Allocator,
|
||
log: *logger.Log,
|
||
source: *const logger.Source,
|
||
define: *Define,
|
||
lexer: js_lexer.Lexer,
|
||
opts: Parser.Options,
|
||
this: *P,
|
||
) anyerror!void {
|
||
var scope_order = try ScopeOrderList.initCapacity(allocator, 1);
|
||
const scope = try allocator.create(Scope);
|
||
scope.* = Scope{
|
||
.members = .{},
|
||
.children = .{},
|
||
.generated = .{},
|
||
.kind = .entry,
|
||
.label_ref = null,
|
||
.parent = null,
|
||
};
|
||
|
||
scope_order.appendAssumeCapacity(ScopeOrder{ .loc = locModuleScope, .scope = scope });
|
||
this.* = P{
|
||
.legacy_cjs_import_stmts = @TypeOf(this.legacy_cjs_import_stmts).init(allocator),
|
||
// This must default to true or else parsing "in" won't work right.
|
||
// It will fail for the case in the "in-keyword.js" file
|
||
.allow_in = true,
|
||
|
||
.call_target = nullExprData,
|
||
.delete_target = nullExprData,
|
||
.stmt_expr_value = nullExprData,
|
||
.loop_body = nullStmtData,
|
||
.define = define,
|
||
.import_records = undefined,
|
||
.named_imports = undefined,
|
||
.named_exports = .{},
|
||
.log = log,
|
||
.stack_check = bun.StackCheck.init(),
|
||
.allocator = allocator,
|
||
.options = opts,
|
||
.then_catch_chain = ThenCatchChain{ .next_target = nullExprData },
|
||
.to_expr_wrapper_namespace = undefined,
|
||
.to_expr_wrapper_hoisted = undefined,
|
||
.import_transposer = undefined,
|
||
.require_transposer = undefined,
|
||
.require_resolve_transposer = undefined,
|
||
.source = source,
|
||
.macro = MacroState.init(allocator),
|
||
.current_scope = scope,
|
||
.module_scope = scope,
|
||
.scopes_in_order = scope_order,
|
||
.needs_jsx_import = if (comptime only_scan_imports_and_do_not_visit) false else NeedsJSXType{},
|
||
.lexer = lexer,
|
||
|
||
// Only enable during bundling, when not bundling CJS
|
||
.commonjs_named_exports_deoptimized = if (opts.bundle) opts.output_format == .cjs else true,
|
||
};
|
||
this.lexer.track_comments = opts.features.minify_identifiers;
|
||
|
||
this.unwrap_all_requires = brk: {
|
||
if (opts.bundle and opts.output_format != .cjs) {
|
||
if (source.path.packageName()) |pkg| {
|
||
if (opts.features.shouldUnwrapRequire(pkg)) {
|
||
if (strings.eqlComptime(pkg, "react") or strings.eqlComptime(pkg, "react-dom")) {
|
||
const version = opts.package_version;
|
||
if (version.len > 2 and (version[0] == '0' or (version[0] == '1' and version[1] < '8'))) {
|
||
break :brk false;
|
||
}
|
||
}
|
||
|
||
break :brk true;
|
||
}
|
||
}
|
||
}
|
||
|
||
break :brk false;
|
||
};
|
||
|
||
this.symbols = std.array_list.Managed(Symbol).init(allocator);
|
||
|
||
if (comptime !only_scan_imports_and_do_not_visit) {
|
||
this.import_records = @TypeOf(this.import_records).init(allocator);
|
||
this.named_imports = .{};
|
||
}
|
||
|
||
this.to_expr_wrapper_namespace = Binding2ExprWrapper.Namespace.init(this);
|
||
this.to_expr_wrapper_hoisted = Binding2ExprWrapper.Hoisted.init(this);
|
||
this.import_transposer = @TypeOf(this.import_transposer).init(this);
|
||
this.require_transposer = @TypeOf(this.require_transposer).init(this);
|
||
this.require_resolve_transposer = @TypeOf(this.require_resolve_transposer).init(this);
|
||
|
||
if (opts.features.top_level_await or comptime only_scan_imports_and_do_not_visit) {
|
||
this.fn_or_arrow_data_parse.allow_await = .allow_expr;
|
||
this.fn_or_arrow_data_parse.is_top_level = true;
|
||
}
|
||
|
||
if (comptime !is_typescript_enabled) {
|
||
// This is so it doesn't impact runtime transpiler caching when not in use
|
||
this.options.features.emit_decorator_metadata = false;
|
||
}
|
||
}
|
||
};
|
||
}
|
||
|
||
var e_missing_data = E.Missing{};
|
||
var s_missing = S.Empty{};
|
||
var nullExprData = Expr.Data{ .e_missing = e_missing_data };
|
||
var nullStmtData = Stmt.Data{ .s_empty = s_missing };
|
||
|
||
var keyExprData = Expr.Data{ .e_string = &Prefill.String.Key };
|
||
var nullExprValueData = E.Null{};
|
||
var falseExprValueData = E.Boolean{ .value = false };
|
||
var nullValueExpr = Expr.Data{ .e_null = nullExprValueData };
|
||
var falseValueExpr = Expr.Data{ .e_boolean = E.Boolean{ .value = false } };
|
||
|
||
const string = []const u8;
|
||
|
||
const Define = @import("../defines.zig").Define;
|
||
const DefineData = @import("../defines.zig").DefineData;
|
||
|
||
const bun = @import("bun");
|
||
const Environment = bun.Environment;
|
||
const FeatureFlags = bun.FeatureFlags;
|
||
const ImportRecord = bun.ImportRecord;
|
||
const Output = bun.Output;
|
||
const assert = bun.assert;
|
||
const js_lexer = bun.js_lexer;
|
||
const logger = bun.logger;
|
||
const strings = bun.strings;
|
||
|
||
const js_ast = bun.ast;
|
||
const B = js_ast.B;
|
||
const Binding = js_ast.Binding;
|
||
const BindingNodeIndex = js_ast.BindingNodeIndex;
|
||
const DeclaredSymbol = js_ast.DeclaredSymbol;
|
||
const E = js_ast.E;
|
||
const Expr = js_ast.Expr;
|
||
const ExprNodeIndex = js_ast.ExprNodeIndex;
|
||
const ExprNodeList = js_ast.ExprNodeList;
|
||
const Flags = js_ast.Flags;
|
||
const LocRef = js_ast.LocRef;
|
||
const S = js_ast.S;
|
||
const Scope = js_ast.Scope;
|
||
const Stmt = js_ast.Stmt;
|
||
const StmtNodeIndex = js_ast.StmtNodeIndex;
|
||
const StmtNodeList = js_ast.StmtNodeList;
|
||
const Symbol = js_ast.Symbol;
|
||
|
||
const G = js_ast.G;
|
||
const Arg = G.Arg;
|
||
const Decl = G.Decl;
|
||
const Property = G.Property;
|
||
|
||
const SymbolPropertyUseMap = js_ast.Part.SymbolPropertyUseMap;
|
||
const SymbolUseMap = js_ast.Part.SymbolUseMap;
|
||
|
||
const js_parser = bun.js_parser;
|
||
const ConvertESMExportsForHmr = js_parser.ConvertESMExportsForHmr;
|
||
const DeferredArrowArgErrors = js_parser.DeferredArrowArgErrors;
|
||
const DeferredErrors = js_parser.DeferredErrors;
|
||
const DeferredImportNamespace = js_parser.DeferredImportNamespace;
|
||
const ExprBindingTuple = js_parser.ExprBindingTuple;
|
||
const ExpressionTransposer = js_parser.ExpressionTransposer;
|
||
const FindLabelSymbolResult = js_parser.FindLabelSymbolResult;
|
||
const FnOnlyDataVisit = js_parser.FnOnlyDataVisit;
|
||
const FnOrArrowDataParse = js_parser.FnOrArrowDataParse;
|
||
const FnOrArrowDataVisit = js_parser.FnOrArrowDataVisit;
|
||
const FunctionKind = js_parser.FunctionKind;
|
||
const IdentifierOpts = js_parser.IdentifierOpts;
|
||
const ImportItemForNamespaceMap = js_parser.ImportItemForNamespaceMap;
|
||
const ImportKind = js_parser.ImportKind;
|
||
const ImportNamespaceCallOrConstruct = js_parser.ImportNamespaceCallOrConstruct;
|
||
const ImportScanner = js_parser.ImportScanner;
|
||
const InvalidLoc = js_parser.InvalidLoc;
|
||
const JSXImport = js_parser.JSXImport;
|
||
const JSXTransformType = js_parser.JSXTransformType;
|
||
const Jest = js_parser.Jest;
|
||
const LocList = js_parser.LocList;
|
||
const MacroState = js_parser.MacroState;
|
||
const ParseStatementOptions = js_parser.ParseStatementOptions;
|
||
const ParsedPath = js_parser.ParsedPath;
|
||
const Parser = js_parser.Parser;
|
||
const Prefill = js_parser.Prefill;
|
||
const PrependTempRefsOpts = js_parser.PrependTempRefsOpts;
|
||
const ReactRefresh = js_parser.ReactRefresh;
|
||
const Ref = js_parser.Ref;
|
||
const RefMap = js_parser.RefMap;
|
||
const RefRefMap = js_parser.RefRefMap;
|
||
const RuntimeFeatures = js_parser.RuntimeFeatures;
|
||
const RuntimeImports = js_parser.RuntimeImports;
|
||
const ScanPassResult = js_parser.ScanPassResult;
|
||
const ScopeOrder = js_parser.ScopeOrder;
|
||
const ScopeOrderList = js_parser.ScopeOrderList;
|
||
const SideEffects = js_parser.SideEffects;
|
||
const StmtList = js_parser.StmtList;
|
||
const StrictModeFeature = js_parser.StrictModeFeature;
|
||
const StringBoolMap = js_parser.StringBoolMap;
|
||
const Substitution = js_parser.Substitution;
|
||
const TempRef = js_parser.TempRef;
|
||
const ThenCatchChain = js_parser.ThenCatchChain;
|
||
const TransposeState = js_parser.TransposeState;
|
||
const TypeScript = js_parser.TypeScript;
|
||
const WrapMode = js_parser.WrapMode;
|
||
const arguments_str = js_parser.arguments_str;
|
||
const exports_string_name = js_parser.exports_string_name;
|
||
const fs = js_parser.fs;
|
||
const generatedSymbolName = js_parser.generatedSymbolName;
|
||
const isEvalOrArguments = js_parser.isEvalOrArguments;
|
||
const locModuleScope = js_parser.locModuleScope;
|
||
const options = js_parser.options;
|
||
const renamer = js_parser.renamer;
|
||
const statementCaresAboutScope = js_parser.statementCaresAboutScope;
|
||
|
||
const std = @import("std");
|
||
const List = std.ArrayListUnmanaged;
|
||
const Map = std.AutoHashMapUnmanaged;
|
||
const Allocator = std.mem.Allocator;
|
||
const ListManaged = std.array_list.Managed;
|