1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
open Ppxlib
module StringSet = Set.Make (String)
module StringMap = Map.Make (String)
type t = {
static_deps : StringSet.t;
outer_bindings : StringSet.t;
component_bindings : StringSet.t;
browser_only_hooks : StringSet.t;
stable_hooks : Stable.shape StringMap.t;
}
type kind = Component | Custom_hook | Function | Value
let component_attributes =
[ "react.component"; "react.client.component"; "react.async.component" ]
let has_component_attribute attrs =
List.exists (fun name -> Hook.has_attribute name attrs) component_attributes
let classify (t : Parsetree.value_binding) =
let is_function =
match Platform.match_payload t.pvb_expr with
| Some (_, cases) ->
List.exists
(fun (case : Parsetree.case) ->
Option.is_some (Bindings.function_body case.pc_rhs))
cases
| None -> Option.is_some (Bindings.function_body t.pvb_expr)
in
if not is_function then Value
else
let marked_as_component =
has_component_attribute t.pvb_attributes
|| has_component_attribute t.pvb_pat.ppat_attributes
in
let named_like_hook =
List.exists Hook.is_hook_name (Bindings.of_pattern t.pvb_pat)
in
if marked_as_component then Component
else if named_like_hook then Custom_hook
else Function
let initial =
{
static_deps = StringSet.empty;
outer_bindings = StringSet.empty;
component_bindings = StringSet.empty;
browser_only_hooks = StringSet.empty;
stable_hooks = StringMap.empty;
}
let stable_lookup scope name = StringMap.find_opt name scope.stable_hooks
let enter_binding kind ~in_component_or_hook (t : Parsetree.value_binding) scope
=
let binding_names = Bindings.of_pattern t.pvb_pat in
let bound = StringSet.of_list binding_names in
let scope =
{
scope with
browser_only_hooks = StringSet.diff scope.browser_only_hooks bound;
stable_hooks =
List.fold_left
(fun map name -> StringMap.remove name map)
scope.stable_hooks binding_names;
}
in
let is_plain_binding = kind = Value || kind = Function in
let scope =
if not is_plain_binding then scope
else if in_component_or_hook then
{
scope with
component_bindings = StringSet.union scope.component_bindings bound;
}
else
{ scope with outer_bindings = StringSet.union scope.outer_bindings bound }
in
let scope =
let new_static_deps =
Stable.static_deps_of_binding ~lookup:(stable_lookup scope) t
in
{
scope with
static_deps =
StringSet.union scope.static_deps (StringSet.of_list new_static_deps);
}
in
match Stable.wrapper_shape ~lookup:(stable_lookup scope) t with
| Some (name, shape) ->
{ scope with stable_hooks = StringMap.add name shape scope.stable_hooks }
| None -> scope
let binding_body kind (t : Parsetree.value_binding) scope =
match kind with
| Component | Custom_hook ->
let function_params =
StringSet.of_list
(Bindings.function_params t.pvb_expr @ Bindings.label_names t.pvb_expr)
in
{
scope with
static_deps = StringSet.empty;
component_bindings = function_params;
}
| Function | Value -> scope
let exit_binding kind ~entered ~traversed =
let enters_new_scope = kind = Component || kind = Custom_hook in
if enters_new_scope then
{
traversed with
static_deps = entered.static_deps;
component_bindings = entered.component_bindings;
stable_hooks = entered.stable_hooks;
}
else traversed
let static_deps scope = scope.static_deps
let outer_bindings scope = scope.outer_bindings
let component_bindings scope = scope.component_bindings
let is_browser_only_hook_wrapper (vb : Parsetree.value_binding) : bool =
match Bindings.function_body vb.pvb_expr with
| Some _ -> Hook.expression_has_hook_calls vb.pvb_expr
| None -> (
match vb.pvb_expr.pexp_desc with
| Pexp_ident { txt; _ } -> Hook.is_hook_ident txt
| _ -> false)
let track_browser_only_wrappers (vbs : Parsetree.value_binding list) scope =
let names =
vbs
|> List.filter is_browser_only_hook_wrapper
|> List.concat_map (fun (vb : Parsetree.value_binding) ->
Bindings.of_pattern vb.pvb_pat)
in
{
scope with
browser_only_hooks =
StringSet.union scope.browser_only_hooks (StringSet.of_list names);
}
let is_tracked_browser_only_hook scope (lident : Longident.t) : bool =
match lident with
| Lident name -> StringSet.mem name scope.browser_only_hooks
| _ -> false
let browser_only_snapshot scope = scope.browser_only_hooks
let restore_browser_only saved scope = { scope with browser_only_hooks = saved }