diff options
-rw-r--r-- | src/js_ast.zig | 6 | ||||
-rw-r--r-- | src/js_parser.zig | 294 |
2 files changed, 281 insertions, 19 deletions
diff --git a/src/js_ast.zig b/src/js_ast.zig index fb7f26900..a25c0f30a 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -2244,10 +2244,8 @@ pub const Expr = struct { return false; } - - pub fn isStringValue(self: *Expr) bool { - // TODO: - return false; + pub fn isStringValue(self: Data) callconv(.Inline) bool { + return @as(Expr.Tag, self) == .e_string; } }; }; diff --git a/src/js_parser.zig b/src/js_parser.zig index 5b5ffe949..0302bf310 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -608,7 +608,7 @@ const ImportNamespaceCallOrConstruct = struct { }; const ThenCatchChain = struct { - next_target: js_ast.E, + next_target: js_ast.Expr.Data, has_multiple_args: bool = false, has_catch: bool = false, }; @@ -1121,7 +1121,7 @@ pub const P = struct { // 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.E = null, + await_target: ?js_ast.Expr.Data = null, to_expr_wrapper_namespace: Binding2ExprWrapper.Namespace, to_expr_wrapper_hoisted: Binding2ExprWrapper.Hoisted, @@ -7178,34 +7178,269 @@ pub const P = struct { } } } + + // Track ".then().catch()" chains + if (is_call_target 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, + .has_catch = true, + }; + } else if (strings.eql(e_.name, "then")) { + p.then_catch_chain = ThenCatchChain{ + .next_target = e_.target.data, + .has_catch = p.then_catch_chain.has_catch or p.then_catch_chain.has_multiple_args, + }; + } + } + + e_.target = p.visitExpr(e_.target); + if (e_.optional_chain == null) { + if (p.maybeRewritePropertyAccess( + expr.loc, + in.assign_target, + is_delete_target, + e_.target, + e_.name, + e_.name_loc, + is_call_target, + )) |_expr| { + return _expr; + } + } + }, + .e_if => |e_| { + const is_call_target = e_ == p.call_target.e_if; + + e_.test_ = p.visitExpr(e_.test_); + + const side_effects = SideEffects.toBoolean(e_.test_.data); + + if (side_effects.ok) { + // Mark the control flow as dead if the branch is never taken + if (side_effects.value) { + // "true ? live : dead" + e_.yes = p.visitExpr(e_.yes); + const old = p.is_control_flow_dead; + p.is_control_flow_dead = true; + e_.no = p.visitExpr(e_.no); + p.is_control_flow_dead = old; + } else { + // "false ? dead : live" + const old = p.is_control_flow_dead; + p.is_control_flow_dead = true; + e_.yes = p.visitExpr(e_.yes); + p.is_control_flow_dead = old; + e_.no = p.visitExpr(e_.no); + } + } else { + e_.yes = p.visitExpr(e_.yes); + e_.no = p.visitExpr(e_.no); + } }, - .e_if => |e_| {}, .e_await => |e_| { - notimpl(); + p.await_target = e_.value.data; + e_.value = p.visitExpr(e_.value); }, .e_yield => |e_| { - notimpl(); + if (e_.value) |val| { + e_.value = p.visitExpr(val); + } }, .e_array => |e_| { - notimpl(); + if (in.assign_target != .none) { + if (e_.comma_after_spread) |spread| { + p.log.addRangeError(p.source, logger.Range{ .loc = spread, .len = 1 }, "Unexpected \",\" after rest pattern") catch unreachable; + } + } + + var has_spread = false; + var i: usize = 0; + while (i < e_.items.len) : (i += 1) { + var item = e_.items[i]; + const data = item.data; + switch (data) { + .e_missing => {}, + .e_spread => |spread| { + spread.value = p.visitExprInOut(spread.value, ExprIn{ .assign_target = in.assign_target }); + }, + .e_binary => |e2| { + if (in.assign_target != .none and e2.op == .bin_assign) { + const was_anonymous_named_expr = p.isAnonymousNamedExpr(e2.right); + e2.left = p.visitExprInOut(e2.left, ExprIn{ .assign_target = .replace }); + e2.right = p.visitExpr(e2.right); + + if (@as(Expr.Tag, e2.left.data) == .e_identifier) { + e2.right = p.maybeKeepExprSymbolName( + e2.right, + p.symbols.items[e2.left.data.e_identifier.ref.inner_index].original_name, + was_anonymous_named_expr, + ); + } + } else { + item = p.visitExprInOut(item, ExprIn{ .assign_target = in.assign_target }); + } + }, + else => { + item = p.visitExprInOut(item, ExprIn{ .assign_target = in.assign_target }); + }, + } + e_.items[i] = item; + } }, .e_object => |e_| { - notimpl(); + if (in.assign_target != .none) { + p.maybeCommaSpreadError(e_.comma_after_spread); + var has_spread = false; + var has_proto = false; + + var i: usize = 0; + while (i < e_.properties.len) : (i += 1) { + var property = e_.properties[i]; + + if (property.kind != .spread) { + const key = p.visitExpr(property.key orelse std.debug.panic("Expected property key", .{})); + e_.properties[i].key = key; + + // Forbid duplicate "__proto__" properties according to the specification + if (!property.flags.is_computed and !property.flags.was_shorthand and !property.flags.is_method and in.assign_target == .none and key.data.isStringValue() and strings.utf16EqlString( + key.data.e_string.value, + "__proto__", + )) { + if (has_proto) { + const r = js_lexer.rangeOfIdentifier(&p.source, key.loc); + p.log.addRangeError(p.source, r, "Cannot specify the \"__proto__\" property more than once per object") catch unreachable; + } + has_proto = true; + } + } else { + has_spread = true; + } + + // Extract the initializer for expressions like "({ a: b = c } = d)" + if (in.assign_target != .none and property.initializer != null and property.value != null) { + switch (property.value.?.data) { + .e_binary => |bin| { + if (bin.op == .bin_assign) { + property.initializer = bin.right; + property.value = bin.left; + } + }, + else => {}, + } + } + + if (property.value != null) { + property.value = p.visitExprInOut(property.value.?, ExprIn{ .assign_target = in.assign_target }); + } + + if (property.initializer != null) { + const was_anonymous_named_expr = p.isAnonymousNamedExpr(property.initializer orelse unreachable); + property.initializer = p.visitExprInOut(property.initializer.?, ExprIn{ .assign_target = in.assign_target }); + + if (property.value) |val| { + if (@as(Expr.Tag, val.data) == .e_identifier) { + property.initializer = p.maybeKeepExprSymbolName( + property.initializer orelse unreachable, + p.symbols.items[val.data.e_identifier.ref.inner_index].original_name, + was_anonymous_named_expr, + ); + } + } + } + + // TODO: can we avoid htis copy + e_.properties[i] = property; + } + } }, .e_import => |e_| { - notimpl(); + 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; + e_.expr = p.visitExpr(e_.expr); + + // TODO: maybeTransposeIfExprChain }, .e_call => |e_| { - notimpl(); + p.call_target = e_.target.data; + + 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, + }; + + // Prepare to recognize "require.resolve()" calls + // const could_be_require_resolve = (e_.args.len == 1 and @as( + // Expr.Tag, + // e_.target.data, + // ) == .e_dot and e_.target.data.e_dot.optional_chain == null and strings.eql( + // e_.target.dat.e_dot.name, + // "resolve", + // )); + + e_.target = p.visitExprInOut(e_.target, ExprIn{ + .has_chain_parent = (e_.optional_chain orelse js_ast.OptionalChain.start) == .ccontinue, + }); + // TODO: wan about import namespace call + var has_spread = false; + var i: usize = 0; + while (i < e_.args.len) : (i += 1) { + e_.args[i] = p.visitExpr(e_.args[i]); + has_spread = has_spread or @as(Expr.Tag, e_.args[i].data) == .e_spread; + } + + // TODO: maybeTransposeIfExprChain + + return expr; }, .e_new => |e_| { - notimpl(); + e_.target = p.visitExpr(e_.target); + // p.warnA + + var i: usize = 0; + while (i < e_.args.len) : (i += 1) { + e_.args[i] = p.visitExpr(e_.args[i]); + } }, .e_arrow => |e_| { - notimpl(); + const old_fn_or_arrow_data = p.fn_or_arrow_data_visit; + p.fn_or_arrow_data_visit = FnOrArrowDataVisit{ + .is_arrow = true, + .is_async = e_.is_async, + }; + defer p.fn_or_arrow_data_visit = old_fn_or_arrow_data; + + // Mark if we're inside an async arrow function. This value should be true + // even if we're inside multiple arrow functions and the closest inclosing + // arrow function isn't async, as long as at least one enclosing arrow + // function within the current enclosing function is async. + const old_inside_async_arrow_fn = p.fn_only_data_visit.is_inside_async_arrow_fn; + p.fn_only_data_visit.is_inside_async_arrow_fn = e_.is_async or p.fn_only_data_visit.is_inside_async_arrow_fn; + defer p.fn_only_data_visit.is_inside_async_arrow_fn = old_inside_async_arrow_fn; + + p.pushScopeForVisitPass(.function_args, expr.loc) catch unreachable; + defer p.popScope(); + + p.visitArgs(e_.args, VisitArgsOpts{ + .has_rest_arg = e_.has_rest_arg, + .body = e_.body.stmts, + .is_unique_formal_parameters = true, + }); + + p.pushScopeForVisitPass(.function_body, e_.body.loc) catch unreachable; + defer p.popScope(); + + var stmts_list = List(Stmt).fromOwnedSlice(p.allocator, e_.body.stmts); + var temp_opts = PrependTempRefsOpts{ .kind = StmtsKind.fn_body }; + p.visitStmtsAndPrependTempRefs(&stmts_list, &temp_opts) catch unreachable; + e_.body.stmts = stmts_list.toOwnedSlice(); }, .e_function => |e_| { - notimpl(); + p.visitFunc(&e_.func, expr.loc); + if (e_.func.name) |name| { + return p.keepExprSymbolName(expr, p.symbols.items[name.ref.?.inner_index].original_name); + } }, .e_class => |e_| { notimpl(); @@ -7215,6 +7450,31 @@ pub const P = struct { return expr; } + const VisitArgsOpts = struct { + body: []Stmt = &([_]Stmt{}), + has_rest_arg: bool = false, + + // This is true if the function is an arrow function or a method + is_unique_formal_parameters: bool = false, + }; + + pub fn visitArgs(p: *P, args: []G.Arg, opts: VisitArgsOpts) void { + notimpl(); + } + + pub fn keepExprSymbolName(p: *P, _value: Expr, name: string) Expr { + var exprs = p.allocator.alloc(Expr, 2) catch unreachable; + exprs[0] = _value; + exprs[1] = p.e(E.String{ + .value = p.lexer.stringToUTF16(name), + }, _value.loc); + var value = p.callRuntime(_value.loc, "__name", exprs); + + // Make sure tree shaking removes this if the function is never used + value.data.e_call.can_be_unwrapped_if_unused = true; + return value; + } + pub fn classCanBeRemovedIfUnused(p: *P, class: *G.Class) bool { if (class.extends) |extends| { if (!p.exprCanBeRemovedIfUnused(extends)) { @@ -8694,9 +8954,7 @@ pub const P = struct { // whether this is an arrow function, and only pick an arrow function if // there were no conversion errors. if (p.lexer.token == .t_equals_greater_than or (invalidLog.items.len == 0 and (p.trySkipTypeScriptTypeParametersThenOpenParenWithBacktracking() or opts.force_arrow_fn))) { - if (comma_after_spread.start > 0) { - p.log.addRangeError(p.source, logger.Range{ .loc = comma_after_spread, .len = 1 }, "Unexpected \",\" after rest pattern") catch unreachable; - } + p.maybeCommaSpreadError(comma_after_spread); p.logArrowArgErrors(&arrowArgErrors); } } @@ -8704,6 +8962,12 @@ pub const P = struct { return p.e(E.Missing{}, loc); } + pub fn maybeCommaSpreadError(p: *P, _comma_after_spread: ?logger.Loc) void { + if (_comma_after_spread) |comma_after_spread| { + p.log.addRangeError(p.source, logger.Range{ .loc = comma_after_spread, .len = 1 }, "Unexpected \",\" after rest pattern") catch unreachable; + } + } + pub fn init(allocator: *std.mem.Allocator, log: *logger.Log, source: logger.Source, define: *Define, lexer: js_lexer.Lexer, opts: Parser.Options) !*P { var parser = try allocator.create(P); parser.allocated_names = @TypeOf(parser.allocated_names).init(allocator); |