From dc9d41860edce8631ca3b6c462c37de6dc3b9562 Mon Sep 17 00:00:00 2001 From: Pedro Castro Date: Sun, 18 Dec 2022 00:35:06 -0300 Subject: [PATCH 1/6] move prepareRename to analysis binary --- CHANGELOG.md | 1 + analysis/src/Cli.ml | 9 ++++++ analysis/src/Commands.ml | 21 +++++++++++++ analysis/tests/src/PrepareRename.res | 5 ++++ .../tests/src/expected/PrepareRename.res.txt | 6 ++++ server/src/server.ts | 30 +++++-------------- 6 files changed, 49 insertions(+), 23 deletions(-) create mode 100644 analysis/tests/src/PrepareRename.res create mode 100644 analysis/tests/src/expected/PrepareRename.res.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index b75a006df..fe38ea078 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - Rename custom LSP methods names. https://github.com/rescript-lang/rescript-vscode/pull/611 - Better performance for Inlay Hints and Codelens. - Accept both `@ns.doc` and the new `@res.doc` for the internal representation of doc comments. And both `@ns.optional` and `@res.optional` for the optional fields. https://github.com/rescript-lang/rescript-vscode/pull/642 +- Migrate `prepareRename` to analysis #### :bug: Bug Fix diff --git a/analysis/src/Cli.ml b/analysis/src/Cli.ml index c0887eee4..4c3021484 100644 --- a/analysis/src/Cli.ml +++ b/analysis/src/Cli.ml @@ -10,6 +10,7 @@ API examples: ./rescript-editor-analysis.exe hover src/MyFile.res 10 2 true ./rescript-editor-analysis.exe references src/MyFile.res 10 2 ./rescript-editor-analysis.exe rename src/MyFile.res 10 2 foo + ./rescript-editor-analysis.exe prepareRename src/MyFile.res 10 2 ./rescript-editor-analysis.exe diagnosticSyntax src/MyFile.res ./rescript-editor-analysis.exe inlayHint src/MyFile.res 0 3 25 ./rescript-editor-analysis.exe codeLens src/MyFile.res @@ -51,6 +52,10 @@ Options: ./rescript-editor-analysis.exe rename src/MyFile.res 10 2 foo + prepareRename: Validity of a rename operation at a given location. + + ./rescript-editor-analysis.exe prepareRename src/MyFile.res 10 2 + semanticTokens: return token semantic highlighting info for MyFile.res ./rescript-editor-analysis.exe semanticTokens src/MyFile.res @@ -136,6 +141,10 @@ let main () = Commands.rename ~path ~pos:(int_of_string line, int_of_string col) ~newName ~debug:false + | [_; "prepareRename"; path; line; col] -> + Commands.prepareRename ~path + ~pos:(int_of_string line, int_of_string col) + ~debug:false | [_; "semanticTokens"; currentFile] -> SemanticTokens.semanticTokens ~currentFile | [_; "createInterface"; path; cmiFile] -> diff --git a/analysis/src/Commands.ml b/analysis/src/Commands.ml index 756c6fb43..7367f028e 100644 --- a/analysis/src/Commands.ml +++ b/analysis/src/Commands.ml @@ -253,6 +253,20 @@ let rename ~path ~pos ~newName ~debug = in print_endline result +let prepareRename ~path ~pos ~debug = + let currentLoc = + match Cmt.loadFullCmtFromPath ~path with + | None -> None + | Some full -> ( + match References.getLocItem ~full ~pos ~debug with + | None -> None + | Some {loc} -> Some (Utils.cmtLocToRange loc)) + in + (match currentLoc with + | None -> Protocol.null + | Some range -> range |> Protocol.stringifyRange) + |> print_endline + let format ~path = if Filename.check_suffix path ".res" then let {Res_driver.parsetree = structure; comments; diagnostics} = @@ -382,6 +396,13 @@ let test ~path = ^ string_of_int col ^ " " ^ newName) in rename ~path ~pos:(line, col) ~newName ~debug:true + | "pre" -> + let () = + print_endline + ("PrepareRename " ^ path ^ " " ^ string_of_int line ^ ":" + ^ string_of_int col) + in + prepareRename ~path ~pos:(line, col) ~debug:true | "typ" -> print_endline ("TypeDefinition " ^ path ^ " " ^ string_of_int line ^ ":" diff --git a/analysis/tests/src/PrepareRename.res b/analysis/tests/src/PrepareRename.res new file mode 100644 index 000000000..87346347f --- /dev/null +++ b/analysis/tests/src/PrepareRename.res @@ -0,0 +1,5 @@ +let a = 1 +// ^pre + +let b = 2 and c = 3 +// ^pre \ No newline at end of file diff --git a/analysis/tests/src/expected/PrepareRename.res.txt b/analysis/tests/src/expected/PrepareRename.res.txt new file mode 100644 index 000000000..89d4a3830 --- /dev/null +++ b/analysis/tests/src/expected/PrepareRename.res.txt @@ -0,0 +1,6 @@ +PrepareRename src/PrepareRename.res 0:4 +{"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}} + +PrepareRename src/PrepareRename.res 3:19 +{"start": {"line": 3, "character": 18}, "end": {"line": 3, "character": 19}} + diff --git a/server/src/server.ts b/server/src/server.ts index 5b3be2505..3d7b14b72 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -556,30 +556,14 @@ function prepareRename(msg: p.RequestMessage): p.ResponseMessage { // https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_prepareRename let params = msg.params as p.PrepareRenameParams; let filePath = fileURLToPath(params.textDocument.uri); - let locations: null | p.Location[] = utils.getReferencesForPosition( + + let result = utils.runAnalysisAfterSanityCheck(filePath, [ + "prepareRename", filePath, - params.position - ); - let result: p.Range | null = null; - if (locations !== null) { - locations.forEach((loc) => { - if ( - path.normalize(fileURLToPath(loc.uri)) === - path.normalize(fileURLToPath(params.textDocument.uri)) - ) { - let { start, end } = loc.range; - let pos = params.position; - if ( - start.character <= pos.character && - start.line <= pos.line && - end.character >= pos.character && - end.line >= pos.line - ) { - result = loc.range; - } - } - }); - } + params.position.line, + params.position.character, + ]); + return { jsonrpc: c.jsonrpcVersion, id: msg.id, From a512bacb85cd248d3239c25a12ce14dc0f520e3b Mon Sep 17 00:00:00 2001 From: Pedro Castro Date: Sun, 18 Dec 2022 00:47:22 -0300 Subject: [PATCH 2/6] update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe38ea078..ab20c09ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ - Rename custom LSP methods names. https://github.com/rescript-lang/rescript-vscode/pull/611 - Better performance for Inlay Hints and Codelens. - Accept both `@ns.doc` and the new `@res.doc` for the internal representation of doc comments. And both `@ns.optional` and `@res.optional` for the optional fields. https://github.com/rescript-lang/rescript-vscode/pull/642 -- Migrate `prepareRename` to analysis +- Migrate `prepareRename` to analysis. https://github.com/rescript-lang/rescript-vscode/pull/657 #### :bug: Bug Fix From 589fed99af0881d368222402aaef363edd70b3f4 Mon Sep 17 00:00:00 2001 From: Pedro Castro Date: Sun, 18 Dec 2022 01:04:10 -0300 Subject: [PATCH 3/6] remove utils.getReferencesForPosition --- server/src/server.ts | 26 +++++++++----------------- server/src/utils.ts | 11 ----------- 2 files changed, 9 insertions(+), 28 deletions(-) diff --git a/server/src/server.ts b/server/src/server.ts index 3d7b14b72..d2cc9dfb5 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -539,16 +539,13 @@ function references(msg: p.RequestMessage) { // https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references let params = msg.params as p.ReferenceParams; let filePath = fileURLToPath(params.textDocument.uri); - let result: typeof p.ReferencesRequest.type = utils.getReferencesForPosition( + let response = utils.runAnalysisCommand(filePath, [ + "references", filePath, - params.position - ); - let response: p.ResponseMessage = { - jsonrpc: c.jsonrpcVersion, - id: msg.id, - result, - // error: code and message set in case an exception happens during the definition request. - }; + params.position.line, + params.position.character, + ], msg, false); + return response; } @@ -556,19 +553,14 @@ function prepareRename(msg: p.RequestMessage): p.ResponseMessage { // https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_prepareRename let params = msg.params as p.PrepareRenameParams; let filePath = fileURLToPath(params.textDocument.uri); - - let result = utils.runAnalysisAfterSanityCheck(filePath, [ + let response = utils.runAnalysisCommand(filePath, [ "prepareRename", filePath, params.position.line, params.position.character, - ]); + ], msg, false); - return { - jsonrpc: c.jsonrpcVersion, - id: msg.id, - result, - }; + return response } function rename(msg: p.RequestMessage) { diff --git a/server/src/utils.ts b/server/src/utils.ts index cb40d9b03..1a0968a23 100644 --- a/server/src/utils.ts +++ b/server/src/utils.ts @@ -175,17 +175,6 @@ export let runAnalysisCommand = ( return response; }; -export let getReferencesForPosition = ( - filePath: p.DocumentUri, - position: p.Position -) => - runAnalysisAfterSanityCheck(filePath, [ - "references", - filePath, - position.line, - position.character, - ]); - export const toCamelCase = (text: string): string => { return text .replace(/(?:^\w|[A-Z]|\b\w)/g, (s: string) => s.toUpperCase()) From 6de80675170bf86da1c0acea5b43c6c1de3d11e1 Mon Sep 17 00:00:00 2001 From: Pedro Castro Date: Sun, 18 Dec 2022 02:04:30 -0300 Subject: [PATCH 4/6] add more tests --- analysis/tests/src/PrepareRename.res | 5 ++++- analysis/tests/src/expected/PrepareRename.res.txt | 7 +++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/analysis/tests/src/PrepareRename.res b/analysis/tests/src/PrepareRename.res index 87346347f..cce33d260 100644 --- a/analysis/tests/src/PrepareRename.res +++ b/analysis/tests/src/PrepareRename.res @@ -2,4 +2,7 @@ let a = 1 // ^pre let b = 2 and c = 3 -// ^pre \ No newline at end of file +// ^pre + +let d = 0 +//^pre diff --git a/analysis/tests/src/expected/PrepareRename.res.txt b/analysis/tests/src/expected/PrepareRename.res.txt index 89d4a3830..378f7f07a 100644 --- a/analysis/tests/src/expected/PrepareRename.res.txt +++ b/analysis/tests/src/expected/PrepareRename.res.txt @@ -1,6 +1,9 @@ PrepareRename src/PrepareRename.res 0:4 {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}} -PrepareRename src/PrepareRename.res 3:19 -{"start": {"line": 3, "character": 18}, "end": {"line": 3, "character": 19}} +PrepareRename src/PrepareRename.res 3:14 +{"start": {"line": 3, "character": 14}, "end": {"line": 3, "character": 15}} + +PrepareRename src/PrepareRename.res 6:2 +null From a05573c420f2320165cc9d5b455853b202d14ce0 Mon Sep 17 00:00:00 2001 From: Pedro Castro Date: Sun, 25 Dec 2022 14:14:39 -0300 Subject: [PATCH 5/6] Sync with master --- .vscode/settings.json | 4 + CHANGELOG.md | 8 +- CONTRIBUTING.md | 17 +- README.md | 7 - analysis/dune-project | 10 + analysis/rescript-vscode.opam | 27 ++ analysis/src/Commands.ml | 6 +- analysis/src/CompletionBackEnd.ml | 193 ++++++++----- analysis/src/CompletionFrontEnd.ml | 58 +++- analysis/src/Hover.ml | 7 +- analysis/src/ProcessCmt.ml | 61 +++-- analysis/src/ResolvePath.ml | 3 +- analysis/src/SharedTypes.ml | 64 ++++- analysis/src/SignatureHelp.ml | 7 +- analysis/tests/src/Completion.res | 8 +- analysis/tests/src/CompletionPipeChain.res | 64 +++++ .../tests/src/CompletionPipeSubmodules.res | 45 +++ analysis/tests/src/CompletionSupport.res | 6 + .../tests/src/expected/Completion.res.txt | 42 ++- .../src/expected/CompletionPipeChain.res.txt | 258 ++++++++++++++++++ .../expected/CompletionPipeSubmodules.res.txt | 56 ++++ .../src/expected/CompletionSupport.res.txt | 0 22 files changed, 819 insertions(+), 132 deletions(-) create mode 100644 analysis/tests/src/CompletionPipeChain.res create mode 100644 analysis/tests/src/CompletionPipeSubmodules.res create mode 100644 analysis/tests/src/CompletionSupport.res create mode 100644 analysis/tests/src/expected/CompletionPipeChain.res.txt create mode 100644 analysis/tests/src/expected/CompletionPipeSubmodules.res.txt create mode 100644 analysis/tests/src/expected/CompletionSupport.res.txt diff --git a/.vscode/settings.json b/.vscode/settings.json index a0dfd585e..11909bf1d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,5 +5,9 @@ "typescript.preferences.quoteStyle": "single", "editor.codeActionsOnSave": { "source.fixAll.eslint": true + }, + "ocaml.sandbox": { + "kind": "opam", + "switch": "${workspaceFolder:rescript-vscode}/analysis" } } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index ab20c09ff..c1e251278 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,9 +16,15 @@ - Remove spacing between type definition in clients that do not support markdown links. https://github.com/rescript-lang/rescript-vscode/pull/619 - Rename custom LSP methods names. https://github.com/rescript-lang/rescript-vscode/pull/611 -- Better performance for Inlay Hints and Codelens. +- Better performance for Inlay Hints and Codelens. https://github.com/rescript-lang/rescript-vscode/pull/634 - Accept both `@ns.doc` and the new `@res.doc` for the internal representation of doc comments. And both `@ns.optional` and `@res.optional` for the optional fields. https://github.com/rescript-lang/rescript-vscode/pull/642 - Migrate `prepareRename` to analysis. https://github.com/rescript-lang/rescript-vscode/pull/657 +- Make pipe completion work more reliably after function calls. https://github.com/rescript-lang/rescript-vscode/pull/656 +- Make pipe completion work in pipe chains, not just on the first pipe. https://github.com/rescript-lang/rescript-vscode/pull/656 +- Make pipe completion work reliably when the path resolution needs to traverse submodules https://github.com/rescript-lang/rescript-vscode/pull/663 +- Make pipe completion work (after saving/compiling) when the return type of a function call is unknown until compilation https://github.com/rescript-lang/rescript-vscode/pull/662 +- Add pipe completion for `int` and `float` constants https://github.com/rescript-lang/rescript-vscode/pull/664 +- Migrate `prepareRename` to analysis. https://github.com/rescript-lang/rescript-vscode/pull/657 #### :bug: Bug Fix diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6e7ee4481..9bbc16c1e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,8 +25,21 @@ Thanks for your interest. Below is an informal spec of how the plugin's server c ## Install Dependencies - Run `npm install` at the root. This will also install the npm modules for both the `client` and `server` folders. -- `opam switch 4.14.0` (if you haven't created the switch, do it). OPAM [here](https://opam.ocaml.org). This is needed for the `analysis` folder, which is native code. -- Optionally, you can `opam install ocamlformat` and format the `.ml` files in `analysis`. + +## Analysis Binary + +This is needed for the `analysis` folder, which is native code. + +```sh +# If you haven't created the switch, do it. OPAM(https://opam.ocaml.org) +opam switch 4.14.0 # can also create local switch with opam switch create . 4.14.0 + +# Install dev dependencies from OPAM +opam install . --deps-only + +# For IDE support, install the OCaml language server +opam install ocaml-lsp-server +``` ## Build & Run diff --git a/README.md b/README.md index e67a69018..e67716011 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,6 @@

