From fd06398a2073bcb792012044f69c7731e5449c88 Mon Sep 17 00:00:00 2001 From: Sam Zhou Date: Tue, 2 Apr 2024 15:30:30 -0700 Subject: [PATCH] [flow][try] Signature help Summary: Changelog: [website] We now provide signature help service in try-flow Reviewed By: panagosg7 Differential Revision: D55652340 fbshipit-source-id: f7e51ff8953abdb1c266729b8cce43a1c4155ddf --- src/flow_dot_js.ml | 94 +++++++++++++++++++++++ website/libs/flow_js.js | 7 ++ website/src/try-flow/TryFlow.js | 2 + website/src/try-flow/configured-monaco.js | 27 +++++++ website/src/try-flow/flow-services.js | 9 +++ 5 files changed, 139 insertions(+) diff --git a/src/flow_dot_js.ml b/src/flow_dot_js.ml index f4b97dfbef7..e77fe029570 100644 --- a/src/flow_dot_js.ml +++ b/src/flow_dot_js.ml @@ -491,6 +491,40 @@ let infer_type filename content line col js_config_object : Loc.t * (string, str (loc, Ok (Ty_printer.string_of_type_at_pos_result ~exact_by_default:true result)) end +let signature_help filename content line col js_config_object : + ((ServerProt.Response.func_details_result list * int) option, string) result = + let filename = File_key.SourceFile filename in + let root = File_path.dummy_path in + match parse_content filename content with + | Error _ -> Error "parse error" + | Ok (ast, file_sig) -> + let (_, docblock) = + Docblock_parser.( + parse_docblock + ~max_tokens:docblock_max_tokens + ~file_options:Files.default_options + filename + content + ) + in + let (cx, typed_ast) = infer_and_merge ~root filename js_config_object docblock ast file_sig in + let cursor_loc = mk_loc filename line col in + let func_details = + Signature_help.find_signatures + ~loc_of_aloc + ~get_ast_from_shared_mem:(fun _ -> None) + ~cx + ~file_sig + ~ast + ~typed_ast + cursor_loc + in + begin + match func_details with + | Ok details -> Ok details + | Error _ -> Error "Failed to normalize type" + end + let types_to_json types ~strip_root = Hh_json.( Reason.( @@ -607,6 +641,46 @@ let completion_item_to_json in JSON_Object props +let signature_to_json { ServerProt.Response.func_documentation; param_tys; return_ty } = + let open Hh_json in + let open Utils_js in + let documentation_props = function + | None -> [] + | Some doc -> [("documentation", JSON_Object [("value", JSON_String doc)])] + in + let props = documentation_props func_documentation in + let props = + ( "parameters", + JSON_Array + (Base.List.map + param_tys + ~f:(fun { ServerProt.Response.param_documentation; param_name; param_ty } -> + JSON_Object + (("label", JSON_String (spf "%s: %s" param_name param_ty)) + :: documentation_props param_documentation + ) + ) + ) + ) + :: props + in + let props = + let sig_str = + Utils_js.spf + "(%s): %s" + (Base.List.map + param_tys + ~f:(fun { ServerProt.Response.param_documentation = _; param_name; param_ty } -> + spf "%s: %s" param_name param_ty + ) + |> Base.String.concat ~sep:", " + ) + return_ty + in + ("label", JSON_String sig_str) :: props + in + JSON_Object props + let autocomplete js_file js_content js_line js_col js_config_object = let filename = Js.to_string js_file in let content = Js.to_string js_content in @@ -634,6 +708,24 @@ let get_def js_file js_content js_line js_col js_config_object = |> js_of_json | Error msg -> failwith msg +let signature_help js_file js_content js_line js_col js_config_object = + let filename = Js.to_string js_file in + let content = Js.to_string js_content in + let line = Js.parseInt js_line in + let col = Js.parseInt js_col in + match signature_help filename content line col js_config_object with + | Error msg -> failwith msg + | Ok None -> Js.Unsafe.inject Js.null + | Ok (Some (signatures, n)) -> + let open Hh_json in + JSON_Object + [ + ("signatures", JSON_Array (Base.List.map signatures ~f:signature_to_json)); + ("activeParameter", JSON_Number (string_of_int n)); + ("activeSignature", JSON_Number "0"); + ] + |> js_of_json + let type_at_pos js_file js_content js_line js_col js_config_object = let filename = Js.to_string js_file in let content = Js.to_string js_content in @@ -790,4 +882,6 @@ let () = Js.Unsafe.set exports "autocomplete" (Js.wrap_callback autocomplete) let () = Js.Unsafe.set exports "getDef" (Js.wrap_callback get_def) +let () = Js.Unsafe.set exports "signatureHelp" (Js.wrap_callback signature_help) + let () = Js.Unsafe.set exports "typeAtPos" (Js.wrap_callback type_at_pos) diff --git a/website/libs/flow_js.js b/website/libs/flow_js.js index 66cc1a22270..b69d8fa368d 100644 --- a/website/libs/flow_js.js +++ b/website/libs/flow_js.js @@ -90,5 +90,12 @@ declare type FlowJs = { col: number, options: {[string]: mixed}, ): string, + signatureHelp( + filename: string, + body: string, + line: number, + col: number, + options: {[string]: mixed}, + ): any, parse(body: string, options: FlowJsParseOptions): interface {}, }; diff --git a/website/src/try-flow/TryFlow.js b/website/src/try-flow/TryFlow.js index 9b9e2651c56..88483b3a92d 100644 --- a/website/src/try-flow/TryFlow.js +++ b/website/src/try-flow/TryFlow.js @@ -20,6 +20,7 @@ import { setAutoCompleteFunction, setGetDefFunction, setTypeAtPosFunction, + setSignatureHelpFunction, } from './configured-monaco'; import FlowJsServices from './flow-services'; import createTokensProvider from './tokens-theme-provider'; @@ -159,6 +160,7 @@ export default component TryFlow( setAutoCompleteFunction(flowService); setGetDefFunction(flowService); setTypeAtPosFunction(flowService); + setSignatureHelpFunction(flowService); const model = monaco.editor.getModels()[0]; if (model == null || flowService == null) return; diff --git a/website/src/try-flow/configured-monaco.js b/website/src/try-flow/configured-monaco.js index 6d45f9be556..99cf319ffa6 100644 --- a/website/src/try-flow/configured-monaco.js +++ b/website/src/try-flow/configured-monaco.js @@ -60,6 +60,19 @@ function setTypeAtPosFunction(flowService: ?FlowJsServices): void { ); } +let signatureHelpFunctionForMonaco = (value: string, position: Position): any => + null; + +function setSignatureHelpFunction(flowService: ?FlowJsServices): void { + signatureHelpFunctionForMonaco = (value, position) => + flowService?.signatureHelp( + '-', + value, + position.lineNumber, + position.column - 1, + ); +} + monaco.languages.register({ id: 'flow', extensions: ['.js', '.flow'], @@ -180,6 +193,19 @@ monaco.languages.registerHoverProvider('flow', { }; }, }); +monaco.languages.registerSignatureHelpProvider('flow', { + signatureHelpTriggerCharacters: ['(', ','], + provideSignatureHelp(model, position) { + try { + const result = signatureHelpFunctionForMonaco(model.getValue(), position); + if (result == null) return null; + return {value: result, dispose() {}}; + } catch (e) { + console.error(e); + return null; + } + }, +}); loader.config({monaco}); export { @@ -187,4 +213,5 @@ export { setAutoCompleteFunction, setGetDefFunction, setTypeAtPosFunction, + setSignatureHelpFunction, }; diff --git a/website/src/try-flow/flow-services.js b/website/src/try-flow/flow-services.js index ecb34d6cb91..03f9b549a02 100644 --- a/website/src/try-flow/flow-services.js +++ b/website/src/try-flow/flow-services.js @@ -99,6 +99,15 @@ export default class FlowJsServices { return this._flow.typeAtPos(filename, body, line, col, this.config); } + signatureHelp( + filename: string, + body: string, + line: number, + col: number, + ): any { + return this._flow.signatureHelp(filename, body, line, col, this.config); + } + parseAstToJsonString(body: string): interface {} { return this._flow.parse(body, PARSE_OPTIONS); }