kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/ocaml/js_indexer/dumpKytheService_js.ml (about) 1 (** 2 * Copyright 2015 The Kythe Authors. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 *) 16 17 (***********************************************************************) 18 (* flow server-side implementation for the dump-kythe command *) 19 (***********************************************************************) 20 21 open Constraint_js 22 open Hh_json 23 open Spider_monkey_ast 24 open Utils 25 26 module TI = Type_inference_js 27 28 type vname = { 29 signature: string; 30 path: string; 31 language : string; 32 root: string; 33 corpus: string; 34 } 35 let null_vname = 36 { signature = ""; path = ""; language = ""; root = ""; corpus = "" } 37 let js_vname = 38 { signature = ""; path = ""; language = "js"; root = ""; corpus = "" } 39 40 (* A Kythe entry: source, edge, destination, fact name, fact value. *) 41 type entry = (vname * string * vname option * string * string) 42 43 let json_of_vname vname = 44 JAssoc [ 45 ("signature", JString vname.signature); 46 ("path", JString vname.path); 47 ("corpus", JString vname.corpus); 48 ("root", JString vname.root); 49 ("language", JString vname.language) 50 ] 51 52 let json_of_entry (src, edge, tgt, fname, fval) = 53 let assocs = [ 54 ("source", json_of_vname src); 55 ("edge_kind", JString edge); 56 ("fact_name", JString fname); 57 ("fact_value", JString (B64.encode fval)) 58 ] in 59 match tgt with 60 | None -> JAssoc assocs 61 | Some v -> JAssoc (("target", json_of_vname v) :: assocs) 62 63 let fact node fact_name fact_val = 64 (node, "", None, "/kythe/" ^ fact_name, fact_val) 65 let edge src edge_name tgt = 66 (src, "/kythe/edge/" ^ edge_name, Some tgt, "/", "") 67 let edge_p src edge_name tgt p = 68 (src, "/kythe/edge/" ^ edge_name, Some tgt, "/", p) 69 70 (* We assume for now that every file is a member of some npm module. 71 * We'll use that module's name as the corpus. (Note that the current module 72 * is available from the context using Module_js.info_of) *) 73 let vname_of_path path = 74 let info = Module_js.get_module_info path in 75 { signature = ""; path = path; language = "js"; root = ""; 76 corpus = info.Module_js._module } 77 78 (* If p is valid, return a tuple of p's file's VName and the VName of the 79 * anchor covering p. *) 80 let path_anchor_vname (p:Loc.t) = 81 match p.Loc.source with 82 | None -> None 83 | Some path -> 84 let path_vname = vname_of_path path in 85 (* We use line and col here because there seems to be a bug inside Flow that 86 * causes it to drop pos_bol (and internally, offsets in Loc are calculated 87 * starting from line/col/bol in Pos anyhow). *) 88 let start_l = p.Loc.start.Loc.line in 89 let _end_l = p.Loc._end.Loc.line in 90 let start_c = p.Loc.start.Loc.column in 91 let _end_c = p.Loc._end.Loc.column in 92 let new_sig = (Printf.sprintf "%d-%d-%d-%d%s" 93 start_l start_c _end_l _end_c path_vname.signature) in 94 let vname = {path_vname with signature = new_sig} in 95 Some (path_vname, vname) 96 97 (* If p is invalid, return (es, None); otherwise, return the entries 98 * establishing an anchor node at p prepended to es and that anchor node's 99 * VName. *) 100 let anchor (p:Loc.t) es = 101 match path_anchor_vname p with 102 | None -> (es, None) 103 | Some (path_vname, vname) -> 104 let start = p.Loc.start.Loc.offset in 105 let _end = p.Loc._end.Loc.offset in 106 (json_of_entry (fact vname "node/kind" "anchor") :: 107 json_of_entry (fact vname "loc/start" (string_of_int start)) :: 108 json_of_entry (fact vname "loc/end" (string_of_int _end)) :: 109 json_of_entry (edge vname "childof" path_vname) :: es, Some vname) 110 111 (* A use site that we were notified about by Flow. Since we don't have access 112 * to scope information after typechecking is complete, we rely on callbacks 113 * to fill out a table of mappings from reference locations to referents. 114 * A discovered_ref is a referent that we've been told about, but that may 115 * not yet at that time be fully constrained. *) 116 type discovered_ref = 117 (* The referent was defined at a simple source location (e.g., a variable 118 * definition). *) 119 | DrLoc of string * Spider_monkey_ast.Loc.t 120 (* The referent was a string identifier key in some given `this` type; 121 * it was discovered as part of a function call. *) 122 | DrCall of string * Constraint_js.Type.t 123 (* The referent was a string identifier key in some given `this` type; 124 * it was discovered as part of member access. *) 125 | DrMember of string * Constraint_js.Type.t 126 127 (* Called by Flow when the typechecker discovers a call at loc to a key 128 * called name in type `this`. *) 129 let call_hook ref_table cxt name loc this = 130 Hashtbl.add ref_table loc (DrCall (name, this)) 131 132 (* Called by Flow when the typechecker discovers a reference at loc to a 133 * key called name in type `this`. *) 134 let member_hook ref_table cxt name loc this = 135 Hashtbl.add ref_table loc (DrMember (name, this)); false 136 137 (* Called by Flow when the typechecker discovers a reference at loc to an 138 * identifier called name. This hook looks in the current scope to get the 139 * definition location of loc and insert it into ref_table, where this is 140 * possible. *) 141 let id_hook ref_table cxt name loc = 142 let env = Env_js.all_entries () in 143 match Utils.SMap.get name env with 144 | Some {Scope.def_loc;_} -> ( 145 match def_loc with 146 | Some loc' -> ( 147 Hashtbl.add ref_table loc (DrLoc (name, loc')); 148 false 149 ) 150 | None -> false 151 ) 152 | None -> false 153 154 (* A resolved reference: its identifier, use location, and def location. *) 155 type finalized_ref = (string * Pos.t * Pos.t) 156 157 (* Builds a finalized_ref from a context, use location, and discovered_ref. *) 158 let finalize_ref cx loc kind = 159 match kind with 160 | DrLoc (v, loc') -> Some (v, loc, loc') 161 | DrCall (name, this) 162 | DrMember (name, this) -> 163 let this_t = Flow_js.resolve_type cx this in 164 try let result_map = Flow_js.extract_members cx this_t 165 in match Utils.SMap.get name result_map with 166 | Some t 167 (* loc_of_t t points to the initializing expression *) 168 -> Some (name, loc, Constraint_js.loc_of_t t) 169 | None -> None 170 with Not_found -> None 171 172 (* Prepends all the entries belonging to the file node for path to es. *) 173 let file_entries_of_path path es = 174 let file_in = open_in path in 175 let file_len = in_channel_length file_in in 176 let file_content = String.create file_len in 177 really_input file_in file_content 0 file_len; 178 close_in file_in; 179 json_of_entry (fact (vname_of_path path) "text" file_content) :: 180 json_of_entry (fact (vname_of_path path) "node/kind" "file") :: 181 es 182 183 (* Dumps Flow's information for path using the context cx and the provided 184 * reference table. *) 185 let dump_xrefs path cx ref_table = 186 let es = file_entries_of_path path [] in 187 Hashtbl.fold (fun k v es -> 188 match finalize_ref cx k v with 189 | None -> es 190 | Some (name, use_loc, def_loc) -> 191 let (es, use_vname') = anchor use_loc es in 192 let (es, def_vname') = anchor def_loc es in 193 match (use_vname', def_vname') with 194 | (Some use_vname, Some def_vname) -> 195 let def_tgt = 196 { def_vname with signature = name ^ "#" ^ def_vname.signature } in 197 json_of_entry (edge use_vname "ref" def_tgt) :: 198 json_of_entry (edge def_vname "defines" def_tgt) :: 199 json_of_entry (fact def_tgt "node/kind" "js/todo") :: es 200 (* Ignore those cases where the locations are invalid. *) 201 | _ -> es 202 ) ref_table es 203 204 (* RPC response to the query: a list of errors and a list of JSON entries. *) 205 type resp_t = (Pos.t * string) option * Hh_json.json list option 206 207 let mk_pos file line col = 208 { 209 Pos. 210 pos_file = Relative_path.create Relative_path.Dummy file; 211 pos_start = Reason_js.lexpos file line col; 212 pos_end = Reason_js.lexpos file line (col+1); 213 } 214 215 (* Emit Flow's inferred data about file_input as JSON-encoded Kythe entries. *) 216 let query file_input : resp_t = 217 let table : (Spider_monkey_ast.Loc.t, discovered_ref) Hashtbl.t = 218 Hashtbl.create 0 in 219 Type_inference_hooks_js.set_id_hook (id_hook table); 220 Type_inference_hooks_js.set_member_hook (member_hook table); 221 Type_inference_hooks_js.set_call_hook (call_hook table); 222 let file = ServerProt.file_input_get_filename file_input in 223 try 224 let cx = match file_input with 225 | ServerProt.FileName file -> 226 let content = ServerProt.file_input_get_content file_input in 227 (match Types_js.typecheck_contents content file with 228 | Some cx, _ -> cx 229 | _, errors -> failwith "Couldn't parse file") 230 | ServerProt.FileContent (_, content) -> 231 (match Types_js.typecheck_contents content file with 232 | Some cx, _ -> cx 233 | _, errors -> failwith "Couldn't parse file") in 234 Type_inference_hooks_js.reset_hooks(); 235 (None, Some (dump_xrefs file cx table)) 236 with exn -> 237 let pos = mk_pos file 0 0 in 238 let err = (pos, Printexc.to_string exn) in 239 (Some err, None)