<commit_before>(*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *)

open Base.Result
open ServerEnv
open Utils_js
open Lsp

let ( >|= ) = Lwt.( >|= )

let type_contents_with_cache ~options ~env ~profiling ~type_contents_cache content file =
  let lazy_result = lazy (Types_js.type_contents ~options ~env ~profiling content file) in
  match type_contents_cache with
  | None -> Lazy.force lazy_result
  | Some cache ->
    let (result, _did_hit) = FilenameCache.with_cache file lazy_result cache in
    result

let status_log errors =
  if Errors.ConcreteLocPrintableErrorSet.is_empty errors then
    Hh_logger.info "Status: OK"
  else
    Hh_logger.info "Status: Error";
  flush stdout

let convert_errors ~errors ~warnings ~suppressed_errors =
  if
    Errors.ConcreteLocPrintableErrorSet.is_empty errors
    && Errors.ConcreteLocPrintableErrorSet.is_empty warnings
    && suppressed_errors = []
  then
    ServerProt.Response.NO_ERRORS
  else
    ServerProt.Response.ERRORS { errors; warnings; suppressed_errors }

let json_of_parse_error =
  let int x = Hh_json.JSON_Number (string_of_int x) in
  let position p = Hh_json.JSON_Object [("line", int p.Loc.line); ("column", int p.Loc.column)] in
  let location loc =
    Hh_json.JSON_Object [("start", position loc.Loc.start); ("end", position loc.Loc._end)]
  in
  fun (loc, err) ->
    Hh_json.JSON_Object
      [("loc", location loc); ("message", Hh_json.JSON_String (Parse_error.PP.error err))]

let fold_json_of_parse_errors parse_errors acc =
  match parse_errors with
  | err :: _ ->
    ("parse_error", json_of_parse_error err)
    :: ("parse_error_count", Hh_json.JSON_Number (parse_errors |> List.length |> string_of_int))
    :: acc
  | [] -> acc

let get_status ~reader genv env client_root =
  let options = genv.ServerEnv.options in
  let server_root = Options.root options in
  let lazy_stats = Rechecker.get_lazy_stats ~options env in
  let status_response =
    if server_root <> client_root then
      ServerProt.Response.DIRECTORY_MISMATCH
        { ServerProt.Response.server = server_root; ServerProt.Response.client = client_root }
    else
      (* collate errors by origin *)
      let (errors, warnings, suppressed_errors) = ErrorCollator.get ~reader ~options env in
      let warnings =
        if Options.should_include_warnings options then
          warnings
        else
          Errors.ConcreteLocPrintableErrorSet.empty
      in
      let suppressed_errors =
        if Options.include_suppressions options then
          suppressed_errors
        else
          []
      in
      (* TODO: check status.directory *)
      status_log errors;
      FlowEventLogger.status_response
        ~num_errors:(Errors.ConcreteLocPrintableErrorSet.cardinal errors);
      convert_errors ~errors ~warnings ~suppressed_errors
  in
  (status_response, lazy_stats)