The Official VSCode plugin for ReScript

-

- - - - -

-

diff --git a/analysis/dune-project b/analysis/dune-project index df2dfc688..6d2e34ad0 100644 --- a/analysis/dune-project +++ b/analysis/dune-project @@ -1,5 +1,15 @@ (lang dune 2.0) +(generate_opam_files true) + +(authors "Cristiano Calcagno") + +(maintainers "Cristiano Calcagno") + +(homepage "https://github.com/rescript-lang/rescript-vscode") + +(bug_reports "https://github.com/rescript-lang/rescript-vscode/issues") + (package (name rescript-vscode) (synopsis "ReScript vscode support") diff --git a/analysis/rescript-vscode.opam b/analysis/rescript-vscode.opam index e69de29bb..2e2d3f539 100644 --- a/analysis/rescript-vscode.opam +++ b/analysis/rescript-vscode.opam @@ -0,0 +1,27 @@ +# This file is generated by dune, edit dune-project instead +opam-version: "2.0" +synopsis: "ReScript vscode support" +maintainer: ["Cristiano Calcagno"] +authors: ["Cristiano Calcagno"] +homepage: "https://github.com/rescript-lang/rescript-vscode" +bug-reports: "https://github.com/rescript-lang/rescript-vscode/issues" +depends: [ + "ocaml" {>= "4.10"} + "ocamlformat" {= "0.22.4"} + "reanalyze" {= "2.23.0"} + "dune" +] +build: [ + ["dune" "subst"] {pinned} + [ + "dune" + "build" + "-p" + name + "-j" + jobs + "@install" + "@runtest" {with-test} + "@doc" {with-doc} + ] +] diff --git a/analysis/src/Commands.ml b/analysis/src/Commands.ml index 7367f028e..63fbfdea4 100644 --- a/analysis/src/Commands.ml +++ b/analysis/src/Commands.ml @@ -15,10 +15,10 @@ let getCompletions ~debug ~path ~pos ~currentFile ~forHover = (* Only perform expensive ast operations if there are completables *) match Cmt.loadFullCmtFromPath ~path with | None -> [] - | Some {file; package} -> - let env = SharedTypes.QueryEnv.fromFile file in + | Some full -> + let env = SharedTypes.QueryEnv.fromFile full.file in completable - |> CompletionBackEnd.processCompletable ~debug ~package ~pos ~scope ~env + |> CompletionBackEnd.processCompletable ~debug ~full ~pos ~scope ~env ~forHover)) let completion ~debug ~path ~pos ~currentFile = diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index b7240a297..e1ace9947 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -1157,8 +1157,17 @@ let completionsGetTypeEnv = function | {Completion.kind = Field ({typ}, _); env} :: _ -> Some (typ, env) | _ -> None -let rec getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos - ~env ~exact ~scope (contextPath : Completable.contextPath) = +let findReturnTypeOfFunctionAtLoc loc ~(env : QueryEnv.t) ~full ~debug = + match References.getLocItem ~full ~pos:(loc |> Loc.end_) ~debug with + | Some {locType = Typed (_, typExpr, _)} -> ( + match extractFunctionType ~env ~package:full.package typExpr with + | args, tRet when args <> [] -> Some tRet + | _ -> None) + | _ -> None + +let rec getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env + ~exact ~scope (contextPath : Completable.contextPath) = + let package = full.package in match contextPath with | CPString -> [ @@ -1167,6 +1176,20 @@ let rec getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos (Completion.Value (Ctype.newconstr (Path.Pident (Ident.create "string")) [])); ] + | CPInt -> + [ + Completion.create ~name:"int" ~env + ~kind: + (Completion.Value + (Ctype.newconstr (Path.Pident (Ident.create "int")) [])); + ] + | CPFloat -> + [ + Completion.create ~name:"float" ~env + ~kind: + (Completion.Value + (Ctype.newconstr (Path.Pident (Ident.create "float")) [])); + ] | CPArray -> [ Completion.create ~name:"array" ~env @@ -1181,8 +1204,8 @@ let rec getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos | CPApply (cp, labels) -> ( match cp - |> getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos - ~env ~exact:true ~scope + |> getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env + ~exact:true ~scope |> completionsGetTypeEnv with | Some (typ, env) -> ( @@ -1227,8 +1250,8 @@ let rec getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos | CPField (cp, fieldName) -> ( match cp - |> getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos - ~env ~exact:true ~scope + |> getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env + ~exact:true ~scope |> completionsGetTypeEnv with | Some (typ, env) -> ( @@ -1250,8 +1273,8 @@ let rec getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos | CPObj (cp, label) -> ( match cp - |> getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos - ~env ~exact:true ~scope + |> getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env + ~exact:true ~scope |> completionsGetTypeEnv with | Some (typ, env) -> ( @@ -1275,14 +1298,28 @@ let rec getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos else None) | None -> []) | None -> []) - | CPPipe (cp, funNamePrefix) -> ( + | CPPipe {contextPath = cp; id = funNamePrefix; lhsLoc} -> ( match cp - |> getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos - ~env ~exact:true ~scope + |> getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env + ~exact:true ~scope |> completionsGetTypeEnv with - | Some (typ, _envNotUsed) -> ( + | Some (typ, envFromCompletionItem) -> ( + (* If the type we're completing on is a type parameter, we won't be able to do + completion unless we know what that type parameter is compiled as. This + attempts to look up the compiled type for that type parameter by looking + for compiled information at the loc of that expression. *) + let typ = + match typ with + | {Types.desc = Tvar _} -> ( + match + findReturnTypeOfFunctionAtLoc lhsLoc ~env ~full ~debug:false + with + | None -> typ + | Some typFromLoc -> typFromLoc) + | _ -> typ + in let { arrayModulePath; optionModulePath; @@ -1295,30 +1332,28 @@ let rec getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos } = package.builtInCompletionModules in - let getModulePath path = - let rec loop (path : Path.t) = - match path with - | Pident id -> [Ident.name id] - | Pdot (p, s, _) -> s :: loop p - | Papply _ -> [] - in + let getBuiltinTypePath path = + match path with + | Path.Pident id when Ident.name id = "array" -> Some arrayModulePath + | Path.Pident id when Ident.name id = "option" -> Some optionModulePath + | Path.Pident id when Ident.name id = "string" -> Some stringModulePath + | Path.Pident id when Ident.name id = "int" -> Some intModulePath + | Path.Pident id when Ident.name id = "float" -> Some floatModulePath + | Path.Pident id when Ident.name id = "promise" -> + Some promiseModulePath + | Path.Pident id when Ident.name id = "list" -> Some listModulePath + | Path.Pident id when Ident.name id = "result" -> Some resultModulePath + | Path.Pident id when Ident.name id = "lazy_t" -> Some ["Lazy"] + | Path.Pident id when Ident.name id = "char" -> Some ["Char"] + | _ -> None + in + let rec expandPath (path : Path.t) = match path with - | Path.Pident id when Ident.name id = "array" -> arrayModulePath - | Path.Pident id when Ident.name id = "option" -> optionModulePath - | Path.Pident id when Ident.name id = "string" -> stringModulePath - | Path.Pident id when Ident.name id = "int" -> intModulePath - | Path.Pident id when Ident.name id = "float" -> floatModulePath - | Path.Pident id when Ident.name id = "promise" -> promiseModulePath - | Path.Pident id when Ident.name id = "list" -> listModulePath - | Path.Pident id when Ident.name id = "result" -> resultModulePath - | Path.Pident id when Ident.name id = "lazy_t" -> ["Lazy"] - | Path.Pident id when Ident.name id = "char" -> ["Char"] - | _ -> ( - match loop path with - | _ :: rest -> List.rev rest - | [] -> []) + | Pident id -> [Ident.name id] + | Pdot (p, s, _) -> s :: expandPath p + | Papply _ -> [] in - let getConstrPath typ = + let getTypePath typ = match typ.Types.desc with | Tconstr (path, _typeArgs, _) | Tlink {desc = Tconstr (path, _typeArgs, _)} @@ -1327,12 +1362,6 @@ let rec getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos Some path | _ -> None in - let fromType typ = - match getConstrPath typ with - | None -> None - | Some path -> Some (getModulePath path) - in - let lhsPath = fromType typ in let rec removeRawOpen rawOpen modulePath = match (rawOpen, modulePath) with | [_], _ -> Some modulePath @@ -1349,33 +1378,54 @@ let rec getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos | Some mp -> mp) | [] -> modulePath in - match lhsPath with - | Some modulePath -> ( - match modulePath with - | _ :: _ -> - let modulePathMinusOpens = - modulePath - |> removeRawOpens package.opens - |> removeRawOpens rawOpens |> String.concat "." - in - let completionName name = - if modulePathMinusOpens = "" then name - else modulePathMinusOpens ^ "." ^ name - in - let completions = - modulePath @ [funNamePrefix] - |> getCompletionsForPath ~completionContext:Value ~exact:false - ~package ~opens ~allFiles ~pos ~env ~scope - in - completions - |> List.map (fun (completion : Completion.t) -> - { - completion with - name = completionName completion.name; - env - (* Restore original env for the completion after x->foo()... *); - }) - | [] -> []) + let completionPath = + match getTypePath typ with + | Some typePath -> ( + match getBuiltinTypePath typePath with + | Some path -> Some path + | None -> ( + match expandPath typePath with + | _ :: pathRev -> + (* type path is relative to the completion environment + express it from the root of the file *) + let pathFromEnv_ = + QueryEnv.pathFromEnv envFromCompletionItem (List.rev pathRev) + in + if pathFromEnv_ = [] then None + else + let pathFromEnv = + if env.file.moduleName = envFromCompletionItem.file.moduleName + then pathFromEnv_ + else envFromCompletionItem.file.moduleName :: pathFromEnv_ + in + Some pathFromEnv + | _ -> None)) + | None -> None + in + match completionPath with + | Some completionPath -> + let completionPathMinusOpens = + completionPath + |> removeRawOpens package.opens + |> removeRawOpens rawOpens |> String.concat "." + in + let completionName name = + if completionPathMinusOpens = "" then name + else completionPathMinusOpens ^ "." ^ name + in + let completions = + completionPath @ [funNamePrefix] + |> getCompletionsForPath ~completionContext:Value ~exact:false + ~package ~opens ~allFiles ~pos ~env ~scope + in + completions + |> List.map (fun (completion : Completion.t) -> + { + completion with + name = completionName completion.name; + env + (* Restore original env for the completion after x->foo()... *); + }) | None -> []) | None -> []) @@ -1405,8 +1455,9 @@ let getOpens ~debug ~rawOpens ~package ~env = (* Last open takes priority *) List.rev resolvedOpens -let processCompletable ~debug ~package ~scope ~env ~pos ~forHover +let processCompletable ~debug ~full ~scope ~env ~pos ~forHover (completable : Completable.t) = + let package = full.package in let rawOpens = Scope.getRawOpens scope in let opens = getOpens ~debug ~rawOpens ~package ~env in let allFiles = FileSet.union package.projectFiles package.dependenciesFiles in @@ -1420,8 +1471,8 @@ let processCompletable ~debug ~package ~scope ~env ~pos ~forHover | Cnone -> [] | Cpath contextPath -> contextPath - |> getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos - ~env ~exact:forHover ~scope + |> getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env + ~exact:forHover ~scope | Cjsx ([id], prefix, identsSeen) when String.uncapitalize_ascii id = id -> let mkLabel (name, typString) = Completion.create ~name ~kind:(Label typString) ~env @@ -1770,7 +1821,7 @@ Note: The `@react.component` decorator requires the react-jsx config to be set i let labels = match cp - |> getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos + |> getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env ~exact:true ~scope |> completionsGetTypeEnv with diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index bce92250b..953163b60 100644 --- a/analysis/src/CompletionFrontEnd.ml +++ b/analysis/src/CompletionFrontEnd.ml @@ -138,6 +138,8 @@ let findNamedArgCompletable ~(args : arg list) ~endPos ~posBeforeCursor let rec exprToContextPath (e : Parsetree.expression) = match e.pexp_desc with | Pexp_constant (Pconst_string _) -> Some Completable.CPString + | Pexp_constant (Pconst_integer _) -> Some CPInt + | Pexp_constant (Pconst_float _) -> Some CPFloat | Pexp_array _ -> Some CPArray | Pexp_ident {txt} -> Some (CPId (Utils.flattenLongIdent txt, Value)) | Pexp_field (e1, {txt = Lident name}) -> ( @@ -157,6 +159,48 @@ let rec exprToContextPath (e : Parsetree.expression) = | Some contexPath -> Some (CPApply (contexPath, args |> List.map fst))) | _ -> None +let completePipeChain ~(lhs : Parsetree.expression) = + (* Complete the end of pipe chains by reconstructing the pipe chain as a single pipe, + so it can be completed. + Example: + someArray->Js.Array2.filter(v => v > 10)->Js.Array2.map(v => v + 2)-> + will complete as: + Js.Array2.map(someArray->Js.Array2.filter(v => v > 10), v => v + 2)-> + *) + match lhs.pexp_desc with + (* When the left side of the pipe we're completing is a function application. + Example: someArray->Js.Array2.map(v => v + 2)-> *) + | Pexp_apply + ( {pexp_desc = Pexp_ident {txt = Lident "|."}}, + [ + (_, lhs); + (_, {pexp_desc = Pexp_apply (d, args); pexp_loc; pexp_attributes}); + ] ) -> + exprToContextPath + { + pexp_desc = Pexp_apply (d, (Nolabel, lhs) :: args); + pexp_loc; + pexp_attributes; + } + |> Option.map (fun ctxPath -> (ctxPath, d.pexp_loc)) + (* When the left side of the pipe we're completing is an identifier application. + Example: someArray->filterAllTheGoodStuff-> *) + | Pexp_apply + ( {pexp_desc = Pexp_ident {txt = Lident "|."}}, + [(_, lhs); (_, {pexp_desc = Pexp_ident id; pexp_loc; pexp_attributes})] + ) -> + exprToContextPath + { + pexp_desc = + Pexp_apply + ( {pexp_desc = Pexp_ident id; pexp_loc; pexp_attributes}, + [(Nolabel, lhs)] ); + pexp_loc; + pexp_attributes; + } + |> Option.map (fun ctxPath -> (ctxPath, pexp_loc)) + | _ -> None + let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = let offsetNoWhite = skipWhite text (offset - 1) in let posNoWhite = @@ -392,11 +436,17 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = (Loc.toString expr.pexp_loc) in let setPipeResult ~(lhs : Parsetree.expression) ~id = - match exprToContextPath lhs with - | Some pipe -> - setResult (Cpath (CPPipe (pipe, id))); + match completePipeChain ~lhs with + | None -> ( + match exprToContextPath lhs with + | Some pipe -> + setResult + (Cpath (CPPipe {contextPath = pipe; id; lhsLoc = lhs.pexp_loc})); + true + | None -> false) + | Some (pipe, lhsLoc) -> + setResult (Cpath (CPPipe {contextPath = pipe; id; lhsLoc})); true - | None -> false in match expr.pexp_desc with | Pexp_apply diff --git a/analysis/src/Hover.ml b/analysis/src/Hover.ml index 599852273..2cfeb8348 100644 --- a/analysis/src/Hover.ml +++ b/analysis/src/Hover.ml @@ -136,12 +136,13 @@ let getHoverViaCompletions ~debug ~path ~pos ~currentFile ~forHover (* Only perform expensive ast operations if there are completables *) match Cmt.loadFullCmtFromPath ~path with | None -> None - | Some {file; package} -> ( + | Some full -> ( + let {file; package} = full in let env = SharedTypes.QueryEnv.fromFile file in let completions = completable - |> CompletionBackEnd.processCompletable ~debug ~package ~pos ~scope - ~env ~forHover + |> CompletionBackEnd.processCompletable ~debug ~full ~pos ~scope ~env + ~forHover in match completions with | {kind = Label typString; docstring} :: _ -> diff --git a/analysis/src/ProcessCmt.ml b/analysis/src/ProcessCmt.ml index b005cf2fe..dab690758 100644 --- a/analysis/src/ProcessCmt.ml +++ b/analysis/src/ProcessCmt.ml @@ -104,10 +104,11 @@ let rec forTypeSignatureItem ~(env : SharedTypes.Env.t) ~(exported : Exported.t) in [{Module.kind = Type (declared.item, recStatus); name = declared.name.txt}] | Sig_module (ident, {md_type; md_attributes; md_loc}, _) -> + let name = Ident.name ident in let declared = addDeclared ~extent:md_loc - ~item:(forTypeModule env md_type) - ~name:(Location.mkloc (Ident.name ident) md_loc) + ~item:(forTypeModule ~name ~env md_type) + ~name:(Location.mkloc name md_loc) ~stamp:(Ident.binding_time ident) ~env md_attributes (Exported.add exported Exported.Module) Stamps.addModule @@ -115,22 +116,22 @@ let rec forTypeSignatureItem ~(env : SharedTypes.Env.t) ~(exported : Exported.t) [{Module.kind = Module declared.item; name = declared.name.txt}] | _ -> [] -and forTypeSignature env signature = +and forTypeSignature ~name ~env signature = let exported = Exported.init () in let items = List.fold_right (fun item items -> forTypeSignatureItem ~env ~exported item @ items) signature [] in - {Module.docstring = []; exported; items} + {Module.name; docstring = []; exported; items} -and forTypeModule env moduleType = +and forTypeModule ~name ~env moduleType = match moduleType with | Types.Mty_ident path -> Ident path | Mty_alias (_ (* 402 *), path) -> Ident path - | Mty_signature signature -> Structure (forTypeSignature env signature) + | Mty_signature signature -> Structure (forTypeSignature ~name ~env signature) | Mty_functor (_argIdent, _argType, resultType) -> - forTypeModule env resultType + forTypeModule ~name ~env resultType let getModuleTypePath mod_desc = match mod_desc with @@ -249,7 +250,11 @@ let rec forSignatureItem ~env ~(exported : Exported.t) decl |> forTypeDeclaration ~env ~exported ~recStatus) | Tsig_module {md_id; md_attributes; md_loc; md_name = name; md_type = {mty_type}} -> - let item = forTypeModule (env |> Env.addModule ~name:name.txt) mty_type in + let item = + forTypeModule ~name:name.txt + ~env:(env |> Env.addModule ~name:name.txt) + mty_type + in let declared = addDeclared ~item ~name ~extent:md_loc ~stamp:(Ident.binding_time md_id) ~env md_attributes @@ -279,7 +284,7 @@ let rec forSignatureItem ~env ~(exported : Exported.t) (* TODO: process other things here *) | _ -> [] -let forSignature ~env sigItems = +let forSignature ~name ~env sigItems = let exported = Exported.init () in let items = sigItems |> List.map (forSignatureItem ~env ~exported) |> List.flatten @@ -294,13 +299,13 @@ let forSignature ~env sigItems = | None -> [] | Some d -> [d] in - {Module.docstring; exported; items} + {Module.name; docstring; exported; items} -let forTreeModuleType ~env {Typedtree.mty_desc} = +let forTreeModuleType ~name ~env {Typedtree.mty_desc} = match mty_desc with | Tmty_ident _ -> None | Tmty_signature {sig_items} -> - let contents = forSignature ~env sig_items in + let contents = forSignature ~name ~env sig_items in Some (Module.Structure contents) | _ -> None @@ -352,7 +357,7 @@ let rec forStructureItem ~env ~(exported : Exported.t) item = (String.length name.txt >= 6 && (String.sub name.txt 0 6 = "local_") [@doesNotRaise]) (* %%private generates a dummy module called local_... *) -> - let item = forModule env mod_desc name.txt in + let item = forModule ~env mod_desc name.txt in let declared = addDeclared ~item ~name ~extent:mb_loc ~stamp:(Ident.binding_time mb_id) ~env mb_attributes @@ -375,7 +380,7 @@ let rec forStructureItem ~env ~(exported : Exported.t) item = mtd_loc; } -> let env = env |> Env.addModuleType ~name:name.txt in - let modTypeItem = forTypeModule env modType in + let modTypeItem = forTypeModule ~name:name.txt ~env modType in let declared = addDeclared ~item:modTypeItem ~name ~extent:mtd_loc ~stamp:(Ident.binding_time mtd_id) @@ -418,18 +423,18 @@ let rec forStructureItem ~env ~(exported : Exported.t) item = decl |> forTypeDeclaration ~env ~exported ~recStatus) | _ -> [] -and forModule env mod_desc moduleName = +and forModule ~env mod_desc moduleName = match mod_desc with | Tmod_ident (path, _lident) -> Ident path | Tmod_structure structure -> let env = env |> Env.addModule ~name:moduleName in - let contents = forStructure ~env structure.str_items in + let contents = forStructure ~name:moduleName ~env structure.str_items in Structure contents | Tmod_functor (ident, argName, maybeType, resultExpr) -> (match maybeType with | None -> () | Some t -> ( - match forTreeModuleType ~env t with + match forTreeModuleType ~name:argName.txt ~env t with | None -> () | Some kind -> let stamp = Ident.binding_time ident in @@ -438,20 +443,20 @@ and forModule env mod_desc moduleName = ~extent:t.Typedtree.mty_loc ~stamp ~modulePath:NotVisible false [] in Stamps.addModule env.stamps stamp declared)); - forModule env resultExpr.mod_desc moduleName + forModule ~env resultExpr.mod_desc moduleName | Tmod_apply (functor_, _arg, _coercion) -> - forModule env functor_.mod_desc moduleName + forModule ~env functor_.mod_desc moduleName | Tmod_unpack (_expr, moduleType) -> let env = env |> Env.addModule ~name:moduleName in - forTypeModule env moduleType + forTypeModule ~name:moduleName ~env moduleType | Tmod_constraint (expr, typ, _constraint, _coercion) -> (* TODO do this better I think *) - let modKind = forModule env expr.mod_desc moduleName in + let modKind = forModule ~env expr.mod_desc moduleName in let env = env |> Env.addModule ~name:moduleName in - let modTypeKind = forTypeModule env typ in + let modTypeKind = forTypeModule ~name:moduleName ~env typ in Constraint (modKind, modTypeKind) -and forStructure ~env strItems = +and forStructure ~name ~env strItems = let exported = Exported.init () in let items = List.fold_right @@ -468,7 +473,7 @@ and forStructure ~env strItems = | None -> [] | Some d -> [d] in - {docstring; exported; items} + {Module.name; docstring; exported; items} let fileForCmtInfos ~moduleName ~uri ({cmt_modname; cmt_annots} : Cmt_format.cmt_infos) = @@ -486,7 +491,7 @@ let fileForCmtInfos ~moduleName ~uri | _ -> None) |> List.concat in - let structure = forStructure ~env items in + let structure = forStructure ~name:moduleName ~env items in {File.uri; moduleName = cmt_modname; stamps = env.stamps; structure} | Partial_interface parts -> let items = @@ -498,13 +503,13 @@ let fileForCmtInfos ~moduleName ~uri | _ -> None) |> List.concat in - let structure = forSignature ~env items in + let structure = forSignature ~name:moduleName ~env items in {uri; moduleName = cmt_modname; stamps = env.stamps; structure} | Implementation structure -> - let structure = forStructure ~env structure.str_items in + let structure = forStructure ~name:moduleName ~env structure.str_items in {uri; moduleName = cmt_modname; stamps = env.stamps; structure} | Interface signature -> - let structure = forSignature ~env signature.sig_items in + let structure = forSignature ~name:moduleName ~env signature.sig_items in {uri; moduleName = cmt_modname; stamps = env.stamps; structure} | _ -> File.create moduleName uri diff --git a/analysis/src/ResolvePath.ml b/analysis/src/ResolvePath.ml index dc930a7e7..877e273fe 100644 --- a/analysis/src/ResolvePath.ml +++ b/analysis/src/ResolvePath.ml @@ -46,7 +46,8 @@ and resolvePathInner ~(env : QueryEnv.t) ~path = and findInModule ~(env : QueryEnv.t) module_ path = match module_ with - | Structure {exported} -> resolvePathInner ~env:{env with exported} ~path + | Structure structure -> + resolvePathInner ~env:(QueryEnv.enterStructure env structure) ~path | Constraint (_, module1) -> findInModule ~env module1 path | Ident modulePath -> ( let stamp, moduleName, fullPath = joinPaths modulePath path in diff --git a/analysis/src/SharedTypes.ml b/analysis/src/SharedTypes.ml index 549c5ea32..358e25856 100644 --- a/analysis/src/SharedTypes.ml +++ b/analysis/src/SharedTypes.ml @@ -105,6 +105,7 @@ module Module = struct and item = {kind: kind; name: string} and structure = { + name: string; docstring: string list; exported: Exported.t; items: item list; @@ -226,14 +227,56 @@ module File = struct uri; stamps = Stamps.init (); moduleName; - structure = {docstring = []; exported = Exported.init (); items = []}; + structure = + { + name = moduleName; + docstring = []; + exported = Exported.init (); + items = []; + }; } end -module QueryEnv = struct - type t = {file: File.t; exported: Exported.t} +module QueryEnv : sig + type t = private { + file: File.t; + exported: Exported.t; + pathRev: path; + parent: t option; + } + val fromFile : File.t -> t + val enterStructure : t -> Module.structure -> t + + (* Express a path starting from the module represented by the env. + E.g. the env is at A.B.C and the path is D. + The result is A.B.C.D if D is inside C. + Or A.B.D or A.D or D if it's in one of its parents. *) + val pathFromEnv : t -> path -> path +end = struct + type t = {file: File.t; exported: Exported.t; pathRev: path; parent: t option} + + let fromFile (file : File.t) = + {file; exported = file.structure.exported; pathRev = []; parent = None} - let fromFile file = {file; exported = file.structure.exported} + (* Prune a path and find a parent environment that contains the module name *) + let rec prunePath pathRev env name = + if Exported.find env.exported Module name <> None then pathRev + else + match (pathRev, env.parent) with + | _ :: rest, Some env -> prunePath rest env name + | _ -> [] + + let pathFromEnv env path = + match path with + | [] -> env.pathRev |> List.rev + | name :: _ -> + let prunedPathRev = prunePath env.pathRev env name in + List.rev_append prunedPathRev path + + let enterStructure env (structure : Module.structure) = + let name = structure.name in + let pathRev = name :: prunePath env.pathRev env name in + {env with exported = structure.exported; pathRev; parent = Some env} end module Completion = struct @@ -462,11 +505,18 @@ module Completable = struct type contextPath = | CPString | CPArray + | CPInt + | CPFloat | CPApply of contextPath * Asttypes.arg_label list | CPId of string list * completionContext | CPField of contextPath * string | CPObj of contextPath * string - | CPPipe of contextPath * string + | CPPipe of { + contextPath: contextPath; + id: string; + lhsLoc: Location.t; + (** The loc item for the left hand side of the pipe. *) + } type t = | Cdecorator of string (** e.g. @module *) @@ -486,6 +536,8 @@ module Completable = struct in let rec contextPathToString = function | CPString -> "string" + | CPInt -> "int" + | CPFloat -> "float" | CPApply (cp, labels) -> contextPathToString cp ^ "(" ^ (labels @@ -500,7 +552,7 @@ module Completable = struct completionContextToString completionContext ^ list sl | CPField (cp, s) -> contextPathToString cp ^ "." ^ str s | CPObj (cp, s) -> contextPathToString cp ^ "[\"" ^ s ^ "\"]" - | CPPipe (cp, s) -> contextPathToString cp ^ "->" ^ s + | CPPipe {contextPath; id} -> contextPathToString contextPath ^ "->" ^ id in function | Cpath cp -> "Cpath " ^ contextPathToString cp diff --git a/analysis/src/SignatureHelp.ml b/analysis/src/SignatureHelp.ml index 364d463b2..69a0ff006 100644 --- a/analysis/src/SignatureHelp.ml +++ b/analysis/src/SignatureHelp.ml @@ -18,12 +18,13 @@ let findFunctionType ~currentFile ~debug ~path ~pos = | Some (completable, scope) -> ( match Cmt.loadFullCmtFromPath ~path with | None -> None - | Some {file; package} -> + | Some full -> + let {file; package} = full in let env = QueryEnv.fromFile file in Some ( completable - |> CompletionBackEnd.processCompletable ~debug ~package ~pos - ~scope ~env ~forHover:true, + |> CompletionBackEnd.processCompletable ~debug ~full ~pos ~scope + ~env ~forHover:true, env, package, file ))) diff --git a/analysis/tests/src/Completion.res b/analysis/tests/src/Completion.res index 39b57c138..c79bed175 100644 --- a/analysis/tests/src/Completion.res +++ b/analysis/tests/src/Completion.res @@ -403,7 +403,7 @@ let header2 = ` background-color: ${red}; ` // ^com -// let _ = `color: ${r +// let _ = `color: ${r // ^com let onClick = evt => { @@ -414,3 +414,9 @@ let onClick = evt => { // ^com Js.log("Hello") } + +// let _ = 123->t +// ^com + +// let _ = 123.0->t +// ^com diff --git a/analysis/tests/src/CompletionPipeChain.res b/analysis/tests/src/CompletionPipeChain.res new file mode 100644 index 000000000..f9bcd2345 --- /dev/null +++ b/analysis/tests/src/CompletionPipeChain.res @@ -0,0 +1,64 @@ +module Integer: { + type t + let increment: (t, int) => t + let decrement: (t, int => int) => t + let make: int => t + let toInt: t => int +} = { + type t = int + let increment = (t, num) => t + num + let decrement = (t, decrementer) => decrementer(t) + let make = t => t + let toInt = t => t +} + +module SuperFloat: { + type t + let fromInteger: Integer.t => t + let toInteger: t => Integer.t +} = { + type t = float + let fromInteger = t => t->Integer.toInt->Belt.Float.fromInt + let toInteger = t => t->Belt.Float.toInt->Integer.make +} + +let toFlt = i => i->SuperFloat.fromInteger +let int = Integer.make(1) +let f = int->Integer.increment(2) +// let _ = int-> +// ^com + +// let _ = int->toFlt-> +// ^com + +// let _ = int->Integer.increment(2)-> +// ^com + +// let _ = Integer.increment(int, 2)-> +// ^com + +// let _ = int->Integer.decrement(t => t - 1)-> +// ^com + +// let _ = int->Integer.increment(2)->Integer.decrement(t => t - 1)-> +// ^com + +// let _ = int->Integer.increment(2)->SuperFloat.fromInteger-> +// ^com + +// let _ = int->Integer.increment(2)->SuperFloat.fromInteger->t +// ^com + +// let _ = int->Integer.increment(2)->Integer.toInt->CompletionSupport.Test.make-> +// ^com + +// let _ = CompletionSupport.Test.make(1)->CompletionSupport.Test.addSelf(2)-> +// ^com + +let _ = [123]->Js.Array2.forEach(v => Js.log(v)) +// -> +// ^com + +let _ = [123]->Belt.Array.reduce(0, (acc, curr) => acc + curr) +// ->t +// ^com diff --git a/analysis/tests/src/CompletionPipeSubmodules.res b/analysis/tests/src/CompletionPipeSubmodules.res new file mode 100644 index 000000000..a3ee0af8d --- /dev/null +++ b/analysis/tests/src/CompletionPipeSubmodules.res @@ -0,0 +1,45 @@ +module A = { + module B1 = { + type b1 = B1 + let xx = B1 + } + module B2 = { + let yy = 20 + } + type t = {v: B1.b1} + let x = {v: B1.B1} +} + +// let _ = A.B1.xx-> +// ^com +// b1 seen from B1 is A.B1.b1 + +// let _ = A.x.v-> +// ^com +// B1.b1 seen from A is A.B1.b1 + +module C = { + type t = C +} + +module D = { + module C2 = { + type t2 = C2 + } + + type d = {v: C.t, v2: C2.t2} + let d = {v: C.C, v2: C2.C2} +} + +module E = { + type e = {v: D.d} + let e = {v: D.d} +} + +// let _ = E.e.v.v-> +// ^com +// C.t seen from D is C.t + +// let _ = E.e.v.v2-> +// ^com +// C2.t2 seen from D is D.C2.t2 diff --git a/analysis/tests/src/CompletionSupport.res b/analysis/tests/src/CompletionSupport.res new file mode 100644 index 000000000..371ca57b1 --- /dev/null +++ b/analysis/tests/src/CompletionSupport.res @@ -0,0 +1,6 @@ +module Test = { + type t = {name: int} + let add = (ax: t) => ax.name + 1 + let addSelf = (ax: t) => {name: ax.name + 1} + let make = (name: int): t => {name: name} +} diff --git a/analysis/tests/src/expected/Completion.res.txt b/analysis/tests/src/expected/Completion.res.txt index 1f0c66e56..9ee0144ed 100644 --- a/analysis/tests/src/expected/Completion.res.txt +++ b/analysis/tests/src/expected/Completion.res.txt @@ -1625,8 +1625,8 @@ Resolved opens 2 Completion.res Completion.res }] Complete src/Completion.res 405:22 -posCursor:[405:22] posNoWhite:[405:21] Found expr:[405:11->417:0] -Pexp_apply ...__ghost__[0:-1->0:-1] (...[405:11->415:1], ...[417:0->417:0]) +posCursor:[405:22] posNoWhite:[405:21] Found expr:[405:11->423:0] +Pexp_apply ...__ghost__[0:-1->0:-1] (...[405:11->415:1], ...[423:0->423:0]) posCursor:[405:22] posNoWhite:[405:21] Found expr:[405:11->415:1] Pexp_apply ...__ghost__[0:-1->0:-1] (...[405:11->405:19], ...[405:21->415:1]) posCursor:[405:22] posNoWhite:[405:21] Found expr:[405:21->415:1] @@ -1702,3 +1702,41 @@ Resolved opens 2 Completion.res Completion.res "documentation": null }] +Complete src/Completion.res 417:17 +posCursor:[417:17] posNoWhite:[417:16] Found expr:[417:11->417:17] +Completable: Cpath int->t +Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder +Resolved opens 2 Completion.res Completion.res +[{ + "label": "Belt.Int.toString", + "kind": 12, + "tags": [], + "detail": "int => string", + "documentation": {"kind": "markdown", "value": "\n Converts a given `int` to a `string`. Uses the JavaScript `String` constructor under the hood.\n\n ```res example\n Js.log(Belt.Int.toString(1) === \"1\") /* true */\n ```\n"} + }, { + "label": "Belt.Int.toFloat", + "kind": 12, + "tags": [], + "detail": "int => float", + "documentation": {"kind": "markdown", "value": "\n Converts a given `int` to a `float`.\n\n ```res example\n Js.log(Belt.Int.toFloat(1) === 1.0) /* true */\n ```\n"} + }] + +Complete src/Completion.res 420:19 +posCursor:[420:19] posNoWhite:[420:18] Found expr:[420:11->420:19] +Completable: Cpath float->t +Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder +Resolved opens 2 Completion.res Completion.res +[{ + "label": "Belt.Float.toInt", + "kind": 12, + "tags": [], + "detail": "float => int", + "documentation": {"kind": "markdown", "value": "\nConverts a given `float` to an `int`.\n\n```res example\nJs.log(Belt.Float.toInt(1.0) === 1) /* true */\n```\n"} + }, { + "label": "Belt.Float.toString", + "kind": 12, + "tags": [], + "detail": "float => string", + "documentation": {"kind": "markdown", "value": "\n Converts a given `float` to a `string`. Uses the JavaScript `String` constructor under the hood.\n\n ```res example\n Js.log(Belt.Float.toString(1.0) === \"1.0\") /* true */\n ```\n"} + }] + diff --git a/analysis/tests/src/expected/CompletionPipeChain.res.txt b/analysis/tests/src/expected/CompletionPipeChain.res.txt new file mode 100644 index 000000000..c7cff492f --- /dev/null +++ b/analysis/tests/src/expected/CompletionPipeChain.res.txt @@ -0,0 +1,258 @@ +Complete src/CompletionPipeChain.res 27:16 +posCursor:[27:16] posNoWhite:[27:15] Found expr:[27:11->0:-1] +Completable: Cpath Value[int]-> +[{ + "label": "Integer.toInt", + "kind": 12, + "tags": [], + "detail": "t => int", + "documentation": null + }, { + "label": "Integer.increment", + "kind": 12, + "tags": [], + "detail": "(t, int) => t", + "documentation": null + }, { + "label": "Integer.decrement", + "kind": 12, + "tags": [], + "detail": "(t, int => int) => t", + "documentation": null + }, { + "label": "Integer.make", + "kind": 12, + "tags": [], + "detail": "int => t", + "documentation": null + }] + +Complete src/CompletionPipeChain.res 30:23 +posCursor:[30:23] posNoWhite:[30:22] Found expr:[30:11->0:-1] +Completable: Cpath Value[toFlt](Nolabel)-> +[{ + "label": "SuperFloat.fromInteger", + "kind": 12, + "tags": [], + "detail": "Integer.t => t", + "documentation": null + }, { + "label": "SuperFloat.toInteger", + "kind": 12, + "tags": [], + "detail": "t => Integer.t", + "documentation": null + }] + +Complete src/CompletionPipeChain.res 33:38 +posCursor:[33:38] posNoWhite:[33:37] Found expr:[33:11->0:-1] +Completable: Cpath Value[Integer, increment](Nolabel, Nolabel)-> +[{ + "label": "Integer.toInt", + "kind": 12, + "tags": [], + "detail": "t => int", + "documentation": null + }, { + "label": "Integer.increment", + "kind": 12, + "tags": [], + "detail": "(t, int) => t", + "documentation": null + }, { + "label": "Integer.decrement", + "kind": 12, + "tags": [], + "detail": "(t, int => int) => t", + "documentation": null + }, { + "label": "Integer.make", + "kind": 12, + "tags": [], + "detail": "int => t", + "documentation": null + }] + +Complete src/CompletionPipeChain.res 36:38 +posCursor:[36:38] posNoWhite:[36:37] Found expr:[36:11->0:-1] +Completable: Cpath Value[Integer, increment](Nolabel, Nolabel)-> +[{ + "label": "Integer.toInt", + "kind": 12, + "tags": [], + "detail": "t => int", + "documentation": null + }, { + "label": "Integer.increment", + "kind": 12, + "tags": [], + "detail": "(t, int) => t", + "documentation": null + }, { + "label": "Integer.decrement", + "kind": 12, + "tags": [], + "detail": "(t, int => int) => t", + "documentation": null + }, { + "label": "Integer.make", + "kind": 12, + "tags": [], + "detail": "int => t", + "documentation": null + }] + +Complete src/CompletionPipeChain.res 39:47 +posCursor:[39:47] posNoWhite:[39:46] Found expr:[39:11->0:-1] +Completable: Cpath Value[Integer, decrement](Nolabel, Nolabel)-> +[{ + "label": "Integer.toInt", + "kind": 12, + "tags": [], + "detail": "t => int", + "documentation": null + }, { + "label": "Integer.increment", + "kind": 12, + "tags": [], + "detail": "(t, int) => t", + "documentation": null + }, { + "label": "Integer.decrement", + "kind": 12, + "tags": [], + "detail": "(t, int => int) => t", + "documentation": null + }, { + "label": "Integer.make", + "kind": 12, + "tags": [], + "detail": "int => t", + "documentation": null + }] + +Complete src/CompletionPipeChain.res 42:69 +posCursor:[42:69] posNoWhite:[42:68] Found expr:[42:11->0:-1] +Completable: Cpath Value[Integer, decrement](Nolabel, Nolabel)-> +[{ + "label": "Integer.toInt", + "kind": 12, + "tags": [], + "detail": "t => int", + "documentation": null + }, { + "label": "Integer.increment", + "kind": 12, + "tags": [], + "detail": "(t, int) => t", + "documentation": null + }, { + "label": "Integer.decrement", + "kind": 12, + "tags": [], + "detail": "(t, int => int) => t", + "documentation": null + }, { + "label": "Integer.make", + "kind": 12, + "tags": [], + "detail": "int => t", + "documentation": null + }] + +Complete src/CompletionPipeChain.res 45:62 +posCursor:[45:62] posNoWhite:[45:61] Found expr:[45:11->0:-1] +Completable: Cpath Value[SuperFloat, fromInteger](Nolabel)-> +[{ + "label": "SuperFloat.fromInteger", + "kind": 12, + "tags": [], + "detail": "Integer.t => t", + "documentation": null + }, { + "label": "SuperFloat.toInteger", + "kind": 12, + "tags": [], + "detail": "t => Integer.t", + "documentation": null + }] + +Complete src/CompletionPipeChain.res 48:63 +posCursor:[48:63] posNoWhite:[48:62] Found expr:[48:11->48:63] +Completable: Cpath Value[SuperFloat, fromInteger](Nolabel)->t +[{ + "label": "SuperFloat.toInteger", + "kind": 12, + "tags": [], + "detail": "t => Integer.t", + "documentation": null + }] + +Complete src/CompletionPipeChain.res 51:82 +posCursor:[51:82] posNoWhite:[51:81] Found expr:[51:11->0:-1] +Completable: Cpath Value[CompletionSupport, Test, make](Nolabel)-> +[{ + "label": "CompletionSupport.Test.add", + "kind": 12, + "tags": [], + "detail": "t => int", + "documentation": null + }, { + "label": "CompletionSupport.Test.addSelf", + "kind": 12, + "tags": [], + "detail": "t => t", + "documentation": null + }, { + "label": "CompletionSupport.Test.make", + "kind": 12, + "tags": [], + "detail": "int => t", + "documentation": null + }] + +Complete src/CompletionPipeChain.res 54:78 +posCursor:[54:78] posNoWhite:[54:77] Found expr:[54:11->0:-1] +Completable: Cpath Value[CompletionSupport, Test, addSelf](Nolabel, Nolabel)-> +[{ + "label": "CompletionSupport.Test.add", + "kind": 12, + "tags": [], + "detail": "t => int", + "documentation": null + }, { + "label": "CompletionSupport.Test.addSelf", + "kind": 12, + "tags": [], + "detail": "t => t", + "documentation": null + }, { + "label": "CompletionSupport.Test.make", + "kind": 12, + "tags": [], + "detail": "int => t", + "documentation": null + }] + +Complete src/CompletionPipeChain.res 58:5 +posCursor:[58:5] posNoWhite:[58:4] Found expr:[57:8->0:-1] +Completable: Cpath Value[Js, Array2, forEach](Nolabel, Nolabel)-> +[] + +Complete src/CompletionPipeChain.res 62:6 +posCursor:[62:6] posNoWhite:[62:5] Found expr:[61:8->62:6] +Completable: Cpath Value[Belt, Array, reduce](Nolabel, Nolabel, Nolabel)->t +[{ + "label": "Belt.Int.toString", + "kind": 12, + "tags": [], + "detail": "int => string", + "documentation": {"kind": "markdown", "value": "\n Converts a given `int` to a `string`. Uses the JavaScript `String` constructor under the hood.\n\n ```res example\n Js.log(Belt.Int.toString(1) === \"1\") /* true */\n ```\n"} + }, { + "label": "Belt.Int.toFloat", + "kind": 12, + "tags": [], + "detail": "int => float", + "documentation": {"kind": "markdown", "value": "\n Converts a given `int` to a `float`.\n\n ```res example\n Js.log(Belt.Int.toFloat(1) === 1.0) /* true */\n ```\n"} + }] + diff --git a/analysis/tests/src/expected/CompletionPipeSubmodules.res.txt b/analysis/tests/src/expected/CompletionPipeSubmodules.res.txt new file mode 100644 index 000000000..a699eb8e6 --- /dev/null +++ b/analysis/tests/src/expected/CompletionPipeSubmodules.res.txt @@ -0,0 +1,56 @@ +Complete src/CompletionPipeSubmodules.res 12:20 +posCursor:[12:20] posNoWhite:[12:19] Found expr:[12:11->20:8] +Completable: Cpath Value[A, B1, xx]-> +[{ + "label": "A.B1.xx", + "kind": 12, + "tags": [], + "detail": "b1", + "documentation": null + }, { + "label": "A.B1.B1", + "kind": 4, + "tags": [], + "detail": "B1\n\ntype b1 = B1", + "documentation": null + }] + +Complete src/CompletionPipeSubmodules.res 16:18 +posCursor:[16:18] posNoWhite:[16:17] Found expr:[16:11->20:8] +Completable: Cpath Value[A, x].v-> +[{ + "label": "A.B1.xx", + "kind": 12, + "tags": [], + "detail": "b1", + "documentation": null + }, { + "label": "A.B1.B1", + "kind": 4, + "tags": [], + "detail": "B1\n\ntype b1 = B1", + "documentation": null + }] + +Complete src/CompletionPipeSubmodules.res 38:20 +posCursor:[38:20] posNoWhite:[38:19] Found expr:[38:11->0:-1] +Completable: Cpath Value[E, e].v.v-> +[{ + "label": "C.C", + "kind": 4, + "tags": [], + "detail": "C\n\ntype t = C", + "documentation": null + }] + +Complete src/CompletionPipeSubmodules.res 42:21 +posCursor:[42:21] posNoWhite:[42:20] Found expr:[42:11->0:-1] +Completable: Cpath Value[E, e].v.v2-> +[{ + "label": "D.C2.C2", + "kind": 4, + "tags": [], + "detail": "C2\n\ntype t2 = C2", + "documentation": null + }] + diff --git a/analysis/tests/src/expected/CompletionSupport.res.txt b/analysis/tests/src/expected/CompletionSupport.res.txt new file mode 100644 index 000000000..e69de29bb From 4cbee6c4c918477eb99ce19051e30f5994ef17c3 Mon Sep 17 00:00:00 2001 From: Pedro Castro Date: Mon, 26 Dec 2022 15:01:57 -0300 Subject: [PATCH 6/6] sync with master --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1e251278..44f690bdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,6 @@ - Make pipe completion work reliably when the path resolution needs to traverse submodules https://github.com/rescript-lang/rescript-vscode/pull/663 - Make pipe completion work (after saving/compiling) when the return type of a function call is unknown until compilation https://github.com/rescript-lang/rescript-vscode/pull/662 - Add pipe completion for `int` and `float` constants https://github.com/rescript-lang/rescript-vscode/pull/664 -- Migrate `prepareRename` to analysis. https://github.com/rescript-lang/rescript-vscode/pull/657 #### :bug: Bug Fix