From 4c4a9a939ddb3197b750fe599b3019e465f09929 Mon Sep 17 00:00:00 2001 From: Sam Zhou Date: Thu, 28 Mar 2024 17:36:09 -0700 Subject: [PATCH] [try-flow] Get-def support in try-flow Summary: Changelog: [try-flow] Try flow now supports go-to-definition language service Reviewed By: panagosg7 Differential Revision: D55489133 fbshipit-source-id: 18eb6bf58c77578b39159df7f803070833fe1583 --- src/__tests__/flow_dot_js_smoke_test.js | 24 ++++++++++++ src/dune | 1 + src/flow_dot_js.ml | 47 +++++++++++++++++++++++ src/services/get_def/dune | 7 +--- src/services/get_def/getDefUtils.ml | 4 +- src/services/get_def/getDefUtils.mli | 2 +- src/services/references/findRefs_js.ml | 2 +- website/libs/flow_js.js | 19 ++++++--- website/src/try-flow/TryFlow.js | 7 +++- website/src/try-flow/configured-monaco.js | 41 ++++++++++++++++++-- website/src/try-flow/flow-services.js | 9 +++++ 11 files changed, 145 insertions(+), 18 deletions(-) diff --git a/src/__tests__/flow_dot_js_smoke_test.js b/src/__tests__/flow_dot_js_smoke_test.js index 210c4e90198..0fed06d93a4 100644 --- a/src/__tests__/flow_dot_js_smoke_test.js +++ b/src/__tests__/flow_dot_js_smoke_test.js @@ -46,3 +46,27 @@ function Foo(x: string) {} ) { throw 'There should be no errors if jsx pragma is correctly parsed.'; } + +if ( + JSON.stringify( + flow.getDef('test.js', 'const foo = 1;\nfoo', 2, 1, config), + undefined, + 2, + ) !== + `[ + { + "source": "test.js", + "type": "SourceFile", + "start": { + "line": 1, + "column": 7 + }, + "end": { + "line": 1, + "column": 9 + } + } +]` +) { + throw 'Incorrect get-def result'; +} diff --git a/src/dune b/src/dune index 8b4af03e215..5774dc1f2fc 100644 --- a/src/dune +++ b/src/dune @@ -29,6 +29,7 @@ flow_parser_js flow_parser_utils_aloc flow_parsing_docblock + flow_service_get_def flow_typing flow_typing_errors js_of_ocaml) diff --git a/src/flow_dot_js.ml b/src/flow_dot_js.ml index a5a1b897355..af29da3209d 100644 --- a/src/flow_dot_js.ml +++ b/src/flow_dot_js.ml @@ -351,6 +351,40 @@ let mk_loc file line col = _end = { Loc.line; column = col + 1 }; } +let get_def filename content line col js_config_object : (Loc.t list, 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 loc = mk_loc filename line col in + (match + GetDef_js.get_def + ~loc_of_aloc:ALoc.to_loc_exn + ~cx + ~file_sig + ~ast + ~available_ast:(Typed_ast_utils.Typed_ast typed_ast) + ~purpose:Get_def_types.Purpose.GoToDefinition + loc + with + | GetDef_js.Get_def_result.Def (locs, _) + | GetDef_js.Get_def_result.Partial (locs, _, _) -> + Ok (Loc_collections.LocSet.elements locs) + | GetDef_js.Get_def_result.Bad_loc err_msg + | GetDef_js.Get_def_result.Def_error err_msg -> + Error err_msg) + let infer_type filename content line col js_config_object : Loc.t * (string, string) result = let filename = File_key.SourceFile filename in let root = File_path.dummy_path in @@ -437,6 +471,17 @@ let dump_types js_file js_content js_config_object = let types_json = types_to_json types ~strip_root in js_of_json types_json +let get_def 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 get_def filename content line col js_config_object with + | Ok locs -> + Hh_json.JSON_Array (List.map (Reason.json_of_loc ~strip_root:None ~offset_table:None) locs) + |> js_of_json + | Error msg -> failwith msg + 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 @@ -589,4 +634,6 @@ let () = Js.Unsafe.set exports "flowVersion" (Js.string Flow_version.version) let () = Js.Unsafe.set exports "parse" (Js.wrap_callback Flow_parser_js.parse) +let () = Js.Unsafe.set exports "getDef" (Js.wrap_callback get_def) + let () = Js.Unsafe.set exports "typeAtPos" (Js.wrap_callback type_at_pos) diff --git a/src/services/get_def/dune b/src/services/get_def/dune index e2f40db61b4..6eccce8b185 100644 --- a/src/services/get_def/dune +++ b/src/services/get_def/dune @@ -3,10 +3,7 @@ (wrapped false) (libraries flow_parser_utils - flow_procs - flow_server_env flow_service_get_def_types - flow_service_inference_module - flow_state_readers) + flow_types) (preprocess - (pps lwt_ppx ppx_deriving.show))) + (pps ppx_deriving.show))) diff --git a/src/services/get_def/getDefUtils.ml b/src/services/get_def/getDefUtils.ml index 916b6037b64..85fef959970 100644 --- a/src/services/get_def/getDefUtils.ml +++ b/src/services/get_def/getDefUtils.ml @@ -543,9 +543,9 @@ let get_property_def_info ~loc_of_aloc type_info loc : (property_def_info option ~default:(Ok None) def_kind -let get_def_info ~reader ~purpose (ast, file_sig, _) type_info loc : (def_info, string) result = +let get_def_info ~loc_of_aloc ~purpose (ast, file_sig, _) type_info loc : (def_info, string) result + = let (Types_js_types.Typecheck_artifacts { cx; typed_ast; obj_to_obj_map = _ }) = type_info in - let loc_of_aloc = Parsing_heaps.Reader.loc_of_aloc ~reader in match get_property_def_info ~loc_of_aloc type_info loc with | Error error -> Error error | Ok (Some props_info) -> Ok (PropertyDefinition props_info) diff --git a/src/services/get_def/getDefUtils.mli b/src/services/get_def/getDefUtils.mli index b12b1526f75..3ab995c5159 100644 --- a/src/services/get_def/getDefUtils.mli +++ b/src/services/get_def/getDefUtils.mli @@ -31,7 +31,7 @@ val get_property_def_info : (property_def_info option, string) result val get_def_info : - reader:Parsing_heaps.Reader.reader -> + loc_of_aloc:(ALoc.t -> Loc.t) -> purpose:Purpose.t -> FindRefsUtils.ast_info -> Types_js_types.typecheck_artifacts -> diff --git a/src/services/references/findRefs_js.ml b/src/services/references/findRefs_js.ml index 2e0faaca8a8..98bc0fed63a 100644 --- a/src/services/references/findRefs_js.ml +++ b/src/services/references/findRefs_js.ml @@ -76,7 +76,7 @@ let find_local_refs ~reader ~file_key ~parse_artifacts ~typecheck_artifacts ~kin in let%bind def_info = GetDefUtils.get_def_info - ~reader + ~loc_of_aloc:(Parsing_heaps.Reader.loc_of_aloc ~reader) ~purpose:Get_def_types.Purpose.FindReferences ast_info typecheck_artifacts diff --git a/website/libs/flow_js.js b/website/libs/flow_js.js index 60dfa47e647..c6ded012a2a 100644 --- a/website/libs/flow_js.js +++ b/website/libs/flow_js.js @@ -7,12 +7,14 @@ * @format */ +declare type FlowLoc = { + source: string, + start: {line: number, column: number}, + end: {line: number, column: number}, +}; + declare type FlowJsErrorMessage = { - loc: { - source: string, - start: {line: number, column: number}, - end: {line: number, column: number}, - }, + loc: FlowLoc, context: string, type: string, descr: string, @@ -67,6 +69,13 @@ declare type FlowJs = { body: string, options: {[string]: mixed}, ): $ReadOnlyArray, + getDef( + filename: string, + body: string, + line: number, + col: number, + options: {[string]: mixed}, + ): $ReadOnlyArray, typeAtPos( filename: string, body: string, diff --git a/website/src/try-flow/TryFlow.js b/website/src/try-flow/TryFlow.js index 2d2dad70abe..89a43528e98 100644 --- a/website/src/try-flow/TryFlow.js +++ b/website/src/try-flow/TryFlow.js @@ -15,7 +15,11 @@ import * as LZString from 'lz-string'; import styles from './TryFlow.module.css'; import TryFlowConfigEditor from './TryFlowConfigEditor'; import TryFlowResults from './TryFlowResults'; -import {monaco, setTypeAtPosFunction} from './configured-monaco'; +import { + monaco, + setGetDefFunction, + setTypeAtPosFunction, +} from './configured-monaco'; import FlowJsServices from './flow-services'; import createTokensProvider from './tokens-theme-provider'; import flowLanguageConfiguration from './flow-configuration.json'; @@ -151,6 +155,7 @@ export default component TryFlow( } function forceRecheck() { + setGetDefFunction(flowService); setTypeAtPosFunction(flowService); const model = monaco.editor.getModels()[0]; diff --git a/website/src/try-flow/configured-monaco.js b/website/src/try-flow/configured-monaco.js index fc826a42f74..8212234b8c7 100644 --- a/website/src/try-flow/configured-monaco.js +++ b/website/src/try-flow/configured-monaco.js @@ -13,11 +13,28 @@ import type FlowJsServices from './flow-services'; import createTokensProvider from './tokens-theme-provider'; import flowLanguageConfiguration from './flow-configuration.json'; -let typeAsPosFunctionForMonaco = (value: string, position: any): ?string => +type Position = {lineNumber: number, column: number}; + +let getDefFunctionForMonaco = ( + value: string, + position: Position, +): $ReadOnlyArray => []; + +function setGetDefFunction(flowService: ?FlowJsServices): void { + getDefFunctionForMonaco = (value, position) => + flowService?.getDef?.( + '-', + value, + position.lineNumber, + position.column - 1, + ) ?? []; +} + +let typeAsPosFunctionForMonaco = (value: string, position: Position): ?string => null; function setTypeAtPosFunction(flowService: ?FlowJsServices): void { - typeAsPosFunctionForMonaco = (value: string, position) => + typeAsPosFunctionForMonaco = (value, position) => flowService?.typeAtPos( '-', value, @@ -34,6 +51,24 @@ monaco.languages.register({ monaco.languages.setLanguageConfiguration('flow', flowLanguageConfiguration); const languageId = monaco.languages.getEncodedLanguageId('flow'); monaco.languages.setTokensProvider('flow', createTokensProvider(languageId)); +monaco.languages.registerDefinitionProvider('flow', { + provideDefinition(model, position) { + try { + return getDefFunctionForMonaco(model.getValue(), position).map(loc => ({ + uri: model.uri, + range: { + startLineNumber: loc.start.line, + startColumn: loc.start.column, + endLineNumber: loc.end.line, + endColumn: loc.end.column + 1, + }, + })); + } catch (e) { + console.error(e); + return null; + } + }, +}); monaco.languages.registerHoverProvider('flow', { provideHover(model, position) { const result = typeAsPosFunctionForMonaco(model.getValue(), position); @@ -49,4 +84,4 @@ monaco.languages.registerHoverProvider('flow', { }); loader.config({monaco}); -export {monaco, setTypeAtPosFunction}; +export {monaco, setGetDefFunction, setTypeAtPosFunction}; diff --git a/website/src/try-flow/flow-services.js b/website/src/try-flow/flow-services.js index 203907fb9c7..677819a26a8 100644 --- a/website/src/try-flow/flow-services.js +++ b/website/src/try-flow/flow-services.js @@ -82,6 +82,15 @@ export default class FlowJsServices { return this._flow.checkContent(filename, body, this.config); } + getDef( + filename: string, + body: string, + line: number, + col: number, + ): $ReadOnlyArray { + return this._flow.getDef(filename, body, line, col, this.config); + } + typeAtPos(filename: string, body: string, line: number, col: number): string { return this._flow.typeAtPos(filename, body, line, col, this.config); }