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)