Unified symbol-to-docblock server command
[hiphop-php.git] / hphp / hack / src / server / identifySymbolService.ml
blobeb9ed111cb8f540e382633d6d5571bc8d2edc3f9
1 (**
2 * Copyright (c) 2015, Facebook, Inc.
3 * All rights reserved.
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the "hack" directory of this source tree.
8 *)
10 open Core_kernel
11 open SymbolOccurrence
12 open Typing_defs
14 module Result_set = Caml.Set.Make(struct
15 type t = Relative_path.t SymbolOccurrence.t
16 let compare = Pervasives.compare
17 end)
19 let is_target target_line target_char { pos; _ } =
20 let l, start, end_ = Pos.info_pos pos in
21 l = target_line && start <= target_char && target_char - 1 <= end_
23 let process_class_id ?(is_declaration=false) (pos, cid) =
24 Result_set.singleton {
25 name = cid;
26 type_ = Class;
27 is_declaration;
28 pos = pos
31 let clean_member_name name = String_utils.lstrip name "$"
33 let process_member ?(is_declaration=false) c_name id ~is_method ~is_const =
34 let member_name = (snd id) in
35 let type_ =
36 if is_const then ClassConst (c_name, member_name)
37 else if is_method then Method (c_name, member_name)
38 else Property (c_name, member_name)
40 Result_set.singleton {
41 name = (c_name ^ "::" ^ (clean_member_name member_name));
42 type_;
43 is_declaration;
44 pos = fst id
47 let process_fun_id ?(is_declaration=false) id =
48 Result_set.singleton {
49 name = snd id;
50 type_ = Function;
51 is_declaration;
52 pos = fst id
55 let process_global_const ?(is_declaration=false) id =
56 Result_set.singleton {
57 name = snd id;
58 type_ = GConst;
59 is_declaration;
60 pos = fst id
63 let process_lvar_id ?(is_declaration=false) id =
64 Result_set.singleton {
65 name = snd id;
66 type_ = LocalVar;
67 is_declaration;
68 pos = fst id
71 let process_typeconst ?(is_declaration=false) (class_name, tconst_name, pos) =
72 Result_set.singleton {
73 name = class_name ^ "::" ^ tconst_name;
74 type_ = Typeconst (class_name, tconst_name);
75 is_declaration;
76 pos;
79 let process_class class_ =
80 let acc = process_class_id ~is_declaration:true class_.Tast.c_name in
81 let c_name = snd class_.Tast.c_name in
82 let constructor, static_methods, methods = Tast.split_methods class_ in
83 let all_methods = static_methods @ methods in
84 let acc = List.fold all_methods ~init:acc ~f:begin fun acc method_ ->
85 Result_set.union acc @@
86 process_member c_name method_.Tast.m_name
87 ~is_declaration:true ~is_method:true ~is_const:false
88 end in
89 let all_props = class_.Tast.c_vars in
90 let acc = List.fold all_props ~init:acc ~f:begin fun acc prop ->
91 Result_set.union acc @@
92 process_member c_name prop.Tast.cv_id
93 ~is_declaration:true ~is_method:false ~is_const:false
94 end in
95 let acc = List.fold class_.Tast.c_consts ~init:acc ~f:begin fun acc (_, const_id, _) ->
96 Result_set.union acc @@
97 process_member c_name const_id
98 ~is_declaration:true ~is_method:false ~is_const:true
99 end in
100 let acc = List.fold class_.Tast.c_typeconsts ~init:acc ~f:begin fun acc typeconst ->
101 let pos, tconst_name = typeconst.Tast.c_tconst_name in
102 Result_set.union acc @@
103 process_typeconst ~is_declaration:true (c_name, tconst_name, pos)
104 end in
105 (* We don't check anything about xhp attributes, so the hooks won't fire when
106 typechecking the class. Need to look at them individually. *)
107 let acc = List.fold class_.Tast.c_xhp_attr_uses ~init:acc ~f:begin fun acc attr ->
108 match attr with
109 | _, Aast.Happly (cid, _) ->
110 Result_set.union acc @@
111 process_class_id cid
112 | _ -> acc
113 end in
114 match constructor with
115 | Some method_ ->
116 let id = fst method_.Tast.m_name, SN.Members.__construct in
117 Result_set.union acc @@
118 process_member c_name id
119 ~is_declaration:true ~is_method:true ~is_const:false
120 | None -> acc
122 let typed_member_id env receiver_ty mid ~is_method ~is_const =
123 Tast_env.get_class_ids env receiver_ty
124 |> List.map ~f:(fun cid -> process_member cid mid ~is_method ~is_const)
125 |> List.fold ~init:Result_set.empty ~f:Result_set.union
127 let typed_method = typed_member_id ~is_method:true ~is_const:false
128 let typed_const = typed_member_id ~is_method:false ~is_const:true
129 let typed_property = typed_member_id ~is_method:false ~is_const:false
130 let typed_constructor env ty pos =
131 typed_method env ty (pos, SN.Members.__construct)
133 let typed_class_id env ty pos =
134 Tast_env.get_class_ids env ty
135 |> List.map ~f:(fun cid -> process_class_id (pos, cid))
136 |> List.fold ~init:Result_set.empty ~f:Result_set.union
138 (* When we detect a function reference encapsulated in a string,
139 * we want to update the function reference without removing the apostrophes.
141 * Example: class_meth(myclass::class, 'myfunc');
143 * In this case, we only want to replace the text 'myfunc' - so we need
144 * to shrink our positional data by the apostrophes. *)
145 let remove_apostrophes_from_function_eval (mid: Ast_defs.pstring): Ast_defs.pstring =
146 let pos, member_name = mid in
147 let new_pos = (Pos.shrink_by_one_char_both_sides pos) in
148 (new_pos, member_name)
150 let visitor = object (self)
151 inherit [_] Tast_visitor.reduce as super
153 method zero = Result_set.empty
154 method plus = Result_set.union
156 method! on_expr env expr =
157 let pos = fst (fst expr) in
158 let (+) = self#plus in
159 let acc =
160 match snd expr with
161 | Tast.New (((p, ty), _), _, _, _, _) ->
162 typed_constructor env ty p
163 | Tast.Obj_get (((_, ty), _), (_, Tast.Id mid), _) ->
164 typed_property env ty mid
165 | Tast.Class_const (((_, ty), _), mid) ->
166 typed_const env ty mid
167 | Tast.Class_get (((_, ty), _), Tast.CGstring mid) ->
168 typed_property env ty mid
169 | Tast.Xml (cid, _, _) ->
170 process_class_id cid
171 | Tast.Fun_id id ->
172 process_fun_id (pos, "\\HH\\"^SN.SpecialFunctions.fun_) +
173 process_fun_id (remove_apostrophes_from_function_eval id)
174 | Tast.Method_id (((_, ty), _), mid) ->
175 process_fun_id (pos, "\\HH\\"^SN.SpecialFunctions.inst_meth) +
176 typed_method env ty (remove_apostrophes_from_function_eval mid)
177 | Tast.Smethod_id ((_, cid) as pcid, mid) ->
178 process_fun_id (pos, "\\HH\\"^SN.SpecialFunctions.class_meth) +
179 process_class_id pcid +
180 process_member cid (remove_apostrophes_from_function_eval mid)
181 ~is_method:true ~is_const:false
182 | Tast.Method_caller ((_, cid) as pcid, mid) ->
183 process_fun_id (pos, "\\HH\\"^SN.SpecialFunctions.meth_caller) +
184 process_class_id pcid +
185 process_member cid (remove_apostrophes_from_function_eval mid)
186 ~is_method:true ~is_const:false
187 | _ -> self#zero
189 acc + super#on_expr env expr
191 method! on_class_id env ((p, ty), cid) =
192 match cid with
193 | Tast.CIexpr expr ->
194 (* We want to special case this because we want to get the type of the
195 inner expression, which will have a type like `classname<Foo>`, rather
196 than the resolved type of the class ID, which will have a type like
197 `Foo`. Since the class ID and the inner expression have the same span,
198 it is not easy to distinguish them later. *)
199 self#on_expr env expr
200 | _ -> typed_class_id env ty p
202 method! on_Call env ct e tal el uel =
203 (* For Id, Obj_get (with an Id member), and Class_const, we don't want to
204 * use the result of `self#on_expr env e`, since it would record a
205 * property, class const, or global const access instead of a method call.
206 * So instead of invoking super#on_Call, we reimplement it here, omitting
207 * `self#on_expr env e` when necessary. *)
208 let (+) = self#plus in
209 let cta = self#on_call_type env ct in
210 let ea =
211 match snd e with
212 | Tast.Id id ->
213 process_fun_id id
214 | Tast.Obj_get (((_, ty), _) as obj, (_, Tast.Id mid), _) ->
215 self#on_expr env obj + typed_method env ty mid
216 | Tast.Class_const (((_, ty), _) as cid, mid) ->
217 self#on_class_id env cid + typed_method env ty mid
218 | _ -> self#on_expr env e
220 let tala = self#on_list self#on_targ env tal in
221 let ela = self#on_list self#on_expr env el in
222 let uela = self#on_list self#on_expr env uel in
223 cta + ea + tala + ela + uela
225 method! on_Haccess env root ids =
226 let acc =
227 Tast_env.referenced_typeconsts env root ids
228 |> List.map ~f:process_typeconst
229 |> List.fold ~init:self#zero ~f:self#plus
231 self#plus acc (super#on_Haccess env root ids)
233 method! on_Lvar env (pos, id) =
234 let acc = process_lvar_id (pos, Local_id.get_name id) in
235 self#plus acc (super#on_Lvar env (pos, id))
237 method! on_fun_param env param =
238 let acc = process_lvar_id (param.Tast.param_pos, param.Tast.param_name) in
239 self#plus acc (super#on_fun_param env param)
241 method! on_Happly env sid hl =
242 let acc = process_class_id sid in
243 self#plus acc (super#on_Happly env sid hl)
245 method! on_catch env (sid, lid, block) =
246 let acc = process_class_id sid in
247 self#plus acc (super#on_catch env (sid, lid, block))
249 method! on_class_ env class_ =
250 let open Tast in
251 let open Aast in
252 let acc = process_class class_ in
254 Enums implicitly extend BuiltinEnum. However, BuiltinEnums also extend
255 the same Enum as a type parameter.
257 Ex: enum Size extends BuiltinEnum<Size> { ... }
259 This will return the definition of the enum twice when finding references
260 on it. As a result, we set the extends property of an enum's tast to an empty list.
262 let class_ = match class_.c_extends with
263 | [(_,
264 Happly ((_, builtin_enum), [(_,
265 Happly (c_name, []))]
268 when c_name = class_.c_name && builtin_enum = Naming_special_names.Classes.cHH_BuiltinEnum
269 -> { class_ with c_extends = [] }
270 | _ -> class_
272 self#plus acc (super#on_class_ env class_)
274 method! on_fun_ env fun_ =
275 let acc = process_fun_id ~is_declaration:true fun_.Tast.f_name in
276 self#plus acc (super#on_fun_ env fun_)
278 method! on_typedef env typedef =
279 let acc = process_class_id ~is_declaration:true typedef.Tast.t_name in
280 self#plus acc (super#on_typedef env typedef)
282 method! on_gconst env cst =
283 let acc = process_global_const ~is_declaration:true cst.Tast.cst_name in
284 self#plus acc (super#on_gconst env cst)
286 method! on_Id env id =
287 let acc = process_global_const id in
288 self#plus acc (super#on_Id env id)
290 method! on_Obj_get env obj member ognf =
291 match snd member with
292 | Tast.Id _ ->
293 (* Don't visit this Id, since we would record it as a gconst access. *)
294 let obja = self#on_expr env obj in
295 let ognfa = self#on_og_null_flavor env ognf in
296 self#plus obja ognfa
297 | _ -> super#on_Obj_get env obj member ognf
299 method! on_SFclass_const env cid mid =
300 let (+) = Result_set.union in
301 process_class_id cid +
302 process_member (snd cid) mid ~is_method:false ~is_const:true +
303 super#on_SFclass_const env cid mid
306 let all_symbols tast =
307 visitor#go tast
308 |> Result_set.elements
310 let go tast line char =
311 all_symbols tast
312 |> List.filter ~f:(is_target line char)