let autocomplete ~trigger_character ~reader ~options ~env ~profiling ~filename ~contents ~cursor =
  let path =
    match filename with
    | Some filename -> filename
    | None -> "-"
  in
  let path = File_key.SourceFile path in
  let (contents, broader_context) =
    let (line, column) = cursor in
    AutocompleteService_js.add_autocomplete_token contents line column
  in
  Autocomplete_js.autocomplete_set_hooks trigger_character;
  let%lwt check_contents_result = Types_js.type_contents ~options ~env ~profiling contents path in
  Autocomplete_js.autocomplete_unset_hooks ();
  let initial_json_props =
    let open Hh_json in
    [
      ("ac_trigger", JSON_String (Option.value trigger_character ~default:"None"));
      ("broader_context", JSON_String broader_context);
    ]
  in
  match check_contents_result with
  | Error err ->
    let json_data_to_log =
      let open Hh_json in
      JSON_Object
        ( ("errors", JSON_Array [JSON_String err])
        :: ("result", JSON_String "FAILURE_CHECK_CONTENTS")
        :: ("count", JSON_Number "0")
        :: initial_json_props )
    in
    Lwt.return (Error err, Some json_data_to_log)
  | Ok (cx, info, file_sig, tast, parse_errors) ->
    Profiling_js.with_timer_lwt profiling ~timer:"GetResults" ~f:(fun () ->
        try_with_json2 (fun () ->
            let open AutocompleteService_js in
            let (ac_type_string, results_res) =
              let cursor_loc =
                let (line, column) = cursor in
                Loc.make path line column
              in
              autocomplete_get_results ~reader cx file_sig tast trigger_character cursor_loc
            in
            let json_props_to_log =
              ("ac_type", Hh_json.JSON_String ac_type_string)
              :: ("docblock", Docblock.json_of_docblock info)
              :: initial_json_props
            in
            let (response, json_props_to_log) =
              let open Hh_json in
              match results_res with
              | AcResult { results; errors_to_log } ->
                let result_string =
                  match (results, errors_to_log) with
                  | (_, []) -> "SUCCESS"
                  | ([], _ :: _) -> "FAILURE"
                  | (_ :: _, _ :: _) -> "PARTIAL"
                in
                ( Ok results,
                  ("result", JSON_String result_string)
                  :: ("count", JSON_Number (results |> List.length |> string_of_int))
                  :: ("errors", JSON_Array (List.map (fun s -> JSON_String s) errors_to_log))
                  :: json_props_to_log )
              | AcEmpty reason ->
                ( Ok [],
                  ("result", JSON_String "SUCCESS")
                  :: ("count", JSON_Number "0")
                  :: ("empty_reason", JSON_String reason)
                  :: json_props_to_log )
              | AcFatalError error ->
                ( Error error,
                  ("result", JSON_String "FAILURE")
                  :: ("errors", JSON_Array [JSON_String error])
                  :: json_props_to_log )
            in
            let json_props_to_log = fold_json_of_parse_errors parse_errors json_props_to_log in
            Lwt.return (response, Some (Hh_json.JSON_Object json_props_to_log))))

let check_file ~options ~env ~profiling ~force file_input =
  let file = File_input.filename_of_file_input file_input in
  match file_input with
  | File_input.FileName _ -> failwith "Not implemented"
  | File_input.FileContent (_, content) ->
    let should_check =
      if force then
        true
      else
        let (_, docblock) =
          Parsing_service_js.(parse_docblock docblock_max_tokens (File_key.SourceFile file) content)
        in
        Docblock.is_flow docblock
    in
    if should_check then
      let file = File_key.SourceFile file in
      let%lwt (_, errors, warnings) =
        Types_js.typecheck_contents ~options ~env ~profiling content file
      in
      Lwt.return (convert_errors ~errors ~warnings ~suppressed_errors:[])
    else
      Lwt.return ServerProt.Response.NOT_COVERED

let infer_type
    ~(options : Options.t)
    ~(env : ServerEnv.env)
    ~(profiling : Profiling_js.running)
    ~type_contents_cache
    ((file_input, line, col, verbose, expand_aliases, omit_targ_defaults, evaluate_type_destructors) :
      File_input.t * int * int * Verbose.t option * bool * bool * bool) :
    ((Loc.t * Ty.t option, string) result * Hh_json.json option) Lwt.t =
  let file = File_input.filename_of_file_input file_input in
  let file = File_key.SourceFile file in
  let options = { options with Options.opt_verbose = verbose } in
  match File_input.content_of_file_input file_input with
  | Error e -> Lwt.return (Error e, None)
  | Ok content ->
    let%lwt result =
      try_with_json (fun () ->
          let%lwt type_contents_result =
            type_contents_with_cache ~options ~env ~profiling ~type_contents_cache content file
          in
          let result =
            match type_contents_result with
            | Error str -> Error (str, None)
            | Ok (cx, _info, file_sig, typed_ast, _parse_errors) ->
              let (result, json_props) =
                Type_info_service.type_at_pos
                  ~cx
                  ~file_sig
                  ~typed_ast
                  ~expand_aliases
                  ~omit_targ_defaults
                  ~evaluate_type_destructors
                  file
                  line
                  col
              in
              Ok (result, Some (Hh_json.JSON_Object json_props))
          in
          Lwt.return result)
    in
    Lwt.return (split_result result)

