-module(rally@tree_shaker).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/rally/tree_shaker.gleam").
-export([shake/2]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-file("src/rally/tree_shaker.gleam", 604).
-spec render_import(glance:definition(glance:import())) -> binary().
render_import(Def) ->
Imp = erlang:element(3, Def),
Base = <<"import "/utf8, (erlang:element(3, Imp))/binary>>,
Alias_part = case erlang:element(4, Imp) of
{some, {named, Name}} ->
<<" as "/utf8, Name/binary>>;
{some, {discarded, Name@1}} ->
<<" as _"/utf8, Name@1/binary>>;
none ->
<<""/utf8>>
end,
Unqualified_types = gleam@list:map(
erlang:element(5, Imp),
fun(Ut) -> case erlang:element(3, Ut) of
{some, Alias} ->
<<<<<<"type "/utf8, (erlang:element(2, Ut))/binary>>/binary,
" as "/utf8>>/binary,
Alias/binary>>;
none ->
<<"type "/utf8, (erlang:element(2, Ut))/binary>>
end end
),
Unqualified_values = gleam@list:map(
erlang:element(6, Imp),
fun(Uv) -> case erlang:element(3, Uv) of
{some, Alias@1} ->
<<<<(erlang:element(2, Uv))/binary, " as "/utf8>>/binary,
Alias@1/binary>>;
none ->
erlang:element(2, Uv)
end end
),
All_unqualified = lists:append(Unqualified_types, Unqualified_values),
Unqualified_part = case All_unqualified of
[] ->
<<""/utf8>>;
Items ->
<<<<".{"/utf8, (gleam@string:join(Items, <<", "/utf8>>))/binary>>/binary,
"}"/utf8>>
end,
<<<<Base/binary, Alias_part/binary>>/binary, Unqualified_part/binary>>.
-file("src/rally/tree_shaker.gleam", 586).
-spec render_attr_expr(glance:expression()) -> binary().
render_attr_expr(Expr) ->
case Expr of
{string, _, Value} ->
<<<<"\""/utf8, Value/binary>>/binary, "\""/utf8>>;
{variable, _, Name} ->
Name;
_ ->
<<"..."/utf8>>
end.
-file("src/rally/tree_shaker.gleam", 578).
-spec render_attribute(glance:attribute()) -> binary().
render_attribute(Attr) ->
Args = begin
_pipe = erlang:element(3, Attr),
_pipe@1 = gleam@list:map(_pipe, fun render_attr_expr/1),
gleam@string:join(_pipe@1, <<", "/utf8>>)
end,
<<<<<<<<"@"/utf8, (erlang:element(2, Attr))/binary>>/binary, "("/utf8>>/binary,
Args/binary>>/binary,
")"/utf8>>.
-file("src/rally/tree_shaker.gleam", 554).
-spec extract_client_functions(
glance:module_(),
gleam@set:set(binary()),
binary()
) -> list(binary()).
extract_client_functions(Ast, Client_fn_names, Source) ->
_pipe = erlang:element(6, Ast),
_pipe@1 = gleam@list:filter(
_pipe,
fun(Def) ->
gleam@set:contains(
Client_fn_names,
erlang:element(3, erlang:element(3, Def))
)
end
),
gleam@list:map(
_pipe@1,
fun(Def@1) ->
Fn_source = rally_tree_shaker_ffi:byte_slice(
Source,
erlang:element(2, erlang:element(3, Def@1))
),
Attrs = begin
_pipe@2 = erlang:element(2, Def@1),
gleam@list:filter_map(
_pipe@2,
fun(Attr) -> case erlang:element(2, Attr) of
<<"external"/utf8>> ->
{ok, render_attribute(Attr)};
_ ->
{error, nil}
end end
)
end,
case Attrs of
[] ->
Fn_source;
_ ->
<<<<(gleam@string:join(Attrs, <<"\n"/utf8>>))/binary,
"\n"/utf8>>/binary,
Fn_source/binary>>
end
end
).
-file("src/rally/tree_shaker.gleam", 545).
-spec filter_constants(
list(glance:definition(glance:constant())),
gleam@set:set(binary())
) -> list(glance:definition(glance:constant())).
filter_constants(Constants, Client_refs) ->
gleam@list:filter(
Constants,
fun(Def) ->
gleam@set:contains(
Client_refs,
erlang:element(3, erlang:element(3, Def))
)
end
).
-file("src/rally/tree_shaker.gleam", 533).
-spec filter_type_aliases(
list(glance:definition(glance:type_alias())),
gleam@set:set(binary()),
gleam@set:set(binary())
) -> list(glance:definition(glance:type_alias())).
filter_type_aliases(Aliases, Server_symbols, Client_refs) ->
gleam@list:filter(
Aliases,
fun(Def) ->
Name = erlang:element(3, erlang:element(3, Def)),
not gleam@set:contains(Server_symbols, Name) andalso ((gleam@set:contains(
Client_refs,
Name
)
orelse (Name =:= <<"Model"/utf8>>))
orelse (Name =:= <<"Msg"/utf8>>))
end
).
-file("src/rally/tree_shaker.gleam", 512).
-spec filter_types(
list(glance:definition(glance:custom_type())),
gleam@set:set(binary()),
gleam@set:set(binary())
) -> list(glance:definition(glance:custom_type())).
filter_types(Types, Server_symbols, Client_refs) ->
gleam@list:filter(
Types,
fun(Def) ->
Name = erlang:element(3, erlang:element(3, Def)),
Is_server = gleam@set:contains(Server_symbols, Name),
case Is_server of
false ->
true;
true ->
gleam@set:contains(Client_refs, Name) orelse gleam@list:any(
erlang:element(7, erlang:element(3, Def)),
fun(V) ->
gleam@set:contains(
Client_refs,
erlang:element(2, V)
)
end
)
end
end
).
-file("src/rally/tree_shaker.gleam", 505).
-spec last_segment(binary()) -> binary().
last_segment(Module_path) ->
case begin
_pipe = gleam@string:split(Module_path, <<"/"/utf8>>),
gleam@list:last(_pipe)
end of
{ok, Seg} ->
Seg;
{error, nil} ->
Module_path
end.
-file("src/rally/tree_shaker.gleam", 470).
-spec filter_imports(
list(glance:definition(glance:import())),
gleam@set:set(binary()),
gleam@set:set(binary())
) -> list(glance:definition(glance:import())).
filter_imports(Imports, Server_symbols, Client_refs) ->
gleam@list:filter(
Imports,
fun(Def) ->
Imp = erlang:element(3, Def),
Imports_server_type = gleam@list:any(
erlang:element(5, Imp),
fun(Uq) ->
gleam@set:contains(Server_symbols, erlang:element(2, Uq))
end
),
case Imports_server_type of
true ->
false;
false ->
Has_unqualified_ref = gleam@list:any(
erlang:element(6, Imp),
fun(Uv) ->
gleam@set:contains(
Client_refs,
erlang:element(2, Uv)
)
end
)
orelse gleam@list:any(
erlang:element(5, Imp),
fun(Ut) ->
gleam@set:contains(
Client_refs,
erlang:element(2, Ut)
)
end
),
Module_alias = case erlang:element(4, Imp) of
{some, {named, Name}} ->
Name;
_ ->
last_segment(erlang:element(3, Imp))
end,
Module_referenced = gleam@set:contains(
Client_refs,
Module_alias
),
Has_unqualified_ref orelse Module_referenced
end
end
).
-file("src/rally/tree_shaker.gleam", 445).
-spec extract_type_refs(glance:type()) -> list(binary()).
extract_type_refs(T) ->
case T of
{named_type, _, Name, Module, Parameters} ->
Mod_ref = case Module of
{some, M} ->
[M];
none ->
[]
end,
lists:append(
[[Name],
Mod_ref,
gleam@list:flat_map(Parameters, fun extract_type_refs/1)]
);
{tuple_type, _, Elements} ->
gleam@list:flat_map(Elements, fun extract_type_refs/1);
{function_type, _, Parameters@1, Return} ->
lists:append(
gleam@list:flat_map(Parameters@1, fun extract_type_refs/1),
extract_type_refs(Return)
);
{variable_type, _, Name@1} ->
[Name@1];
{hole_type, _, _} ->
[]
end.
-file("src/rally/tree_shaker.gleam", 333).
-spec extract_pattern_refs(glance:pattern()) -> list(binary()).
extract_pattern_refs(Pattern) ->
case Pattern of
{pattern_variant, _, Module, Constructor, Arguments, _} ->
Module_refs = case Module of
{some, Module@1} ->
[Module@1];
none ->
[]
end,
lists:append(
[[Constructor],
Module_refs,
gleam@list:flat_map(Arguments, fun(Arg) -> case Arg of
{labelled_field, _, _, P} ->
extract_pattern_refs(P);
{shorthand_field, _, _} ->
[];
{unlabelled_field, P@1} ->
extract_pattern_refs(P@1)
end end)]
);
{pattern_tuple, _, Elements} ->
gleam@list:flat_map(Elements, fun extract_pattern_refs/1);
{pattern_list, _, Elements@1, Tail} ->
lists:append(
gleam@list:flat_map(Elements@1, fun extract_pattern_refs/1),
case Tail of
{some, Tail@1} ->
extract_pattern_refs(Tail@1);
none ->
[]
end
);
{pattern_assignment, _, Pattern@1, _} ->
extract_pattern_refs(Pattern@1);
{pattern_bit_string, _, Segments} ->
gleam@list:flat_map(
Segments,
fun(Segment) ->
extract_pattern_refs(erlang:element(1, Segment))
end
);
{pattern_int, _, _} ->
[];
{pattern_float, _, _} ->
[];
{pattern_string, _, _} ->
[];
{pattern_discard, _, _} ->
[];
{pattern_variable, _, _} ->
[];
{pattern_concatenate, _, _, _, _} ->
[]
end.
-file("src/rally/tree_shaker.gleam", 231).
-spec extract_expr_refs(glance:expression()) -> list(binary()).
extract_expr_refs(Expr) ->
case Expr of
{variable, _, Name} ->
[Name];
{call, _, Function, Arguments} ->
lists:append(
extract_expr_refs(Function),
gleam@list:flat_map(Arguments, fun(A) -> case A of
{labelled_field, _, _, V} ->
extract_expr_refs(V);
{shorthand_field, _, _} ->
[];
{unlabelled_field, V@1} ->
extract_expr_refs(V@1)
end end)
);
{fn, _, _, _, Body} ->
gleam@list:flat_map(Body, fun extract_statement_refs/1);
{block, _, Statements} ->
gleam@list:flat_map(Statements, fun extract_statement_refs/1);
{'case', _, Subjects, Clauses} ->
lists:append(
gleam@list:flat_map(Subjects, fun extract_expr_refs/1),
gleam@list:flat_map(
Clauses,
fun(C) ->
lists:append(
[gleam@list:flat_map(
erlang:element(2, C),
fun(Patterns) ->
gleam@list:flat_map(
Patterns,
fun extract_pattern_refs/1
)
end
),
case erlang:element(3, C) of
{some, Guard} ->
extract_expr_refs(Guard);
none ->
[]
end,
extract_expr_refs(erlang:element(4, C))]
)
end
)
);
{tuple, _, Elements} ->
gleam@list:flat_map(Elements, fun extract_expr_refs/1);
{list, _, Elements@1, Rest} ->
lists:append(
gleam@list:flat_map(Elements@1, fun extract_expr_refs/1),
case Rest of
{some, R} ->
extract_expr_refs(R);
none ->
[]
end
);
{record_update, _, _, _, Record, Fields} ->
lists:append(
extract_expr_refs(Record),
gleam@list:flat_map(
Fields,
fun(F) -> case erlang:element(3, F) of
{some, V@2} ->
extract_expr_refs(V@2);
none ->
[]
end end
)
);
{field_access, _, Container, _} ->
extract_expr_refs(Container);
{binary_operator, _, _, Left, Right} ->
lists:append(extract_expr_refs(Left), extract_expr_refs(Right));
{negate_int, _, Value} ->
extract_expr_refs(Value);
{negate_bool, _, Value@1} ->
extract_expr_refs(Value@1);
{panic, _, Message} ->
case Message of
{some, V@3} ->
extract_expr_refs(V@3);
none ->
[]
end;
{todo, _, Message@1} ->
case Message@1 of
{some, V@4} ->
extract_expr_refs(V@4);
none ->
[]
end;
{tuple_index, _, Tuple, _} ->
extract_expr_refs(Tuple);
{fn_capture, _, _, Function@1, Arguments_before, Arguments_after} ->
lists:append(
[extract_expr_refs(Function@1),
gleam@list:flat_map(
Arguments_before,
fun(A@1) -> case A@1 of
{labelled_field, _, _, V@5} ->
extract_expr_refs(V@5);
{shorthand_field, _, _} ->
[];
{unlabelled_field, V@6} ->
extract_expr_refs(V@6)
end end
),
gleam@list:flat_map(Arguments_after, fun(A@2) -> case A@2 of
{labelled_field, _, _, V@7} ->
extract_expr_refs(V@7);
{shorthand_field, _, _} ->
[];
{unlabelled_field, V@8} ->
extract_expr_refs(V@8)
end end)]
);
{bit_string, _, Segments} ->
gleam@list:flat_map(
Segments,
fun(Seg) -> extract_expr_refs(erlang:element(1, Seg)) end
);
{echo, _, Expression, _} ->
case Expression of
{some, V@9} ->
extract_expr_refs(V@9);
none ->
[]
end;
{int, _, _} ->
[];
{float, _, _} ->
[];
{string, _, _} ->
[]
end.
-file("src/rally/tree_shaker.gleam", 222).
-spec extract_statement_refs(glance:statement()) -> list(binary()).
extract_statement_refs(Stmt) ->
case Stmt of
{use, _, _, Expr} ->
extract_expr_refs(Expr);
{assignment, _, _, _, _, Expr@1} ->
extract_expr_refs(Expr@1);
{assert, _, Expr@2, _} ->
extract_expr_refs(Expr@2);
{expression, Expr@3} ->
extract_expr_refs(Expr@3)
end.
-file("src/rally/tree_shaker.gleam", 217).
-spec extract_fn_references(glance:function_()) -> list(binary()).
extract_fn_references(F) ->
_pipe = erlang:element(7, F),
gleam@list:flat_map(_pipe, fun extract_statement_refs/1).
-file("src/rally/tree_shaker.gleam", 374).
?DOC(" Collect all referenced names from client function bodies, signatures, and types.\n").
-spec collect_all_client_refs(
glance:module_(),
gleam@set:set(binary()),
gleam@set:set(binary())
) -> gleam@set:set(binary()).
collect_all_client_refs(Ast, Client_fn_names, Server_symbols) ->
Client_fns = begin
_pipe = erlang:element(6, Ast),
gleam@list:filter(
_pipe,
fun(Def) ->
gleam@set:contains(
Client_fn_names,
erlang:element(3, erlang:element(3, Def))
)
end
)
end,
Body_refs = gleam@list:flat_map(
Client_fns,
fun(Def@1) -> extract_fn_references(erlang:element(3, Def@1)) end
),
Sig_refs = gleam@list:flat_map(
Client_fns,
fun(Def@2) ->
Function = erlang:element(3, Def@2),
Param_refs = gleam@list:flat_map(
erlang:element(5, Function),
fun(P) -> case erlang:element(4, P) of
{some, T} ->
extract_type_refs(T);
none ->
[]
end end
),
Return_refs = case erlang:element(6, Function) of
{some, T@1} ->
extract_type_refs(T@1);
none ->
[]
end,
lists:append(Param_refs, Return_refs)
end
),
Client_types = begin
_pipe@1 = erlang:element(3, Ast),
gleam@list:filter(
_pipe@1,
fun(Def@3) ->
not gleam@set:contains(
Server_symbols,
erlang:element(3, erlang:element(3, Def@3))
)
end
)
end,
Type_refs = gleam@list:flat_map(
Client_types,
fun(Def@4) ->
gleam@list:flat_map(
erlang:element(7, erlang:element(3, Def@4)),
fun(V) ->
lists:append(
[[erlang:element(2, V)],
gleam@list:flat_map(
erlang:element(3, V),
fun(F) -> case F of
{labelled_variant_field, T@2, _} ->
extract_type_refs(T@2);
{unlabelled_variant_field, T@3} ->
extract_type_refs(T@3)
end end
)]
)
end
)
end
),
Alias_refs = gleam@list:flat_map(
erlang:element(4, Ast),
fun(Def@5) ->
Name = erlang:element(3, erlang:element(3, Def@5)),
case gleam@set:contains(Server_symbols, Name) orelse (((Name /= <<"Model"/utf8>>)
andalso (Name /= <<"Msg"/utf8>>))
andalso not gleam@set:contains(Client_fn_names, Name)) of
true ->
[];
false ->
extract_type_refs(
erlang:element(6, erlang:element(3, Def@5))
)
end
end
),
_pipe@2 = lists:append([Body_refs, Sig_refs, Type_refs, Alias_refs]),
gleam@set:from_list(_pipe@2).
-file("src/rally/tree_shaker.gleam", 188).
-spec expand_reachable(
gleam@set:set(binary()),
list(glance:definition(glance:function_())),
gleam@set:set(binary()),
gleam@set:set(binary())
) -> gleam@set:set(binary()).
expand_reachable(Frontier, Private_fns, All_private, Visited) ->
case gleam@set:is_empty(Frontier) of
true ->
Visited;
_ ->
New_refs = begin
_pipe = Private_fns,
_pipe@1 = gleam@list:filter(
_pipe,
fun(Def) ->
gleam@set:contains(
Frontier,
erlang:element(3, erlang:element(3, Def))
)
end
),
_pipe@2 = gleam@list:flat_map(
_pipe@1,
fun(Def@1) ->
extract_fn_references(erlang:element(3, Def@1))
end
),
_pipe@3 = gleam@set:from_list(_pipe@2),
_pipe@4 = gleam@set:intersection(_pipe@3, All_private),
gleam@set:difference(_pipe@4, Visited)
end,
expand_reachable(
New_refs,
Private_fns,
All_private,
gleam@set:union(Visited, New_refs)
)
end.
-file("src/rally/tree_shaker.gleam", 158).
-spec collect_reachable_private_fns(
glance:module_(),
list(glance:definition(glance:function_())),
gleam@set:set(binary())
) -> gleam@set:set(binary()).
collect_reachable_private_fns(Ast, Client_fns, Server_fns) ->
Private_fns = begin
_pipe = erlang:element(6, Ast),
gleam@list:filter(
_pipe,
fun(Def) ->
Function = erlang:element(3, Def),
(erlang:element(4, Function) /= public) andalso not gleam@set:contains(
Server_fns,
erlang:element(3, Function)
)
end
)
end,
Private_fn_names = begin
_pipe@1 = gleam@list:map(
Private_fns,
fun(Def@1) -> erlang:element(3, erlang:element(3, Def@1)) end
),
gleam@set:from_list(_pipe@1)
end,
Initial = begin
_pipe@2 = Client_fns,
_pipe@3 = gleam@list:flat_map(
_pipe@2,
fun(Def@2) -> extract_fn_references(erlang:element(3, Def@2)) end
),
_pipe@4 = gleam@set:from_list(_pipe@3),
gleam@set:intersection(_pipe@4, Private_fn_names)
end,
expand_reachable(Initial, Private_fns, Private_fn_names, Initial).
-file("src/rally/tree_shaker.gleam", 132).
-spec type_references_server_symbol(glance:type(), gleam@set:set(binary())) -> boolean().
type_references_server_symbol(T, Server_symbols) ->
case T of
{named_type, _, Name, _, Parameters} ->
gleam@set:contains(Server_symbols, Name) orelse gleam@list:any(
Parameters,
fun(P) -> type_references_server_symbol(P, Server_symbols) end
);
{tuple_type, _, Elements} ->
gleam@list:any(
Elements,
fun(E) -> type_references_server_symbol(E, Server_symbols) end
);
{function_type, _, Parameters@1, Return} ->
gleam@list:any(
Parameters@1,
fun(P@1) ->
type_references_server_symbol(P@1, Server_symbols)
end
)
orelse type_references_server_symbol(Return, Server_symbols);
{variable_type, _, _} ->
false;
{hole_type, _, _} ->
false
end.
-file("src/rally/tree_shaker.gleam", 121).
?DOC(
" A function is server-only if:\n"
" - its name starts with \"server_\"\n"
" - its name is \"load\"\n"
" - any parameter type references a server symbol\n"
).
-spec is_server_function(glance:function_(), gleam@set:set(binary())) -> boolean().
is_server_function(F, Server_symbols) ->
(gleam_stdlib:string_starts_with(erlang:element(3, F), <<"server_"/utf8>>)
orelse (erlang:element(3, F) =:= <<"load"/utf8>>))
orelse gleam@list:any(
erlang:element(5, F),
fun(Param) -> case erlang:element(4, Param) of
{some, T} ->
type_references_server_symbol(T, Server_symbols);
none ->
false
end end
).
-file("src/rally/tree_shaker.gleam", 10).
?DOC(
" Extract client-safe source code from a page module.\n"
" Removes server_* functions, load, and anything only reachable from server code.\n"
" server_symbols: known server-only type names (e.g. \"ServerContext\", handler message types)\n"
).
-spec shake(binary(), list(binary())) -> binary().
shake(Source, Server_symbols) ->
Server_set = gleam@set:from_list(Server_symbols),
case glance:module(Source) of
{error, unexpected_end_of_input} ->
Source;
{error, {unexpected_token, _, _}} ->
Source;
{ok, Ast} ->
Server_fns = begin
_pipe = erlang:element(6, Ast),
_pipe@1 = gleam@list:filter_map(
_pipe,
fun(Def) ->
Function = erlang:element(3, Def),
case is_server_function(Function, Server_set) of
true ->
{ok, erlang:element(3, Function)};
false ->
{error, nil}
end
end
),
gleam@set:from_list(_pipe@1)
end,
Client_pub_fns = begin
_pipe@2 = erlang:element(6, Ast),
gleam@list:filter(
_pipe@2,
fun(Def@1) ->
Function@1 = erlang:element(3, Def@1),
(erlang:element(4, Function@1) =:= public) andalso not gleam@set:contains(
Server_fns,
erlang:element(3, Function@1)
)
end
)
end,
Reachable_private = collect_reachable_private_fns(
Ast,
Client_pub_fns,
Server_fns
),
All_client_fn_names = begin
_pipe@3 = gleam@list:map(
Client_pub_fns,
fun(Def@2) ->
erlang:element(3, erlang:element(3, Def@2))
end
),
_pipe@4 = gleam@set:from_list(_pipe@3),
gleam@set:union(_pipe@4, Reachable_private)
end,
Client_refs = collect_all_client_refs(
Ast,
All_client_fn_names,
Server_set
),
Client_imports = filter_imports(
erlang:element(2, Ast),
Server_set,
Client_refs
),
Client_types = filter_types(
erlang:element(3, Ast),
Server_set,
Client_refs
),
Client_type_aliases = filter_type_aliases(
erlang:element(4, Ast),
Server_set,
Client_refs
),
Client_constants = filter_constants(
erlang:element(5, Ast),
Client_refs
),
Client_functions = extract_client_functions(
Ast,
All_client_fn_names,
Source
),
Import_lines = gleam@list:map(Client_imports, fun render_import/1),
Type_lines = gleam@list:map(
Client_types,
fun(Ct) ->
rally_tree_shaker_ffi:byte_slice(
Source,
erlang:element(2, erlang:element(3, Ct))
)
end
),
Type_alias_lines = gleam@list:map(
Client_type_aliases,
fun(Ta) ->
rally_tree_shaker_ffi:byte_slice(
Source,
erlang:element(2, erlang:element(3, Ta))
)
end
),
Const_lines = gleam@list:map(
Client_constants,
fun(C) ->
rally_tree_shaker_ffi:byte_slice(
Source,
erlang:element(2, erlang:element(3, C))
)
end
),
<<(gleam@string:join(
lists:append(
[Import_lines,
Type_lines,
Type_alias_lines,
Const_lines,
Client_functions]
),
<<"\n\n"/utf8>>
))/binary,
"\n"/utf8>>
end.