Fix special case of alternate if statements
[hiphop-php.git] / hphp / hack / src / server / cstSearchService.ml
blobd9450e1c9d88db979d8d92f0683e96ef9e0f8a13
1 (**
2 * Copyright (c) 2018, 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 Hh_core
12 module Syntax = Full_fidelity_positioned_syntax
13 module SyntaxKind = Full_fidelity_syntax_kind
14 module SyntaxTree = Full_fidelity_syntax_tree
15 .WithSyntax(Full_fidelity_positioned_syntax)
17 (**
18 * A `SyntaxKind.t`. We can generate a string from a kind, but not the other way
19 * around, so we store the string value given to us in the input directly. Then,
20 * when examining a node to see if it's a match, we'll convert the node's kind
21 * to a string and simply use string comparison.
23 * (Note that multiple syntax kinds map to the same string anyways, so it would
24 * be hard to reverse the mapping.)
26 type node_kind = NodeKind of string
28 (**
29 * An identifier in the pattern used to identify the this node when returning
30 * the results of a match to the caller. This identifier may not be unique in
31 * the list of returned results.
33 type match_name = MatchName of string
35 (**
36 * The name of a child of a node.
38 * For example, a binary expression has three possible child names:
40 * * binary_left_operand
41 * * binary_operator
42 * * binary_right_operand
44 * See the FFP schema definition in `src/parser/schema/schema_definition.ml`.
45 * Note that `BinaryExpression` has the prefix of `binary` and children like
46 * `left_operand`. When combined, this forms the full name
47 * `binary_left_operand`.
49 type child_type = ChildType of string
51 (**
52 * A query that can be run on the syntax tree. It will return the matched nodes
53 * of any matched `MatchPattern`s.
55 type pattern =
56 (**
57 * Match any node with the given kind, and whose children match the given
58 * patterns.
60 | NodePattern of {
61 kind: node_kind;
63 (**
64 * Mapping from child name to pattern that the child must satisfy.
65 * Children may be omitted from this map. No child type may be specified
66 * more than once.
68 children: (child_type * pattern) list;
71 (**
72 * Return the given node in the result list, assuming that the pattern overall
73 * matches. (This pattern by itself always matches; it's often used with
74 * `AndPattern`).
76 | MatchPattern of {
77 match_name: match_name;
80 (**
81 * Matches a given node if there is descendant node matching the given pattern
82 * anywhere in the subtree underneath the parent node.
84 | DescendantPattern of {
85 pattern: pattern;
88 type matched_node = {
89 match_name: match_name;
90 node: Syntax.t;
93 type result = {
94 (**
95 * The list of nodes for which a `MatchPattern` matched.
97 matched_nodes: matched_node list;
100 type env = {
101 syntax_tree: SyntaxTree.t;
104 let empty_result: result option = Some { matched_nodes = [] }
106 let merge_results (lhs: result option) (rhs: result option): result option =
107 Option.merge lhs rhs ~f:(fun lhs rhs ->
108 { matched_nodes = lhs.matched_nodes @ rhs.matched_nodes }
111 let find_child_with_type
112 (node: Syntax.t)
113 (child_type: child_type)
114 : Syntax.t option =
115 let child_index =
116 Syntax.children_names node
117 |> List.findi ~f:(fun _i actual_child_type ->
118 match child_type with
119 | ChildType expected_child_type ->
120 expected_child_type = actual_child_type
122 |> Option.map ~f:fst
124 match child_index with
125 | None -> None
126 | Some index -> List.nth (Syntax.children node) index
128 let rec search_node
129 ~(env: env)
130 ~(pattern: pattern)
131 ~(node: Syntax.t)
132 : env * result option =
133 match pattern with
134 | NodePattern { kind; children } ->
135 let kind = match kind with NodeKind kind -> kind in
136 if (node |> Syntax.kind |> SyntaxKind.to_string) <> kind
137 then (env, None)
138 else
139 let (env, result) = List.fold_left_env env children
140 ~init:empty_result
141 ~f:(fun env acc_result (child_type, pattern) ->
142 match acc_result with
143 (* We failed to match a previous child pattern; short-circuit. *)
144 | None -> (env, None)
146 | Some _ as result ->
147 let child_node = find_child_with_type node child_type in
148 let child_node = Option.value_exn child_node in
149 let (env, child_result) =
150 search_node ~env ~pattern ~node:child_node in
151 match child_result with
152 | None -> (env, None)
153 | Some _ as child_result ->
154 let result = merge_results result child_result in
155 (env, result)
158 (env, result)
160 | MatchPattern { match_name } ->
161 let result = {
162 matched_nodes = [
163 { match_name; node }
165 } in
166 (env, Some result)
168 | DescendantPattern { pattern } ->
169 search_descendants ~env ~pattern ~node
171 (* TODO: this will likely have to become more intelligent *)
172 and search_descendants
173 ~(env: env)
174 ~(pattern: pattern)
175 ~(node: Syntax.t)
176 : env * result option =
177 List.fold_left_env
179 (Syntax.children node)
180 ~init:None
181 ~f:(fun env acc_result child ->
182 let (env, child_result) = search_node ~env ~pattern ~node:child in
183 let (env, descendants_result) =
184 search_descendants ~env ~pattern ~node:child in
185 let result = merge_results child_result descendants_result in
186 (env, (merge_results result acc_result))
189 let compile_pattern (json: Hh_json.json): (pattern, string) Core_result.t =
190 let open Core_result in
191 let open Core_result.Monad_infix in
193 let wrap_json_accessor f =
194 fun x -> Core_result.map_error (f x)
195 ~f:Hh_json.Access.access_failure_to_string
198 let get_string x = wrap_json_accessor (Hh_json.Access.get_string x) in
199 let get_obj x = wrap_json_accessor (Hh_json.Access.get_obj x) in
200 let keytrace_to_string = Hh_json.Access.keytrace_to_string in
201 let error_at_keytrace ~keytrace error_message =
202 Error (error_message ^ (keytrace_to_string keytrace))
205 let rec compile_pattern ~json ~keytrace : (pattern, string) Core_result.t =
206 get_string "pattern_type" (json, keytrace)
208 >>= fun (pattern_type, pattern_type_keytrace) ->
209 match pattern_type with
210 | "node_pattern" ->
211 compile_node_pattern ~json ~keytrace
212 | "match_pattern" ->
213 compile_match_pattern ~json ~keytrace
214 | "descendant_pattern" ->
215 compile_descendant_pattern ~json ~keytrace
216 | pattern_type ->
217 error_at_keytrace ~keytrace:pattern_type_keytrace
218 (Printf.sprintf "Unknown pattern type '%s'" pattern_type)
220 and compile_node_pattern ~json ~keytrace : (pattern, string) Core_result.t =
221 get_string "kind" (json, keytrace)
223 >>= fun (kind, kind_keytrace) ->
224 let open Schema_definition in
225 let kind_info = List.find schema ~f:(fun schema_node ->
226 schema_node.description = kind
227 ) in
228 match kind_info with
229 | None ->
230 error_at_keytrace ~keytrace:kind_keytrace
231 (Printf.sprintf "Kind '%s' doesn't exist" kind)
232 | Some kind_info -> Ok kind_info
234 >>= fun kind_info ->
235 get_obj "children" (json, keytrace)
237 >>= fun (children_json, children_keytrace) ->
238 (* This has already been verified to be an object above. *)
239 let children = Hh_json.get_object_exn children_json in
241 let get_child_type
242 (child_name: string)
243 : (child_type, string) Core_result.t =
244 (* We're given a field name like `binary_right_operand`, but the field
245 names in the schema are things like `right_operand`, and you have to
246 affix the prefix yourself. For consistency with other tooling, we want
247 to use `binary_right_operand` instead of just `right_operand`. *)
248 let get_prefixed_field_name field_name =
249 kind_info.prefix ^ "_" ^ field_name
251 let field = List.find kind_info.fields ~f:(fun (field_name, _) ->
252 (get_prefixed_field_name field_name) = child_name)
254 match field with
255 | None ->
256 let valid_types = List.map kind_info.fields ~f:(fun (field_name, _) ->
257 get_prefixed_field_name field_name
258 ) in
259 error_at_keytrace ~keytrace:children_keytrace
260 (Printf.sprintf
261 ("Unknown child type '%s'; "^^
262 "valid child types for a node of kind '%s' are: %s")
263 child_name
264 kind
265 (String.concat ", " valid_types))
266 | Some _ -> Ok (ChildType child_name)
268 let children_patterns =
269 List.map children ~f:(fun (child_name, pattern_json) ->
270 get_child_type child_name >>= fun child_name ->
271 compile_pattern ~json:pattern_json ~keytrace:children_keytrace
272 >>| fun pattern ->
273 (child_name, pattern)
276 all children_patterns >>| fun children ->
277 NodePattern {
278 kind = NodeKind kind;
279 children;
282 and compile_match_pattern ~json ~keytrace =
283 get_string "match_name" (json, keytrace)
284 >>| fun (match_name, _match_name_keytrace) ->
285 MatchPattern {
286 match_name = MatchName match_name;
289 and compile_descendant_pattern ~json ~keytrace =
290 get_obj "pattern" (json, keytrace) >>= fun (pattern, pattern_keytrace) ->
291 compile_pattern ~json:pattern ~keytrace:pattern_keytrace >>| fun pattern ->
292 DescendantPattern {
293 pattern;
297 compile_pattern ~json ~keytrace:[]
299 (* TODO(T28496995): This only converts a single result to JSON. We also need to
300 convert an entire response -- a mapping from file path to result -- to JSON. *)
301 let result_to_json (result: result option): Hh_json.json =
302 let open Hh_json in
303 match result with
304 | None -> JSON_Null
305 | Some result ->
306 let matched_nodes = List.map result.matched_nodes ~f:(fun matched_node ->
307 let match_name =
308 match matched_node.match_name
309 with MatchName match_name -> match_name
311 JSON_Object [
312 "match_name", JSON_String match_name;
313 "node", Syntax.to_json matched_node.node;
316 JSON_Object [
317 "matched_nodes", JSON_Array matched_nodes;
320 let search
321 ~(syntax_tree: SyntaxTree.t)
322 (pattern: pattern)
323 : result option =
324 let env = { syntax_tree } in
325 let (_env, result) =
326 search_node ~env ~pattern ~node:(SyntaxTree.root env.syntax_tree) in
327 result
329 let job
330 (acc: (Relative_path.t * result) list)
331 (inputs: (Relative_path.t * pattern) list)
332 : (Relative_path.t * result) list =
333 List.fold inputs
334 ~init:acc
335 ~f:(fun acc (path, pattern) ->
336 let source_text = Full_fidelity_source_text.from_file path in
337 let syntax_tree = SyntaxTree.make source_text in
338 match search ~syntax_tree pattern with
339 | Some result -> (path, result) :: acc
340 | None -> acc
343 let go
344 ~(workers: MultiWorker.worker list option)
345 (files: Relative_path.t list)
346 (input: Hh_json.json)
347 : (Hh_json.json, string) Core_result.t
349 let open Core_result.Monad_infix in
350 compile_pattern input >>| fun pattern ->
351 let inputs = List.map files ~f:(fun path -> (path, pattern)) in
352 let results = MultiWorker.call
353 workers
354 ~job
355 ~neutral:[]
356 ~merge:List.rev_append
357 ~next:(MultiWorker.next workers inputs)
360 Hh_json.JSON_Object (List.map results ~f:(fun (path, result) ->
361 (Relative_path.to_absolute path, result_to_json (Some result))