let insert_type
    ~options
    ~env
    ~profiling
    ~file_input
    ~target
    ~verbose
    ~expand_aliases
    ~omit_targ_defaults
    ~location_is_strict
    ~ambiguity_strategy =
  let filename = File_input.filename_of_file_input file_input in
  let file_key = File_key.SourceFile filename in
  let options = { options with Options.opt_verbose = verbose } in
  File_input.content_of_file_input file_input %>>= fun file_content ->
  try_with (fun _ ->
      let%lwt result =
        Type_info_service.insert_type
          ~options
          ~env
          ~profiling
          ~file_key
          ~file_content
          ~target
          ~expand_aliases
          ~omit_targ_defaults
          ~location_is_strict
          ~ambiguity_strategy
      in
      Lwt.return result)

let autofix_exports ~options ~env ~profiling ~input =
  let filename = File_input.filename_of_file_input input in
  let file_key = File_key.SourceFile filename in
  File_input.content_of_file_input input %>>= fun file_content ->
  try_with (fun _ ->
      let%lwt result =
        Type_info_service.autofix_exports ~options ~env ~profiling ~file_key ~file_content
      in
      Lwt.return result)

let collect_rage ~options ~reader ~env ~files =
  let items = [] in
  (* options *)
  let data = Printf.sprintf "lazy_mode=%s\n" Options.(lazy_mode options |> lazy_mode_to_string) in
  let items = ("options", data) :: items in
  (* env: checked files *)
  let data =
    Printf.sprintf
      "%s\n\n%s\n"
      (CheckedSet.debug_counts_to_string env.checked_files)
      (CheckedSet.debug_to_string ~limit:200 env.checked_files)
  in
  let items = ("env.checked_files", data) :: items in
  (* env: dependency graph *)
  let dependency_to_string (file, deps) =
    let file = File_key.to_string file in
    let deps =
      Utils_js.FilenameSet.elements deps
      |> Base.List.map ~f:File_key.to_string
      |> ListUtils.first_upto_n 20 (fun t -> Some (Printf.sprintf " ...%d more" t))
      |> String.concat ","
    in
    file ^ ":" ^ deps ^ "\n"
  in
  let dependencies =
    Dependency_info.implementation_dependency_graph env.ServerEnv.dependency_info
    |> Utils_js.FilenameGraph.to_map
    |> Utils_js.FilenameMap.bindings
    |> Base.List.map ~f:dependency_to_string
    |> ListUtils.first_upto_n 200 (fun t -> Some (Printf.sprintf "[shown 200/%d]\n" t))
    |> String.concat ""
  in
  let data = "DEPENDENCIES:\n" ^ dependencies in
  let items = ("env.dependencies", data) :: items in
  (* env: errors *)
  let (errors, warnings, _) = ErrorCollator.get ~reader ~options env in
  let json =
    Errors.Json_output.json_of_errors_with_context
      ~strip_root:None
      ~stdin_file:None
      ~offset_kind:Offset_utils.Utf8
      ~suppressed_errors:[]
      ~errors
      ~warnings
      ()
  in
  let data = "ERRORS:\n" ^ Hh_json.json_to_multiline json in
  let items = ("env.errors", data) :: items in
  (* Checking if file hashes are up to date *)
  let items =
    Option.value_map files ~default:items ~f:(fun files ->
        let buf = Buffer.create 1024 in
        Printf.bprintf
          buf
          "Does the content on the disk match the most recent version of the file?\n\n";
        List.iter
          (fun file ->
            (* TODO - this isn't exactly right. It could be something else, right? *)
            let file_key = File_key.SourceFile file in
            let file_state =
              if not (FilenameSet.mem file_key env.ServerEnv.files) then
                "FILE NOT PARSED BY FLOW (likely ignored implicitly or explicitly)"
              else
                match Sys_utils.cat_or_failed file with
                | None -> "ERROR! FAILED TO READ"
                | Some content ->
                  if Parsing_service_js.does_content_match_file_hash ~reader file_key content then
                    "OK"
                  else
                    "HASH OUT OF DATE"
            in
            Printf.bprintf buf "%s: %s\n" file file_state)
          files;
        ("file hash check", Buffer.contents buf) :: items)
  in
  let items =
    let buf = Buffer.create 127 in
    let log str =
      Buffer.add_string buf str;
      Buffer.add_char buf '\n'
    in
    LoggingUtils.dump_server_options ~server_options:options ~log;
    ("server_options", Buffer.contents buf) :: items
  in
  items

let dump_types ~options ~env ~profiling ~expand_aliases ~evaluate_type_destructors file_input =
  let file = File_input.filename_of_file_input file_input in
  let file = File_key.SourceFile file in
  File_input.content_of_file_input file_input %>>= fun content ->
  try_with (fun () ->
      Type_info_service.dump_types
        ~options
        ~env
        ~profiling
        ~expand_aliases
        ~evaluate_type_destructors
        file
        content)

let coverage ~options ~env ~profiling ~type_contents_cache ~force ~trust file_input =
  if Options.trust_mode options = Options.NoTrust && trust then
    Error
      "Coverage cannot be run in trust mode if the server is not in trust mode. \n\nRestart the Flow server with --trust-mode=check' to enable this command."
    |> Lwt.return
  else
    let file = File_input.filename_of_file_input file_input in
    let file = File_key.SourceFile file in
    File_input.content_of_file_input file_input %>>= fun content ->
    try_with (fun () ->
        type_contents_with_cache ~options ~env ~profiling ~type_contents_cache content file
        >|= Base.Result.map ~f:(fun (cx, _, _, typed_ast, _) ->
                Type_info_service.coverage ~cx ~typed_ast ~force ~trust file content))<commit_msg>Make Lwt operations clearer in `CommandHandler.coverage`<commit_after>(*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *)

open Base.Result
open ServerEnv
open Utils_js
open Lsp

let type_contents_with_cache ~options ~env ~profiling ~type_contents_cache content file =
  let lazy_result = lazy (Types_js.type_contents ~options ~env ~profiling content file) in
  match type_contents_cache with
  | None -> Lazy.force lazy_result
  | Some cache ->
    let (result, _did_hit) = FilenameCache.with_cache file lazy_result cache in
    result

let status_log errors =
  if Errors.ConcreteLocPrintableErrorSet.is_empty errors then
    Hh_logger.info "Status: OK"
  else
    Hh_logger.info "Status: Error";
  flush stdout

let convert_errors ~errors ~warnings ~suppressed_errors =
  if
    Errors.ConcreteLocPrintableErrorSet.is_empty errors
    && Errors.ConcreteLocPrintableErrorSet.is_empty warnings
    && suppressed_errors = []
  then
    ServerProt.Response.NO_ERRORS
  else
    ServerProt.Response.ERRORS { errors; warnings; suppressed_errors }

let json_of_parse_error =
  let int x = Hh_json.JSON_Number (string_of_int x) in
  let position p = Hh_json.JSON_Object [("line", int p.Loc.line); ("column", int p.Loc.column)] in
  let location loc =
    Hh_json.JSON_Object [("start", position loc.Loc.start); ("end", position loc.Loc._end)]
  in
  fun (loc, err) ->
    Hh_json.JSON_Object
      [("loc", location loc); ("message", Hh_json.JSON_String (Parse_error.PP.error err))]

let fold_json_of_parse_errors parse_errors acc =
  match parse_errors with
  | err :: _ ->
    ("parse_error", json_of_parse_error err)
    :: ("parse_error_count", Hh_json.JSON_Number (parse_errors |> List.length |> string_of_int))
    :: acc
  | [] -> acc

let get_status ~reader genv env client_root =
  let options = genv.ServerEnv.options in
  let server_root = Options.root options in
  let lazy_stats = Rechecker.get_lazy_stats ~options env in
  let status_response =
    if server_root <> client_root then
      ServerProt.Response.DIRECTORY_MISMATCH
        { ServerProt.Response.server = server_root; ServerProt.Response.client = client_root }
    else
      (* collate errors by origin *)
      let (errors, warnings, suppressed_errors) = ErrorCollator.get ~reader ~options env in
      let warnings =
        if Options.should_include_warnings options then
          warnings
        else
          Errors.ConcreteLocPrintableErrorSet.empty
      in
      let suppressed_errors =
        if Options.include_suppressions options then
          suppressed_errors
        else
          []
      in
      (* TODO: check status.directory *)
      status_log errors;
      FlowEventLogger.status_response
        ~num_errors:(Errors.ConcreteLocPrintableErrorSet.cardinal errors);
      convert_errors ~errors ~warnings ~suppressed_errors
  in
  (status_response, lazy_stats)

let autocomplete ~trigger_character ~reader ~options ~env ~profiling ~filename ~contents ~cursor =
  let path =
    match filename with
    | Some filename -> filename
    | None -> "-"
  in
  let path = File_key.SourceFile path in
  let (contents, broader_context) =
    let (line, column) = cursor in
    AutocompleteService_js.add_autocomplete_token contents line column
  in
  Autocomplete_js.autocomplete_set_hooks trigger_character;
  let%lwt check_contents_result = Types_js.type_contents ~options ~env ~profiling contents path in
  Autocomplete_js.autocomplete_unset_hooks ();
  let initial_json_props =
    let open Hh_json in
    [
      ("ac_trigger", JSON_String (Option.value trigger_character ~default:"None"));
      ("broader_context", JSON_String broader_context);
    ]
  in
  match check_contents_result with
  | Error err ->
    let json_data_to_log =
      let open Hh_json in
      JSON_Object
        ( ("errors", JSON_Array [JSON_String err])
        :: ("result", JSON_String "FAILURE_CHECK_CONTENTS")
        :: ("count", JSON_Number "0")
        :: initial_json_props )
    in
    Lwt.return (Error err, Some json_data_to_log)
  | Ok (cx, info, file_sig, tast, parse_errors) ->
    Profiling_js.with_timer_lwt profiling ~timer:"GetResults" ~f:(fun () ->
        try_with_json2 (fun () ->
            let open AutocompleteService_js in
            let (ac_type_string, results_res) =
              let cursor_loc =
                let (line, column) = cursor in
                Loc.make path line column
              in
              autocomplete_get_results ~reader cx file_sig tast trigger_character cursor_loc
            in
            let json_props_to_log =
              ("ac_type", Hh_json.JSON_String ac_type_string)
              :: ("docblock", Docblock.json_of_docblock info)
              :: initial_json_props
            in
            let (response, json_props_to_log) =
              let open Hh_json in
              match results_res with
              | AcResult { results; errors_to_log } ->
                let result_string =
                  match (results, errors_to_log) with
                  | (_, []) -> "SUCCESS"
                  | ([], _ :: _) -> "FAILURE"
                  | (_ :: _, _ :: _) -> "PARTIAL"
                in
                ( Ok results,
                  ("result", JSON_String result_string)
                  :: ("count", JSON_Number (results |> List.length |> string_of_int))
                  :: ("errors", JSON_Array (List.map (fun s -> JSON_String s) errors_to_log))
                  :: json_props_to_log )
              | AcEmpty reason ->
                ( Ok [],
                  ("result", JSON_String "SUCCESS")
                  :: ("count", JSON_Number "0")
                  :: ("empty_reason", JSON_String reason)
                  :: json_props_to_log )
              | AcFatalError error ->
                ( Error error,
                  ("result", JSON_String "FAILURE")
                  :: ("errors", JSON_Array [JSON_String error])
                  :: json_props_to_log )
            in
            let json_props_to_log = fold_json_of_parse_errors parse_errors json_props_to_log in
            Lwt.return (response, Some (Hh_json.JSON_Object json_props_to_log))))

let check_file ~options ~env ~profiling ~force file_input =
  let file = File_input.filename_of_file_input file_input in
  match file_input with
  | File_input.FileName _ -> failwith "Not implemented"
  | File_input.FileContent (_, content) ->
    let should_check =
      if force then
        true
      else
        let (_, docblock) =
          Parsing_service_js.(parse_docblock docblock_max_tokens (File_key.SourceFile file) content)
        in
        Docblock.is_flow docblock
    in
    if should_check then
      let file = File_key.SourceFile file in
      let%lwt (_, errors, warnings) =
        Types_js.typecheck_contents ~options ~env ~profiling content file
      in
      Lwt.return (convert_errors ~errors ~warnings ~suppressed_errors:[])
    else
      Lwt.return ServerProt.Response.NOT_COVERED

let infer_type
    ~(options : Options.t)
    ~(env : ServerEnv.env)
    ~(profiling : Profiling_js.running)
    ~type_contents_cache
    ((file_input, line, col, verbose, expand_aliases, omit_targ_defaults, evaluate_type_destructors) :
      File_input.t * int * int * Verbose.t option * bool * bool * bool) :
    ((Loc.t * Ty.t option, string) result * Hh_json.json option) Lwt.t =
  let file = File_input.filename_of_file_input file_input in
  let file = File_key.SourceFile file in
  let options = { options with Options.opt_verbose = verbose } in
  match File_input.content_of_file_input file_input with
  | Error e -> Lwt.return (Error e, None)
  | Ok content ->
    let%lwt result =
      try_with_json (fun () ->
          let%lwt type_contents_result =
            type_contents_with_cache ~options ~env ~profiling ~type_contents_cache content file
          in
          let result =
            match type_contents_result with
            | Error str -> Error (str, None)
            | Ok (cx, _info, file_sig, typed_ast, _parse_errors) ->
              let (result, json_props) =
                Type_info_service.type_at_pos
                  ~cx
                  ~file_sig
                  ~typed_ast
                  ~expand_aliases
                  ~omit_targ_defaults
                  ~evaluate_type_destructors
                  file
                  line
                  col
              in
              Ok (result, Some (Hh_json.JSON_Object json_props))
          in
          Lwt.return result)
    in
    Lwt.return (split_result result)

let insert_type
    ~options
    ~env
    ~profiling
    ~file_input
    ~target
    ~verbose
    ~expand_aliases
    ~omit_targ_defaults
    ~location_is_strict
    ~ambiguity_strategy =
  let filename = File_input.filename_of_file_input file_input in
  let file_key = File_key.SourceFile filename in
  let options = { options with Options.opt_verbose = verbose } in
  File_input.content_of_file_input file_input %>>= fun file_content ->
  try_with (fun _ ->
      let%lwt result =
        Type_info_service.insert_type
          ~options
          ~env
          ~profiling
          ~file_key
          ~file_content
          ~target
          ~expand_aliases
          ~omit_targ_defaults
          ~location_is_strict
          ~ambiguity_strategy
      in
      Lwt.return result)

let autofix_exports ~options ~env ~profiling ~input =
  let filename = File_input.filename_of_file_input input in
  let file_key = File_key.SourceFile filename in
  File_input.content_of_file_input input %>>= fun file_content ->
  try_with (fun _ ->
      let%lwt result =
        Type_info_service.autofix_exports ~options ~env ~profiling ~file_key ~file_content
      in
      Lwt.return result)

let collect_rage ~options ~reader ~env ~files =
  let items = [] in
  (* options *)
  let data = Printf.sprintf "lazy_mode=%s\n" Options.(lazy_mode options |> lazy_mode_to_string) in
  let items = ("options", data) :: items in
  (* env: checked files *)
  let data =
    Printf.sprintf
      "%s\n\n%s\n"
      (CheckedSet.debug_counts_to_string env.checked_files)
      (CheckedSet.debug_to_string ~limit:200 env.checked_files)
  in
  let items = ("env.checked_files", data) :: items in
  (* env: dependency graph *)
  let dependency_to_string (file, deps) =
    let file = File_key.to_string file in
    let deps =
      Utils_js.FilenameSet.elements deps
      |> Base.List.map ~f:File_key.to_string
      |> ListUtils.first_upto_n 20 (fun t -> Some (Printf.sprintf " ...%d more" t))
      |> String.concat ","
    in
    file ^ ":" ^ deps ^ "\n"
  in
  let dependencies =
    Dependency_info.implementation_dependency_graph env.ServerEnv.dependency_info
    |> Utils_js.FilenameGraph.to_map
    |> Utils_js.FilenameMap.bindings
    |> Base.List.map ~f:dependency_to_string
    |> ListUtils.first_upto_n 200 (fun t -> Some (Printf.sprintf "[shown 200/%d]\n" t))
    |> String.concat ""
  in
  let data = "DEPENDENCIES:\n" ^ dependencies in
  let items = ("env.dependencies", data) :: items in
  (* env: errors *)
  let (errors, warnings, _) = ErrorCollator.get ~reader ~options env in
  let json =
    Errors.Json_output.json_of_errors_with_context
      ~strip_root:None
      ~stdin_file:None
      ~offset_kind:Offset_utils.Utf8
      ~suppressed_errors:[]
      ~errors
      ~warnings
      ()
  in
  let data = "ERRORS:\n" ^ Hh_json.json_to_multiline json in
  let items = ("env.errors", data) :: items in
  (* Checking if file hashes are up to date *)
  let items =
    Option.value_map files ~default:items ~f:(fun files ->
        let buf = Buffer.create 1024 in
        Printf.bprintf
          buf
          "Does the content on the disk match the most recent version of the file?\n\n";
        List.iter
          (fun file ->
            (* TODO - this isn't exactly right. It could be something else, right? *)
            let file_key = File_key.SourceFile file in
            let file_state =
              if not (FilenameSet.mem file_key env.ServerEnv.files) then
                "FILE NOT PARSED BY FLOW (likely ignored implicitly or explicitly)"
              else
                match Sys_utils.cat_or_failed file with
                | None -> "ERROR! FAILED TO READ"
                | Some content ->
                  if Parsing_service_js.does_content_match_file_hash ~reader file_key content then
                    "OK"
                  else
                    "HASH OUT OF DATE"
            in
            Printf.bprintf buf "%s: %s\n" file file_state)
          files;
        ("file hash check", Buffer.contents buf) :: items)
  in
  let items =
    let buf = Buffer.create 127 in
    let log str =
      Buffer.add_string buf str;
      Buffer.add_char buf '\n'
    in
    LoggingUtils.dump_server_options ~server_options:options ~log;
    ("server_options", Buffer.contents buf) :: items
  in
  items

let dump_types ~options ~env ~profiling ~expand_aliases ~evaluate_type_destructors file_input =
  let file = File_input.filename_of_file_input file_input in
  let file = File_key.SourceFile file in
  File_input.content_of_file_input file_input %>>= fun content ->
  try_with (fun () ->
      Type_info_service.dump_types
        ~options
        ~env
        ~profiling
        ~expand_aliases
        ~evaluate_type_destructors
        file
        content)

let coverage ~options ~env ~profiling ~type_contents_cache ~force ~trust file_input =
  if Options.trust_mode options = Options.NoTrust && trust then
    Error
      "Coverage cannot be run in trust mode if the server is not in trust mode. \n\nRestart the Flow server with --trust-mode=check' to enable this command."
    |> Lwt.return
  else
    let file = File_input.filename_of_file_input file_input in
    let file = File_key.SourceFile file in
    File_input.content_of_file_input file_input %>>= fun content ->
    try_with (fun () ->
        let%lwt type_contents_result =
          type_contents_with_cache ~options ~env ~profiling ~type_contents_cache content file
        in
        let result =
          Base.Result.map type_contents_result ~f:(fun (cx, _, _, typed_ast, _) ->
              Type_info_service.coverage ~cx ~typed_ast ~force ~trust file content)
        in
        Lwt.return result)
{'fraction_non_alphanumeric': 0.08597332586550915, 'fraction_numerical': 0.0015402387370042356, 'mean_word_length': 2.457758412006778, 'pattern_counts': {'":': 2, '<': 5, '<?xml version=': 0, '>': 132, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6072194', 'n_tokens_mistral': 8794, 'n_tokens_neox': 8508, 'n_words': 2257}