aboutsummaryrefslogtreecommitdiff
path: root/src/js_parser.zig
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-04-30 17:26:17 -0700
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-04-30 17:26:17 -0700
commit7005f4c43c8fa403b85f14afcf4c9b5a4c8c4cbc (patch)
tree75e09650d515049a15743987e121dc0e70ad2da3 /src/js_parser.zig
parent2961b7c8cac1033c1f039272038fe309e3fe6c01 (diff)
downloadbun-7005f4c43c8fa403b85f14afcf4c9b5a4c8c4cbc.tar.gz
bun-7005f4c43c8fa403b85f14afcf4c9b5a4c8c4cbc.tar.zst
bun-7005f4c43c8fa403b85f14afcf4c9b5a4c8c4cbc.zip
it prints end to end though doesn't work yet
Former-commit-id: 006ca4f13c0866ff843cc47046f5b7dce18635ef
Diffstat (limited to 'src/js_parser.zig')
-rw-r--r--src/js_parser.zig224
1 files changed, 192 insertions, 32 deletions
diff --git a/src/js_parser.zig b/src/js_parser.zig
index d30813a03..b8bf09793 100644
--- a/src/js_parser.zig
+++ b/src/js_parser.zig
@@ -40,6 +40,32 @@ const Op = js_ast.Op;
const Scope = js_ast.Scope;
const locModuleScope = logger.Loc.Empty;
+pub fn ExpressionTransposer(comptime ctx: type, visitor: fn (ptr: *ctx, arg: Expr, state: anytype) Expr) type {
+ return struct {
+ context: *Context,
+
+ pub fn init(c: *Context) @This() {
+ return @This(){
+ .context = c,
+ };
+ }
+
+ pub fn maybeTransposeIf(self: *@This(), arg: Expr, state: anytype) Expr {
+ switch (arg.data) {
+ .e_if => |ex| {
+ ex.yes = self.maybeTransposeIf(ex.yes, state);
+ ex.no = self.maybeTransposeIf(ex.no, state);
+ return arg;
+ },
+ else => {
+ return visitor(self.context, arg, state);
+ },
+ }
+ }
+ pub const Context = ctx;
+ };
+}
+
pub fn locAfterOp(e: E.Binary) logger.Loc {
if (e.left.loc.start < e.right.loc.start) {
return e.right.loc;
@@ -121,6 +147,8 @@ pub const ImportScanner = struct {
// should omit unused imports.
//
const keep_unused_imports = !p.options.trim_unused_imports;
+ var did_remove_star_loc = false;
+ // const keep_unused_imports = true;
// TypeScript always trims unused imports. This is important for
// correctness since some imports might be fake (only in the type
@@ -168,6 +196,7 @@ pub const ImportScanner = struct {
if (!has_any) {
st.star_name_loc = null;
+ did_remove_star_loc = true;
}
}
}
@@ -194,12 +223,7 @@ pub const ImportScanner = struct {
}
}
- // Filter the array by taking a slice
- if (items_end == 0 and st.items.len > 0) {
- p.allocator.free(st.items);
- // zero out the slice
- st.items = &([_]js_ast.ClauseItem{});
- } else if (items_end < st.items.len) {
+ if (items_end < st.items.len - 1) {
var list = List(js_ast.ClauseItem).fromOwnedSlice(p.allocator, st.items);
list.shrinkAndFree(items_end);
st.items = list.toOwnedSlice();
@@ -240,7 +264,7 @@ pub const ImportScanner = struct {
}
if (p.options.trim_unused_imports) {
- if (st.star_name_loc != null) {
+ if (st.star_name_loc != null or did_remove_star_loc) {
// -- Original Comment --
// If we're bundling a star import and the namespace is only ever
// used for property accesses, then convert each unique property to
@@ -1295,27 +1319,93 @@ pub const Parser = struct {
// We don't need to worry about "use strict" directives because this only
// happens when bundling, in which case we are flatting the module scopes of
// all modules together anyway so such directives are meaningless.
- if (!p.import_meta_ref.isNull()) {
- // heap so it lives beyond this function call
- var decls = try p.allocator.alloc(G.Decl, 1);
- decls[0] = Decl{ .binding = p.b(B.Identifier{
- .ref = p.import_meta_ref,
- }, logger.Loc.Empty), .value = p.e(E.Object{}, logger.Loc.Empty) };
- var importMetaStatement = p.s(S.Local{
- .kind = .k_const,
- .decls = decls,
- }, logger.Loc.Empty);
- }
+ // if (!p.import_meta_ref.isSourceIndexNull()) {
+ // // heap so it lives beyond this function call
+ // var decls = try p.allocator.alloc(G.Decl, 1);
+ // decls[0] = Decl{ .binding = p.b(B.Identifier{
+ // .ref = p.import_meta_ref,
+ // }, logger.Loc.Empty), .value = p.e(E.Object{}, logger.Loc.Empty) };
+ // var importMetaStatement = p.s(S.Local{
+ // .kind = .k_const,
+ // .decls = decls,
+ // }, logger.Loc.Empty);
+ // }
debugl("<p.appendPart>");
- var parts = try List(js_ast.Part).initCapacity(p.allocator, 1);
+ var before = List(js_ast.Part).init(p.allocator);
+ var after = List(js_ast.Part).init(p.allocator);
+ var parts = List(js_ast.Part).init(p.allocator);
try p.appendPart(&parts, stmts);
+ // for (stmts) |stmt| {
+ // var _stmts = ([_]Stmt{stmt});
+
+ // switch (stmt.data) {
+ // // Split up top-level multi-declaration variable statements
+
+ // .s_local => |local| {
+ // for (local.decls) |decl| {
+ // var decls = try p.allocator.alloc(Decl, 1);
+ // var clone = S.Local{
+ // .kind = local.kind,
+ // .decls = decls,
+ // .is_export = local.is_export,
+ // .was_ts_import_equals = local.was_ts_import_equals,
+ // };
+ // _stmts[0] = p.s(clone, stmt.loc);
+
+ // try p.appendPart(&parts, &_stmts);
+ // }
+ // },
+ // // Move imports (and import-like exports) to the top of the file to
+ // // ensure that if they are converted to a require() call, the effects
+ // // will take place before any other statements are evaluated.
+ // .s_import, .s_export_from, .s_export_star => {
+ // try p.appendPart(&before, &_stmts);
+ // },
+
+ // .s_export_equals => {
+ // try p.appendPart(&after, &_stmts);
+ // },
+ // else => {
+ // try p.appendPart(&parts, &_stmts);
+ // },
+ // }
+ // }
+ // p.popScope();
+ var parts_slice: []js_ast.Part = undefined;
+
+ if (before.items.len > 0 or after.items.len > 0) {
+ const before_len = before.items.len;
+ const after_len = after.items.len;
+ const parts_len = parts.items.len;
+ var _parts = try p.allocator.alloc(
+ js_ast.Part,
+ before_len +
+ after_len +
+ parts_len,
+ );
+ if (before_len > 0) {
+ std.mem.copy(js_ast.Part, _parts, before.toOwnedSlice());
+ }
+ if (parts_len > 0) {
+ std.mem.copy(js_ast.Part, _parts[before_len .. before_len + parts_len], parts.toOwnedSlice());
+ }
+
+ if (after_len > 0) {
+ std.mem.copy(js_ast.Part, _parts[before_len + parts_len .. _parts.len], after.toOwnedSlice());
+ }
+ parts_slice = _parts;
+ } else {
+ after.deinit();
+ before.deinit();
+ parts_slice = parts.toOwnedSlice();
+ }
debugl("</p.appendPart>");
// Pop the module scope to apply the "ContainsDirectEval" rules
// p.popScope();
debugl("<result.Ast>");
- result.ast = try p.toAST(parts.toOwnedSlice());
+ result.ast = try p.toAST(parts_slice);
result.ok = true;
debugl("</result.Ast>");
@@ -1595,6 +1685,58 @@ pub const P = struct {
// Expression , AssignmentExpression
//
after_arrow_body_loc: logger.Loc = logger.Loc.Empty,
+ import_transposer: ImportTransposer,
+ require_transposer: RequireTransposer,
+ require_resolve_transposer: RequireResolveTransposer,
+
+ const TransposeState = struct {
+ is_await_target: bool = false,
+ is_then_catch_target: bool = false,
+ loc: logger.Loc,
+ };
+
+ pub fn transposeImport(p: *P, arg: Expr, state: anytype) Expr {
+ // The argument must be a string
+ if (@as(Expr.Tag, 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.e(E.Null{}, arg.loc);
+ }
+ const str = arg.data.e_string;
+
+ const import_record_index = p.addImportRecord(.dynamic, arg.loc, p.lexer.utf16ToString(str.value));
+ p.import_records.items[import_record_index].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(import_record_index) catch unreachable;
+ return p.e(E.Import{
+ .expr = arg,
+ .import_record_index = @intCast(Ref.Int, import_record_index),
+ // .leading_interior_comments = arg.data.e_string.
+ }, state.loc);
+ }
+
+ // 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 will not be bundled because the argument is not a string literal") catch unreachable;
+
+ return p.e(E.Import{
+ .expr = arg,
+ .import_record_index = Ref.None.source_index,
+ }, state.loc);
+ }
+
+ pub fn transposeRequireResolve(p: *P, arg: Expr, transpose_state: anytype) Expr {
+ return arg;
+ }
+
+ pub fn transposeRequire(p: *P, arg: Expr, transpose_state: anytype) Expr {
+ return arg;
+ }
+
+ const ImportTransposer = ExpressionTransposer(P, P.transposeImport);
+ const RequireTransposer = ExpressionTransposer(P, P.transposeRequire);
+ const RequireResolveTransposer = ExpressionTransposer(P, P.transposeRequireResolve);
const Binding2ExprWrapper = struct {
pub const Namespace = Binding.ToExpr(P, P.wrapIdentifierNamespace);
@@ -3462,9 +3604,9 @@ pub const P = struct {
p.lexer.unexpected();
},
}
-
- p.lexer.expectContextualKeyword("from");
}
+
+ p.lexer.expectContextualKeyword("from");
},
else => {
p.lexer.unexpected();
@@ -7260,7 +7402,7 @@ pub const P = struct {
exp.value = p.visitExpr(exp.value);
},
.e_identifier => |e_| {
- const is_delete_target = e_ == p.delete_target.e_identifier;
+ const is_delete_target = @as(Expr.Tag, p.delete_target) == .e_identifier and e_ == p.delete_target.e_identifier;
const name = p.loadNameFromRef(e_.ref);
if (p.isStrictMode() and js_lexer.StrictModeReservedWords.has(name)) {
@@ -7281,7 +7423,7 @@ pub const P = struct {
var original_name: ?string = null;
// Substitute user-specified defines for unbound symbols
- if (p.symbols.items[e_.ref.inner_index].kind == .unbound and !result.is_inside_with_scope and e_ != p.delete_target.e_identifier) {
+ if (p.symbols.items[e_.ref.inner_index].kind == .unbound and !result.is_inside_with_scope and !is_delete_target) {
if (p.define.identifiers.get(name)) |def| {
if (!def.isUndefined()) {
const newvalue = p.valueForDefine(expr.loc, in.assign_target, is_delete_target, &def);
@@ -7804,8 +7946,8 @@ pub const P = struct {
}
},
.e_dot => |e_| {
- const is_delete_target = e_ == p.delete_target.e_dot;
- const is_call_target = e_ == p.call_target.e_dot;
+ const is_delete_target = @as(Expr.Tag, p.delete_target) == .e_dot and e_ == p.delete_target.e_dot;
+ const is_call_target = @as(Expr.Tag, p.call_target) == .e_dot and e_ == p.call_target.e_dot;
if (p.define.dots.get(e_.name)) |parts| {
for (parts) |define| {
@@ -7831,7 +7973,7 @@ pub const P = struct {
}
// Track ".then().catch()" chains
- if (is_call_target and p.then_catch_chain.next_target.e_dot == e_) {
+ if (is_call_target and @as(Expr.Tag, p.then_catch_chain.next_target) == .e_dot and p.then_catch_chain.next_target.e_dot == e_) {
if (strings.eql(e_.name, "catch")) {
p.then_catch_chain = ThenCatchChain{
.next_target = e_.target.data,
@@ -8006,9 +8148,14 @@ pub const P = struct {
}
},
.e_import => |e_| {
- const is_await_target = if (p.await_target != null) p.await_target.?.e_import == e_ else false;
- const is_then_catch_target = e_ == p.then_catch_chain.next_target.e_import and p.then_catch_chain.has_catch;
+ const state = TransposeState{
+ .is_await_target = if (p.await_target != null) p.await_target.?.e_import == e_ else false,
+ .is_then_catch_target = e_ == p.then_catch_chain.next_target.e_import and p.then_catch_chain.has_catch,
+ .loc = e_.expr.loc,
+ };
+
e_.expr = p.visitExpr(e_.expr);
+ return p.import_transposer.maybeTransposeIf(e_.expr, state);
// TODO: maybeTransposeIfExprChain
},
@@ -8018,7 +8165,7 @@ pub const P = struct {
p.then_catch_chain = ThenCatchChain{
.next_target = e_.target.data,
.has_multiple_args = e_.args.len >= 2,
- .has_catch = p.then_catch_chain.next_target.e_call == e_ and p.then_catch_chain.has_catch,
+ .has_catch = @as(Expr.Tag, p.then_catch_chain.next_target) == .e_call and p.then_catch_chain.next_target.e_call == e_ and p.then_catch_chain.has_catch,
};
// Prepare to recognize "require.resolve()" calls
@@ -8041,7 +8188,17 @@ pub const P = struct {
has_spread = has_spread or @as(Expr.Tag, e_.args[i].data) == .e_spread;
}
- // TODO: maybeTransposeIfExprChain
+ if (e_.optional_chain == null and @as(Expr.Tag, e_.target.data) == .e_identifier and e_.target.data.e_identifier.ref.eql(p.require_ref)) {
+ // Heuristic: omit warnings inside try/catch blocks because presumably
+ // the try/catch statement is there to handle the potential run-time
+ // error from the unbundled require() call failing.
+ if (e_.args.len == 1) {
+ return p.require_transposer.maybeTransposeIf(e_.args[0], null);
+ } else {
+ const r = js_lexer.rangeOfIdentifier(&p.source, e_.target.loc);
+ p.log.addRangeDebug(p.source, r, "This call to \"require\" will not be bundled because it has multiple arguments") catch unreachable;
+ }
+ }
return expr;
},
@@ -9968,13 +10125,16 @@ pub const P = struct {
parser.temp_refs_to_declare = @TypeOf(parser.temp_refs_to_declare).init(allocator);
parser.relocated_top_level_vars = @TypeOf(parser.relocated_top_level_vars).init(allocator);
parser.log = log;
+ parser.is_import_item = @TypeOf(parser.is_import_item).init(allocator);
parser.allocator = allocator;
parser.runtime_imports = StringRefMap.init(allocator);
parser.options = opts;
parser.to_expr_wrapper_namespace = Binding2ExprWrapper.Namespace.init(parser);
parser.to_expr_wrapper_hoisted = Binding2ExprWrapper.Hoisted.init(parser);
parser.source = source;
-
+ parser.import_transposer = @TypeOf(parser.import_transposer).init(parser);
+ parser.require_transposer = @TypeOf(parser.require_transposer).init(parser);
+ parser.require_resolve_transposer = @TypeOf(parser.require_resolve_transposer).init(parser);
parser.lexer = lexer;
parser.data = js_ast.AstData.init(allocator);