From 565edda9d5128fd0b1a88ba61c329954d16df125 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Tue, 5 Nov 2024 20:19:48 +0100 Subject: [PATCH 01/34] wip complete pipeable functions from dot completion on record when record is type t of module --- analysis/src/CompletionBackEnd.ml | 102 +++++++++++++++--- analysis/src/SharedTypes.ml | 1 + analysis/src/TypeUtils.ml | 12 ++- analysis/tests/src/CompletionFromModule.res | 14 +++ .../tests/src/expected/Completion.res.txt | 13 +++ .../expected/CompletionExpressions.res.txt | 3 + .../src/expected/CompletionFromModule.res.txt | 31 ++++++ .../expected/CompletionInferValues.res.txt | 8 ++ .../src/expected/CompletionPipeChain.res.txt | 2 + .../expected/CompletionPipeSubmodules.res.txt | 5 + analysis/tests/src/expected/Hover.res.txt | 9 ++ .../src/expected/RecordCompletion.res.txt | 3 + 12 files changed, 187 insertions(+), 16 deletions(-) create mode 100644 analysis/tests/src/CompletionFromModule.res create mode 100644 analysis/tests/src/expected/CompletionFromModule.res.txt diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 5d7542e83..aeaea88c9 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -970,30 +970,103 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact with | Some (TypeExpr typ, env) -> ( match typ |> TypeUtils.extractRecordType ~env ~package with - | Some (env, fields, typDecl) -> + | Some (env, fields, typDecl, path) -> Some ( env, fields, - typDecl.item.decl |> Shared.declToString typDecl.name.txt ) + typDecl.item.decl |> Shared.declToString typDecl.name.txt, + Some path ) | None -> None) | Some (ExtractedType typ, env) -> ( match typ with - | Trecord {fields} -> - Some (env, fields, typ |> TypeUtils.extractedTypeToString) + | Trecord {fields; path} -> + Some (env, fields, typ |> TypeUtils.extractedTypeToString, path) | _ -> None) | None -> None in match extracted with | None -> [] - | Some (env, fields, recordAsString) -> - fields - |> Utils.filterMap (fun field -> - if Utils.checkName field.fname.txt ~prefix:fieldName ~exact then - Some - (Completion.create field.fname.txt ~env - ?deprecated:field.deprecated ~docstring:field.docstring - ~kind:(Completion.Field (field, recordAsString))) - else None)) + | Some (env, fields, recordAsString, path) -> + let pipeCompletionsForModule = + match path with + | Some path -> + let completionPath = + (* Remove the last part of the path since we're only after the parent module *) + match + path |> SharedTypes.pathIdentToString |> String.split_on_char '.' + |> List.rev + with + | _ :: rest -> rest + | [] -> [] + in + (* Most of this is copied from the pipe completion code. Should probably be unified. *) + let completions = + completionPath @ [fieldName] + |> getCompletionsForPath ~debug ~completionContext:Value + ~exact:false ~opens ~full ~pos ~env ~scope + in + let completionPathMinusOpens = + TypeUtils.removeOpensFromCompletionPath ~rawOpens ~package + completionPath + |> String.concat "." + in + let completionName name = + if completionPathMinusOpens = "" then name + else completionPathMinusOpens ^ "." ^ name + in + (* Find all functions in the module that takes type t *) + let rec fnTakesTypeT t = + match t.Types.desc with + | Tlink t1 + | Tsubst t1 + | Tpoly (t1, []) + | Tconstr (Pident {name = "function$"}, [t1; _], _) -> + fnTakesTypeT t1 + | Tarrow _ -> ( + match + TypeUtils.extractFunctionType ~env ~package:full.package t + with + | ( (Nolabel, {desc = Tconstr (Path.Pident {name = "t"}, _, _)}) + :: _, + _ ) -> + true + | _ -> false) + | _ -> false + in + completions + |> List.filter_map (fun (completion : Completion.t) -> + match completion.kind with + | Value t when fnTakesTypeT t -> + let name = completionName completion.name in + let nameWithPipe = "->" ^ name in + (* TODO: We need to add support for setting the text insertion location explicitly, + so we can account for the dot that triggered the completion, but that we want + removed when inserting the pipe. This means we also need to track the loc of + the dot + the identifier that we're filtering with (fieldName here). + That should be easy to do by extending CPField. *) + Some + { + completion with + name = nameWithPipe; + sortText = + Some + (name |> String.split_on_char '.' |> List.rev + |> List.hd); + insertText = Some nameWithPipe; + env; + } + | _ -> None) + | None -> [] + in + pipeCompletionsForModule + @ (fields + |> Utils.filterMap (fun field -> + if Utils.checkName field.fname.txt ~prefix:fieldName ~exact then + Some + (Completion.create field.fname.txt ~env + ?deprecated:field.deprecated ~docstring:field.docstring + ~kind:(Completion.Field (field, recordAsString))) + else None))) | CPObj (cp, label) -> ( (* TODO: Also needs to support ExtractedType *) if Debug.verbose () then print_endline "[ctx_path]--> CPObj"; @@ -1936,6 +2009,7 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover completable = { env; definition = `NameOnly "jsxConfig"; + path = None; fields = [ mkField ~name:"version" ~primitive:"int"; @@ -1965,6 +2039,7 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover completable = Trecord { env; + path = None; definition = `NameOnly "importAttributesConfig"; fields = [mkField ~name:"type_" ~primitive:"string"]; } @@ -1973,6 +2048,7 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover completable = Trecord { env; + path = None; definition = `NameOnly "moduleConfig"; fields = [ diff --git a/analysis/src/SharedTypes.ml b/analysis/src/SharedTypes.ml index aa3dffb1e..837253695 100644 --- a/analysis/src/SharedTypes.ml +++ b/analysis/src/SharedTypes.ml @@ -354,6 +354,7 @@ and completionType = | Trecord of { env: QueryEnv.t; fields: field list; + path: Path.t option; definition: [ `NameOnly of string (** When we only have the name, like when pulling the record from a declared type. *) diff --git a/analysis/src/TypeUtils.ml b/analysis/src/TypeUtils.ml index 6701b57a7..72f5c089b 100644 --- a/analysis/src/TypeUtils.ml +++ b/analysis/src/TypeUtils.ml @@ -213,7 +213,7 @@ let rec extractRecordType ~env ~package (t : Types.type_expr) = in {field with typ = fieldTyp}) in - Some (env, fields, typ) + Some (env, fields, typ, path) | Some ( env, {item = {decl = {type_manifest = Some t1; type_params = typeParams}}} @@ -390,7 +390,13 @@ let rec extractType ?(printOpeningDebug = true) maybeSetTypeArgCtx ~typeParams:decl.type_params ~typeArgs env in Some - ( Trecord {env = envFromDeclaration; fields; definition = `TypeExpr t}, + ( Trecord + { + env = envFromDeclaration; + path = Some path; + fields; + definition = `TypeExpr t; + }, typeArgContext ) | Some (envFromDeclaration, {item = {name = "t"; decl = {type_params}}}) -> let typeArgContext = @@ -571,7 +577,7 @@ let extractTypeFromResolvedType (typ : Type.t) ~env ~full = match typ.kind with | Tuple items -> Some (Tuple (env, items, Ctype.newty (Ttuple items))) | Record fields -> - Some (Trecord {env; fields; definition = `NameOnly typ.name}) + Some (Trecord {env; fields; path = None; definition = `NameOnly typ.name}) | Variant constructors -> Some (Tvariant diff --git a/analysis/tests/src/CompletionFromModule.res b/analysis/tests/src/CompletionFromModule.res new file mode 100644 index 000000000..70ecada79 --- /dev/null +++ b/analysis/tests/src/CompletionFromModule.res @@ -0,0 +1,14 @@ +module SomeModule = { + type t = {name: string} + + @get external getName: t => string = "name" + + let thisShouldNotBeCompletedFor = () => "hi" +} + +let n = {SomeModule.name: "hello"} + +// ^dv+ +// n. +// ^com +// ^dv- diff --git a/analysis/tests/src/expected/Completion.res.txt b/analysis/tests/src/expected/Completion.res.txt index ccadf462f..acae3c53f 100644 --- a/analysis/tests/src/expected/Completion.res.txt +++ b/analysis/tests/src/expected/Completion.res.txt @@ -728,6 +728,7 @@ Resolved opens 1 pervasives ContextPath Value[r]."" ContextPath Value[r] Path r +Path [{ "label": "x", "kind": 5, @@ -751,6 +752,7 @@ Resolved opens 1 pervasives ContextPath Value[Objects, Rec, recordVal]."" ContextPath Value[Objects, Rec, recordVal] Path Objects.Rec.recordVal +Path [{ "label": "xx", "kind": 5, @@ -833,6 +835,8 @@ ContextPath Value[q].aa."" ContextPath Value[q].aa ContextPath Value[q] Path q +Path aa +Path [{ "label": "x", "kind": 5, @@ -857,6 +861,8 @@ ContextPath Value[q].aa.n ContextPath Value[q].aa ContextPath Value[q] Path q +Path aa +Path n [{ "label": "name", "kind": 5, @@ -1081,6 +1087,7 @@ ContextPath Value[FAO, forAutoObject]["forAutoLabel"]."" ContextPath Value[FAO, forAutoObject]["forAutoLabel"] ContextPath Value[FAO, forAutoObject] Path FAO.forAutoObject +Path FAR. [{ "label": "forAuto", "kind": 5, @@ -1106,6 +1113,7 @@ ContextPath Value[FAO, forAutoObject]["forAutoLabel"].forAuto ContextPath Value[FAO, forAutoObject]["forAutoLabel"] ContextPath Value[FAO, forAutoObject] Path FAO.forAutoObject +Path FAR.forAuto CPPipe env:Completion envFromCompletionItem:Completion.FAR CPPipe type path:ForAuto.t CPPipe pathFromEnv:ForAuto found:false @@ -1189,6 +1197,7 @@ Resolved opens 3 pervasives Completion.res Completion.res ContextPath Value[_z]."" ContextPath Value[_z] Path _z +Path [{ "label": "x", "kind": 5, @@ -1347,6 +1356,7 @@ Resolved opens 3 pervasives Completion.res Completion.res ContextPath Value[funRecord].someFun ContextPath Value[funRecord] Path funRecord +Path someFun Found type for function (~name: string) => unit [{ "label": "name", @@ -1367,6 +1377,7 @@ ContextPath Value[retAA](Nolabel)."" ContextPath Value[retAA](Nolabel) ContextPath Value[retAA] Path retAA +Path [{ "label": "x", "kind": 5, @@ -1897,6 +1908,7 @@ Resolved opens 3 pervasives Completion.res Completion.res ContextPath Value[funRecord]."" ContextPath Value[funRecord] Path funRecord +Path [{ "label": "someFun", "kind": 5, @@ -2153,6 +2165,7 @@ Resolved opens 3 pervasives Completion.res Completion.res ContextPath Value[rWithDepr].so ContextPath Value[rWithDepr] Path rWithDepr +Path so [{ "label": "someInt", "kind": 5, diff --git a/analysis/tests/src/expected/CompletionExpressions.res.txt b/analysis/tests/src/expected/CompletionExpressions.res.txt index 2d5c7438c..dece6fbc6 100644 --- a/analysis/tests/src/expected/CompletionExpressions.res.txt +++ b/analysis/tests/src/expected/CompletionExpressions.res.txt @@ -946,6 +946,7 @@ Resolved opens 1 pervasives ContextPath Value[fff].someOpt ContextPath Value[fff] Path fff +Path someOpt [{ "label": "someOptField", "kind": 5, @@ -1415,6 +1416,7 @@ Resolved opens 2 pervasives CompletionSupport.res ContextPath Value[someTyp]."" ContextPath Value[someTyp] Path someTyp +Path [{ "label": "test", "kind": 5, @@ -1470,6 +1472,7 @@ Resolved opens 2 pervasives CompletionSupport.res ContextPath Value[someTyp]."" ContextPath Value[someTyp] Path someTyp +Path [{ "label": "test", "kind": 5, diff --git a/analysis/tests/src/expected/CompletionFromModule.res.txt b/analysis/tests/src/expected/CompletionFromModule.res.txt new file mode 100644 index 000000000..543684363 --- /dev/null +++ b/analysis/tests/src/expected/CompletionFromModule.res.txt @@ -0,0 +1,31 @@ + +Complete src/CompletionFromModule.res 11:5 +posCursor:[11:5] posNoWhite:[11:4] Found expr:[11:3->11:5] +Pexp_field [11:3->11:4] _:[15:0->11:5] +[set_result] set new result to Cpath Value[n]."" +Completable: Cpath Value[n]."" +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[n]."" +[ctx_path]--> CPField +ContextPath Value[n] +[ctx_path]--> CPId +Path n +Path SomeModule. +[{ + "label": "->SomeModule.getName", + "kind": 12, + "tags": [], + "detail": "t => string", + "documentation": null, + "sortText": "getName", + "insertText": "->SomeModule.getName" + }, { + "label": "name", + "kind": 5, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype t = {name: string}\n```"} + }] + + diff --git a/analysis/tests/src/expected/CompletionInferValues.res.txt b/analysis/tests/src/expected/CompletionInferValues.res.txt index 356a23819..748cde761 100644 --- a/analysis/tests/src/expected/CompletionInferValues.res.txt +++ b/analysis/tests/src/expected/CompletionInferValues.res.txt @@ -37,6 +37,7 @@ Path x ContextPath Value[getSomeRecord](Nolabel) ContextPath Value[getSomeRecord] Path getSomeRecord +Path [{ "label": "name", "kind": 5, @@ -65,6 +66,7 @@ Path x ContextPath Value[getSomeRecord](Nolabel) ContextPath Value[getSomeRecord] Path getSomeRecord +Path [{ "label": "name", "kind": 5, @@ -98,6 +100,7 @@ ContextPath CArgument CArgument Value[someFnWithCallback]($0)(~someRecord) ContextPath CArgument Value[someFnWithCallback]($0) ContextPath Value[someFnWithCallback] Path someFnWithCallback +Path [{ "label": "name", "kind": 5, @@ -133,6 +136,7 @@ ContextPath Value[aliasedFn] Path aliasedFn ContextPath Value[someFnWithCallback] Path someFnWithCallback +Path [{ "label": "name", "kind": 5, @@ -377,6 +381,7 @@ ContextPath Value[x] Path x ContextPath Type[someRecordWithNestedStuff] Path someRecordWithNestedStuff +Path [{ "label": "name", "kind": 5, @@ -405,6 +410,7 @@ ContextPath Value[x] Path x ContextPath Type[someRecordWithNestedStuff] Path someRecordWithNestedStuff +Path [{ "label": "someRecord", "kind": 5, @@ -427,6 +433,7 @@ ContextPath Value[x] Path x ContextPath Type[someRecordWithNestedStuff] Path someRecordWithNestedStuff +Path [{ "label": "name", "kind": 5, @@ -845,6 +852,7 @@ ContextPath CArgument CArgument Value[CompletionSupport2, makeRenderer](~render) ContextPath CArgument Value[CompletionSupport2, makeRenderer](~render) ContextPath Value[CompletionSupport2, makeRenderer] Path CompletionSupport2.makeRenderer +Path Nested.CompletionSupport. [{ "label": "root", "kind": 5, diff --git a/analysis/tests/src/expected/CompletionPipeChain.res.txt b/analysis/tests/src/expected/CompletionPipeChain.res.txt index 91f6ca0e1..7430ff644 100644 --- a/analysis/tests/src/expected/CompletionPipeChain.res.txt +++ b/analysis/tests/src/expected/CompletionPipeChain.res.txt @@ -452,6 +452,8 @@ ContextPath Value[props].support.root ContextPath Value[props].support ContextPath Value[props] Path props +Path Internal.CompletionSupport2.support +Path Nested.CompletionSupport.root CPPipe env:CompletionPipeChain envFromCompletionItem:CompletionSupport.Nested CPPipe type path:ReactDOM.Client.Root.t CPPipe pathFromEnv:ReactDOM.Client.Root found:false diff --git a/analysis/tests/src/expected/CompletionPipeSubmodules.res.txt b/analysis/tests/src/expected/CompletionPipeSubmodules.res.txt index 73f9ab4a5..962003d73 100644 --- a/analysis/tests/src/expected/CompletionPipeSubmodules.res.txt +++ b/analysis/tests/src/expected/CompletionPipeSubmodules.res.txt @@ -33,6 +33,7 @@ ContextPath Value[A, x].v-> ContextPath Value[A, x].v ContextPath Value[A, x] Path A.x +Path v CPPipe env:CompletionPipeSubmodules envFromCompletionItem:CompletionPipeSubmodules.A CPPipe type path:B1.b1 CPPipe pathFromEnv:A.B1 found:true @@ -61,6 +62,8 @@ ContextPath Value[E, e].v.v ContextPath Value[E, e].v ContextPath Value[E, e] Path E.e +Path v +Path D.v CPPipe env:CompletionPipeSubmodules envFromCompletionItem:CompletionPipeSubmodules.D CPPipe type path:C.t CPPipe pathFromEnv:C found:false @@ -83,6 +86,8 @@ ContextPath Value[E, e].v.v2 ContextPath Value[E, e].v ContextPath Value[E, e] Path E.e +Path v +Path D.v2 CPPipe env:CompletionPipeSubmodules envFromCompletionItem:CompletionPipeSubmodules.D CPPipe type path:C2.t2 CPPipe pathFromEnv:D.C2 found:true diff --git a/analysis/tests/src/expected/Hover.res.txt b/analysis/tests/src/expected/Hover.res.txt index 725c5a93a..1b2ea8009 100644 --- a/analysis/tests/src/expected/Hover.res.txt +++ b/analysis/tests/src/expected/Hover.res.txt @@ -124,6 +124,8 @@ ContextPath Value[x1].content."" ContextPath Value[x1].content ContextPath Value[x1] Path x1 +Path content +Path [{ "label": "age", "kind": 5, @@ -142,6 +144,8 @@ ContextPath Value[x2].content."" ContextPath Value[x2].content ContextPath Value[x2] Path x2 +Path content +Path [{ "label": "age", "kind": 5, @@ -160,6 +164,8 @@ ContextPath Value[y1].content."" ContextPath Value[y1].content ContextPath Value[y1] Path y1 +Path content +Path [{ "label": "age", "kind": 5, @@ -178,6 +184,8 @@ ContextPath Value[y2].content."" ContextPath Value[y2].content ContextPath Value[y2] Path y2 +Path content +Path [{ "label": "age", "kind": 5, @@ -221,6 +229,7 @@ Resolved opens 1 pervasives ContextPath Value[x].someField ContextPath Value[x] Path x +Path someField Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives {"contents": {"kind": "markdown", "value": " Mighty fine field here. \n\n```rescript\nbool\n```"}} diff --git a/analysis/tests/src/expected/RecordCompletion.res.txt b/analysis/tests/src/expected/RecordCompletion.res.txt index 90787363b..7ea242295 100644 --- a/analysis/tests/src/expected/RecordCompletion.res.txt +++ b/analysis/tests/src/expected/RecordCompletion.res.txt @@ -7,6 +7,7 @@ ContextPath Value[t].n->m ContextPath Value[t].n ContextPath Value[t] Path t +Path n CPPipe env:RecordCompletion Path Js.Array2.m [{ @@ -33,6 +34,8 @@ ContextPath Value[t2].n2.n ContextPath Value[t2].n2 ContextPath Value[t2] Path t2 +Path n2 +Path n CPPipe env:RecordCompletion Path Js.Array2.m [{ From 4347fa3ccc49042faed24dced3e2e74408e908cd Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Wed, 6 Nov 2024 21:30:56 +0100 Subject: [PATCH 02/34] correctly insert completion item text removing the dot when selecting a pipe function --- analysis/src/CompletionBackEnd.ml | 29 ++++++-- analysis/src/CompletionFrontEnd.ml | 72 +++++++++++++------ analysis/src/Protocol.ml | 22 +++++- analysis/src/SharedTypes.ml | 16 +++-- .../src/expected/CompletionFromModule.res.txt | 5 +- 5 files changed, 112 insertions(+), 32 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index aeaea88c9..d31794a3e 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -653,7 +653,8 @@ let rec digToRecordFieldsForCompletion ~debug ~package ~opens ~full ~pos ~env | {kind = Type {kind = Record fields}} :: _ -> Some fields | _ -> None -let mkItem ?data name ~kind ~detail ~deprecated ~docstring = +let mkItem ?data ?(range : Location.t option) name ~kind ~detail ~deprecated + ~docstring = let docContent = (match deprecated with | None -> "" @@ -682,6 +683,23 @@ let mkItem ?data name ~kind ~detail ~deprecated ~docstring = insertTextFormat = None; filterText = None; data; + range = + (match range with + | None -> None + | Some range -> + Some + { + start = + { + line = range.loc_start.pos_lnum - 1; + character = range.loc_start.pos_cnum - range.loc_start.pos_bol; + }; + end_ = + { + line = range.loc_end.pos_lnum - 1; + character = range.loc_end.pos_cnum - range.loc_end.pos_bol; + }; + }); } let completionToItem @@ -696,9 +714,10 @@ let completionToItem filterText; detail; env; + range; } ~full = let item = - mkItem name + mkItem name ?range ?data:(kindToData (full.file.uri |> Uri.toPath) kind) ~kind:(Completion.kindToInt kind) ~deprecated @@ -950,13 +969,14 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact [Completion.create "dummy" ~env ~kind:(Completion.Value retType)] | _ -> []) | _ -> []) - | CPField (CPId {path; completionContext = Module}, fieldName) -> + | CPField {contextPath = CPId {path; completionContext = Module}; fieldName} + -> if Debug.verbose () then print_endline "[ctx_path]--> CPField: M.field"; (* M.field *) path @ [fieldName] |> getCompletionsForPath ~debug ~opens ~full ~pos ~exact ~completionContext:Field ~env ~scope - | CPField (cp, fieldName) -> ( + | CPField {contextPath = cp; fieldName; fieldNameLoc} -> ( if Debug.verbose () then print_endline "[ctx_path]--> CPField"; let completionsForCtxPath = cp @@ -1054,6 +1074,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact |> List.hd); insertText = Some nameWithPipe; env; + range = Some fieldNameLoc; } | _ -> None) | None -> [] diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index f5957a8ed..f963a2891 100644 --- a/analysis/src/CompletionFrontEnd.ml +++ b/analysis/src/CompletionFrontEnd.ml @@ -222,21 +222,26 @@ let rec exprToContextPathInner (e : Parsetree.expression) = | Pexp_ident {txt; loc} -> Some (CPId {path = Utils.flattenLongIdent txt; completionContext = Value; loc}) - | Pexp_field (e1, {txt = Lident name}) -> ( + | Pexp_field (e1, {txt = Lident name; loc}) -> ( match exprToContextPath e1 with - | Some contextPath -> Some (CPField (contextPath, name)) + | Some contextPath -> + Some (CPField {contextPath; fieldName = name; fieldNameLoc = loc}) | _ -> None) | Pexp_field (_, {loc; txt = Ldot (lid, name)}) -> (* Case x.M.field ignore the x part *) Some (CPField - ( CPId - { - path = Utils.flattenLongIdent lid; - completionContext = Module; - loc; - }, - name )) + { + contextPath = + CPId + { + path = Utils.flattenLongIdent lid; + completionContext = Module; + loc; + }; + fieldName = name; + fieldNameLoc = loc; + }) | Pexp_send (e1, {txt}) -> ( match exprToContextPath e1 with | None -> None @@ -1130,29 +1135,54 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor | Lident name -> ( match exprToContextPath e with | Some contextPath -> - let contextPath = Completable.CPField (contextPath, name) in + let contextPath = + Completable.CPField + { + contextPath; + fieldName = name; + fieldNameLoc = fieldName.loc; + } + in setResult (Cpath contextPath) | None -> ()) | Ldot (id, name) -> (* Case x.M.field ignore the x part *) let contextPath = Completable.CPField - ( CPId - { - loc = fieldName.loc; - path = Utils.flattenLongIdent id; - completionContext = Module; - }, - if blankAfterCursor = Some '.' then - (* x.M. field ---> M. *) "" - else if name = "_" then "" - else name ) + { + contextPath = + CPId + { + loc = fieldName.loc; + path = Utils.flattenLongIdent id; + completionContext = Module; + }; + fieldName = + (if blankAfterCursor = Some '.' then + (* x.M. field ---> M. *) "" + else if name = "_" then "" + else name); + fieldNameLoc = fieldName.loc; + } in setResult (Cpath contextPath) | Lapply _ -> () else if Loc.end_ e.pexp_loc = posBeforeCursor then match exprToContextPath e with - | Some contextPath -> setResult (Cpath (CPField (contextPath, ""))) + | Some contextPath -> + setResult + (Cpath + (CPField + { + contextPath; + fieldName = ""; + fieldNameLoc = + { + loc_start = e.pexp_loc.loc_end; + loc_end = e.pexp_loc.loc_end; + loc_ghost = false; + }; + })) | None -> ()) | Pexp_apply ({pexp_desc = Pexp_ident compName}, args) when Res_parsetree_viewer.is_jsx_expression expr -> diff --git a/analysis/src/Protocol.ml b/analysis/src/Protocol.ml index 8297b3505..7e42b2964 100644 --- a/analysis/src/Protocol.ml +++ b/analysis/src/Protocol.ml @@ -51,6 +51,7 @@ type completionItem = { insertText: string option; documentation: markupContent option; data: (string * string) list option; + range: range option; } type location = {uri: string; range: range} @@ -142,7 +143,10 @@ let stringifyCompletionItem c = | Some doc -> stringifyMarkupContent doc) ); ("sortText", optWrapInQuotes c.sortText); ("filterText", optWrapInQuotes c.filterText); - ("insertText", optWrapInQuotes c.insertText); + ( "insertText", + match c.range with + | Some _ -> None + | None -> optWrapInQuotes c.insertText ); ( "insertTextFormat", match c.insertTextFormat with | None -> None @@ -156,6 +160,20 @@ let stringifyCompletionItem c = (fields |> List.map (fun (key, value) -> (key, Some (wrapInQuotes value))) |> stringifyObject ~indentation:2) ); + ( "textEdit", + match c.range with + | Some range -> + Some + (stringifyObject + [ + ("range", Some (stringifyRange range)); + ( "newText", + optWrapInQuotes + (match c.insertText with + | None -> Some c.label + | v -> v) ); + ]) + | None -> None ); ] let stringifyHover value = @@ -282,7 +300,7 @@ let stringifyCodeAction ca = (wrapInQuotes (codeActionKindToString ca.codeActionKind)) (ca.edit |> stringifyCodeActionEdit) -let stringifyHint hint = +let stringifyHint (hint : inlayHint) = Printf.sprintf {|{ "position": %s, diff --git a/analysis/src/SharedTypes.ml b/analysis/src/SharedTypes.ml index 837253695..1886f86fa 100644 --- a/analysis/src/SharedTypes.ml +++ b/analysis/src/SharedTypes.ml @@ -612,7 +612,11 @@ module Completable = struct completionContext: completionContext; loc: Location.t; } - | CPField of contextPath * string + | CPField of { + contextPath: contextPath; + fieldName: string; + fieldNameLoc: Location.t; + } | CPObj of contextPath * string | CPAwait of contextPath | CPPipe of { @@ -696,7 +700,8 @@ module Completable = struct | CPArray None -> "array" | CPId {path; completionContext} -> completionContextToString completionContext ^ list path - | CPField (cp, s) -> contextPathToString cp ^ "." ^ str s + | CPField {contextPath = cp; fieldName = s} -> + contextPathToString cp ^ "." ^ str s | CPObj (cp, s) -> contextPathToString cp ^ "[\"" ^ s ^ "\"]" | CPPipe {contextPath; id; inJsx} -> contextPathToString contextPath @@ -808,10 +813,12 @@ module Completion = struct detail: string option; typeArgContext: typeArgContext option; data: (string * string) list option; + range: Location.t option; } - let create ?data ?typeArgContext ?(includesSnippets = false) ?insertText ~kind - ~env ?sortText ?deprecated ?filterText ?detail ?(docstring = []) name = + let create ?range ?data ?typeArgContext ?(includesSnippets = false) + ?insertText ~kind ~env ?sortText ?deprecated ?filterText ?detail + ?(docstring = []) name = { name; env; @@ -826,6 +833,7 @@ module Completion = struct detail; typeArgContext; data; + range; } (* https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion *) diff --git a/analysis/tests/src/expected/CompletionFromModule.res.txt b/analysis/tests/src/expected/CompletionFromModule.res.txt index 543684363..2f1c04b06 100644 --- a/analysis/tests/src/expected/CompletionFromModule.res.txt +++ b/analysis/tests/src/expected/CompletionFromModule.res.txt @@ -19,7 +19,10 @@ Path SomeModule. "detail": "t => string", "documentation": null, "sortText": "getName", - "insertText": "->SomeModule.getName" + "textEdit": { + "range": {"start": {"line": 11, "character": 4}, "end": {"line": 11, "character": 4}}, + "newText": "->SomeModule.getName" + } }, { "label": "name", "kind": 5, From eefe5f6bba0c14b6e43599bab895a00ef6f4463e Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Wed, 6 Nov 2024 22:07:20 +0100 Subject: [PATCH 03/34] add experimental mainTypeForModule annotation --- analysis/src/CompletionBackEnd.ml | 50 +++++++++++-------- analysis/src/ProcessAttributes.ml | 16 ++++++ analysis/src/SharedTypes.ml | 1 + analysis/src/TypeUtils.ml | 19 +++++-- analysis/tests/src/CompletionFromModule.res | 22 ++++++++ .../tests/src/expected/Completion.res.txt | 13 ----- .../expected/CompletionExpressions.res.txt | 3 -- .../src/expected/CompletionFromModule.res.txt | 45 +++++++++++++++++ .../expected/CompletionInferValues.res.txt | 8 --- .../src/expected/CompletionPipeChain.res.txt | 2 - .../expected/CompletionPipeSubmodules.res.txt | 4 -- analysis/tests/src/expected/Hover.res.txt | 9 ---- .../src/expected/RecordCompletion.res.txt | 1 - 13 files changed, 129 insertions(+), 64 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index d31794a3e..4bd32e50f 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -990,35 +990,44 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact with | Some (TypeExpr typ, env) -> ( match typ |> TypeUtils.extractRecordType ~env ~package with - | Some (env, fields, typDecl, path) -> + | Some (env, fields, typDecl, path, attributes) -> Some ( env, fields, typDecl.item.decl |> Shared.declToString typDecl.name.txt, - Some path ) + Some path, + attributes ) | None -> None) | Some (ExtractedType typ, env) -> ( match typ with - | Trecord {fields; path} -> - Some (env, fields, typ |> TypeUtils.extractedTypeToString, path) + | Trecord {fields; path; attributes} -> + Some + ( env, + fields, + typ |> TypeUtils.extractedTypeToString, + path, + attributes ) | _ -> None) | None -> None in match extracted with | None -> [] - | Some (env, fields, recordAsString, path) -> - let pipeCompletionsForModule = - match path with - | Some path -> - let completionPath = - (* Remove the last part of the path since we're only after the parent module *) - match + | Some (env, fields, recordAsString, path, attributes) -> + let pipeCompletion = + match + (path, ProcessAttributes.findMainTypeForModuleAttribute attributes) + with + | Some path, _ when Path.last path = "t" -> + Some + ( path, path |> SharedTypes.pathIdentToString |> String.split_on_char '.' - |> List.rev - with - | _ :: rest -> rest - | [] -> [] - in + |> List.rev |> List.tl ) + | Some path, Some modulePath -> Some (path, modulePath) + | _ -> None + in + let pipeCompletionsForModule = + match pipeCompletion with + | Some (path, completionPath) -> (* Most of this is copied from the pipe completion code. Should probably be unified. *) let completions = completionPath @ [fieldName] @@ -1046,10 +1055,8 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact match TypeUtils.extractFunctionType ~env ~package:full.package t with - | ( (Nolabel, {desc = Tconstr (Path.Pident {name = "t"}, _, _)}) - :: _, - _ ) -> - true + | (Nolabel, {desc = Tconstr (p, _, _)}) :: _, _ -> + Path.same p path || Path.name p = "t" | _ -> false) | _ -> false in @@ -2031,6 +2038,7 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover completable = env; definition = `NameOnly "jsxConfig"; path = None; + attributes = []; fields = [ mkField ~name:"version" ~primitive:"int"; @@ -2061,6 +2069,7 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover completable = { env; path = None; + attributes = []; definition = `NameOnly "importAttributesConfig"; fields = [mkField ~name:"type_" ~primitive:"string"]; } @@ -2070,6 +2079,7 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover completable = { env; path = None; + attributes = []; definition = `NameOnly "moduleConfig"; fields = [ diff --git a/analysis/src/ProcessAttributes.ml b/analysis/src/ProcessAttributes.ml index 60ba88c21..53babe020 100644 --- a/analysis/src/ProcessAttributes.ml +++ b/analysis/src/ProcessAttributes.ml @@ -48,3 +48,19 @@ let newDeclared ~item ~extent ~name ~stamp ~modulePath isExported attributes = | Some d -> [d]); item; } + +let rec findMainTypeForModuleAttribute attributes = + let open Parsetree in + match attributes with + | [] -> None + | ( {Asttypes.txt = "mainTypeForModule"}, + PStr + [ + { + pstr_desc = + Pstr_eval ({pexp_desc = Pexp_construct ({txt = path}, None)}, _); + }; + ] ) + :: _ -> + Some (Utils.flattenLongIdent path) + | _ :: rest -> findMainTypeForModuleAttribute rest diff --git a/analysis/src/SharedTypes.ml b/analysis/src/SharedTypes.ml index 1886f86fa..846ea8245 100644 --- a/analysis/src/SharedTypes.ml +++ b/analysis/src/SharedTypes.ml @@ -355,6 +355,7 @@ and completionType = env: QueryEnv.t; fields: field list; path: Path.t option; + attributes: Parsetree.attributes; definition: [ `NameOnly of string (** When we only have the name, like when pulling the record from a declared type. *) diff --git a/analysis/src/TypeUtils.ml b/analysis/src/TypeUtils.ml index 72f5c089b..3c8e0ccaa 100644 --- a/analysis/src/TypeUtils.ml +++ b/analysis/src/TypeUtils.ml @@ -203,7 +203,7 @@ let rec extractRecordType ~env ~package (t : Types.type_expr) = | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> extractRecordType ~env ~package t1 | Tconstr (path, typeArgs, _) -> ( match References.digConstructor ~env ~package path with - | Some (env, ({item = {kind = Record fields}} as typ)) -> + | Some (env, ({item = {kind = Record fields; attributes}} as typ)) -> let typeParams = typ.item.decl.type_params in let fields = fields @@ -213,7 +213,7 @@ let rec extractRecordType ~env ~package (t : Types.type_expr) = in {field with typ = fieldTyp}) in - Some (env, fields, typ, path) + Some (env, fields, typ, path, attributes) | Some ( env, {item = {decl = {type_manifest = Some t1; type_params = typeParams}}} @@ -383,7 +383,9 @@ let rec extractType ?(printOpeningDebug = true) variantDecl = decl; }, typeArgContext ) - | Some (envFromDeclaration, {item = {kind = Record fields; decl}}) -> + | Some + (envFromDeclaration, {item = {kind = Record fields; decl; attributes}}) + -> if Debug.verbose () then print_endline "[extract_type]--> found record"; (* Need to create a new type arg context here because we're sending along a type expr that might have type vars. *) let typeArgContext = @@ -396,6 +398,7 @@ let rec extractType ?(printOpeningDebug = true) path = Some path; fields; definition = `TypeExpr t; + attributes; }, typeArgContext ) | Some (envFromDeclaration, {item = {name = "t"; decl = {type_params}}}) -> @@ -577,7 +580,15 @@ let extractTypeFromResolvedType (typ : Type.t) ~env ~full = match typ.kind with | Tuple items -> Some (Tuple (env, items, Ctype.newty (Ttuple items))) | Record fields -> - Some (Trecord {env; fields; path = None; definition = `NameOnly typ.name}) + Some + (Trecord + { + env; + fields; + path = None; + definition = `NameOnly typ.name; + attributes = typ.attributes; + }) | Variant constructors -> Some (Tvariant diff --git a/analysis/tests/src/CompletionFromModule.res b/analysis/tests/src/CompletionFromModule.res index 70ecada79..475e6dcef 100644 --- a/analysis/tests/src/CompletionFromModule.res +++ b/analysis/tests/src/CompletionFromModule.res @@ -12,3 +12,25 @@ let n = {SomeModule.name: "hello"} // n. // ^com // ^dv- + +@mainTypeForModule(SomeOtherModule) +type typeOutsideModule = {nname: string} + +module SomeOtherModule = { + type t = typeOutsideModule + + type irrelevantType = string + + @get external getNName: t => string = "nname" + @get external getNName2: typeOutsideModule => string = "nname" + @get external getNName3: irrelevantType => string = "nname" + + let thisShouldNotBeCompletedFor = () => "hi" +} + +let nn: SomeOtherModule.t = {nname: "hello"} + +// ^dv+ +// nn. +// ^com +// ^dv- diff --git a/analysis/tests/src/expected/Completion.res.txt b/analysis/tests/src/expected/Completion.res.txt index acae3c53f..ccadf462f 100644 --- a/analysis/tests/src/expected/Completion.res.txt +++ b/analysis/tests/src/expected/Completion.res.txt @@ -728,7 +728,6 @@ Resolved opens 1 pervasives ContextPath Value[r]."" ContextPath Value[r] Path r -Path [{ "label": "x", "kind": 5, @@ -752,7 +751,6 @@ Resolved opens 1 pervasives ContextPath Value[Objects, Rec, recordVal]."" ContextPath Value[Objects, Rec, recordVal] Path Objects.Rec.recordVal -Path [{ "label": "xx", "kind": 5, @@ -835,8 +833,6 @@ ContextPath Value[q].aa."" ContextPath Value[q].aa ContextPath Value[q] Path q -Path aa -Path [{ "label": "x", "kind": 5, @@ -861,8 +857,6 @@ ContextPath Value[q].aa.n ContextPath Value[q].aa ContextPath Value[q] Path q -Path aa -Path n [{ "label": "name", "kind": 5, @@ -1087,7 +1081,6 @@ ContextPath Value[FAO, forAutoObject]["forAutoLabel"]."" ContextPath Value[FAO, forAutoObject]["forAutoLabel"] ContextPath Value[FAO, forAutoObject] Path FAO.forAutoObject -Path FAR. [{ "label": "forAuto", "kind": 5, @@ -1113,7 +1106,6 @@ ContextPath Value[FAO, forAutoObject]["forAutoLabel"].forAuto ContextPath Value[FAO, forAutoObject]["forAutoLabel"] ContextPath Value[FAO, forAutoObject] Path FAO.forAutoObject -Path FAR.forAuto CPPipe env:Completion envFromCompletionItem:Completion.FAR CPPipe type path:ForAuto.t CPPipe pathFromEnv:ForAuto found:false @@ -1197,7 +1189,6 @@ Resolved opens 3 pervasives Completion.res Completion.res ContextPath Value[_z]."" ContextPath Value[_z] Path _z -Path [{ "label": "x", "kind": 5, @@ -1356,7 +1347,6 @@ Resolved opens 3 pervasives Completion.res Completion.res ContextPath Value[funRecord].someFun ContextPath Value[funRecord] Path funRecord -Path someFun Found type for function (~name: string) => unit [{ "label": "name", @@ -1377,7 +1367,6 @@ ContextPath Value[retAA](Nolabel)."" ContextPath Value[retAA](Nolabel) ContextPath Value[retAA] Path retAA -Path [{ "label": "x", "kind": 5, @@ -1908,7 +1897,6 @@ Resolved opens 3 pervasives Completion.res Completion.res ContextPath Value[funRecord]."" ContextPath Value[funRecord] Path funRecord -Path [{ "label": "someFun", "kind": 5, @@ -2165,7 +2153,6 @@ Resolved opens 3 pervasives Completion.res Completion.res ContextPath Value[rWithDepr].so ContextPath Value[rWithDepr] Path rWithDepr -Path so [{ "label": "someInt", "kind": 5, diff --git a/analysis/tests/src/expected/CompletionExpressions.res.txt b/analysis/tests/src/expected/CompletionExpressions.res.txt index dece6fbc6..2d5c7438c 100644 --- a/analysis/tests/src/expected/CompletionExpressions.res.txt +++ b/analysis/tests/src/expected/CompletionExpressions.res.txt @@ -946,7 +946,6 @@ Resolved opens 1 pervasives ContextPath Value[fff].someOpt ContextPath Value[fff] Path fff -Path someOpt [{ "label": "someOptField", "kind": 5, @@ -1416,7 +1415,6 @@ Resolved opens 2 pervasives CompletionSupport.res ContextPath Value[someTyp]."" ContextPath Value[someTyp] Path someTyp -Path [{ "label": "test", "kind": 5, @@ -1472,7 +1470,6 @@ Resolved opens 2 pervasives CompletionSupport.res ContextPath Value[someTyp]."" ContextPath Value[someTyp] Path someTyp -Path [{ "label": "test", "kind": 5, diff --git a/analysis/tests/src/expected/CompletionFromModule.res.txt b/analysis/tests/src/expected/CompletionFromModule.res.txt index 2f1c04b06..56fcbfc21 100644 --- a/analysis/tests/src/expected/CompletionFromModule.res.txt +++ b/analysis/tests/src/expected/CompletionFromModule.res.txt @@ -32,3 +32,48 @@ Path SomeModule. }] + +Complete src/CompletionFromModule.res 33:6 +posCursor:[33:6] posNoWhite:[33:5] Found expr:[33:3->33:6] +Pexp_field [33:3->33:5] _:[37:0->33:6] +[set_result] set new result to Cpath Value[nn]."" +Completable: Cpath Value[nn]."" +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[nn]."" +[ctx_path]--> CPField +ContextPath Value[nn] +[ctx_path]--> CPId +Path nn +Path SomeOtherModule. +[{ + "label": "->SomeOtherModule.getNName", + "kind": 12, + "tags": [], + "detail": "t => string", + "documentation": null, + "sortText": "getNName", + "textEdit": { + "range": {"start": {"line": 33, "character": 5}, "end": {"line": 33, "character": 5}}, + "newText": "->SomeOtherModule.getNName" + } + }, { + "label": "->SomeOtherModule.getNName2", + "kind": 12, + "tags": [], + "detail": "typeOutsideModule => string", + "documentation": null, + "sortText": "getNName2", + "textEdit": { + "range": {"start": {"line": 33, "character": 5}, "end": {"line": 33, "character": 5}}, + "newText": "->SomeOtherModule.getNName2" + } + }, { + "label": "nname", + "kind": 5, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "```rescript\nnname: string\n```\n\n```rescript\ntype typeOutsideModule = {nname: string}\n```"} + }] + + diff --git a/analysis/tests/src/expected/CompletionInferValues.res.txt b/analysis/tests/src/expected/CompletionInferValues.res.txt index 748cde761..356a23819 100644 --- a/analysis/tests/src/expected/CompletionInferValues.res.txt +++ b/analysis/tests/src/expected/CompletionInferValues.res.txt @@ -37,7 +37,6 @@ Path x ContextPath Value[getSomeRecord](Nolabel) ContextPath Value[getSomeRecord] Path getSomeRecord -Path [{ "label": "name", "kind": 5, @@ -66,7 +65,6 @@ Path x ContextPath Value[getSomeRecord](Nolabel) ContextPath Value[getSomeRecord] Path getSomeRecord -Path [{ "label": "name", "kind": 5, @@ -100,7 +98,6 @@ ContextPath CArgument CArgument Value[someFnWithCallback]($0)(~someRecord) ContextPath CArgument Value[someFnWithCallback]($0) ContextPath Value[someFnWithCallback] Path someFnWithCallback -Path [{ "label": "name", "kind": 5, @@ -136,7 +133,6 @@ ContextPath Value[aliasedFn] Path aliasedFn ContextPath Value[someFnWithCallback] Path someFnWithCallback -Path [{ "label": "name", "kind": 5, @@ -381,7 +377,6 @@ ContextPath Value[x] Path x ContextPath Type[someRecordWithNestedStuff] Path someRecordWithNestedStuff -Path [{ "label": "name", "kind": 5, @@ -410,7 +405,6 @@ ContextPath Value[x] Path x ContextPath Type[someRecordWithNestedStuff] Path someRecordWithNestedStuff -Path [{ "label": "someRecord", "kind": 5, @@ -433,7 +427,6 @@ ContextPath Value[x] Path x ContextPath Type[someRecordWithNestedStuff] Path someRecordWithNestedStuff -Path [{ "label": "name", "kind": 5, @@ -852,7 +845,6 @@ ContextPath CArgument CArgument Value[CompletionSupport2, makeRenderer](~render) ContextPath CArgument Value[CompletionSupport2, makeRenderer](~render) ContextPath Value[CompletionSupport2, makeRenderer] Path CompletionSupport2.makeRenderer -Path Nested.CompletionSupport. [{ "label": "root", "kind": 5, diff --git a/analysis/tests/src/expected/CompletionPipeChain.res.txt b/analysis/tests/src/expected/CompletionPipeChain.res.txt index 7430ff644..91f6ca0e1 100644 --- a/analysis/tests/src/expected/CompletionPipeChain.res.txt +++ b/analysis/tests/src/expected/CompletionPipeChain.res.txt @@ -452,8 +452,6 @@ ContextPath Value[props].support.root ContextPath Value[props].support ContextPath Value[props] Path props -Path Internal.CompletionSupport2.support -Path Nested.CompletionSupport.root CPPipe env:CompletionPipeChain envFromCompletionItem:CompletionSupport.Nested CPPipe type path:ReactDOM.Client.Root.t CPPipe pathFromEnv:ReactDOM.Client.Root found:false diff --git a/analysis/tests/src/expected/CompletionPipeSubmodules.res.txt b/analysis/tests/src/expected/CompletionPipeSubmodules.res.txt index 962003d73..df1bd42cd 100644 --- a/analysis/tests/src/expected/CompletionPipeSubmodules.res.txt +++ b/analysis/tests/src/expected/CompletionPipeSubmodules.res.txt @@ -62,8 +62,6 @@ ContextPath Value[E, e].v.v ContextPath Value[E, e].v ContextPath Value[E, e] Path E.e -Path v -Path D.v CPPipe env:CompletionPipeSubmodules envFromCompletionItem:CompletionPipeSubmodules.D CPPipe type path:C.t CPPipe pathFromEnv:C found:false @@ -86,8 +84,6 @@ ContextPath Value[E, e].v.v2 ContextPath Value[E, e].v ContextPath Value[E, e] Path E.e -Path v -Path D.v2 CPPipe env:CompletionPipeSubmodules envFromCompletionItem:CompletionPipeSubmodules.D CPPipe type path:C2.t2 CPPipe pathFromEnv:D.C2 found:true diff --git a/analysis/tests/src/expected/Hover.res.txt b/analysis/tests/src/expected/Hover.res.txt index 1b2ea8009..725c5a93a 100644 --- a/analysis/tests/src/expected/Hover.res.txt +++ b/analysis/tests/src/expected/Hover.res.txt @@ -124,8 +124,6 @@ ContextPath Value[x1].content."" ContextPath Value[x1].content ContextPath Value[x1] Path x1 -Path content -Path [{ "label": "age", "kind": 5, @@ -144,8 +142,6 @@ ContextPath Value[x2].content."" ContextPath Value[x2].content ContextPath Value[x2] Path x2 -Path content -Path [{ "label": "age", "kind": 5, @@ -164,8 +160,6 @@ ContextPath Value[y1].content."" ContextPath Value[y1].content ContextPath Value[y1] Path y1 -Path content -Path [{ "label": "age", "kind": 5, @@ -184,8 +178,6 @@ ContextPath Value[y2].content."" ContextPath Value[y2].content ContextPath Value[y2] Path y2 -Path content -Path [{ "label": "age", "kind": 5, @@ -229,7 +221,6 @@ Resolved opens 1 pervasives ContextPath Value[x].someField ContextPath Value[x] Path x -Path someField Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives {"contents": {"kind": "markdown", "value": " Mighty fine field here. \n\n```rescript\nbool\n```"}} diff --git a/analysis/tests/src/expected/RecordCompletion.res.txt b/analysis/tests/src/expected/RecordCompletion.res.txt index 7ea242295..92ec8f49a 100644 --- a/analysis/tests/src/expected/RecordCompletion.res.txt +++ b/analysis/tests/src/expected/RecordCompletion.res.txt @@ -34,7 +34,6 @@ ContextPath Value[t2].n2.n ContextPath Value[t2].n2 ContextPath Value[t2] Path t2 -Path n2 Path n CPPipe env:RecordCompletion Path Js.Array2.m From ca1c8d194ae2dd7845a63ae83d5b7cd57403b990 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Thu, 7 Nov 2024 07:21:48 +0100 Subject: [PATCH 04/34] complete modules in payload of mainTypeForModule --- analysis/src/CompletionFrontEnd.ml | 26 +++++++++++++++++++ analysis/tests/src/CompletionFromModule.res | 3 +++ .../src/expected/CompletionFromModule.res.txt | 17 +++++++++++- 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index f963a2891..ee2ab6e37 100644 --- a/analysis/src/CompletionFrontEnd.ml +++ b/analysis/src/CompletionFrontEnd.ml @@ -946,6 +946,32 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor setResult (Completable.CdecoratorPayload (JsxConfig {nested = List.rev nested; prefix}))) + | _ -> () + else if id.txt = "mainTypeForModule" then + match payload with + | PStr + [ + { + pstr_desc = + Pstr_eval + ( { + pexp_loc; + pexp_desc = Pexp_construct ({txt = path; loc}, None); + }, + _ ); + }; + ] + when locHasCursor pexp_loc -> + if Debug.verbose () then + print_endline "[decoratorCompletion] Found @mainTypeForModule"; + setResult + (Completable.Cpath + (CPId + { + path = Utils.flattenLongIdent path; + completionContext = Module; + loc; + })) | _ -> ()); Ast_iterator.default_iterator.attribute iterator (id, payload) in diff --git a/analysis/tests/src/CompletionFromModule.res b/analysis/tests/src/CompletionFromModule.res index 475e6dcef..ce34f4401 100644 --- a/analysis/tests/src/CompletionFromModule.res +++ b/analysis/tests/src/CompletionFromModule.res @@ -34,3 +34,6 @@ let nn: SomeOtherModule.t = {nname: "hello"} // nn. // ^com // ^dv- + +// @mainTypeForModule(SomeOthe) type typeOutsideModule = {nname: string} +// ^com diff --git a/analysis/tests/src/expected/CompletionFromModule.res.txt b/analysis/tests/src/expected/CompletionFromModule.res.txt index 56fcbfc21..033e2d0c8 100644 --- a/analysis/tests/src/expected/CompletionFromModule.res.txt +++ b/analysis/tests/src/expected/CompletionFromModule.res.txt @@ -35,7 +35,7 @@ Path SomeModule. Complete src/CompletionFromModule.res 33:6 posCursor:[33:6] posNoWhite:[33:5] Found expr:[33:3->33:6] -Pexp_field [33:3->33:5] _:[37:0->33:6] +Pexp_field [33:3->33:5] _:[40:0->33:6] [set_result] set new result to Cpath Value[nn]."" Completable: Cpath Value[nn]."" Package opens Pervasives.JsxModules.place holder @@ -77,3 +77,18 @@ Path SomeOtherModule. }] +Complete src/CompletionFromModule.res 37:30 +XXX Not found! +Completable: Cpath Module[SomeOthe] +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Module[SomeOthe] +Path SomeOthe +[{ + "label": "SomeOtherModule", + "kind": 9, + "tags": [], + "detail": "module SomeOtherModule", + "documentation": null + }] + From 7110db8f3f37fb2e7109ee2ff796c5887726eefd Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Tue, 12 Nov 2024 15:40:58 +0100 Subject: [PATCH 05/34] rename attribute --- analysis/src/CompletionBackEnd.ml | 2 +- analysis/src/CompletionFrontEnd.ml | 4 ++-- analysis/src/ProcessAttributes.ml | 6 +++--- analysis/tests/src/CompletionFromModule.res | 6 +++--- analysis/tests/src/expected/CompletionFromModule.res.txt | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 4bd32e50f..2ade80054 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -1015,7 +1015,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact | Some (env, fields, recordAsString, path, attributes) -> let pipeCompletion = match - (path, ProcessAttributes.findMainTypeForModuleAttribute attributes) + (path, ProcessAttributes.findEditorCompleteFromAttribute attributes) with | Some path, _ when Path.last path = "t" -> Some diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index ee2ab6e37..ecb1d7954 100644 --- a/analysis/src/CompletionFrontEnd.ml +++ b/analysis/src/CompletionFrontEnd.ml @@ -947,7 +947,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor (Completable.CdecoratorPayload (JsxConfig {nested = List.rev nested; prefix}))) | _ -> () - else if id.txt = "mainTypeForModule" then + else if id.txt = "editor.completeFrom" then match payload with | PStr [ @@ -963,7 +963,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ] when locHasCursor pexp_loc -> if Debug.verbose () then - print_endline "[decoratorCompletion] Found @mainTypeForModule"; + print_endline "[decoratorCompletion] Found @editor.completeFrom"; setResult (Completable.Cpath (CPId diff --git a/analysis/src/ProcessAttributes.ml b/analysis/src/ProcessAttributes.ml index 53babe020..61491f4be 100644 --- a/analysis/src/ProcessAttributes.ml +++ b/analysis/src/ProcessAttributes.ml @@ -49,11 +49,11 @@ let newDeclared ~item ~extent ~name ~stamp ~modulePath isExported attributes = item; } -let rec findMainTypeForModuleAttribute attributes = +let rec findEditorCompleteFromAttribute attributes = let open Parsetree in match attributes with | [] -> None - | ( {Asttypes.txt = "mainTypeForModule"}, + | ( {Asttypes.txt = "editor.completeFrom"}, PStr [ { @@ -63,4 +63,4 @@ let rec findMainTypeForModuleAttribute attributes = ] ) :: _ -> Some (Utils.flattenLongIdent path) - | _ :: rest -> findMainTypeForModuleAttribute rest + | _ :: rest -> findEditorCompleteFromAttribute rest diff --git a/analysis/tests/src/CompletionFromModule.res b/analysis/tests/src/CompletionFromModule.res index ce34f4401..0f26f81a2 100644 --- a/analysis/tests/src/CompletionFromModule.res +++ b/analysis/tests/src/CompletionFromModule.res @@ -13,7 +13,7 @@ let n = {SomeModule.name: "hello"} // ^com // ^dv- -@mainTypeForModule(SomeOtherModule) +@editor.completeFrom(SomeOtherModule) type typeOutsideModule = {nname: string} module SomeOtherModule = { @@ -35,5 +35,5 @@ let nn: SomeOtherModule.t = {nname: "hello"} // ^com // ^dv- -// @mainTypeForModule(SomeOthe) type typeOutsideModule = {nname: string} -// ^com +// @editor.completeFrom(SomeOthe) type typeOutsideModule = {nname: string} +// ^com diff --git a/analysis/tests/src/expected/CompletionFromModule.res.txt b/analysis/tests/src/expected/CompletionFromModule.res.txt index 033e2d0c8..d42684b7a 100644 --- a/analysis/tests/src/expected/CompletionFromModule.res.txt +++ b/analysis/tests/src/expected/CompletionFromModule.res.txt @@ -77,7 +77,7 @@ Path SomeOtherModule. }] -Complete src/CompletionFromModule.res 37:30 +Complete src/CompletionFromModule.res 37:32 XXX Not found! Completable: Cpath Module[SomeOthe] Package opens Pervasives.JsxModules.place holder From af54200ef46d901368079735082821019a0fccc5 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Tue, 12 Nov 2024 15:42:28 +0100 Subject: [PATCH 06/34] hover for new decorator --- analysis/src/CompletionDecorators.ml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/analysis/src/CompletionDecorators.ml b/analysis/src/CompletionDecorators.ml index 8319a5f4c..f43e5e16b 100644 --- a/analysis/src/CompletionDecorators.ml +++ b/analysis/src/CompletionDecorators.ml @@ -259,6 +259,11 @@ You will need this decorator whenever you want to use a JSX component in ReScrip [Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#variadic-decorator).|}; ] ); + ( "editor.completeFrom", + None, + [ + {|The `@editor.completeFrom` decorator instructs the editor where it can draw additional completions from for this type.|}; + ] ); ] let toplevel = From e3d1470fc3f419b072e5622a822aa246002894b0 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Wed, 13 Nov 2024 08:59:57 +0100 Subject: [PATCH 07/34] allow extra module completions also for pipe --- analysis/src/CompletionBackEnd.ml | 124 +++++++----------- analysis/src/TypeUtils.ml | 56 ++++++++ analysis/tests/src/CompletionFromModule.res | 9 +- .../src/expected/CompletionFromModule.res.txt | 68 +++++++--- 4 files changed, 153 insertions(+), 104 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 2ade80054..828c4dfd3 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -635,6 +635,30 @@ let getCompletionsForPath ~debug ~opens ~full ~pos ~exact ~scope findAllCompletions ~env ~prefix ~exact ~namesUsed ~completionContext | None -> []) +(** Completions intended for piping, from a completion path. *) +let completionsForPipeFromCompletionPath ~opens ~pos ~scope ~debug ~prefix ~env + ~rawOpens ~full completionPath = + let completionPathMinusOpens = + TypeUtils.removeOpensFromCompletionPath ~rawOpens ~package:full.package + completionPath + |> String.concat "." + in + let completionName name = + if completionPathMinusOpens = "" then name + else completionPathMinusOpens ^ "." ^ name + in + let completions = + completionPath @ [prefix] + |> getCompletionsForPath ~debug ~completionContext:Value ~exact:false ~opens + ~full ~pos ~env ~scope + in + let completions = + completions + |> List.map (fun (completion : Completion.t) -> + {completion with name = completionName completion.name; env}) + in + completions + let rec digToRecordFieldsForCompletion ~debug ~package ~opens ~full ~pos ~env ~scope path = match @@ -1028,62 +1052,10 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact let pipeCompletionsForModule = match pipeCompletion with | Some (path, completionPath) -> - (* Most of this is copied from the pipe completion code. Should probably be unified. *) - let completions = - completionPath @ [fieldName] - |> getCompletionsForPath ~debug ~completionContext:Value - ~exact:false ~opens ~full ~pos ~env ~scope - in - let completionPathMinusOpens = - TypeUtils.removeOpensFromCompletionPath ~rawOpens ~package - completionPath - |> String.concat "." - in - let completionName name = - if completionPathMinusOpens = "" then name - else completionPathMinusOpens ^ "." ^ name - in - (* Find all functions in the module that takes type t *) - let rec fnTakesTypeT t = - match t.Types.desc with - | Tlink t1 - | Tsubst t1 - | Tpoly (t1, []) - | Tconstr (Pident {name = "function$"}, [t1; _], _) -> - fnTakesTypeT t1 - | Tarrow _ -> ( - match - TypeUtils.extractFunctionType ~env ~package:full.package t - with - | (Nolabel, {desc = Tconstr (p, _, _)}) :: _, _ -> - Path.same p path || Path.name p = "t" - | _ -> false) - | _ -> false - in - completions - |> List.filter_map (fun (completion : Completion.t) -> - match completion.kind with - | Value t when fnTakesTypeT t -> - let name = completionName completion.name in - let nameWithPipe = "->" ^ name in - (* TODO: We need to add support for setting the text insertion location explicitly, - so we can account for the dot that triggered the completion, but that we want - removed when inserting the pipe. This means we also need to track the loc of - the dot + the identifier that we're filtering with (fieldName here). - That should be easy to do by extending CPField. *) - Some - { - completion with - name = nameWithPipe; - sortText = - Some - (name |> String.split_on_char '.' |> List.rev - |> List.hd); - insertText = Some nameWithPipe; - env; - range = Some fieldNameLoc; - } - | _ -> None) + completionsForPipeFromCompletionPath ~opens ~pos ~scope ~debug + ~prefix:fieldName ~env ~rawOpens ~full completionPath + |> TypeUtils.filterPipeableFunctions ~env ~full ~path + ~replaceRange:fieldNameLoc | None -> [] in pipeCompletionsForModule @@ -1134,6 +1106,10 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact with | None -> [] | Some (typ, envFromCompletionItem) -> ( + (* Extract any module to draw extra completions from for the identified type. *) + let extraModuleToCompleteFrom = + TypeUtils.getExtraModuleToCompleteFromForType typ ~env ~full + in let env, typ = typ |> TypeUtils.resolveTypeForPipeCompletion ~env ~package ~full ~lhsLoc @@ -1184,32 +1160,20 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact ~envFromItem:envFromCompletionItem (Utils.expandPath path) | _ -> None) in + let completionsFromExtraModule = + match extraModuleToCompleteFrom with + | None -> [] + | Some completionPath -> + completionsForPipeFromCompletionPath ~opens ~pos ~scope ~debug + ~prefix:funNamePrefix ~env ~rawOpens ~full completionPath + in match completionPath with | Some completionPath -> ( - let completionPathMinusOpens = - TypeUtils.removeOpensFromCompletionPath ~rawOpens ~package - completionPath - |> String.concat "." - in - let completionName name = - if completionPathMinusOpens = "" then name - else completionPathMinusOpens ^ "." ^ name - in - let completions = - completionPath @ [funNamePrefix] - |> getCompletionsForPath ~debug ~completionContext:Value ~exact:false - ~opens ~full ~pos ~env ~scope - in - let completions = - completions - |> List.map (fun (completion : Completion.t) -> - { - completion with - name = completionName completion.name; - env - (* Restore original env for the completion after x->foo()... *); - }) + let completionsFromMainFn = + completionsForPipeFromCompletionPath ~opens ~pos ~scope ~debug + ~prefix:funNamePrefix ~env ~rawOpens ~full completionPath in + let completions = completionsFromMainFn @ completionsFromExtraModule in (* We add React element functions to the completion if we're in a JSX context *) let forJsxCompletion = if inJsx then @@ -1246,7 +1210,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact ] @ completions | _ -> completions) - | None -> [])) + | None -> completionsFromExtraModule)) | CTuple ctxPaths -> if Debug.verbose () then print_endline "[ctx_path]--> CTuple"; (* Turn a list of context paths into a list of type expressions. *) diff --git a/analysis/src/TypeUtils.ml b/analysis/src/TypeUtils.ml index 3c8e0ccaa..6b484334b 100644 --- a/analysis/src/TypeUtils.ml +++ b/analysis/src/TypeUtils.ml @@ -1140,3 +1140,59 @@ let pathToElementProps package = match package.genericJsxModule with | None -> ["ReactDOM"; "domProps"] | Some g -> (g |> String.split_on_char '.') @ ["Elements"; "props"] + +(** Extracts module to draw extra completions from for the type, if it has been annotated with @editor.completeFrom. *) +let getExtraModuleToCompleteFromForType ~env ~full (t : Types.type_expr) = + match t |> Shared.digConstructor with + | Some path -> ( + match References.digConstructor ~env ~package:full.package path with + | None -> None + (*| Some (env, {item = {decl = {type_manifest = Some t}}}) -> + getExtraModuleToCompleteFromForType ~env ~full t + + This could be commented back in to traverse type aliases. + Not clear as of now if that makes sense to do or not. + *) + | Some (_, {item = {attributes}}) -> + ProcessAttributes.findEditorCompleteFromAttribute attributes) + | None -> None + +(** Checks whether the provided type represents a function that takes the provided path + as the first argument (meaning it's pipeable). *) +let rec fnTakesType ~env ~full ~path t = + match t.Types.desc with + | Tlink t1 + | Tsubst t1 + | Tpoly (t1, []) + | Tconstr (Pident {name = "function$"}, [t1; _], _) -> + fnTakesType ~env ~full ~path t1 + | Tarrow _ -> ( + match extractFunctionType ~env ~package:full.package t with + | (Nolabel, {desc = Tconstr (p, _, _)}) :: _, _ -> + Path.same p path || Path.name p = "t" + | _ -> false) + | _ -> false + +(** Turns a completion into a pipe completion. *) +let transformCompletionToPipeCompletion ~env ~replaceRange + (completion : Completion.t) = + let name = completion.name in + let nameWithPipe = "->" ^ name in + Some + { + completion with + name = nameWithPipe; + sortText = Some (name |> String.split_on_char '.' |> List.rev |> List.hd); + insertText = Some nameWithPipe; + env; + range = Some replaceRange; + } + +(** Filters out completions that are not pipeable from a list of completions. *) +let filterPipeableFunctions ~env ~full ~path ~replaceRange completions = + completions + |> List.filter_map (fun (completion : Completion.t) -> + match completion.kind with + | Value t when fnTakesType ~env ~full ~path t -> + transformCompletionToPipeCompletion ~env ~replaceRange completion + | _ -> None) diff --git a/analysis/tests/src/CompletionFromModule.res b/analysis/tests/src/CompletionFromModule.res index 0f26f81a2..60785a5ab 100644 --- a/analysis/tests/src/CompletionFromModule.res +++ b/analysis/tests/src/CompletionFromModule.res @@ -8,10 +8,8 @@ module SomeModule = { let n = {SomeModule.name: "hello"} -// ^dv+ // n. // ^com -// ^dv- @editor.completeFrom(SomeOtherModule) type typeOutsideModule = {nname: string} @@ -30,10 +28,13 @@ module SomeOtherModule = { let nn: SomeOtherModule.t = {nname: "hello"} -// ^dv+ // nn. // ^com -// ^dv- // @editor.completeFrom(SomeOthe) type typeOutsideModule = {nname: string} // ^com + +let nnn: typeOutsideModule = {nname: "hello"} + +// nnn-> +// ^com diff --git a/analysis/tests/src/expected/CompletionFromModule.res.txt b/analysis/tests/src/expected/CompletionFromModule.res.txt index d42684b7a..3b5915981 100644 --- a/analysis/tests/src/expected/CompletionFromModule.res.txt +++ b/analysis/tests/src/expected/CompletionFromModule.res.txt @@ -1,15 +1,11 @@ - -Complete src/CompletionFromModule.res 11:5 -posCursor:[11:5] posNoWhite:[11:4] Found expr:[11:3->11:5] -Pexp_field [11:3->11:4] _:[15:0->11:5] -[set_result] set new result to Cpath Value[n]."" +Complete src/CompletionFromModule.res 10:5 +posCursor:[10:5] posNoWhite:[10:4] Found expr:[10:3->10:5] +Pexp_field [10:3->10:4] _:[13:0->10:5] Completable: Cpath Value[n]."" Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives ContextPath Value[n]."" -[ctx_path]--> CPField ContextPath Value[n] -[ctx_path]--> CPId Path n Path SomeModule. [{ @@ -20,7 +16,7 @@ Path SomeModule. "documentation": null, "sortText": "getName", "textEdit": { - "range": {"start": {"line": 11, "character": 4}, "end": {"line": 11, "character": 4}}, + "range": {"start": {"line": 10, "character": 4}, "end": {"line": 10, "character": 4}}, "newText": "->SomeModule.getName" } }, { @@ -31,19 +27,14 @@ Path SomeModule. "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype t = {name: string}\n```"} }] - - -Complete src/CompletionFromModule.res 33:6 -posCursor:[33:6] posNoWhite:[33:5] Found expr:[33:3->33:6] -Pexp_field [33:3->33:5] _:[40:0->33:6] -[set_result] set new result to Cpath Value[nn]."" +Complete src/CompletionFromModule.res 30:6 +posCursor:[30:6] posNoWhite:[30:5] Found expr:[30:3->30:6] +Pexp_field [30:3->30:5] _:[36:0->30:6] Completable: Cpath Value[nn]."" Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives ContextPath Value[nn]."" -[ctx_path]--> CPField ContextPath Value[nn] -[ctx_path]--> CPId Path nn Path SomeOtherModule. [{ @@ -54,7 +45,7 @@ Path SomeOtherModule. "documentation": null, "sortText": "getNName", "textEdit": { - "range": {"start": {"line": 33, "character": 5}, "end": {"line": 33, "character": 5}}, + "range": {"start": {"line": 30, "character": 5}, "end": {"line": 30, "character": 5}}, "newText": "->SomeOtherModule.getNName" } }, { @@ -65,7 +56,7 @@ Path SomeOtherModule. "documentation": null, "sortText": "getNName2", "textEdit": { - "range": {"start": {"line": 33, "character": 5}, "end": {"line": 33, "character": 5}}, + "range": {"start": {"line": 30, "character": 5}, "end": {"line": 30, "character": 5}}, "newText": "->SomeOtherModule.getNName2" } }, { @@ -76,8 +67,7 @@ Path SomeOtherModule. "documentation": {"kind": "markdown", "value": "```rescript\nnname: string\n```\n\n```rescript\ntype typeOutsideModule = {nname: string}\n```"} }] - -Complete src/CompletionFromModule.res 37:32 +Complete src/CompletionFromModule.res 33:32 XXX Not found! Completable: Cpath Module[SomeOthe] Package opens Pervasives.JsxModules.place holder @@ -92,3 +82,41 @@ Path SomeOthe "documentation": null }] +Complete src/CompletionFromModule.res 38:8 +posCursor:[38:8] posNoWhite:[38:7] Found expr:[38:3->0:-1] +Completable: Cpath Value[nnn]-> +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[nnn]-> +ContextPath Value[nnn] +Path nnn +CPPipe env:CompletionFromModule +CPPipe type path:typeOutsideModule +CPPipe pathFromEnv: found:true +Path SomeOtherModule. +[{ + "label": "SomeOtherModule.getNName", + "kind": 12, + "tags": [], + "detail": "t => string", + "documentation": null + }, { + "label": "SomeOtherModule.getNName3", + "kind": 12, + "tags": [], + "detail": "irrelevantType => string", + "documentation": null + }, { + "label": "SomeOtherModule.thisShouldNotBeCompletedFor", + "kind": 12, + "tags": [], + "detail": "unit => string", + "documentation": null + }, { + "label": "SomeOtherModule.getNName2", + "kind": 12, + "tags": [], + "detail": "typeOutsideModule => string", + "documentation": null + }] + From 72a522cb3cc62f6b0c12115a3d8e2ae4dfa47d6d Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Thu, 14 Nov 2024 14:33:16 +0100 Subject: [PATCH 08/34] make sure completions work across files --- analysis/src/CompletionBackEnd.ml | 45 ++++-- analysis/src/TypeUtils.ml | 7 + analysis/tests/src/CompletionFromModule.res | 6 +- analysis/tests/src/CompletionFromModule2.res | 14 ++ .../src/expected/CompletionFromModule.res.txt | 39 +++++ .../expected/CompletionFromModule2.res.txt | 135 ++++++++++++++++++ 6 files changed, 233 insertions(+), 13 deletions(-) create mode 100644 analysis/tests/src/CompletionFromModule2.res create mode 100644 analysis/tests/src/expected/CompletionFromModule2.res.txt diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 828c4dfd3..88c4ddc33 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -636,8 +636,12 @@ let getCompletionsForPath ~debug ~opens ~full ~pos ~exact ~scope | None -> []) (** Completions intended for piping, from a completion path. *) -let completionsForPipeFromCompletionPath ~opens ~pos ~scope ~debug ~prefix ~env - ~rawOpens ~full completionPath = +let completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens ~pos + ~scope ~debug ~prefix ~env ~rawOpens ~full completionPath = + let completionPath = + TypeUtils.removeCurrentModuleIfNeeded ~envCompletionIsMadeFrom + completionPath + in let completionPathMinusOpens = TypeUtils.removeOpensFromCompletionPath ~rawOpens ~package:full.package completionPath @@ -814,6 +818,7 @@ and completionsGetTypeEnv2 ~debug (completions : Completion.t list) ~full ~opens and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact ~scope ?(mode = Regular) contextPath = + let envCompletionIsMadeFrom = env in if debug then Printf.printf "ContextPath %s\n" (Completable.contextPathToString contextPath); @@ -1036,25 +1041,34 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact in match extracted with | None -> [] - | Some (env, fields, recordAsString, path, attributes) -> + | Some (envFromExtracted, fields, recordAsString, path, attributes) -> let pipeCompletion = match (path, ProcessAttributes.findEditorCompleteFromAttribute attributes) with | Some path, _ when Path.last path = "t" -> + if Debug.verbose () then Printf.printf "CPField--> type is type t\n"; Some ( path, path |> SharedTypes.pathIdentToString |> String.split_on_char '.' |> List.rev |> List.tl ) - | Some path, Some modulePath -> Some (path, modulePath) + | Some path, Some modulePath -> + if Debug.verbose () then + Printf.printf + "CPField--> type has completeFrom config for module %s, hd: %s, \ + env moduleName: %s\n" + (modulePath |> SharedTypes.pathToString) + (List.hd modulePath) env.file.moduleName; + Some (path, modulePath) | _ -> None in let pipeCompletionsForModule = match pipeCompletion with | Some (path, completionPath) -> completionsForPipeFromCompletionPath ~opens ~pos ~scope ~debug - ~prefix:fieldName ~env ~rawOpens ~full completionPath - |> TypeUtils.filterPipeableFunctions ~env ~full ~path + ~prefix:fieldName ~envCompletionIsMadeFrom:env ~env:envFromExtracted + ~rawOpens ~full completionPath + |> TypeUtils.filterPipeableFunctions ~env:envFromExtracted ~full ~path ~replaceRange:fieldNameLoc | None -> [] in @@ -1063,7 +1077,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact |> Utils.filterMap (fun field -> if Utils.checkName field.fname.txt ~prefix:fieldName ~exact then Some - (Completion.create field.fname.txt ~env + (Completion.create field.fname.txt ~env:envFromExtracted ?deprecated:field.deprecated ~docstring:field.docstring ~kind:(Completion.Field (field, recordAsString))) else None))) @@ -1108,7 +1122,8 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact | Some (typ, envFromCompletionItem) -> ( (* Extract any module to draw extra completions from for the identified type. *) let extraModuleToCompleteFrom = - TypeUtils.getExtraModuleToCompleteFromForType typ ~env ~full + TypeUtils.getExtraModuleToCompleteFromForType typ + ~env:envFromCompletionItem ~full in let env, typ = typ @@ -1164,14 +1179,20 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact match extraModuleToCompleteFrom with | None -> [] | Some completionPath -> - completionsForPipeFromCompletionPath ~opens ~pos ~scope ~debug - ~prefix:funNamePrefix ~env ~rawOpens ~full completionPath + if Debug.verbose () then + Printf.printf + "[ctx_path]--> CPPipe --> Found extra module to complete from: %s\n" + (completionPath |> SharedTypes.pathToString); + completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens + ~pos ~scope ~debug ~prefix:funNamePrefix ~env ~rawOpens ~full + completionPath in match completionPath with | Some completionPath -> ( let completionsFromMainFn = - completionsForPipeFromCompletionPath ~opens ~pos ~scope ~debug - ~prefix:funNamePrefix ~env ~rawOpens ~full completionPath + completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens + ~pos ~scope ~debug ~prefix:funNamePrefix ~env ~rawOpens ~full + completionPath in let completions = completionsFromMainFn @ completionsFromExtraModule in (* We add React element functions to the completion if we're in a JSX context *) diff --git a/analysis/src/TypeUtils.ml b/analysis/src/TypeUtils.ml index 6b484334b..4b83036ef 100644 --- a/analysis/src/TypeUtils.ml +++ b/analysis/src/TypeUtils.ml @@ -1196,3 +1196,10 @@ let filterPipeableFunctions ~env ~full ~path ~replaceRange completions = | Value t when fnTakesType ~env ~full ~path t -> transformCompletionToPipeCompletion ~env ~replaceRange completion | _ -> None) + +let removeCurrentModuleIfNeeded ~envCompletionIsMadeFrom completionPath = + if + List.length completionPath > 0 + && List.hd completionPath = envCompletionIsMadeFrom.QueryEnv.file.moduleName + then List.tl completionPath + else completionPath diff --git a/analysis/tests/src/CompletionFromModule.res b/analysis/tests/src/CompletionFromModule.res index 60785a5ab..d18624836 100644 --- a/analysis/tests/src/CompletionFromModule.res +++ b/analysis/tests/src/CompletionFromModule.res @@ -11,7 +11,7 @@ let n = {SomeModule.name: "hello"} // n. // ^com -@editor.completeFrom(SomeOtherModule) +@editor.completeFrom(CompletionFromModule.SomeOtherModule) type typeOutsideModule = {nname: string} module SomeOtherModule = { @@ -38,3 +38,7 @@ let nnn: typeOutsideModule = {nname: "hello"} // nnn-> // ^com + +open SomeOtherModule +// nnn-> +// ^com diff --git a/analysis/tests/src/CompletionFromModule2.res b/analysis/tests/src/CompletionFromModule2.res new file mode 100644 index 000000000..f73403621 --- /dev/null +++ b/analysis/tests/src/CompletionFromModule2.res @@ -0,0 +1,14 @@ +// Used to check completions across files +// WRONG! Missing pipe functions (because of type t) +// CompletionFromModule.n. +// ^com + +// CompletionFromModule.nn. +// ^com + +// CompletionFromModule.nnn-> +// ^com + +open CompletionFromModule.SomeOtherModule +// CompletionFromModule.nnn-> +// ^com diff --git a/analysis/tests/src/expected/CompletionFromModule.res.txt b/analysis/tests/src/expected/CompletionFromModule.res.txt index 3b5915981..a5967b5c6 100644 --- a/analysis/tests/src/expected/CompletionFromModule.res.txt +++ b/analysis/tests/src/expected/CompletionFromModule.res.txt @@ -120,3 +120,42 @@ Path SomeOtherModule. "documentation": null }] +Complete src/CompletionFromModule.res 42:8 +posCursor:[42:8] posNoWhite:[42:7] Found expr:[42:3->0:-1] +Completable: Cpath Value[nnn]-> +Raw opens: 1 SomeOtherModule.place holder +Package opens Pervasives.JsxModules.place holder +Resolved opens 2 pervasives CompletionFromModule.res +ContextPath Value[nnn]-> +ContextPath Value[nnn] +Path nnn +CPPipe env:CompletionFromModule +CPPipe type path:typeOutsideModule +CPPipe pathFromEnv: found:true +Path SomeOtherModule. +[{ + "label": "getNName", + "kind": 12, + "tags": [], + "detail": "t => string", + "documentation": null + }, { + "label": "getNName3", + "kind": 12, + "tags": [], + "detail": "irrelevantType => string", + "documentation": null + }, { + "label": "thisShouldNotBeCompletedFor", + "kind": 12, + "tags": [], + "detail": "unit => string", + "documentation": null + }, { + "label": "getNName2", + "kind": 12, + "tags": [], + "detail": "typeOutsideModule => string", + "documentation": null + }] + diff --git a/analysis/tests/src/expected/CompletionFromModule2.res.txt b/analysis/tests/src/expected/CompletionFromModule2.res.txt new file mode 100644 index 000000000..9649c2767 --- /dev/null +++ b/analysis/tests/src/expected/CompletionFromModule2.res.txt @@ -0,0 +1,135 @@ +Complete src/CompletionFromModule2.res 2:26 +posCursor:[2:26] posNoWhite:[2:25] Found expr:[2:3->2:26] +Pexp_field [2:3->2:25] _:[11:0->2:26] +Completable: Cpath Value[CompletionFromModule, n]."" +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[CompletionFromModule, n]."" +ContextPath Value[CompletionFromModule, n] +Path CompletionFromModule.n +Path SomeModule. +[{ + "label": "name", + "kind": 5, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype t = {name: string}\n```"} + }] + +Complete src/CompletionFromModule2.res 5:27 +posCursor:[5:27] posNoWhite:[5:26] Found expr:[5:3->5:27] +Pexp_field [5:3->5:26] _:[11:0->5:27] +Completable: Cpath Value[CompletionFromModule, nn]."" +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[CompletionFromModule, nn]."" +ContextPath Value[CompletionFromModule, nn] +Path CompletionFromModule.nn +Path CompletionFromModule.SomeOtherModule. +[{ + "label": "->CompletionFromModule.SomeOtherModule.getNName", + "kind": 12, + "tags": [], + "detail": "t => string", + "documentation": null, + "sortText": "getNName", + "textEdit": { + "range": {"start": {"line": 5, "character": 26}, "end": {"line": 5, "character": 26}}, + "newText": "->CompletionFromModule.SomeOtherModule.getNName" + } + }, { + "label": "->CompletionFromModule.SomeOtherModule.getNName2", + "kind": 12, + "tags": [], + "detail": "typeOutsideModule => string", + "documentation": null, + "sortText": "getNName2", + "textEdit": { + "range": {"start": {"line": 5, "character": 26}, "end": {"line": 5, "character": 26}}, + "newText": "->CompletionFromModule.SomeOtherModule.getNName2" + } + }, { + "label": "nname", + "kind": 5, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "```rescript\nnname: string\n```\n\n```rescript\ntype typeOutsideModule = {nname: string}\n```"} + }] + +Complete src/CompletionFromModule2.res 8:29 +posCursor:[8:29] posNoWhite:[8:28] Found expr:[8:3->0:-1] +Completable: Cpath Value[CompletionFromModule, nnn]-> +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[CompletionFromModule, nnn]-> +ContextPath Value[CompletionFromModule, nnn] +Path CompletionFromModule.nnn +CPPipe env:CompletionFromModule2 envFromCompletionItem:CompletionFromModule +CPPipe type path:typeOutsideModule +CPPipe pathFromEnv: found:true +Path CompletionFromModule.SomeOtherModule. +[{ + "label": "CompletionFromModule.SomeOtherModule.getNName", + "kind": 12, + "tags": [], + "detail": "t => string", + "documentation": null + }, { + "label": "CompletionFromModule.SomeOtherModule.getNName3", + "kind": 12, + "tags": [], + "detail": "irrelevantType => string", + "documentation": null + }, { + "label": "CompletionFromModule.SomeOtherModule.thisShouldNotBeCompletedFor", + "kind": 12, + "tags": [], + "detail": "unit => string", + "documentation": null + }, { + "label": "CompletionFromModule.SomeOtherModule.getNName2", + "kind": 12, + "tags": [], + "detail": "typeOutsideModule => string", + "documentation": null + }] + +Complete src/CompletionFromModule2.res 12:29 +posCursor:[12:29] posNoWhite:[12:28] Found expr:[12:3->0:-1] +Completable: Cpath Value[CompletionFromModule, nnn]-> +Raw opens: 1 CompletionFromModule.SomeOtherModule.place holder +Package opens Pervasives.JsxModules.place holder +Resolved opens 2 pervasives CompletionFromModule.res +ContextPath Value[CompletionFromModule, nnn]-> +ContextPath Value[CompletionFromModule, nnn] +Path CompletionFromModule.nnn +CPPipe env:CompletionFromModule2 envFromCompletionItem:CompletionFromModule +CPPipe type path:typeOutsideModule +CPPipe pathFromEnv: found:true +Path CompletionFromModule.SomeOtherModule. +[{ + "label": "getNName", + "kind": 12, + "tags": [], + "detail": "t => string", + "documentation": null + }, { + "label": "getNName3", + "kind": 12, + "tags": [], + "detail": "irrelevantType => string", + "documentation": null + }, { + "label": "thisShouldNotBeCompletedFor", + "kind": 12, + "tags": [], + "detail": "unit => string", + "documentation": null + }, { + "label": "getNName2", + "kind": 12, + "tags": [], + "detail": "typeOutsideModule => string", + "documentation": null + }] + From ce80c6cda35ba23eaac245703156f756c1693a19 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Thu, 14 Nov 2024 15:32:05 +0100 Subject: [PATCH 09/34] filter pipe completions to only applicable functions --- analysis/src/CompletionBackEnd.ml | 49 ++++++------- analysis/src/TypeUtils.ml | 32 ++++++--- analysis/tests/src/CompletePrioritize1.res | 1 + analysis/tests/src/CompletionInferValues.res | 4 +- .../tests/src/CompletionPipeSubmodules.res | 7 +- .../src/expected/CompletePrioritize1.res.txt | 8 +-- .../src/expected/CompletionFromModule.res.txt | 24 ------- .../expected/CompletionFromModule2.res.txt | 24 ------- .../expected/CompletionInferValues.res.txt | 24 +++---- .../tests/src/expected/CompletionJsx.res.txt | 24 ------- .../src/expected/CompletionPipeChain.res.txt | 72 ------------------- .../expected/CompletionPipeSubmodules.res.txt | 58 ++++++--------- 12 files changed, 87 insertions(+), 240 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 88c4ddc33..f73642cb2 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -828,41 +828,31 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact if Debug.verbose () then print_endline "[ctx_path]--> CPString"; [ Completion.create "dummy" ~env - ~kind: - (Completion.Value - (Ctype.newconstr (Path.Pident (Ident.create "string")) [])); + ~kind:(Completion.Value (Ctype.newconstr Predef.path_string [])); ] | CPBool -> if Debug.verbose () then print_endline "[ctx_path]--> CPBool"; [ Completion.create "dummy" ~env - ~kind: - (Completion.Value - (Ctype.newconstr (Path.Pident (Ident.create "bool")) [])); + ~kind:(Completion.Value (Ctype.newconstr Predef.path_bool [])); ] | CPInt -> if Debug.verbose () then print_endline "[ctx_path]--> CPInt"; [ Completion.create "dummy" ~env - ~kind: - (Completion.Value - (Ctype.newconstr (Path.Pident (Ident.create "int")) [])); + ~kind:(Completion.Value (Ctype.newconstr Predef.path_int [])); ] | CPFloat -> if Debug.verbose () then print_endline "[ctx_path]--> CPFloat"; [ Completion.create "dummy" ~env - ~kind: - (Completion.Value - (Ctype.newconstr (Path.Pident (Ident.create "float")) [])); + ~kind:(Completion.Value (Ctype.newconstr Predef.path_float [])); ] | CPArray None -> if Debug.verbose () then print_endline "[ctx_path]--> CPArray (no payload)"; [ Completion.create "array" ~env - ~kind: - (Completion.Value - (Ctype.newconstr (Path.Pident (Ident.create "array")) [])); + ~kind:(Completion.Value (Ctype.newconstr Predef.path_array [])); ] | CPArray (Some cp) -> ( if Debug.verbose () then @@ -887,9 +877,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact what inner type it has. *) [ Completion.create "dummy" ~env - ~kind: - (Completion.Value - (Ctype.newconstr (Path.Pident (Ident.create "array")) [])); + ~kind:(Completion.Value (Ctype.newconstr Predef.path_array [])); ]) | CPOption cp -> ( if Debug.verbose () then print_endline "[ctx_path]--> CPOption"; @@ -1135,6 +1123,10 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact (QueryEnv.toString env) (QueryEnv.toString envFromCompletionItem) else Printf.printf "CPPipe env:%s\n" (QueryEnv.toString env); + let tPath = + match typ with + | Builtin (_, t) | TypExpr t -> TypeUtils.pathFromTypeExpr t + in let completionPath = match typ with | Builtin (builtin, _) -> @@ -1186,6 +1178,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens ~pos ~scope ~debug ~prefix:funNamePrefix ~env ~rawOpens ~full completionPath + |> TypeUtils.filterPipeableFunctions ~env ~full ?path:tPath in match completionPath with | Some completionPath -> ( @@ -1193,6 +1186,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens ~pos ~scope ~debug ~prefix:funNamePrefix ~env ~rawOpens ~full completionPath + |> TypeUtils.filterPipeableFunctions ~env ~full ?path:tPath in let completions = completionsFromMainFn @ completionsFromExtraModule in (* We add React element functions to the completion if we're in a JSX context *) @@ -1822,8 +1816,7 @@ let rec completeTypedValue ?(typeArgContext : typeArgContext option) ~rawOpens if prefix = "" then [ create "\"\"" ~includesSnippets:true ~insertText:"\"$0\"" ~sortText:"A" - ~kind: - (Value (Ctype.newconstr (Path.Pident (Ident.create "string")) [])) + ~kind:(Value (Ctype.newconstr Predef.path_string [])) ~env; ] else [] @@ -2012,7 +2005,7 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover completable = stamp = -1; fname = {loc = Location.none; txt = name}; optional = true; - typ = Ctype.newconstr (Path.Pident (Ident.create primitive)) []; + typ = Ctype.newconstr primitive []; docstring = []; deprecated = None; } @@ -2026,9 +2019,9 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover completable = attributes = []; fields = [ - mkField ~name:"version" ~primitive:"int"; - mkField ~name:"module_" ~primitive:"string"; - mkField ~name:"mode" ~primitive:"string"; + mkField ~name:"version" ~primitive:Predef.path_int; + mkField ~name:"module_" ~primitive:Predef.path_string; + mkField ~name:"mode" ~primitive:Predef.path_string; ]; } in @@ -2044,7 +2037,7 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover completable = stamp = -1; fname = {loc = Location.none; txt = name}; optional = true; - typ = Ctype.newconstr (Path.Pident (Ident.create primitive)) []; + typ = Ctype.newconstr primitive []; docstring = []; deprecated = None; } @@ -2056,7 +2049,7 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover completable = path = None; attributes = []; definition = `NameOnly "importAttributesConfig"; - fields = [mkField ~name:"type_" ~primitive:"string"]; + fields = [mkField ~name:"type_" ~primitive:Predef.path_string]; } in let rootConfig : completionType = @@ -2068,8 +2061,8 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover completable = definition = `NameOnly "moduleConfig"; fields = [ - mkField ~name:"from" ~primitive:"string"; - mkField ~name:"with" ~primitive:"string"; + mkField ~name:"from" ~primitive:Predef.path_string; + mkField ~name:"with" ~primitive:Predef.path_string; ]; } in diff --git a/analysis/src/TypeUtils.ml b/analysis/src/TypeUtils.ml index 4b83036ef..9ee3ee18a 100644 --- a/analysis/src/TypeUtils.ml +++ b/analysis/src/TypeUtils.ml @@ -1159,17 +1159,20 @@ let getExtraModuleToCompleteFromForType ~env ~full (t : Types.type_expr) = (** Checks whether the provided type represents a function that takes the provided path as the first argument (meaning it's pipeable). *) -let rec fnTakesType ~env ~full ~path t = +let rec fnTakesTypeAsFirstArg ~env ~full ~path t = match t.Types.desc with | Tlink t1 | Tsubst t1 | Tpoly (t1, []) | Tconstr (Pident {name = "function$"}, [t1; _], _) -> - fnTakesType ~env ~full ~path t1 + fnTakesTypeAsFirstArg ~env ~full ~path t1 | Tarrow _ -> ( match extractFunctionType ~env ~package:full.package t with - | (Nolabel, {desc = Tconstr (p, _, _)}) :: _, _ -> - Path.same p path || Path.name p = "t" + | (Nolabel, t) :: _, _ -> ( + let p = pathFromTypeExpr t in + match p with + | None -> false + | Some p -> Path.same p path || Path.name p = "t") | _ -> false) | _ -> false @@ -1189,13 +1192,20 @@ let transformCompletionToPipeCompletion ~env ~replaceRange } (** Filters out completions that are not pipeable from a list of completions. *) -let filterPipeableFunctions ~env ~full ~path ~replaceRange completions = - completions - |> List.filter_map (fun (completion : Completion.t) -> - match completion.kind with - | Value t when fnTakesType ~env ~full ~path t -> - transformCompletionToPipeCompletion ~env ~replaceRange completion - | _ -> None) +let filterPipeableFunctions ~env ~full ?path ?replaceRange completions = + match path with + | None -> completions + | Some path -> + completions + |> List.filter_map (fun (completion : Completion.t) -> + match completion.kind with + | Value t when fnTakesTypeAsFirstArg ~env ~full ~path t -> ( + match replaceRange with + | None -> Some completion + | Some replaceRange -> + transformCompletionToPipeCompletion ~env ~replaceRange completion + ) + | _ -> None) let removeCurrentModuleIfNeeded ~envCompletionIsMadeFrom completionPath = if diff --git a/analysis/tests/src/CompletePrioritize1.res b/analysis/tests/src/CompletePrioritize1.res index 5fcccf3de..39c1775ec 100644 --- a/analysis/tests/src/CompletePrioritize1.res +++ b/analysis/tests/src/CompletePrioritize1.res @@ -1,6 +1,7 @@ module Test = { type t = {name: int} let add = (a: float) => a +. 1.0 + let name = t => t.name } let a: Test.t = {name: 4} // a-> diff --git a/analysis/tests/src/CompletionInferValues.res b/analysis/tests/src/CompletionInferValues.res index f0edbfe8c..1f0d1e242 100644 --- a/analysis/tests/src/CompletionInferValues.res +++ b/analysis/tests/src/CompletionInferValues.res @@ -13,7 +13,7 @@ let reactEventFn = (cb: ReactEvent.Mouse.t => unit) => { @val external getSomeRecord: unit => someRecord = "getSomeRecord" -// let x = 123; let aliased = x; aliased->f +// let x = 123; let aliased = x; aliased->t // ^com // let x = getSomeRecord(); x. @@ -166,4 +166,4 @@ let fn3 = (~cb: sameFileRecord => unit) => { // Handles reusing the same name already in scope for bindings let res = 1 // switch res { | res => res } -// ^hov \ No newline at end of file +// ^hov diff --git a/analysis/tests/src/CompletionPipeSubmodules.res b/analysis/tests/src/CompletionPipeSubmodules.res index a3ee0af8d..191e6d4ee 100644 --- a/analysis/tests/src/CompletionPipeSubmodules.res +++ b/analysis/tests/src/CompletionPipeSubmodules.res @@ -1,12 +1,14 @@ module A = { module B1 = { type b1 = B1 + type t = b1 // TODO(pipe-filter) Should be allowed without needing type t let xx = B1 + let d = (_: t) => "" } module B2 = { let yy = 20 } - type t = {v: B1.b1} + type t = {v: B1.t} // TODO(pipe-filter) Should be allowed without needing type t let x = {v: B1.B1} } @@ -20,11 +22,14 @@ module A = { module C = { type t = C + let do = (_: t) => "" } module D = { module C2 = { type t2 = C2 + type t = t2 // TODO(pipe-filter) Should be allowed without needing type t + let do = (_: t) => "" } type d = {v: C.t, v2: C2.t2} diff --git a/analysis/tests/src/expected/CompletePrioritize1.res.txt b/analysis/tests/src/expected/CompletePrioritize1.res.txt index b520a84a2..33d053eea 100644 --- a/analysis/tests/src/expected/CompletePrioritize1.res.txt +++ b/analysis/tests/src/expected/CompletePrioritize1.res.txt @@ -1,5 +1,5 @@ -Complete src/CompletePrioritize1.res 5:6 -posCursor:[5:6] posNoWhite:[5:5] Found expr:[5:3->0:-1] +Complete src/CompletePrioritize1.res 6:6 +posCursor:[6:6] posNoWhite:[6:5] Found expr:[6:3->0:-1] Completable: Cpath Value[a]-> Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives @@ -11,10 +11,10 @@ CPPipe type path:Test.t CPPipe pathFromEnv:Test found:true Path Test. [{ - "label": "Test.add", + "label": "Test.name", "kind": 12, "tags": [], - "detail": "float => float", + "detail": "t => int", "documentation": null }] diff --git a/analysis/tests/src/expected/CompletionFromModule.res.txt b/analysis/tests/src/expected/CompletionFromModule.res.txt index a5967b5c6..49906c207 100644 --- a/analysis/tests/src/expected/CompletionFromModule.res.txt +++ b/analysis/tests/src/expected/CompletionFromModule.res.txt @@ -100,18 +100,6 @@ Path SomeOtherModule. "tags": [], "detail": "t => string", "documentation": null - }, { - "label": "SomeOtherModule.getNName3", - "kind": 12, - "tags": [], - "detail": "irrelevantType => string", - "documentation": null - }, { - "label": "SomeOtherModule.thisShouldNotBeCompletedFor", - "kind": 12, - "tags": [], - "detail": "unit => string", - "documentation": null }, { "label": "SomeOtherModule.getNName2", "kind": 12, @@ -139,18 +127,6 @@ Path SomeOtherModule. "tags": [], "detail": "t => string", "documentation": null - }, { - "label": "getNName3", - "kind": 12, - "tags": [], - "detail": "irrelevantType => string", - "documentation": null - }, { - "label": "thisShouldNotBeCompletedFor", - "kind": 12, - "tags": [], - "detail": "unit => string", - "documentation": null }, { "label": "getNName2", "kind": 12, diff --git a/analysis/tests/src/expected/CompletionFromModule2.res.txt b/analysis/tests/src/expected/CompletionFromModule2.res.txt index 9649c2767..174169ccc 100644 --- a/analysis/tests/src/expected/CompletionFromModule2.res.txt +++ b/analysis/tests/src/expected/CompletionFromModule2.res.txt @@ -74,18 +74,6 @@ Path CompletionFromModule.SomeOtherModule. "tags": [], "detail": "t => string", "documentation": null - }, { - "label": "CompletionFromModule.SomeOtherModule.getNName3", - "kind": 12, - "tags": [], - "detail": "irrelevantType => string", - "documentation": null - }, { - "label": "CompletionFromModule.SomeOtherModule.thisShouldNotBeCompletedFor", - "kind": 12, - "tags": [], - "detail": "unit => string", - "documentation": null }, { "label": "CompletionFromModule.SomeOtherModule.getNName2", "kind": 12, @@ -113,18 +101,6 @@ Path CompletionFromModule.SomeOtherModule. "tags": [], "detail": "t => string", "documentation": null - }, { - "label": "getNName3", - "kind": 12, - "tags": [], - "detail": "irrelevantType => string", - "documentation": null - }, { - "label": "thisShouldNotBeCompletedFor", - "kind": 12, - "tags": [], - "detail": "unit => string", - "documentation": null }, { "label": "getNName2", "kind": 12, diff --git a/analysis/tests/src/expected/CompletionInferValues.res.txt b/analysis/tests/src/expected/CompletionInferValues.res.txt index 356a23819..7a7092b7d 100644 --- a/analysis/tests/src/expected/CompletionInferValues.res.txt +++ b/analysis/tests/src/expected/CompletionInferValues.res.txt @@ -1,28 +1,28 @@ Complete src/CompletionInferValues.res 15:43 posCursor:[15:43] posNoWhite:[15:42] Found expr:[15:33->15:43] -Completable: Cpath Value[aliased]->f +Completable: Cpath Value[aliased]->t Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives -ContextPath Value[aliased]->f +ContextPath Value[aliased]->t ContextPath Value[aliased] Path aliased ContextPath Value[x] Path x ContextPath int CPPipe env:CompletionInferValues -Path Belt.Int.f +Path Belt.Int.t [{ - "label": "Belt.Int.fromString", + "label": "Belt.Int.toString", "kind": 12, "tags": [], - "detail": "string => option", - "documentation": {"kind": "markdown", "value": "\nConverts a given `string` to an `int`. Returns `Some(int)` when the input is a number, `None` otherwise.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.fromString(\"1\") === Some(1)) /* true */\n```\n"} + "detail": "int => string", + "documentation": {"kind": "markdown", "value": "\nConverts a given `int` to a `string`. Uses the JavaScript `String` constructor under the hood.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.toString(1) === \"1\") /* true */\n```\n"} }, { - "label": "Belt.Int.fromFloat", + "label": "Belt.Int.toFloat", "kind": 12, "tags": [], - "detail": "float => int", - "documentation": {"kind": "markdown", "value": "\nConverts a given `float` to an `int`.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.fromFloat(1.0) === 1) /* true */\n```\n"} + "detail": "int => float", + "documentation": {"kind": "markdown", "value": "\nConverts a given `int` to a `float`.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.toFloat(1) === 1.0) /* true */\n```\n"} }] Complete src/CompletionInferValues.res 18:30 @@ -781,12 +781,6 @@ Path CompletionSupport.Test. "tags": [], "detail": "t => t", "documentation": null - }, { - "label": "CompletionSupport.Test.make", - "kind": 12, - "tags": [], - "detail": "int => t", - "documentation": null }] Complete src/CompletionInferValues.res 150:47 diff --git a/analysis/tests/src/expected/CompletionJsx.res.txt b/analysis/tests/src/expected/CompletionJsx.res.txt index 65c5e9e89..8c668e969 100644 --- a/analysis/tests/src/expected/CompletionJsx.res.txt +++ b/analysis/tests/src/expected/CompletionJsx.res.txt @@ -231,12 +231,6 @@ Path Belt.Int. "documentation": {"kind": "markdown", "value": "Turns `int` into a JSX element so it can be used inside of JSX."}, "sortText": "A", "insertTextFormat": 2 - }, { - "label": "Belt.Int.fromString", - "kind": 12, - "tags": [], - "detail": "string => option", - "documentation": {"kind": "markdown", "value": "\nConverts a given `string` to an `int`. Returns `Some(int)` when the input is a number, `None` otherwise.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.fromString(\"1\") === Some(1)) /* true */\n```\n"} }, { "label": "Belt.Int.*", "kind": 12, @@ -261,12 +255,6 @@ Path Belt.Int. "tags": [], "detail": "int => float", "documentation": {"kind": "markdown", "value": "\nConverts a given `int` to a `float`.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.toFloat(1) === 1.0) /* true */\n```\n"} - }, { - "label": "Belt.Int.fromFloat", - "kind": 12, - "tags": [], - "detail": "float => int", - "documentation": {"kind": "markdown", "value": "\nConverts a given `float` to an `int`.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.fromFloat(1.0) === 1) /* true */\n```\n"} }, { "label": "Belt.Int.-", "kind": 12, @@ -312,12 +300,6 @@ Path Belt.Int. "documentation": {"kind": "markdown", "value": "Turns `int` into a JSX element so it can be used inside of JSX."}, "sortText": "A", "insertTextFormat": 2 - }, { - "label": "Belt.Int.fromString", - "kind": 12, - "tags": [], - "detail": "string => option", - "documentation": {"kind": "markdown", "value": "\nConverts a given `string` to an `int`. Returns `Some(int)` when the input is a number, `None` otherwise.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.fromString(\"1\") === Some(1)) /* true */\n```\n"} }, { "label": "Belt.Int.*", "kind": 12, @@ -342,12 +324,6 @@ Path Belt.Int. "tags": [], "detail": "int => float", "documentation": {"kind": "markdown", "value": "\nConverts a given `int` to a `float`.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.toFloat(1) === 1.0) /* true */\n```\n"} - }, { - "label": "Belt.Int.fromFloat", - "kind": 12, - "tags": [], - "detail": "float => int", - "documentation": {"kind": "markdown", "value": "\nConverts a given `float` to an `int`.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.fromFloat(1.0) === 1) /* true */\n```\n"} }, { "label": "Belt.Int.-", "kind": 12, diff --git a/analysis/tests/src/expected/CompletionPipeChain.res.txt b/analysis/tests/src/expected/CompletionPipeChain.res.txt index 91f6ca0e1..d5b3013e4 100644 --- a/analysis/tests/src/expected/CompletionPipeChain.res.txt +++ b/analysis/tests/src/expected/CompletionPipeChain.res.txt @@ -28,12 +28,6 @@ Path Integer. "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 @@ -50,12 +44,6 @@ CPPipe type path:SuperFloat.t CPPipe pathFromEnv:SuperFloat found:true Path SuperFloat. [{ - "label": "SuperFloat.fromInteger", - "kind": 12, - "tags": [], - "detail": "Integer.t => t", - "documentation": null - }, { "label": "SuperFloat.toInteger", "kind": 12, "tags": [], @@ -94,12 +82,6 @@ Path Integer. "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 @@ -133,12 +115,6 @@ Path Integer. "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 @@ -172,12 +148,6 @@ Path Integer. "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 @@ -211,12 +181,6 @@ Path Integer. "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 @@ -233,12 +197,6 @@ CPPipe type path:t CPPipe pathFromEnv:SuperFloat found:true Path SuperFloat. [{ - "label": "SuperFloat.fromInteger", - "kind": 12, - "tags": [], - "detail": "Integer.t => t", - "documentation": null - }, { "label": "SuperFloat.toInteger", "kind": 12, "tags": [], @@ -292,12 +250,6 @@ Path CompletionSupport.Test. "tags": [], "detail": "t => t", "documentation": null - }, { - "label": "CompletionSupport.Test.make", - "kind": 12, - "tags": [], - "detail": "int => t", - "documentation": null }] Complete src/CompletionPipeChain.res 54:78 @@ -325,12 +277,6 @@ Path CompletionSupport.Test. "tags": [], "detail": "t => t", "documentation": null - }, { - "label": "CompletionSupport.Test.make", - "kind": 12, - "tags": [], - "detail": "int => t", - "documentation": null }] Complete src/CompletionPipeChain.res 58:5 @@ -396,12 +342,6 @@ Path CompletionSupport.Test. "tags": [], "detail": "t => t", "documentation": null - }, { - "label": "CompletionSupport.Test.make", - "kind": 12, - "tags": [], - "detail": "int => t", - "documentation": null }] Complete src/CompletionPipeChain.res 73:15 @@ -428,12 +368,6 @@ Path CompletionSupport.Test. "tags": [], "detail": "t => t", "documentation": null - }, { - "label": "CompletionSupport.Test.make", - "kind": 12, - "tags": [], - "detail": "int => t", - "documentation": null }] Complete src/CompletionPipeChain.res 82:30 @@ -525,12 +459,6 @@ Path Integer. "tags": [], "detail": "(t, int => int) => t", "documentation": null - }, { - "label": "Integer.make", - "kind": 12, - "tags": [], - "detail": "int => t", - "documentation": null }] Complete src/CompletionPipeChain.res 98:21 diff --git a/analysis/tests/src/expected/CompletionPipeSubmodules.res.txt b/analysis/tests/src/expected/CompletionPipeSubmodules.res.txt index df1bd42cd..e1fb8c673 100644 --- a/analysis/tests/src/expected/CompletionPipeSubmodules.res.txt +++ b/analysis/tests/src/expected/CompletionPipeSubmodules.res.txt @@ -1,5 +1,5 @@ -Complete src/CompletionPipeSubmodules.res 12:20 -posCursor:[12:20] posNoWhite:[12:19] Found expr:[12:11->20:8] +Complete src/CompletionPipeSubmodules.res 14:20 +posCursor:[14:20] posNoWhite:[14:19] Found expr:[14:11->22:8] Completable: Cpath Value[A, B1, xx]-> Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives @@ -11,21 +11,15 @@ CPPipe type path:b1 CPPipe pathFromEnv:A.B1 found:true Path A.B1. [{ - "label": "A.B1.xx", + "label": "A.B1.d", "kind": 12, "tags": [], - "detail": "b1", - "documentation": {"kind": "markdown", "value": "```rescript\ntype b1 = B1\n```"} - }, { - "label": "A.B1.B1", - "kind": 4, - "tags": [], - "detail": "B1", - "documentation": {"kind": "markdown", "value": "```rescript\nB1\n```\n\n```rescript\ntype b1 = B1\n```"} + "detail": "t => string", + "documentation": null }] -Complete src/CompletionPipeSubmodules.res 16:18 -posCursor:[16:18] posNoWhite:[16:17] Found expr:[16:11->20:8] +Complete src/CompletionPipeSubmodules.res 18:18 +posCursor:[18:18] posNoWhite:[18:17] Found expr:[18:11->22:8] Completable: Cpath Value[A, x].v-> Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives @@ -35,25 +29,19 @@ ContextPath Value[A, x] Path A.x Path v CPPipe env:CompletionPipeSubmodules envFromCompletionItem:CompletionPipeSubmodules.A -CPPipe type path:B1.b1 +CPPipe type path:B1.t CPPipe pathFromEnv:A.B1 found:true Path A.B1. [{ - "label": "A.B1.xx", + "label": "A.B1.d", "kind": 12, "tags": [], - "detail": "b1", - "documentation": {"kind": "markdown", "value": "```rescript\ntype b1 = B1\n```"} - }, { - "label": "A.B1.B1", - "kind": 4, - "tags": [], - "detail": "B1", - "documentation": {"kind": "markdown", "value": "```rescript\nB1\n```\n\n```rescript\ntype b1 = B1\n```"} + "detail": "t => string", + "documentation": null }] -Complete src/CompletionPipeSubmodules.res 38:20 -posCursor:[38:20] posNoWhite:[38:19] Found expr:[38:11->0:-1] +Complete src/CompletionPipeSubmodules.res 43:20 +posCursor:[43:20] posNoWhite:[43:19] Found expr:[43:11->0:-1] Completable: Cpath Value[E, e].v.v-> Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives @@ -67,15 +55,15 @@ CPPipe type path:C.t CPPipe pathFromEnv:C found:false Path C. [{ - "label": "C.C", - "kind": 4, + "label": "C.do", + "kind": 12, "tags": [], - "detail": "C", - "documentation": {"kind": "markdown", "value": "```rescript\nC\n```\n\n```rescript\ntype t = C\n```"} + "detail": "t => string", + "documentation": null }] -Complete src/CompletionPipeSubmodules.res 42:21 -posCursor:[42:21] posNoWhite:[42:20] Found expr:[42:11->0:-1] +Complete src/CompletionPipeSubmodules.res 47:21 +posCursor:[47:21] posNoWhite:[47:20] Found expr:[47:11->0:-1] Completable: Cpath Value[E, e].v.v2-> Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives @@ -89,10 +77,10 @@ CPPipe type path:C2.t2 CPPipe pathFromEnv:D.C2 found:true Path D.C2. [{ - "label": "D.C2.C2", - "kind": 4, + "label": "D.C2.do", + "kind": 12, "tags": [], - "detail": "C2", - "documentation": {"kind": "markdown", "value": "```rescript\nC2\n```\n\n```rescript\ntype t2 = C2\n```"} + "detail": "t => string", + "documentation": null }] From 5ad03190509ff7fa6b8fff8e1f6a4771c92de75b Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Sat, 16 Nov 2024 14:12:09 +0100 Subject: [PATCH 10/34] pipe complete only for functions that take the expected type as the first argument --- analysis/src/CompletionBackEnd.ml | 5 +--- analysis/src/TypeUtils.ml | 11 +++++++- analysis/tests/src/CompletionPipeChain.res | 9 +++++++ .../tests/src/CompletionPipeSubmodules.res | 8 +++--- .../src/expected/CompletionPipeChain.res.txt | 20 +++++++++++++++ .../expected/CompletionPipeSubmodules.res.txt | 25 +++++++++---------- 6 files changed, 55 insertions(+), 23 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index f73642cb2..90d001df4 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -1113,6 +1113,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact TypeUtils.getExtraModuleToCompleteFromForType typ ~env:envFromCompletionItem ~full in + let tPath = TypeUtils.pathFromTypeExpr typ in let env, typ = typ |> TypeUtils.resolveTypeForPipeCompletion ~env ~package ~full ~lhsLoc @@ -1123,10 +1124,6 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact (QueryEnv.toString env) (QueryEnv.toString envFromCompletionItem) else Printf.printf "CPPipe env:%s\n" (QueryEnv.toString env); - let tPath = - match typ with - | Builtin (_, t) | TypExpr t -> TypeUtils.pathFromTypeExpr t - in let completionPath = match typ with | Builtin (builtin, _) -> diff --git a/analysis/src/TypeUtils.ml b/analysis/src/TypeUtils.ml index 9ee3ee18a..ac4741f1c 100644 --- a/analysis/src/TypeUtils.ml +++ b/analysis/src/TypeUtils.ml @@ -1172,7 +1172,16 @@ let rec fnTakesTypeAsFirstArg ~env ~full ~path t = let p = pathFromTypeExpr t in match p with | None -> false - | Some p -> Path.same p path || Path.name p = "t") + | Some p -> + (* + Rules: + - The path p of the current type in the module we're looking at is relative to the current module. + - The path we're comparing against, `path`, is assumed to belong to this current module, because we're completing from it. + + Therefore, we can safely pluck out just the last part of the `path`, but need to use the entire name of the current type + we're comparing with. + *) + Path.name p = Path.last path || Path.name p = "t") | _ -> false) | _ -> false diff --git a/analysis/tests/src/CompletionPipeChain.res b/analysis/tests/src/CompletionPipeChain.res index 2a16c21cf..adfdc6548 100644 --- a/analysis/tests/src/CompletionPipeChain.res +++ b/analysis/tests/src/CompletionPipeChain.res @@ -103,3 +103,12 @@ let r = %re("/t/g") // r->la // ^com + +module Xyz = { + type xx = One + let do = (_: xx) => "" +} + +let xx = Xyz.One +// xx-> +// ^com diff --git a/analysis/tests/src/CompletionPipeSubmodules.res b/analysis/tests/src/CompletionPipeSubmodules.res index 191e6d4ee..5870f5345 100644 --- a/analysis/tests/src/CompletionPipeSubmodules.res +++ b/analysis/tests/src/CompletionPipeSubmodules.res @@ -1,14 +1,13 @@ module A = { module B1 = { type b1 = B1 - type t = b1 // TODO(pipe-filter) Should be allowed without needing type t let xx = B1 - let d = (_: t) => "" + let d = (_: b1) => "" } module B2 = { let yy = 20 } - type t = {v: B1.t} // TODO(pipe-filter) Should be allowed without needing type t + type t2 = {v: B1.b1} let x = {v: B1.B1} } @@ -28,8 +27,7 @@ module C = { module D = { module C2 = { type t2 = C2 - type t = t2 // TODO(pipe-filter) Should be allowed without needing type t - let do = (_: t) => "" + let do = (_: t2) => "" } type d = {v: C.t, v2: C2.t2} diff --git a/analysis/tests/src/expected/CompletionPipeChain.res.txt b/analysis/tests/src/expected/CompletionPipeChain.res.txt index d5b3013e4..0ba992c9a 100644 --- a/analysis/tests/src/expected/CompletionPipeChain.res.txt +++ b/analysis/tests/src/expected/CompletionPipeChain.res.txt @@ -501,3 +501,23 @@ Path Js.Re.la "documentation": {"kind": "markdown", "value": "\nReturns the index where the next match will start its search. This property\nwill be modified when the RegExp object is used, if the global (\"g\") flag is\nset.\n\n## Examples\n\n```rescript\nlet re = %re(\"/ab*TODO/g\")\nlet str = \"abbcdefabh\"\n\nlet break = ref(false)\nwhile !break.contents {\n switch Js.Re.exec_(re, str) {\n | Some(result) => Js.Nullable.iter(Js.Re.captures(result)[0], (. match_) => {\n let next = Belt.Int.toString(Js.Re.lastIndex(re))\n Js.log(\"Found \" ++ (match_ ++ (\". Next match starts at \" ++ next)))\n })\n | None => break := true\n }\n}\n```\n\nSee\n[`RegExp: lastIndex`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/lastIndex)\non MDN.\n"} }] +Complete src/CompletionPipeChain.res 112:7 +posCursor:[112:7] posNoWhite:[112:6] Found expr:[112:3->0:-1] +Completable: Cpath Value[xx]-> +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[xx]-> +ContextPath Value[xx] +Path xx +CPPipe env:CompletionPipeChain +CPPipe type path:Xyz.xx +CPPipe pathFromEnv:Xyz found:true +Path Xyz. +[{ + "label": "Xyz.do", + "kind": 12, + "tags": [], + "detail": "xx => string", + "documentation": null + }] + diff --git a/analysis/tests/src/expected/CompletionPipeSubmodules.res.txt b/analysis/tests/src/expected/CompletionPipeSubmodules.res.txt index e1fb8c673..0ae6a56b5 100644 --- a/analysis/tests/src/expected/CompletionPipeSubmodules.res.txt +++ b/analysis/tests/src/expected/CompletionPipeSubmodules.res.txt @@ -1,5 +1,5 @@ -Complete src/CompletionPipeSubmodules.res 14:20 -posCursor:[14:20] posNoWhite:[14:19] Found expr:[14:11->22:8] +Complete src/CompletionPipeSubmodules.res 13:20 +posCursor:[13:20] posNoWhite:[13:19] Found expr:[13:11->21:8] Completable: Cpath Value[A, B1, xx]-> Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives @@ -14,12 +14,12 @@ Path A.B1. "label": "A.B1.d", "kind": 12, "tags": [], - "detail": "t => string", + "detail": "b1 => string", "documentation": null }] -Complete src/CompletionPipeSubmodules.res 18:18 -posCursor:[18:18] posNoWhite:[18:17] Found expr:[18:11->22:8] +Complete src/CompletionPipeSubmodules.res 17:18 +posCursor:[17:18] posNoWhite:[17:17] Found expr:[17:11->21:8] Completable: Cpath Value[A, x].v-> Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives @@ -27,21 +27,20 @@ ContextPath Value[A, x].v-> ContextPath Value[A, x].v ContextPath Value[A, x] Path A.x -Path v CPPipe env:CompletionPipeSubmodules envFromCompletionItem:CompletionPipeSubmodules.A -CPPipe type path:B1.t +CPPipe type path:B1.b1 CPPipe pathFromEnv:A.B1 found:true Path A.B1. [{ "label": "A.B1.d", "kind": 12, "tags": [], - "detail": "t => string", + "detail": "b1 => string", "documentation": null }] -Complete src/CompletionPipeSubmodules.res 43:20 -posCursor:[43:20] posNoWhite:[43:19] Found expr:[43:11->0:-1] +Complete src/CompletionPipeSubmodules.res 41:20 +posCursor:[41:20] posNoWhite:[41:19] Found expr:[41:11->0:-1] Completable: Cpath Value[E, e].v.v-> Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives @@ -62,8 +61,8 @@ Path C. "documentation": null }] -Complete src/CompletionPipeSubmodules.res 47:21 -posCursor:[47:21] posNoWhite:[47:20] Found expr:[47:11->0:-1] +Complete src/CompletionPipeSubmodules.res 45:21 +posCursor:[45:21] posNoWhite:[45:20] Found expr:[45:11->0:-1] Completable: Cpath Value[E, e].v.v2-> Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives @@ -80,7 +79,7 @@ Path D.C2. "label": "D.C2.do", "kind": 12, "tags": [], - "detail": "t => string", + "detail": "t2 => string", "documentation": null }] From 943b01fe0d3b0a8bcf1b6a90ac94cd63827e3094 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Wed, 20 Nov 2024 13:40:31 +0100 Subject: [PATCH 11/34] start refactoring dot completion everywhere --- analysis/src/CompletionBackEnd.ml | 126 ++++++++- analysis/src/TypeUtils.ml | 23 +- .../tests/src/DotCompletionEverywhere.res | 65 +++++ .../expected/DotCompletionEverywhere.res.txt | 246 ++++++++++++++++++ 4 files changed, 439 insertions(+), 21 deletions(-) create mode 100644 analysis/tests/src/DotCompletionEverywhere.res create mode 100644 analysis/tests/src/expected/DotCompletionEverywhere.res.txt diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 90d001df4..626f24309 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -993,6 +993,105 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact path @ [fieldName] |> getCompletionsForPath ~debug ~opens ~full ~pos ~exact ~completionContext:Field ~env ~scope + | CPField {contextPath = cp; fieldName; fieldNameLoc} when Debug.verbose () -> + (* TODO: this should only happen when the dot completion is at the end of the path *) + if Debug.verbose () then print_endline "[ctx_path]--> dot completion!"; + let completionsForCtxPath = + cp + |> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env + ~exact:true ~scope + |> completionsGetTypeEnv2 ~debug ~full ~opens ~rawOpens ~pos + in + (* These are the main completions for the dot. *) + let mainCompletions = + match completionsForCtxPath with + | Some (typ, env) + when typ |> TypeUtils.extractObjectType ~env ~package |> Option.is_some + -> + (* Handle obj completion via dot *) + if Debug.verbose () then + Printf.printf "[dot_completion]--> Obj type found:\n"; + let objEnv, obj = + typ |> TypeUtils.extractObjectType ~env ~package |> Option.get + in + obj |> TypeUtils.getObjFields + |> Utils.filterMap (fun (field, typ) -> + if Utils.checkName field ~prefix:fieldName ~exact then + let fullObjFieldName = Printf.sprintf "[\"%s\"]" field in + Some + (Completion.create fullObjFieldName ~range:fieldNameLoc + ~insertText:fullObjFieldName ~env:objEnv + ~kind:(Completion.ObjLabel typ)) + else None) + | Some (typ, env) + when typ |> TypeUtils.extractRecordType ~env ~package |> Option.is_some + -> + let env, fields, decl, _path, _attributes = + typ |> TypeUtils.extractRecordType ~env ~package |> Option.get + in + if Debug.verbose () then + Printf.printf "[dot_completion]--> Record type found\n"; + let recordAsString = + decl.item.decl |> Shared.declToString decl.name.txt + in + fields + |> Utils.filterMap (fun field -> + if Utils.checkName field.fname.txt ~prefix:fieldName ~exact then + Some + (Completion.create field.fname.txt ~env + ?deprecated:field.deprecated ~docstring:field.docstring + ~kind:(Completion.Field (field, recordAsString))) + else None) + | Some (_typ, _env) -> + (* No more primary completions, for now. *) + [] + | None -> [] + in + let pipeCompletions = + match completionsForCtxPath with + | None -> [] + | Some (typ, envFromCompletionItem) -> ( + let tPath = TypeUtils.pathFromTypeExpr typ in + match tPath with + | None -> [] + | Some tPath -> + let completionPath = + (tPath |> Utils.expandPath |> List.tl |> List.rev) + @ (envFromCompletionItem.pathRev |> List.rev) + in + if List.length completionPath = 0 then [] + else + let completions = + completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom + ~opens ~pos ~scope ~debug ~prefix:fieldName ~env ~rawOpens ~full + completionPath + in + completions + |> TypeUtils.filterPipeableFunctions ~env ~full + ~lastPath:(Path.last tPath) ~replaceRange:fieldNameLoc) + in + (* Extra completions from configure extra module(s) *) + let extraCompletions = + match completionsForCtxPath with + | None -> [] + | Some (typ, envFromCompletionItem) -> ( + match + TypeUtils.getExtraModuleToCompleteFromForType typ + ~env:envFromCompletionItem ~full + with + | None -> [] + | Some completionPath -> + completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens + ~pos ~scope ~debug ~prefix:fieldName ~env ~rawOpens ~full + completionPath + |> TypeUtils.filterPipeableFunctions ~env ~full + ~replaceRange:fieldNameLoc + ?lastPath: + (match TypeUtils.pathFromTypeExpr typ with + | None -> None + | Some tPath -> Some (Path.last tPath))) + in + mainCompletions @ pipeCompletions @ extraCompletions | CPField {contextPath = cp; fieldName; fieldNameLoc} -> ( if Debug.verbose () then print_endline "[ctx_path]--> CPField"; let completionsForCtxPath = @@ -1056,8 +1155,8 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact completionsForPipeFromCompletionPath ~opens ~pos ~scope ~debug ~prefix:fieldName ~envCompletionIsMadeFrom:env ~env:envFromExtracted ~rawOpens ~full completionPath - |> TypeUtils.filterPipeableFunctions ~env:envFromExtracted ~full ~path - ~replaceRange:fieldNameLoc + |> TypeUtils.filterPipeableFunctions ~env:envFromExtracted ~full + ~lastPath:(Path.last path) ~replaceRange:fieldNameLoc | None -> [] in pipeCompletionsForModule @@ -1081,16 +1180,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact | Some (typ, env) -> ( match typ |> TypeUtils.extractObjectType ~env ~package with | Some (env, tObj) -> - let rec getFields (texp : Types.type_expr) = - match texp.desc with - | Tfield (name, _, t1, t2) -> - let fields = t2 |> getFields in - (name, t1) :: fields - | Tlink te | Tsubst te | Tpoly (te, []) -> te |> getFields - | Tvar None -> [] - | _ -> [] - in - tObj |> getFields + tObj |> TypeUtils.getObjFields |> Utils.filterMap (fun (field, typ) -> if Utils.checkName field ~prefix:label ~exact then Some @@ -1175,7 +1265,11 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens ~pos ~scope ~debug ~prefix:funNamePrefix ~env ~rawOpens ~full completionPath - |> TypeUtils.filterPipeableFunctions ~env ~full ?path:tPath + |> TypeUtils.filterPipeableFunctions ~env ~full + ?lastPath: + (match tPath with + | None -> None + | Some tPath -> Some (Path.last tPath)) in match completionPath with | Some completionPath -> ( @@ -1183,7 +1277,11 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens ~pos ~scope ~debug ~prefix:funNamePrefix ~env ~rawOpens ~full completionPath - |> TypeUtils.filterPipeableFunctions ~env ~full ?path:tPath + |> TypeUtils.filterPipeableFunctions ~env ~full + ?lastPath: + (match tPath with + | None -> None + | Some tPath -> Some (Path.last tPath)) in let completions = completionsFromMainFn @ completionsFromExtraModule in (* We add React element functions to the completion if we're in a JSX context *) diff --git a/analysis/src/TypeUtils.ml b/analysis/src/TypeUtils.ml index ac4741f1c..b18949117 100644 --- a/analysis/src/TypeUtils.ml +++ b/analysis/src/TypeUtils.ml @@ -1159,13 +1159,13 @@ let getExtraModuleToCompleteFromForType ~env ~full (t : Types.type_expr) = (** Checks whether the provided type represents a function that takes the provided path as the first argument (meaning it's pipeable). *) -let rec fnTakesTypeAsFirstArg ~env ~full ~path t = +let rec fnTakesTypeAsFirstArg ~env ~full ~lastPath t = match t.Types.desc with | Tlink t1 | Tsubst t1 | Tpoly (t1, []) | Tconstr (Pident {name = "function$"}, [t1; _], _) -> - fnTakesTypeAsFirstArg ~env ~full ~path t1 + fnTakesTypeAsFirstArg ~env ~full ~lastPath t1 | Tarrow _ -> ( match extractFunctionType ~env ~package:full.package t with | (Nolabel, t) :: _, _ -> ( @@ -1181,7 +1181,7 @@ let rec fnTakesTypeAsFirstArg ~env ~full ~path t = Therefore, we can safely pluck out just the last part of the `path`, but need to use the entire name of the current type we're comparing with. *) - Path.name p = Path.last path || Path.name p = "t") + Path.name p = lastPath || Path.name p = "t") | _ -> false) | _ -> false @@ -1201,14 +1201,14 @@ let transformCompletionToPipeCompletion ~env ~replaceRange } (** Filters out completions that are not pipeable from a list of completions. *) -let filterPipeableFunctions ~env ~full ?path ?replaceRange completions = - match path with +let filterPipeableFunctions ~env ~full ?lastPath ?replaceRange completions = + match lastPath with | None -> completions - | Some path -> + | Some lastPath -> completions |> List.filter_map (fun (completion : Completion.t) -> match completion.kind with - | Value t when fnTakesTypeAsFirstArg ~env ~full ~path t -> ( + | Value t when fnTakesTypeAsFirstArg ~env ~full ~lastPath t -> ( match replaceRange with | None -> Some completion | Some replaceRange -> @@ -1222,3 +1222,12 @@ let removeCurrentModuleIfNeeded ~envCompletionIsMadeFrom completionPath = && List.hd completionPath = envCompletionIsMadeFrom.QueryEnv.file.moduleName then List.tl completionPath else completionPath + +let rec getObjFields (texp : Types.type_expr) = + match texp.desc with + | Tfield (name, _, t1, t2) -> + let fields = t2 |> getObjFields in + (name, t1) :: fields + | Tlink te | Tsubst te | Tpoly (te, []) -> te |> getObjFields + | Tvar None -> [] + | _ -> [] diff --git a/analysis/tests/src/DotCompletionEverywhere.res b/analysis/tests/src/DotCompletionEverywhere.res new file mode 100644 index 000000000..2cd59b98a --- /dev/null +++ b/analysis/tests/src/DotCompletionEverywhere.res @@ -0,0 +1,65 @@ +let someObj = { + "name": "hello", + "age": 123, +} +// ^dv+ +// someObj. +// ^com + +// someObj.na +// ^com + +type rrr = {name: string} +let rrr = {name: "hello"} + +// rrr.n +// ^com + +module SomeMod = { + module SomeOtherMod = { + type x + + @send external do: x => unit = "do" + + external xx: x = "xx" + } +} + +external x: SomeMod.SomeOtherMod.x = "x" + +// x. +// ^com + +// SomeMod.SomeOtherMod.xx. +// ^com + +module Sss = { + type rrr = {name: string} + let rrr = {name: "hello"} + let do = rrr => rrr.name +} + +// Sss.rrr. +// ^com + +@editor.completeFrom(DotCompletionEverywhere.X2) +type x2x2 = {namee: string} +let x2x2 = {namee: "hello"} + +module X2 = { + let stuff = x => x.namee +} + +// x2x2. +// ^com + +let obj = { + "name": "ReScript", + "number": 1, + "nothing": true, +} + +// obj. +// ^com + +// ^dv- diff --git a/analysis/tests/src/expected/DotCompletionEverywhere.res.txt b/analysis/tests/src/expected/DotCompletionEverywhere.res.txt new file mode 100644 index 000000000..a51d8980b --- /dev/null +++ b/analysis/tests/src/expected/DotCompletionEverywhere.res.txt @@ -0,0 +1,246 @@ + +Complete src/DotCompletionEverywhere.res 5:11 +posCursor:[5:11] posNoWhite:[5:10] Found expr:[5:3->5:11] +Pexp_field [5:3->5:10] _:[11:0->5:11] +[set_result] set new result to Cpath Value[someObj]."" +Completable: Cpath Value[someObj]."" +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[someObj]."" +[ctx_path]--> dot completion! +ContextPath Value[someObj] +[ctx_path]--> CPId +Path someObj +[dot_completion]--> Obj type found: +[{ + "label": "[\"age\"]", + "kind": 4, + "tags": [], + "detail": "int", + "documentation": null, + "textEdit": { + "range": {"start": {"line": 5, "character": 10}, "end": {"line": 5, "character": 10}}, + "newText": "[\"age\"]" + } + }, { + "label": "[\"name\"]", + "kind": 4, + "tags": [], + "detail": "string", + "documentation": null, + "textEdit": { + "range": {"start": {"line": 5, "character": 10}, "end": {"line": 5, "character": 10}}, + "newText": "[\"name\"]" + } + }] + +Complete src/DotCompletionEverywhere.res 8:13 +posCursor:[8:13] posNoWhite:[8:12] Found expr:[8:3->8:13] +Pexp_field [8:3->8:10] na:[8:11->8:13] +[set_result] set new result to Cpath Value[someObj].na +Completable: Cpath Value[someObj].na +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[someObj].na +[ctx_path]--> dot completion! +ContextPath Value[someObj] +[ctx_path]--> CPId +Path someObj +[dot_completion]--> Obj type found: +[{ + "label": "[\"name\"]", + "kind": 4, + "tags": [], + "detail": "string", + "documentation": null, + "textEdit": { + "range": {"start": {"line": 8, "character": 11}, "end": {"line": 8, "character": 13}}, + "newText": "[\"name\"]" + } + }] + +Complete src/DotCompletionEverywhere.res 14:8 +posCursor:[14:8] posNoWhite:[14:7] Found expr:[14:3->14:8] +Pexp_field [14:3->14:6] n:[14:7->14:8] +[set_result] set new result to Cpath Value[rrr].n +Completable: Cpath Value[rrr].n +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[rrr].n +[ctx_path]--> dot completion! +ContextPath Value[rrr] +[ctx_path]--> CPId +Path rrr +[dot_completion]--> Record type found +[{ + "label": "name", + "kind": 5, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype rrr = {name: string}\n```"} + }] + +Complete src/DotCompletionEverywhere.res 29:5 +posCursor:[29:5] posNoWhite:[29:4] Found expr:[29:3->29:5] +Pexp_field [29:3->29:4] _:[35:0->29:5] +[set_result] set new result to Cpath Value[x]."" +Completable: Cpath Value[x]."" +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[x]."" +[ctx_path]--> dot completion! +ContextPath Value[x] +[ctx_path]--> CPId +Path x +Path SomeMod.SomeOtherMod. +[{ + "label": "->SomeMod.SomeOtherMod.do", + "kind": 12, + "tags": [], + "detail": "x => unit", + "documentation": null, + "sortText": "do", + "textEdit": { + "range": {"start": {"line": 29, "character": 4}, "end": {"line": 29, "character": 4}}, + "newText": "->SomeMod.SomeOtherMod.do" + } + }] + +Complete src/DotCompletionEverywhere.res 32:27 +posCursor:[32:27] posNoWhite:[32:26] Found expr:[32:3->32:27] +Pexp_field [32:3->32:26] _:[35:0->32:27] +[set_result] set new result to Cpath Value[SomeMod, SomeOtherMod, xx]."" +Completable: Cpath Value[SomeMod, SomeOtherMod, xx]."" +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[SomeMod, SomeOtherMod, xx]."" +[ctx_path]--> dot completion! +ContextPath Value[SomeMod, SomeOtherMod, xx] +[ctx_path]--> CPId +Path SomeMod.SomeOtherMod.xx +Path SomeMod.SomeOtherMod. +[{ + "label": "->SomeMod.SomeOtherMod.do", + "kind": 12, + "tags": [], + "detail": "x => unit", + "documentation": null, + "sortText": "do", + "textEdit": { + "range": {"start": {"line": 32, "character": 26}, "end": {"line": 32, "character": 26}}, + "newText": "->SomeMod.SomeOtherMod.do" + } + }] + +Complete src/DotCompletionEverywhere.res 41:11 +posCursor:[41:11] posNoWhite:[41:10] Found expr:[41:3->41:11] +Pexp_field [41:3->41:10] _:[44:0->41:11] +[set_result] set new result to Cpath Value[Sss, rrr]."" +Completable: Cpath Value[Sss, rrr]."" +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[Sss, rrr]."" +[ctx_path]--> dot completion! +ContextPath Value[Sss, rrr] +[ctx_path]--> CPId +Path Sss.rrr +[dot_completion]--> Record type found +Path Sss. +[{ + "label": "name", + "kind": 5, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype rrr = {name: string}\n```"} + }, { + "label": "->Sss.do", + "kind": 12, + "tags": [], + "detail": "rrr => string", + "documentation": null, + "sortText": "do", + "textEdit": { + "range": {"start": {"line": 41, "character": 10}, "end": {"line": 41, "character": 10}}, + "newText": "->Sss.do" + } + }] + +Complete src/DotCompletionEverywhere.res 52:8 +posCursor:[52:8] posNoWhite:[52:7] Found expr:[52:3->52:8] +Pexp_field [52:3->52:7] _:[55:0->52:8] +[set_result] set new result to Cpath Value[x2x2]."" +Completable: Cpath Value[x2x2]."" +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[x2x2]."" +[ctx_path]--> dot completion! +ContextPath Value[x2x2] +[ctx_path]--> CPId +Path x2x2 +[dot_completion]--> Record type found +Path X2. +[{ + "label": "namee", + "kind": 5, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "```rescript\nnamee: string\n```\n\n```rescript\ntype x2x2 = {namee: string}\n```"} + }, { + "label": "->X2.stuff", + "kind": 12, + "tags": [], + "detail": "x2x2 => string", + "documentation": null, + "sortText": "stuff", + "textEdit": { + "range": {"start": {"line": 52, "character": 7}, "end": {"line": 52, "character": 7}}, + "newText": "->X2.stuff" + } + }] + +Complete src/DotCompletionEverywhere.res 61:7 +posCursor:[61:7] posNoWhite:[61:6] Found expr:[61:3->61:7] +Pexp_field [61:3->61:6] _:[66:0->61:7] +[set_result] set new result to Cpath Value[obj]."" +Completable: Cpath Value[obj]."" +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[obj]."" +[ctx_path]--> dot completion! +ContextPath Value[obj] +[ctx_path]--> CPId +Path obj +[dot_completion]--> Obj type found: +[{ + "label": "[\"name\"]", + "kind": 4, + "tags": [], + "detail": "string", + "documentation": null, + "textEdit": { + "range": {"start": {"line": 61, "character": 6}, "end": {"line": 61, "character": 6}}, + "newText": "[\"name\"]" + } + }, { + "label": "[\"nothing\"]", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null, + "textEdit": { + "range": {"start": {"line": 61, "character": 6}, "end": {"line": 61, "character": 6}}, + "newText": "[\"nothing\"]" + } + }, { + "label": "[\"number\"]", + "kind": 4, + "tags": [], + "detail": "int", + "documentation": null, + "textEdit": { + "range": {"start": {"line": 61, "character": 6}, "end": {"line": 61, "character": 6}}, + "newText": "[\"number\"]" + } + }] + + From ebc5d9ea06c5128d3df9b197ca4d304211a81d6d Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Thu, 28 Nov 2024 11:46:05 +0100 Subject: [PATCH 12/34] refactor dot completion --- analysis/src/CompletionBackEnd.ml | 253 ++++++++---------- analysis/src/SharedTypes.ml | 9 +- analysis/src/TypeUtils.ml | 10 +- analysis/tests/src/CompletionFromModule2.res | 2 +- .../tests/src/expected/Completion.res.txt | 12 + .../expected/CompletionExpressions.res.txt | 3 + .../src/expected/CompletionFromModule.res.txt | 13 +- .../expected/CompletionFromModule2.res.txt | 26 +- .../expected/CompletionInferValues.res.txt | 9 + .../src/expected/CompletionPipeChain.res.txt | 4 + .../expected/CompletionPipeSubmodules.res.txt | 10 + .../expected/DotCompletionEverywhere.res.txt | 48 ++-- analysis/tests/src/expected/Hover.res.txt | 9 + .../src/expected/RecordCompletion.res.txt | 5 +- 14 files changed, 226 insertions(+), 187 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 626f24309..86784a457 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -770,46 +770,58 @@ let completionsGetTypeEnv = function type getCompletionsForContextPathMode = Regular | Pipe -let completionsGetCompletionType ~full = function - | {Completion.kind = Value typ; env} :: _ - | {Completion.kind = ObjLabel typ; env} :: _ - | {Completion.kind = Field ({typ}, _); env} :: _ -> +let completionsGetCompletionType ~full completions = + let firstNonSyntheticCompletion = + List.find_opt (fun c -> not c.Completion.synthetic) completions + in + match firstNonSyntheticCompletion with + | Some {Completion.kind = Value typ; env} + | Some {Completion.kind = ObjLabel typ; env} + | Some {Completion.kind = Field ({typ}, _); env} -> typ |> TypeUtils.extractType ~env ~package:full.package |> Option.map (fun (typ, _) -> (typ, env)) - | {Completion.kind = Type typ; env} :: _ -> ( + | Some {Completion.kind = Type typ; env} -> ( match TypeUtils.extractTypeFromResolvedType typ ~env ~full with | None -> None | Some extractedType -> Some (extractedType, env)) - | {Completion.kind = ExtractedType (typ, _); env} :: _ -> Some (typ, env) + | Some {Completion.kind = ExtractedType (typ, _); env} -> Some (typ, env) | _ -> None -let rec completionsGetCompletionType2 ~debug ~full ~opens ~rawOpens ~pos = - function - | {Completion.kind = Value typ; env} :: _ - | {Completion.kind = ObjLabel typ; env} :: _ - | {Completion.kind = Field ({typ}, _); env} :: _ -> +let rec completionsGetCompletionType2 ~debug ~full ~opens ~rawOpens ~pos + completions = + let firstNonSyntheticCompletion = + List.find_opt (fun c -> not c.Completion.synthetic) completions + in + match firstNonSyntheticCompletion with + | Some + ( {Completion.kind = Value typ; env} + | {Completion.kind = ObjLabel typ; env} + | {Completion.kind = Field ({typ}, _); env} ) -> Some (TypeExpr typ, env) - | {Completion.kind = FollowContextPath (ctxPath, scope); env} :: _ -> + | Some {Completion.kind = FollowContextPath (ctxPath, scope); env} -> ctxPath |> getCompletionsForContextPath ~debug ~full ~env ~exact:true ~opens ~rawOpens ~pos ~scope |> completionsGetCompletionType2 ~debug ~full ~opens ~rawOpens ~pos - | {Completion.kind = Type typ; env} :: _ -> ( + | Some {Completion.kind = Type typ; env} -> ( match TypeUtils.extractTypeFromResolvedType typ ~env ~full with | None -> None | Some extractedType -> Some (ExtractedType extractedType, env)) - | {Completion.kind = ExtractedType (typ, _); env} :: _ -> + | Some {Completion.kind = ExtractedType (typ, _); env} -> Some (ExtractedType typ, env) | _ -> None and completionsGetTypeEnv2 ~debug (completions : Completion.t list) ~full ~opens ~rawOpens ~pos = - match completions with - | {Completion.kind = Value typ; env} :: _ -> Some (typ, env) - | {Completion.kind = ObjLabel typ; env} :: _ -> Some (typ, env) - | {Completion.kind = Field ({typ}, _); env} :: _ -> Some (typ, env) - | {Completion.kind = FollowContextPath (ctxPath, scope); env} :: _ -> + let firstNonSyntheticCompletion = + List.find_opt (fun c -> not c.Completion.synthetic) completions + in + match firstNonSyntheticCompletion with + | Some {Completion.kind = Value typ; env} -> Some (typ, env) + | Some {Completion.kind = ObjLabel typ; env} -> Some (typ, env) + | Some {Completion.kind = Field ({typ}, _); env} -> Some (typ, env) + | Some {Completion.kind = FollowContextPath (ctxPath, scope); env} -> ctxPath |> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact:true ~scope @@ -993,19 +1005,21 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact path @ [fieldName] |> getCompletionsForPath ~debug ~opens ~full ~pos ~exact ~completionContext:Field ~env ~scope - | CPField {contextPath = cp; fieldName; fieldNameLoc} when Debug.verbose () -> - (* TODO: this should only happen when the dot completion is at the end of the path *) + | CPField {contextPath = cp; fieldName; fieldNameLoc} -> if Debug.verbose () then print_endline "[ctx_path]--> dot completion!"; - let completionsForCtxPath = + let completions = cp |> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact:true ~scope - |> completionsGetTypeEnv2 ~debug ~full ~opens ~rawOpens ~pos + in + let completionsForCtxPath = + completions + |> completionsGetCompletionType2 ~debug ~full ~opens ~rawOpens ~pos in (* These are the main completions for the dot. *) let mainCompletions = match completionsForCtxPath with - | Some (typ, env) + | Some (TypeExpr typ, env) when typ |> TypeUtils.extractObjectType ~env ~package |> Option.is_some -> (* Handle obj completion via dot *) @@ -1019,46 +1033,70 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact if Utils.checkName field ~prefix:fieldName ~exact then let fullObjFieldName = Printf.sprintf "[\"%s\"]" field in Some - (Completion.create fullObjFieldName ~range:fieldNameLoc - ~insertText:fullObjFieldName ~env:objEnv - ~kind:(Completion.ObjLabel typ)) + (Completion.create fullObjFieldName ~synthetic:true + ~range:fieldNameLoc ~insertText:fullObjFieldName + ~env:objEnv ~kind:(Completion.ObjLabel typ)) else None) - | Some (typ, env) - when typ |> TypeUtils.extractRecordType ~env ~package |> Option.is_some - -> - let env, fields, decl, _path, _attributes = - typ |> TypeUtils.extractRecordType ~env ~package |> Option.get - in - if Debug.verbose () then - Printf.printf "[dot_completion]--> Record type found\n"; - let recordAsString = - decl.item.decl |> Shared.declToString decl.name.txt + | Some (t, env) -> ( + let extracted = + match t with + | TypeExpr typ -> ( + if Debug.verbose () then + Printf.printf + "[dot_completion]--> Found type expr for main completions\n"; + match typ |> TypeUtils.extractRecordType ~env ~package with + | Some (env, fields, typDecl, _path, _attributes) -> + Some + ( env, + fields, + typDecl.item.decl |> Shared.declToString typDecl.name.txt ) + | None -> None) + | ExtractedType typ -> ( + if Debug.verbose () then + Printf.printf + "[dot_completion]--> Found extracted type for main completions\n"; + match typ with + | Trecord {fields} -> + Some (env, fields, typ |> TypeUtils.extractedTypeToString) + | _ -> None) in - fields - |> Utils.filterMap (fun field -> - if Utils.checkName field.fname.txt ~prefix:fieldName ~exact then - Some - (Completion.create field.fname.txt ~env - ?deprecated:field.deprecated ~docstring:field.docstring - ~kind:(Completion.Field (field, recordAsString))) - else None) - | Some (_typ, _env) -> - (* No more primary completions, for now. *) - [] + match extracted with + | None -> [] + | Some (envFromExtracted, fields, recordAsString) -> + fields + |> Utils.filterMap (fun field -> + if Utils.checkName field.fname.txt ~prefix:fieldName ~exact + then + Some + (Completion.create field.fname.txt ~env:envFromExtracted + ?deprecated:field.deprecated ~docstring:field.docstring + ~kind:(Completion.Field (field, recordAsString))) + else None)) | None -> [] in let pipeCompletions = match completionsForCtxPath with - | None -> [] - | Some (typ, envFromCompletionItem) -> ( - let tPath = TypeUtils.pathFromTypeExpr typ in + | Some (TypeExpr typ, envFromCompletionItem) -> ( + if Debug.verbose () then + Printf.printf + "[dot_completion]--> Found Type expr when doing pipe completions\n"; + let tPath = + match TypeUtils.pathFromTypeExpr typ with + | None -> None + | Some tPath -> ( + match + TypeUtils.getPathRelativeToEnv ~debug ~env:envCompletionIsMadeFrom + ~envFromItem:envFromCompletionItem (Utils.expandPath tPath) + with + | None -> None + | Some completionPath -> Some (completionPath, tPath)) + in match tPath with | None -> [] - | Some tPath -> - let completionPath = - (tPath |> Utils.expandPath |> List.tl |> List.rev) - @ (envFromCompletionItem.pathRev |> List.rev) - in + | Some (completionPath, tPath) -> + if Debug.verbose () then + Printf.printf "[dot_completion]--> Got completion path %s\n" + (completionPath |> String.concat "."); if List.length completionPath = 0 then [] else let completions = @@ -1067,14 +1105,20 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact completionPath in completions - |> TypeUtils.filterPipeableFunctions ~env ~full + |> TypeUtils.filterPipeableFunctions ~synthetic:true ~env ~full ~lastPath:(Path.last tPath) ~replaceRange:fieldNameLoc) + | Some (ExtractedType _, _) -> + if Debug.verbose () then + Printf.printf + "[dot_completion]--> PROBLEM: Found extracted type when trying to \ + do pipe completions\n"; + [] + | _ -> [] in (* Extra completions from configure extra module(s) *) let extraCompletions = match completionsForCtxPath with - | None -> [] - | Some (typ, envFromCompletionItem) -> ( + | Some (TypeExpr typ, envFromCompletionItem) -> ( match TypeUtils.getExtraModuleToCompleteFromForType typ ~env:envFromCompletionItem ~full @@ -1084,90 +1128,21 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens ~pos ~scope ~debug ~prefix:fieldName ~env ~rawOpens ~full completionPath - |> TypeUtils.filterPipeableFunctions ~env ~full + |> TypeUtils.filterPipeableFunctions ~synthetic:true ~env ~full ~replaceRange:fieldNameLoc ?lastPath: (match TypeUtils.pathFromTypeExpr typ with | None -> None | Some tPath -> Some (Path.last tPath))) + | Some (ExtractedType _, _) -> + if Debug.verbose () then + Printf.printf + "[dot_completion]--> PROBLEM: Found extracted type when trying to \ + do extra completions\n"; + [] + | _ -> [] in - mainCompletions @ pipeCompletions @ extraCompletions - | CPField {contextPath = cp; fieldName; fieldNameLoc} -> ( - if Debug.verbose () then print_endline "[ctx_path]--> CPField"; - let completionsForCtxPath = - cp - |> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env - ~exact:true ~scope - in - let extracted = - match - completionsForCtxPath - |> completionsGetCompletionType2 ~debug ~full ~opens ~rawOpens ~pos - with - | Some (TypeExpr typ, env) -> ( - match typ |> TypeUtils.extractRecordType ~env ~package with - | Some (env, fields, typDecl, path, attributes) -> - Some - ( env, - fields, - typDecl.item.decl |> Shared.declToString typDecl.name.txt, - Some path, - attributes ) - | None -> None) - | Some (ExtractedType typ, env) -> ( - match typ with - | Trecord {fields; path; attributes} -> - Some - ( env, - fields, - typ |> TypeUtils.extractedTypeToString, - path, - attributes ) - | _ -> None) - | None -> None - in - match extracted with - | None -> [] - | Some (envFromExtracted, fields, recordAsString, path, attributes) -> - let pipeCompletion = - match - (path, ProcessAttributes.findEditorCompleteFromAttribute attributes) - with - | Some path, _ when Path.last path = "t" -> - if Debug.verbose () then Printf.printf "CPField--> type is type t\n"; - Some - ( path, - path |> SharedTypes.pathIdentToString |> String.split_on_char '.' - |> List.rev |> List.tl ) - | Some path, Some modulePath -> - if Debug.verbose () then - Printf.printf - "CPField--> type has completeFrom config for module %s, hd: %s, \ - env moduleName: %s\n" - (modulePath |> SharedTypes.pathToString) - (List.hd modulePath) env.file.moduleName; - Some (path, modulePath) - | _ -> None - in - let pipeCompletionsForModule = - match pipeCompletion with - | Some (path, completionPath) -> - completionsForPipeFromCompletionPath ~opens ~pos ~scope ~debug - ~prefix:fieldName ~envCompletionIsMadeFrom:env ~env:envFromExtracted - ~rawOpens ~full completionPath - |> TypeUtils.filterPipeableFunctions ~env:envFromExtracted ~full - ~lastPath:(Path.last path) ~replaceRange:fieldNameLoc - | None -> [] - in - pipeCompletionsForModule - @ (fields - |> Utils.filterMap (fun field -> - if Utils.checkName field.fname.txt ~prefix:fieldName ~exact then - Some - (Completion.create field.fname.txt ~env:envFromExtracted - ?deprecated:field.deprecated ~docstring:field.docstring - ~kind:(Completion.Field (field, recordAsString))) - else None))) + pipeCompletions @ extraCompletions @ mainCompletions | CPObj (cp, label) -> ( (* TODO: Also needs to support ExtractedType *) if Debug.verbose () then print_endline "[ctx_path]--> CPObj"; @@ -1310,8 +1285,8 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact |> String.concat "." in [ - Completion.create name ~includesSnippets:true ~kind:(Value typ) ~env - ~sortText:"A" + Completion.create name ~synthetic:true ~includesSnippets:true + ~kind:(Value typ) ~env ~sortText:"A" ~docstring: [ "Turns `" ^ builtinNameToComplete diff --git a/analysis/src/SharedTypes.ml b/analysis/src/SharedTypes.ml index 846ea8245..fc6859f84 100644 --- a/analysis/src/SharedTypes.ml +++ b/analysis/src/SharedTypes.ml @@ -815,11 +815,13 @@ module Completion = struct typeArgContext: typeArgContext option; data: (string * string) list option; range: Location.t option; + synthetic: bool; + (** Whether this item is an made up, synthetic item or not. *) } - let create ?range ?data ?typeArgContext ?(includesSnippets = false) - ?insertText ~kind ~env ?sortText ?deprecated ?filterText ?detail - ?(docstring = []) name = + let create ?(synthetic = false) ?range ?data ?typeArgContext + ?(includesSnippets = false) ?insertText ~kind ~env ?sortText ?deprecated + ?filterText ?detail ?(docstring = []) name = { name; env; @@ -835,6 +837,7 @@ module Completion = struct typeArgContext; data; range; + synthetic; } (* https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion *) diff --git a/analysis/src/TypeUtils.ml b/analysis/src/TypeUtils.ml index b18949117..7ec5d746a 100644 --- a/analysis/src/TypeUtils.ml +++ b/analysis/src/TypeUtils.ml @@ -1186,7 +1186,7 @@ let rec fnTakesTypeAsFirstArg ~env ~full ~lastPath t = | _ -> false (** Turns a completion into a pipe completion. *) -let transformCompletionToPipeCompletion ~env ~replaceRange +let transformCompletionToPipeCompletion ?(synthetic = false) ~env ~replaceRange (completion : Completion.t) = let name = completion.name in let nameWithPipe = "->" ^ name in @@ -1198,10 +1198,12 @@ let transformCompletionToPipeCompletion ~env ~replaceRange insertText = Some nameWithPipe; env; range = Some replaceRange; + synthetic; } (** Filters out completions that are not pipeable from a list of completions. *) -let filterPipeableFunctions ~env ~full ?lastPath ?replaceRange completions = +let filterPipeableFunctions ~env ~full ?synthetic ?lastPath ?replaceRange + completions = match lastPath with | None -> completions | Some lastPath -> @@ -1212,8 +1214,8 @@ let filterPipeableFunctions ~env ~full ?lastPath ?replaceRange completions = match replaceRange with | None -> Some completion | Some replaceRange -> - transformCompletionToPipeCompletion ~env ~replaceRange completion - ) + transformCompletionToPipeCompletion ?synthetic ~env ~replaceRange + completion) | _ -> None) let removeCurrentModuleIfNeeded ~envCompletionIsMadeFrom completionPath = diff --git a/analysis/tests/src/CompletionFromModule2.res b/analysis/tests/src/CompletionFromModule2.res index f73403621..61776cee1 100644 --- a/analysis/tests/src/CompletionFromModule2.res +++ b/analysis/tests/src/CompletionFromModule2.res @@ -1,5 +1,5 @@ // Used to check completions across files -// WRONG! Missing pipe functions (because of type t) + // CompletionFromModule.n. // ^com diff --git a/analysis/tests/src/expected/Completion.res.txt b/analysis/tests/src/expected/Completion.res.txt index ccadf462f..22f60609f 100644 --- a/analysis/tests/src/expected/Completion.res.txt +++ b/analysis/tests/src/expected/Completion.res.txt @@ -728,6 +728,7 @@ Resolved opens 1 pervasives ContextPath Value[r]."" ContextPath Value[r] Path r +CPPipe pathFromEnv: found:true [{ "label": "x", "kind": 5, @@ -751,6 +752,8 @@ Resolved opens 1 pervasives ContextPath Value[Objects, Rec, recordVal]."" ContextPath Value[Objects, Rec, recordVal] Path Objects.Rec.recordVal +CPPipe pathFromEnv:Rec found:true +Path Objects.Rec. [{ "label": "xx", "kind": 5, @@ -833,6 +836,8 @@ ContextPath Value[q].aa."" ContextPath Value[q].aa ContextPath Value[q] Path q +CPPipe pathFromEnv: found:true +CPPipe pathFromEnv: found:true [{ "label": "x", "kind": 5, @@ -857,6 +862,8 @@ ContextPath Value[q].aa.n ContextPath Value[q].aa ContextPath Value[q] Path q +CPPipe pathFromEnv: found:true +CPPipe pathFromEnv: found:true [{ "label": "name", "kind": 5, @@ -1189,6 +1196,7 @@ Resolved opens 3 pervasives Completion.res Completion.res ContextPath Value[_z]."" ContextPath Value[_z] Path _z +CPPipe pathFromEnv: found:true [{ "label": "x", "kind": 5, @@ -1347,6 +1355,7 @@ Resolved opens 3 pervasives Completion.res Completion.res ContextPath Value[funRecord].someFun ContextPath Value[funRecord] Path funRecord +CPPipe pathFromEnv: found:true Found type for function (~name: string) => unit [{ "label": "name", @@ -1367,6 +1376,7 @@ ContextPath Value[retAA](Nolabel)."" ContextPath Value[retAA](Nolabel) ContextPath Value[retAA] Path retAA +CPPipe pathFromEnv: found:true [{ "label": "x", "kind": 5, @@ -1897,6 +1907,7 @@ Resolved opens 3 pervasives Completion.res Completion.res ContextPath Value[funRecord]."" ContextPath Value[funRecord] Path funRecord +CPPipe pathFromEnv: found:true [{ "label": "someFun", "kind": 5, @@ -2153,6 +2164,7 @@ Resolved opens 3 pervasives Completion.res Completion.res ContextPath Value[rWithDepr].so ContextPath Value[rWithDepr] Path rWithDepr +CPPipe pathFromEnv: found:true [{ "label": "someInt", "kind": 5, diff --git a/analysis/tests/src/expected/CompletionExpressions.res.txt b/analysis/tests/src/expected/CompletionExpressions.res.txt index 2d5c7438c..ad7ec6c1a 100644 --- a/analysis/tests/src/expected/CompletionExpressions.res.txt +++ b/analysis/tests/src/expected/CompletionExpressions.res.txt @@ -946,6 +946,7 @@ Resolved opens 1 pervasives ContextPath Value[fff].someOpt ContextPath Value[fff] Path fff +CPPipe pathFromEnv: found:true [{ "label": "someOptField", "kind": 5, @@ -1415,6 +1416,7 @@ Resolved opens 2 pervasives CompletionSupport.res ContextPath Value[someTyp]."" ContextPath Value[someTyp] Path someTyp +CPPipe pathFromEnv: found:true [{ "label": "test", "kind": 5, @@ -1470,6 +1472,7 @@ Resolved opens 2 pervasives CompletionSupport.res ContextPath Value[someTyp]."" ContextPath Value[someTyp] Path someTyp +CPPipe pathFromEnv: found:true [{ "label": "test", "kind": 5, diff --git a/analysis/tests/src/expected/CompletionFromModule.res.txt b/analysis/tests/src/expected/CompletionFromModule.res.txt index 49906c207..799cb9c26 100644 --- a/analysis/tests/src/expected/CompletionFromModule.res.txt +++ b/analysis/tests/src/expected/CompletionFromModule.res.txt @@ -7,6 +7,7 @@ Resolved opens 1 pervasives ContextPath Value[n]."" ContextPath Value[n] Path n +CPPipe pathFromEnv:SomeModule found:true Path SomeModule. [{ "label": "->SomeModule.getName", @@ -36,6 +37,7 @@ Resolved opens 1 pervasives ContextPath Value[nn]."" ContextPath Value[nn] Path nn +CPPipe pathFromEnv:SomeOtherModule found:true Path SomeOtherModule. [{ "label": "->SomeOtherModule.getNName", @@ -48,17 +50,6 @@ Path SomeOtherModule. "range": {"start": {"line": 30, "character": 5}, "end": {"line": 30, "character": 5}}, "newText": "->SomeOtherModule.getNName" } - }, { - "label": "->SomeOtherModule.getNName2", - "kind": 12, - "tags": [], - "detail": "typeOutsideModule => string", - "documentation": null, - "sortText": "getNName2", - "textEdit": { - "range": {"start": {"line": 30, "character": 5}, "end": {"line": 30, "character": 5}}, - "newText": "->SomeOtherModule.getNName2" - } }, { "label": "nname", "kind": 5, diff --git a/analysis/tests/src/expected/CompletionFromModule2.res.txt b/analysis/tests/src/expected/CompletionFromModule2.res.txt index 174169ccc..b4c123e7a 100644 --- a/analysis/tests/src/expected/CompletionFromModule2.res.txt +++ b/analysis/tests/src/expected/CompletionFromModule2.res.txt @@ -7,8 +7,20 @@ Resolved opens 1 pervasives ContextPath Value[CompletionFromModule, n]."" ContextPath Value[CompletionFromModule, n] Path CompletionFromModule.n -Path SomeModule. +CPPipe pathFromEnv:SomeModule found:true +Path CompletionFromModule.SomeModule. [{ + "label": "->CompletionFromModule.SomeModule.getName", + "kind": 12, + "tags": [], + "detail": "t => string", + "documentation": null, + "sortText": "getName", + "textEdit": { + "range": {"start": {"line": 2, "character": 25}, "end": {"line": 2, "character": 25}}, + "newText": "->CompletionFromModule.SomeModule.getName" + } + }, { "label": "name", "kind": 5, "tags": [], @@ -25,6 +37,7 @@ Resolved opens 1 pervasives ContextPath Value[CompletionFromModule, nn]."" ContextPath Value[CompletionFromModule, nn] Path CompletionFromModule.nn +CPPipe pathFromEnv:SomeOtherModule found:true Path CompletionFromModule.SomeOtherModule. [{ "label": "->CompletionFromModule.SomeOtherModule.getNName", @@ -37,17 +50,6 @@ Path CompletionFromModule.SomeOtherModule. "range": {"start": {"line": 5, "character": 26}, "end": {"line": 5, "character": 26}}, "newText": "->CompletionFromModule.SomeOtherModule.getNName" } - }, { - "label": "->CompletionFromModule.SomeOtherModule.getNName2", - "kind": 12, - "tags": [], - "detail": "typeOutsideModule => string", - "documentation": null, - "sortText": "getNName2", - "textEdit": { - "range": {"start": {"line": 5, "character": 26}, "end": {"line": 5, "character": 26}}, - "newText": "->CompletionFromModule.SomeOtherModule.getNName2" - } }, { "label": "nname", "kind": 5, diff --git a/analysis/tests/src/expected/CompletionInferValues.res.txt b/analysis/tests/src/expected/CompletionInferValues.res.txt index 7a7092b7d..8bbaf98c1 100644 --- a/analysis/tests/src/expected/CompletionInferValues.res.txt +++ b/analysis/tests/src/expected/CompletionInferValues.res.txt @@ -37,6 +37,7 @@ Path x ContextPath Value[getSomeRecord](Nolabel) ContextPath Value[getSomeRecord] Path getSomeRecord +CPPipe pathFromEnv: found:true [{ "label": "name", "kind": 5, @@ -65,6 +66,7 @@ Path x ContextPath Value[getSomeRecord](Nolabel) ContextPath Value[getSomeRecord] Path getSomeRecord +CPPipe pathFromEnv: found:true [{ "label": "name", "kind": 5, @@ -98,6 +100,7 @@ ContextPath CArgument CArgument Value[someFnWithCallback]($0)(~someRecord) ContextPath CArgument Value[someFnWithCallback]($0) ContextPath Value[someFnWithCallback] Path someFnWithCallback +CPPipe pathFromEnv: found:true [{ "label": "name", "kind": 5, @@ -133,6 +136,7 @@ ContextPath Value[aliasedFn] Path aliasedFn ContextPath Value[someFnWithCallback] Path someFnWithCallback +CPPipe pathFromEnv: found:true [{ "label": "name", "kind": 5, @@ -377,6 +381,7 @@ ContextPath Value[x] Path x ContextPath Type[someRecordWithNestedStuff] Path someRecordWithNestedStuff +CPPipe pathFromEnv: found:true [{ "label": "name", "kind": 5, @@ -405,6 +410,7 @@ ContextPath Value[x] Path x ContextPath Type[someRecordWithNestedStuff] Path someRecordWithNestedStuff +CPPipe pathFromEnv: found:true [{ "label": "someRecord", "kind": 5, @@ -427,6 +433,7 @@ ContextPath Value[x] Path x ContextPath Type[someRecordWithNestedStuff] Path someRecordWithNestedStuff +CPPipe pathFromEnv: found:true [{ "label": "name", "kind": 5, @@ -839,6 +846,8 @@ ContextPath CArgument CArgument Value[CompletionSupport2, makeRenderer](~render) ContextPath CArgument Value[CompletionSupport2, makeRenderer](~render) ContextPath Value[CompletionSupport2, makeRenderer] Path CompletionSupport2.makeRenderer +CPPipe pathFromEnv:CompletionSupport.Nested found:false +Path CompletionSupport.Nested. [{ "label": "root", "kind": 5, diff --git a/analysis/tests/src/expected/CompletionPipeChain.res.txt b/analysis/tests/src/expected/CompletionPipeChain.res.txt index 0ba992c9a..f8aea750b 100644 --- a/analysis/tests/src/expected/CompletionPipeChain.res.txt +++ b/analysis/tests/src/expected/CompletionPipeChain.res.txt @@ -386,6 +386,10 @@ ContextPath Value[props].support.root ContextPath Value[props].support ContextPath Value[props] Path props +CPPipe pathFromEnv:CompletionSupport2.Internal found:false +Path CompletionSupport2.Internal.support +CPPipe pathFromEnv:CompletionSupport.Nested found:false +Path CompletionSupport.Nested.root CPPipe env:CompletionPipeChain envFromCompletionItem:CompletionSupport.Nested CPPipe type path:ReactDOM.Client.Root.t CPPipe pathFromEnv:ReactDOM.Client.Root found:false diff --git a/analysis/tests/src/expected/CompletionPipeSubmodules.res.txt b/analysis/tests/src/expected/CompletionPipeSubmodules.res.txt index 0ae6a56b5..08b0bfbe8 100644 --- a/analysis/tests/src/expected/CompletionPipeSubmodules.res.txt +++ b/analysis/tests/src/expected/CompletionPipeSubmodules.res.txt @@ -27,6 +27,8 @@ ContextPath Value[A, x].v-> ContextPath Value[A, x].v ContextPath Value[A, x] Path A.x +CPPipe pathFromEnv:A found:true +Path A.v CPPipe env:CompletionPipeSubmodules envFromCompletionItem:CompletionPipeSubmodules.A CPPipe type path:B1.b1 CPPipe pathFromEnv:A.B1 found:true @@ -49,6 +51,10 @@ ContextPath Value[E, e].v.v ContextPath Value[E, e].v ContextPath Value[E, e] Path E.e +CPPipe pathFromEnv:E found:true +Path E.v +CPPipe pathFromEnv:D found:true +Path D.v CPPipe env:CompletionPipeSubmodules envFromCompletionItem:CompletionPipeSubmodules.D CPPipe type path:C.t CPPipe pathFromEnv:C found:false @@ -71,6 +77,10 @@ ContextPath Value[E, e].v.v2 ContextPath Value[E, e].v ContextPath Value[E, e] Path E.e +CPPipe pathFromEnv:E found:true +Path E.v +CPPipe pathFromEnv:D found:true +Path D.v2 CPPipe env:CompletionPipeSubmodules envFromCompletionItem:CompletionPipeSubmodules.D CPPipe type path:C2.t2 CPPipe pathFromEnv:D.C2 found:true diff --git a/analysis/tests/src/expected/DotCompletionEverywhere.res.txt b/analysis/tests/src/expected/DotCompletionEverywhere.res.txt index a51d8980b..7682b3e76 100644 --- a/analysis/tests/src/expected/DotCompletionEverywhere.res.txt +++ b/analysis/tests/src/expected/DotCompletionEverywhere.res.txt @@ -12,6 +12,7 @@ ContextPath Value[someObj] [ctx_path]--> CPId Path someObj [dot_completion]--> Obj type found: +[dot_completion]--> Found Type expr when doing pipe completions [{ "label": "[\"age\"]", "kind": 4, @@ -47,6 +48,7 @@ ContextPath Value[someObj] [ctx_path]--> CPId Path someObj [dot_completion]--> Obj type found: +[dot_completion]--> Found Type expr when doing pipe completions [{ "label": "[\"name\"]", "kind": 4, @@ -71,7 +73,9 @@ ContextPath Value[rrr].n ContextPath Value[rrr] [ctx_path]--> CPId Path rrr -[dot_completion]--> Record type found +[dot_completion]--> Found type expr for main completions +[dot_completion]--> Found Type expr when doing pipe completions +CPPipe pathFromEnv: found:true [{ "label": "name", "kind": 5, @@ -92,6 +96,10 @@ ContextPath Value[x]."" ContextPath Value[x] [ctx_path]--> CPId Path x +[dot_completion]--> Found type expr for main completions +[dot_completion]--> Found Type expr when doing pipe completions +CPPipe pathFromEnv:SomeMod.SomeOtherMod found:true +[dot_completion]--> Got completion path SomeMod.SomeOtherMod Path SomeMod.SomeOtherMod. [{ "label": "->SomeMod.SomeOtherMod.do", @@ -118,6 +126,10 @@ ContextPath Value[SomeMod, SomeOtherMod, xx]."" ContextPath Value[SomeMod, SomeOtherMod, xx] [ctx_path]--> CPId Path SomeMod.SomeOtherMod.xx +[dot_completion]--> Found type expr for main completions +[dot_completion]--> Found Type expr when doing pipe completions +CPPipe pathFromEnv:SomeMod.SomeOtherMod found:true +[dot_completion]--> Got completion path SomeMod.SomeOtherMod Path SomeMod.SomeOtherMod. [{ "label": "->SomeMod.SomeOtherMod.do", @@ -144,15 +156,12 @@ ContextPath Value[Sss, rrr]."" ContextPath Value[Sss, rrr] [ctx_path]--> CPId Path Sss.rrr -[dot_completion]--> Record type found +[dot_completion]--> Found type expr for main completions +[dot_completion]--> Found Type expr when doing pipe completions +CPPipe pathFromEnv:Sss found:true +[dot_completion]--> Got completion path Sss Path Sss. [{ - "label": "name", - "kind": 5, - "tags": [], - "detail": "string", - "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype rrr = {name: string}\n```"} - }, { "label": "->Sss.do", "kind": 12, "tags": [], @@ -163,6 +172,12 @@ Path Sss. "range": {"start": {"line": 41, "character": 10}, "end": {"line": 41, "character": 10}}, "newText": "->Sss.do" } + }, { + "label": "name", + "kind": 5, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype rrr = {name: string}\n```"} }] Complete src/DotCompletionEverywhere.res 52:8 @@ -177,15 +192,11 @@ ContextPath Value[x2x2]."" ContextPath Value[x2x2] [ctx_path]--> CPId Path x2x2 -[dot_completion]--> Record type found +[dot_completion]--> Found type expr for main completions +[dot_completion]--> Found Type expr when doing pipe completions +CPPipe pathFromEnv: found:true Path X2. [{ - "label": "namee", - "kind": 5, - "tags": [], - "detail": "string", - "documentation": {"kind": "markdown", "value": "```rescript\nnamee: string\n```\n\n```rescript\ntype x2x2 = {namee: string}\n```"} - }, { "label": "->X2.stuff", "kind": 12, "tags": [], @@ -196,6 +207,12 @@ Path X2. "range": {"start": {"line": 52, "character": 7}, "end": {"line": 52, "character": 7}}, "newText": "->X2.stuff" } + }, { + "label": "namee", + "kind": 5, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "```rescript\nnamee: string\n```\n\n```rescript\ntype x2x2 = {namee: string}\n```"} }] Complete src/DotCompletionEverywhere.res 61:7 @@ -211,6 +228,7 @@ ContextPath Value[obj] [ctx_path]--> CPId Path obj [dot_completion]--> Obj type found: +[dot_completion]--> Found Type expr when doing pipe completions [{ "label": "[\"name\"]", "kind": 4, diff --git a/analysis/tests/src/expected/Hover.res.txt b/analysis/tests/src/expected/Hover.res.txt index 725c5a93a..9d88941c3 100644 --- a/analysis/tests/src/expected/Hover.res.txt +++ b/analysis/tests/src/expected/Hover.res.txt @@ -124,6 +124,8 @@ ContextPath Value[x1].content."" ContextPath Value[x1].content ContextPath Value[x1] Path x1 +CPPipe pathFromEnv: found:true +CPPipe pathFromEnv: found:true [{ "label": "age", "kind": 5, @@ -142,6 +144,8 @@ ContextPath Value[x2].content."" ContextPath Value[x2].content ContextPath Value[x2] Path x2 +CPPipe pathFromEnv: found:true +CPPipe pathFromEnv: found:true [{ "label": "age", "kind": 5, @@ -160,6 +164,8 @@ ContextPath Value[y1].content."" ContextPath Value[y1].content ContextPath Value[y1] Path y1 +CPPipe pathFromEnv: found:true +CPPipe pathFromEnv: found:true [{ "label": "age", "kind": 5, @@ -178,6 +184,8 @@ ContextPath Value[y2].content."" ContextPath Value[y2].content ContextPath Value[y2] Path y2 +CPPipe pathFromEnv: found:true +CPPipe pathFromEnv: found:true [{ "label": "age", "kind": 5, @@ -221,6 +229,7 @@ Resolved opens 1 pervasives ContextPath Value[x].someField ContextPath Value[x] Path x +CPPipe pathFromEnv: found:true Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives {"contents": {"kind": "markdown", "value": " Mighty fine field here. \n\n```rescript\nbool\n```"}} diff --git a/analysis/tests/src/expected/RecordCompletion.res.txt b/analysis/tests/src/expected/RecordCompletion.res.txt index 92ec8f49a..ccd548237 100644 --- a/analysis/tests/src/expected/RecordCompletion.res.txt +++ b/analysis/tests/src/expected/RecordCompletion.res.txt @@ -7,7 +7,7 @@ ContextPath Value[t].n->m ContextPath Value[t].n ContextPath Value[t] Path t -Path n +CPPipe pathFromEnv: found:true CPPipe env:RecordCompletion Path Js.Array2.m [{ @@ -34,7 +34,8 @@ ContextPath Value[t2].n2.n ContextPath Value[t2].n2 ContextPath Value[t2] Path t2 -Path n +CPPipe pathFromEnv: found:true +CPPipe pathFromEnv: found:true CPPipe env:RecordCompletion Path Js.Array2.m [{ From 8e2395e8b3886c29bf8565795c84eb4dfbd18846 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Thu, 28 Nov 2024 11:52:52 +0100 Subject: [PATCH 13/34] add a few more synthetic --- analysis/src/CompletionBackEnd.ml | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 86784a457..10e26b773 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -594,7 +594,8 @@ let getComplementaryCompletionsForTypedValue ~opens ~allFiles ~scope ~env prefix (Utils.fileNameHasUnallowedChars name) then Some - (Completion.create name ~env ~kind:(Completion.FileModule name)) + (Completion.create name ~synthetic:true ~env + ~kind:(Completion.FileModule name)) else None) in localCompletionsWithOpens @ fileModules @@ -863,7 +864,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact | CPArray None -> if Debug.verbose () then print_endline "[ctx_path]--> CPArray (no payload)"; [ - Completion.create "array" ~env + Completion.create "dummy" ~env ~kind:(Completion.Value (Ctype.newconstr Predef.path_array [])); ] | CPArray (Some cp) -> ( @@ -2229,8 +2230,8 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover completable = | Cdecorator prefix -> let mkDecorator (name, docstring, maybeInsertText) = { - (Completion.create name ~includesSnippets:true ~kind:(Label "") ~env - ?insertText:maybeInsertText) + (Completion.create name ~synthetic:true ~includesSnippets:true + ~kind:(Label "") ~env ?insertText:maybeInsertText) with docstring; } @@ -2494,8 +2495,9 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover completable = if Utils.startsWith elementName prefix then let name = "<" ^ elementName ^ ">" in Some - (Completion.create name ~kind:(Label name) ~detail:description - ~env ~docstring:[description] ~insertText:elementName + (Completion.create name ~synthetic:true ~kind:(Label name) + ~detail:description ~env ~docstring:[description] + ~insertText:elementName ?deprecated: (match deprecated with | true -> Some "true" @@ -2508,10 +2510,10 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover completable = implemented." in [ - Completion.create "todo" ~kind:(Label "todo") ~detail ~env - ~insertText:"todo"; - Completion.create "todo (with payload)" ~includesSnippets:true - ~kind:(Label "todo") + Completion.create "todo" ~synthetic:true ~kind:(Label "todo") ~detail + ~env ~insertText:"todo"; + Completion.create "todo (with payload)" ~synthetic:true + ~includesSnippets:true ~kind:(Label "todo") ~detail:(detail ^ " With a payload.") ~env ~insertText:"todo(\"${0:TODO}\")"; ] From 8e166ac08bcf771de73ac1c819dac1ac5336a3c5 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Thu, 28 Nov 2024 11:55:54 +0100 Subject: [PATCH 14/34] disable verbose log --- .../tests/src/DotCompletionEverywhere.res | 4 +- .../expected/DotCompletionEverywhere.res.txt | 47 +------------------ 2 files changed, 2 insertions(+), 49 deletions(-) diff --git a/analysis/tests/src/DotCompletionEverywhere.res b/analysis/tests/src/DotCompletionEverywhere.res index 2cd59b98a..f7cb8e6d2 100644 --- a/analysis/tests/src/DotCompletionEverywhere.res +++ b/analysis/tests/src/DotCompletionEverywhere.res @@ -2,7 +2,7 @@ let someObj = { "name": "hello", "age": 123, } -// ^dv+ + // someObj. // ^com @@ -61,5 +61,3 @@ let obj = { // obj. // ^com - -// ^dv- diff --git a/analysis/tests/src/expected/DotCompletionEverywhere.res.txt b/analysis/tests/src/expected/DotCompletionEverywhere.res.txt index 7682b3e76..1186f4c36 100644 --- a/analysis/tests/src/expected/DotCompletionEverywhere.res.txt +++ b/analysis/tests/src/expected/DotCompletionEverywhere.res.txt @@ -1,18 +1,12 @@ - Complete src/DotCompletionEverywhere.res 5:11 posCursor:[5:11] posNoWhite:[5:10] Found expr:[5:3->5:11] Pexp_field [5:3->5:10] _:[11:0->5:11] -[set_result] set new result to Cpath Value[someObj]."" Completable: Cpath Value[someObj]."" Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives ContextPath Value[someObj]."" -[ctx_path]--> dot completion! ContextPath Value[someObj] -[ctx_path]--> CPId Path someObj -[dot_completion]--> Obj type found: -[dot_completion]--> Found Type expr when doing pipe completions [{ "label": "[\"age\"]", "kind": 4, @@ -38,17 +32,12 @@ Path someObj Complete src/DotCompletionEverywhere.res 8:13 posCursor:[8:13] posNoWhite:[8:12] Found expr:[8:3->8:13] Pexp_field [8:3->8:10] na:[8:11->8:13] -[set_result] set new result to Cpath Value[someObj].na Completable: Cpath Value[someObj].na Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives ContextPath Value[someObj].na -[ctx_path]--> dot completion! ContextPath Value[someObj] -[ctx_path]--> CPId Path someObj -[dot_completion]--> Obj type found: -[dot_completion]--> Found Type expr when doing pipe completions [{ "label": "[\"name\"]", "kind": 4, @@ -64,17 +53,12 @@ Path someObj Complete src/DotCompletionEverywhere.res 14:8 posCursor:[14:8] posNoWhite:[14:7] Found expr:[14:3->14:8] Pexp_field [14:3->14:6] n:[14:7->14:8] -[set_result] set new result to Cpath Value[rrr].n Completable: Cpath Value[rrr].n Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives ContextPath Value[rrr].n -[ctx_path]--> dot completion! ContextPath Value[rrr] -[ctx_path]--> CPId Path rrr -[dot_completion]--> Found type expr for main completions -[dot_completion]--> Found Type expr when doing pipe completions CPPipe pathFromEnv: found:true [{ "label": "name", @@ -87,19 +71,13 @@ CPPipe pathFromEnv: found:true Complete src/DotCompletionEverywhere.res 29:5 posCursor:[29:5] posNoWhite:[29:4] Found expr:[29:3->29:5] Pexp_field [29:3->29:4] _:[35:0->29:5] -[set_result] set new result to Cpath Value[x]."" Completable: Cpath Value[x]."" Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives ContextPath Value[x]."" -[ctx_path]--> dot completion! ContextPath Value[x] -[ctx_path]--> CPId Path x -[dot_completion]--> Found type expr for main completions -[dot_completion]--> Found Type expr when doing pipe completions CPPipe pathFromEnv:SomeMod.SomeOtherMod found:true -[dot_completion]--> Got completion path SomeMod.SomeOtherMod Path SomeMod.SomeOtherMod. [{ "label": "->SomeMod.SomeOtherMod.do", @@ -117,19 +95,13 @@ Path SomeMod.SomeOtherMod. Complete src/DotCompletionEverywhere.res 32:27 posCursor:[32:27] posNoWhite:[32:26] Found expr:[32:3->32:27] Pexp_field [32:3->32:26] _:[35:0->32:27] -[set_result] set new result to Cpath Value[SomeMod, SomeOtherMod, xx]."" Completable: Cpath Value[SomeMod, SomeOtherMod, xx]."" Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives ContextPath Value[SomeMod, SomeOtherMod, xx]."" -[ctx_path]--> dot completion! ContextPath Value[SomeMod, SomeOtherMod, xx] -[ctx_path]--> CPId Path SomeMod.SomeOtherMod.xx -[dot_completion]--> Found type expr for main completions -[dot_completion]--> Found Type expr when doing pipe completions CPPipe pathFromEnv:SomeMod.SomeOtherMod found:true -[dot_completion]--> Got completion path SomeMod.SomeOtherMod Path SomeMod.SomeOtherMod. [{ "label": "->SomeMod.SomeOtherMod.do", @@ -147,19 +119,13 @@ Path SomeMod.SomeOtherMod. Complete src/DotCompletionEverywhere.res 41:11 posCursor:[41:11] posNoWhite:[41:10] Found expr:[41:3->41:11] Pexp_field [41:3->41:10] _:[44:0->41:11] -[set_result] set new result to Cpath Value[Sss, rrr]."" Completable: Cpath Value[Sss, rrr]."" Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives ContextPath Value[Sss, rrr]."" -[ctx_path]--> dot completion! ContextPath Value[Sss, rrr] -[ctx_path]--> CPId Path Sss.rrr -[dot_completion]--> Found type expr for main completions -[dot_completion]--> Found Type expr when doing pipe completions CPPipe pathFromEnv:Sss found:true -[dot_completion]--> Got completion path Sss Path Sss. [{ "label": "->Sss.do", @@ -183,17 +149,12 @@ Path Sss. Complete src/DotCompletionEverywhere.res 52:8 posCursor:[52:8] posNoWhite:[52:7] Found expr:[52:3->52:8] Pexp_field [52:3->52:7] _:[55:0->52:8] -[set_result] set new result to Cpath Value[x2x2]."" Completable: Cpath Value[x2x2]."" Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives ContextPath Value[x2x2]."" -[ctx_path]--> dot completion! ContextPath Value[x2x2] -[ctx_path]--> CPId Path x2x2 -[dot_completion]--> Found type expr for main completions -[dot_completion]--> Found Type expr when doing pipe completions CPPipe pathFromEnv: found:true Path X2. [{ @@ -217,18 +178,13 @@ Path X2. Complete src/DotCompletionEverywhere.res 61:7 posCursor:[61:7] posNoWhite:[61:6] Found expr:[61:3->61:7] -Pexp_field [61:3->61:6] _:[66:0->61:7] -[set_result] set new result to Cpath Value[obj]."" +Pexp_field [61:3->61:6] _:[64:0->61:7] Completable: Cpath Value[obj]."" Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives ContextPath Value[obj]."" -[ctx_path]--> dot completion! ContextPath Value[obj] -[ctx_path]--> CPId Path obj -[dot_completion]--> Obj type found: -[dot_completion]--> Found Type expr when doing pipe completions [{ "label": "[\"name\"]", "kind": 4, @@ -261,4 +217,3 @@ Path obj } }] - From 2b3190c3c0c75f488be9429b544ef8d796342fe4 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Thu, 28 Nov 2024 21:51:15 +0100 Subject: [PATCH 15/34] cleanup --- analysis/src/CompletionBackEnd.ml | 6 ------ analysis/src/SharedTypes.ml | 2 -- analysis/src/TypeUtils.ml | 23 +++-------------------- 3 files changed, 3 insertions(+), 28 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 10e26b773..8c3d79199 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -2086,8 +2086,6 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover completable = { env; definition = `NameOnly "jsxConfig"; - path = None; - attributes = []; fields = [ mkField ~name:"version" ~primitive:Predef.path_int; @@ -2117,8 +2115,6 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover completable = Trecord { env; - path = None; - attributes = []; definition = `NameOnly "importAttributesConfig"; fields = [mkField ~name:"type_" ~primitive:Predef.path_string]; } @@ -2127,8 +2123,6 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover completable = Trecord { env; - path = None; - attributes = []; definition = `NameOnly "moduleConfig"; fields = [ diff --git a/analysis/src/SharedTypes.ml b/analysis/src/SharedTypes.ml index fc6859f84..83c324ba0 100644 --- a/analysis/src/SharedTypes.ml +++ b/analysis/src/SharedTypes.ml @@ -354,8 +354,6 @@ and completionType = | Trecord of { env: QueryEnv.t; fields: field list; - path: Path.t option; - attributes: Parsetree.attributes; definition: [ `NameOnly of string (** When we only have the name, like when pulling the record from a declared type. *) diff --git a/analysis/src/TypeUtils.ml b/analysis/src/TypeUtils.ml index 7ec5d746a..f8adafac3 100644 --- a/analysis/src/TypeUtils.ml +++ b/analysis/src/TypeUtils.ml @@ -383,23 +383,14 @@ let rec extractType ?(printOpeningDebug = true) variantDecl = decl; }, typeArgContext ) - | Some - (envFromDeclaration, {item = {kind = Record fields; decl; attributes}}) - -> + | Some (envFromDeclaration, {item = {kind = Record fields; decl}}) -> if Debug.verbose () then print_endline "[extract_type]--> found record"; (* Need to create a new type arg context here because we're sending along a type expr that might have type vars. *) let typeArgContext = maybeSetTypeArgCtx ~typeParams:decl.type_params ~typeArgs env in Some - ( Trecord - { - env = envFromDeclaration; - path = Some path; - fields; - definition = `TypeExpr t; - attributes; - }, + ( Trecord {env = envFromDeclaration; fields; definition = `TypeExpr t}, typeArgContext ) | Some (envFromDeclaration, {item = {name = "t"; decl = {type_params}}}) -> let typeArgContext = @@ -580,15 +571,7 @@ let extractTypeFromResolvedType (typ : Type.t) ~env ~full = match typ.kind with | Tuple items -> Some (Tuple (env, items, Ctype.newty (Ttuple items))) | Record fields -> - Some - (Trecord - { - env; - fields; - path = None; - definition = `NameOnly typ.name; - attributes = typ.attributes; - }) + Some (Trecord {env; fields; definition = `NameOnly typ.name}) | Variant constructors -> Some (Tvariant From 58cf08991e0007cec0b8a97a8690b38ac41b0422 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Thu, 28 Nov 2024 22:11:36 +0100 Subject: [PATCH 16/34] pipe dot completion for builtins --- analysis/src/CompletionBackEnd.ml | 48 +++++++++++++++++-- .../tests/src/DotCompletionEverywhere.res | 5 ++ .../expected/DotCompletionEverywhere.res.txt | 35 ++++++++++++++ 3 files changed, 85 insertions(+), 3 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 8c3d79199..8e41715c2 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -1092,9 +1092,51 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact | None -> None | Some completionPath -> Some (completionPath, tPath)) in - match tPath with - | None -> [] - | Some (completionPath, tPath) -> + let env, typ = + typ + |> TypeUtils.resolveTypeForPipeCompletion ~env ~package ~full + ~lhsLoc:Location.none + in + match (typ, tPath) with + | Builtin (builtin, _), _ -> + (* TODO: Unify with existing code *) + let { + arrayModulePath; + optionModulePath; + stringModulePath; + intModulePath; + floatModulePath; + promiseModulePath; + listModulePath; + resultModulePath; + regexpModulePath; + } = + package.builtInCompletionModules + in + let completionPath = + match builtin with + | Array -> arrayModulePath + | Option -> optionModulePath + | String -> stringModulePath + | Int -> intModulePath + | Float -> floatModulePath + | Promise -> promiseModulePath + | List -> listModulePath + | Result -> resultModulePath + | RegExp -> regexpModulePath + | Lazy -> ["Lazy"] + | Char -> ["Char"] + in + if Debug.verbose () then + Printf.printf "[dot_completion] Completing from builtin\n"; + completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens + ~pos ~scope ~debug ~prefix:fieldName ~env ~rawOpens ~full + completionPath + |> List.filter_map (fun completion -> + TypeUtils.transformCompletionToPipeCompletion ~synthetic:true + ~env ~replaceRange:fieldNameLoc completion) + | _, None -> [] + | _, Some (completionPath, tPath) -> if Debug.verbose () then Printf.printf "[dot_completion]--> Got completion path %s\n" (completionPath |> String.concat "."); diff --git a/analysis/tests/src/DotCompletionEverywhere.res b/analysis/tests/src/DotCompletionEverywhere.res index f7cb8e6d2..16df8ca70 100644 --- a/analysis/tests/src/DotCompletionEverywhere.res +++ b/analysis/tests/src/DotCompletionEverywhere.res @@ -61,3 +61,8 @@ let obj = { // obj. // ^com + +let arr = [1, 2, 3] + +// arr.m +// ^com diff --git a/analysis/tests/src/expected/DotCompletionEverywhere.res.txt b/analysis/tests/src/expected/DotCompletionEverywhere.res.txt index 1186f4c36..fbf469072 100644 --- a/analysis/tests/src/expected/DotCompletionEverywhere.res.txt +++ b/analysis/tests/src/expected/DotCompletionEverywhere.res.txt @@ -217,3 +217,38 @@ Path obj } }] +Complete src/DotCompletionEverywhere.res 66:8 +posCursor:[66:8] posNoWhite:[66:7] Found expr:[66:3->66:8] +Pexp_field [66:3->66:6] m:[66:7->66:8] +Completable: Cpath Value[arr].m +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[arr].m +ContextPath Value[arr] +Path arr +CPPipe pathFromEnv: found:true +Path Js.Array2.m +[{ + "label": "->Js.Array2.mapi", + "kind": 12, + "tags": [], + "detail": "(t<'a>, ('a, int) => 'b) => t<'b>", + "documentation": {"kind": "markdown", "value": "\nApplies the function (the second argument) to each item in the array, returning\na new array. The function acceps two arguments: an item from the array and its\nindex number. The result array does not have to have elements of the same type\nas the input array. See\n[`Array.map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)\non MDN.\n\n## Examples\n\n```rescript\n// multiply each item in array by its position\nlet product = (item, index) => item * index\nJs.Array2.mapi([10, 11, 12], product) == [0, 11, 24]\n```\n"}, + "sortText": "mapi", + "textEdit": { + "range": {"start": {"line": 66, "character": 7}, "end": {"line": 66, "character": 8}}, + "newText": "->Js.Array2.mapi" + } + }, { + "label": "->Js.Array2.map", + "kind": 12, + "tags": [], + "detail": "(t<'a>, 'a => 'b) => t<'b>", + "documentation": {"kind": "markdown", "value": "\nApplies the function (the second argument) to each item in the array, returning\na new array. The result array does not have to have elements of the same type\nas the input array. See\n[`Array.map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)\non MDN.\n\n## Examples\n\n```rescript\nJs.Array2.map([12, 4, 8], x => x * x) == [144, 16, 64]\nJs.Array2.map([\"animal\", \"vegetable\", \"mineral\"], Js.String.length) == [6, 9, 7]\n```\n"}, + "sortText": "map", + "textEdit": { + "range": {"start": {"line": 66, "character": 7}, "end": {"line": 66, "character": 8}}, + "newText": "->Js.Array2.map" + } + }] + From 70b7db6e3c2a55d400197713d0cfd6094962c554 Mon Sep 17 00:00:00 2001 From: Florian Verdonck Date: Fri, 29 Nov 2024 14:23:07 +0100 Subject: [PATCH 17/34] Add example case (#1058) --- .../tests/src/DotCompletionEverywhere.res | 19 ++++++++++++++++++ .../expected/DotCompletionEverywhere.res.txt | 20 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/analysis/tests/src/DotCompletionEverywhere.res b/analysis/tests/src/DotCompletionEverywhere.res index 16df8ca70..5acd1eebc 100644 --- a/analysis/tests/src/DotCompletionEverywhere.res +++ b/analysis/tests/src/DotCompletionEverywhere.res @@ -66,3 +66,22 @@ let arr = [1, 2, 3] // arr.m // ^com + +module DOMAPI = { + type htmlElement = {prefix: string } + + @editor.completeFrom(HTMLButtonElement) + type rec htmlButtonElement = {mutable disabled: bool} +} + +module HTMLButtonElement = { + open DOMAPI + + @send + external checkValidity: htmlButtonElement => bool = "checkValidity" +} + +let button: DOMAPI.htmlButtonElement = %todo + +// button. +// ^com diff --git a/analysis/tests/src/expected/DotCompletionEverywhere.res.txt b/analysis/tests/src/expected/DotCompletionEverywhere.res.txt index fbf469072..089c35f6c 100644 --- a/analysis/tests/src/expected/DotCompletionEverywhere.res.txt +++ b/analysis/tests/src/expected/DotCompletionEverywhere.res.txt @@ -252,3 +252,23 @@ Path Js.Array2.m } }] +Complete src/DotCompletionEverywhere.res 85:10 +posCursor:[85:10] posNoWhite:[85:9] Found expr:[85:3->85:10] +Pexp_field [85:3->85:9] _:[88:0->85:10] +Completable: Cpath Value[button]."" +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[button]."" +ContextPath Value[button] +Path button +CPPipe pathFromEnv:DOMAPI found:true +Path DOMAPI. +Path HTMLButtonElement. +[{ + "label": "disabled", + "kind": 5, + "tags": [], + "detail": "bool", + "documentation": {"kind": "markdown", "value": "```rescript\ndisabled: bool\n```\n\n```rescript\ntype htmlButtonElement = {mutable disabled: bool}\n```"} + }] + From 931fd288c141feb5a365f64fc07fec341c815f28 Mon Sep 17 00:00:00 2001 From: Florian Verdonck Date: Thu, 19 Dec 2024 18:21:05 +0100 Subject: [PATCH 18/34] Additional completion tests (#1062) * Add Rxjs completion test * Add Rxjs binding example * Extend Rxjs test with fully qualified access * Add@editor.completeFrom and incomplete test output * Add passing test for property completion * Test still works when types are not named t. * Add test for @editor.completeFrom --- .../CompletionMultipleEditorCompleteFrom.res | 22 +++++++ analysis/tests/src/CompletionPipeProperty.res | 23 +++++++ analysis/tests/src/Rxjs.res | 51 +++++++++++++++ analysis/tests/src/RxjsCompletion.res | 39 ++++++++++++ ...mpletionMultipleEditorCompleteFrom.res.txt | 14 +++++ .../expected/CompletionPipeProperty.res.txt | 50 +++++++++++++++ analysis/tests/src/expected/Rxjs.res.txt | 0 .../tests/src/expected/RxjsCompletion.res.txt | 62 +++++++++++++++++++ 8 files changed, 261 insertions(+) create mode 100644 analysis/tests/src/CompletionMultipleEditorCompleteFrom.res create mode 100644 analysis/tests/src/CompletionPipeProperty.res create mode 100644 analysis/tests/src/Rxjs.res create mode 100644 analysis/tests/src/RxjsCompletion.res create mode 100644 analysis/tests/src/expected/CompletionMultipleEditorCompleteFrom.res.txt create mode 100644 analysis/tests/src/expected/CompletionPipeProperty.res.txt create mode 100644 analysis/tests/src/expected/Rxjs.res.txt create mode 100644 analysis/tests/src/expected/RxjsCompletion.res.txt diff --git a/analysis/tests/src/CompletionMultipleEditorCompleteFrom.res b/analysis/tests/src/CompletionMultipleEditorCompleteFrom.res new file mode 100644 index 000000000..35950e161 --- /dev/null +++ b/analysis/tests/src/CompletionMultipleEditorCompleteFrom.res @@ -0,0 +1,22 @@ +@@warning("-26") +@@warning("-27") +@@warning("-110") + +module A = { + @editor.completeFrom(B) @editor.completeFrom(C) + type a +} + +module B = { + let b = (a: A.a) => 1 +} + +module C = { + open A + let c = (a: a) => {'c'} +} + +let a : A.a = %todo +// a. +// ^com +// B.b and C.c should be completed \ No newline at end of file diff --git a/analysis/tests/src/CompletionPipeProperty.res b/analysis/tests/src/CompletionPipeProperty.res new file mode 100644 index 000000000..3ef739ed9 --- /dev/null +++ b/analysis/tests/src/CompletionPipeProperty.res @@ -0,0 +1,23 @@ +module ObservablePoint = { + type op = { + mutable x: int, + mutable y: int, + } + + @send + external setBoth: (op, float) => unit = "set" + + @send + external set: (op, float, float) => unit = "set" +} + +module Sprite = { + type s = { + anchor: ObservablePoint.op, + } +} + +let sprite : Sprite.s = %todo + +// sprite.anchor. +// ^com \ No newline at end of file diff --git a/analysis/tests/src/Rxjs.res b/analysis/tests/src/Rxjs.res new file mode 100644 index 000000000..8d3cecbc1 --- /dev/null +++ b/analysis/tests/src/Rxjs.res @@ -0,0 +1,51 @@ +// These are bindings used in RxjsCompletion.res +// We are using a separate file to test complication for modules of external files. +type target + +module Subscriber = { + type t<'t> = {next: 't => unit} +} + +module Observable = { + // Complete items defined inside the parent module. + @editor.completeFrom(Rxjs) + type t<'t> + + type dispose = unit => unit + + @new @module("rxjs") + external make: (Subscriber.t<'t> => dispose) => t<'t> = "Observable" + + type subscription + + @send + external subscribe: (t<'t>, 't => unit) => subscription = "subscribe" +} + +@module("rxjs") +external fromEvent: (target, string) => Observable.t<'t> = "fromEvent" + +type operation<'t, 'u> + +@send +external pipe: (Observable.t<'t>, operation<'t, 'u>) => Observable.t<'u> = "pipe" + +@send +external pipe2: (Observable.t<'t>, operation<'t, 'u>, operation<'u, 'i>) => Observable.t<'i> = + "pipe" + +@module("rxjs") +external map: ('t => 'u) => operation<'t, 'u> = "map" + +@module("rxjs") +external distinctUntilChanged: unit => operation<'t, 't> = "distinctUntilChanged" + +@module("rxjs") +external merge: (Observable.t<'t>, Observable.t<'t>) => Observable.t<'t> = "merge" + +@module("rxjs") +external scan: (('acc, 't) => 'acc, 'acc) => operation<'t, 'acc> = "scan" + +@module("rxjs") +external combineLatest: (Observable.t<'a>, Observable.t<'b>) => Observable.t<('a, 'b)> = + "combineLatest" diff --git a/analysis/tests/src/RxjsCompletion.res b/analysis/tests/src/RxjsCompletion.res new file mode 100644 index 000000000..eaf2b40d4 --- /dev/null +++ b/analysis/tests/src/RxjsCompletion.res @@ -0,0 +1,39 @@ +@@warning("-26") +@@warning("-110") + +type keyPress = + | Up(string) + | Down(string) + +@val +external window: {..} = "window" + +let main = async () => { + let keyMapObservable = { + open Rxjs + + let keydown = + fromEvent(Obj.magic(window), "keydown")->pipe2( + map(event => Down(event["key"])), + distinctUntilChanged(), + ) + + let keyup = + fromEvent(Obj.magic(window), "keyup")->pipe2( + map(event => Up(event["key"])), + distinctUntilChanged(), + ) + + // merge(keydown, keyup). + // ^com + + // Rxjs.Observable.subscribe, Rxjs.pipe and Rxjs.pipe2 should be completed + } + + let (a,b) : ( Rxjs.Observable.t , Rxjs.Observable.t) = %todo + + // Rxjs.combineLatest(a, b). + // ^com + + // Rxjs.Observable.subscribe, Rxjs.pipe and Rxjs.pipe2 should be completed +} diff --git a/analysis/tests/src/expected/CompletionMultipleEditorCompleteFrom.res.txt b/analysis/tests/src/expected/CompletionMultipleEditorCompleteFrom.res.txt new file mode 100644 index 000000000..8476ebfc1 --- /dev/null +++ b/analysis/tests/src/expected/CompletionMultipleEditorCompleteFrom.res.txt @@ -0,0 +1,14 @@ +Complete src/CompletionMultipleEditorCompleteFrom.res 19:5 +posCursor:[19:5] posNoWhite:[19:4] Found expr:[19:3->19:5] +Pexp_field [19:3->19:4] _:[22:0->19:5] +Completable: Cpath Value[a]."" +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[a]."" +ContextPath Value[a] +Path a +CPPipe pathFromEnv:A found:true +Path A. +Path B. +[] + diff --git a/analysis/tests/src/expected/CompletionPipeProperty.res.txt b/analysis/tests/src/expected/CompletionPipeProperty.res.txt new file mode 100644 index 000000000..7cfbd37c9 --- /dev/null +++ b/analysis/tests/src/expected/CompletionPipeProperty.res.txt @@ -0,0 +1,50 @@ +Complete src/CompletionPipeProperty.res 21:17 +posCursor:[21:17] posNoWhite:[21:16] Found expr:[21:3->21:17] +Pexp_field [21:3->21:16] _:[23:0->21:17] +Completable: Cpath Value[sprite].anchor."" +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[sprite].anchor."" +ContextPath Value[sprite].anchor +ContextPath Value[sprite] +Path sprite +CPPipe pathFromEnv:Sprite found:true +Path Sprite.anchor +CPPipe pathFromEnv:ObservablePoint found:true +Path ObservablePoint. +[{ + "label": "->ObservablePoint.setBoth", + "kind": 12, + "tags": [], + "detail": "(op, float) => unit", + "documentation": null, + "sortText": "setBoth", + "textEdit": { + "range": {"start": {"line": 21, "character": 16}, "end": {"line": 21, "character": 16}}, + "newText": "->ObservablePoint.setBoth" + } + }, { + "label": "->ObservablePoint.set", + "kind": 12, + "tags": [], + "detail": "(op, float, float) => unit", + "documentation": null, + "sortText": "set", + "textEdit": { + "range": {"start": {"line": 21, "character": 16}, "end": {"line": 21, "character": 16}}, + "newText": "->ObservablePoint.set" + } + }, { + "label": "x", + "kind": 5, + "tags": [], + "detail": "int", + "documentation": {"kind": "markdown", "value": "```rescript\nx: int\n```\n\n```rescript\ntype op = {mutable x: int, mutable y: int}\n```"} + }, { + "label": "y", + "kind": 5, + "tags": [], + "detail": "int", + "documentation": {"kind": "markdown", "value": "```rescript\ny: int\n```\n\n```rescript\ntype op = {mutable x: int, mutable y: int}\n```"} + }] + diff --git a/analysis/tests/src/expected/Rxjs.res.txt b/analysis/tests/src/expected/Rxjs.res.txt new file mode 100644 index 000000000..e69de29bb diff --git a/analysis/tests/src/expected/RxjsCompletion.res.txt b/analysis/tests/src/expected/RxjsCompletion.res.txt new file mode 100644 index 000000000..f07e1dd72 --- /dev/null +++ b/analysis/tests/src/expected/RxjsCompletion.res.txt @@ -0,0 +1,62 @@ +Complete src/RxjsCompletion.res 26:29 +posCursor:[26:29] posNoWhite:[26:28] Found expr:[10:17->38:1] +posCursor:[26:29] posNoWhite:[26:28] Found expr:[11:2->32:78] +posCursor:[26:29] posNoWhite:[26:28] Found expr:[12:4->26:29] +posCursor:[26:29] posNoWhite:[26:28] Found expr:[14:4->26:29] +posCursor:[26:29] posNoWhite:[26:28] Found expr:[20:4->26:29] +posCursor:[26:29] posNoWhite:[26:28] Found expr:[26:7->26:29] +Pexp_field [26:7->26:28] _:[30:2->26:29] +Completable: Cpath Value[merge](Nolabel, Nolabel)."" +Raw opens: 1 Rxjs.place holder +Package opens Pervasives.JsxModules.place holder +Resolved opens 2 pervasives Rxjs.res +ContextPath Value[merge](Nolabel, Nolabel)."" +ContextPath Value[merge](Nolabel, Nolabel) +ContextPath Value[merge] +Path merge +CPPipe pathFromEnv:Observable found:true +Path Rxjs.Observable. +Path Rxjs. +[{ + "label": "->Observable.subscribe", + "kind": 12, + "tags": [], + "detail": "(t<'t>, 't => unit) => subscription", + "documentation": null, + "sortText": "subscribe", + "textEdit": { + "range": {"start": {"line": 26, "character": 28}, "end": {"line": 26, "character": 28}}, + "newText": "->Observable.subscribe" + } + }] + +Complete src/RxjsCompletion.res 34:30 +posCursor:[34:30] posNoWhite:[34:29] Found expr:[10:17->38:1] +posCursor:[34:30] posNoWhite:[34:29] Found expr:[10:11->38:1] +posCursor:[34:30] posNoWhite:[34:29] Found expr:[11:2->34:30] +posCursor:[34:30] posNoWhite:[34:29] Found expr:[32:2->34:30] +posCursor:[34:30] posNoWhite:[34:29] Found expr:[34:5->34:30] +Pexp_field [34:5->34:29] _:[38:0->34:30] +Completable: Cpath Value[Rxjs, combineLatest](Nolabel, Nolabel)."" +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[Rxjs, combineLatest](Nolabel, Nolabel)."" +ContextPath Value[Rxjs, combineLatest](Nolabel, Nolabel) +ContextPath Value[Rxjs, combineLatest] +Path Rxjs.combineLatest +CPPipe pathFromEnv:Observable found:true +Path Rxjs.Observable. +Path Rxjs. +[{ + "label": "->Rxjs.Observable.subscribe", + "kind": 12, + "tags": [], + "detail": "(t<'t>, 't => unit) => subscription", + "documentation": null, + "sortText": "subscribe", + "textEdit": { + "range": {"start": {"line": 34, "character": 29}, "end": {"line": 34, "character": 29}}, + "newText": "->Rxjs.Observable.subscribe" + } + }] + From e34714617bbdc5a500cc3c0e24df828e088f80ea Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Wed, 25 Dec 2024 11:19:36 +0100 Subject: [PATCH 19/34] make dot completion everywhere actually work --- analysis/src/CompletionBackEnd.ml | 499 ++++++++---------- analysis/src/DotCompletionUtils.ml | 35 ++ analysis/src/PipeCompletionUtils.ml | 26 + analysis/src/ProcessAttributes.ml | 25 + analysis/src/TypeUtils.ml | 235 ++++++++- .../src/expected/GenericJsxCompletion.res.txt | 32 +- .../tests/src/DotCompletionEverywhere.res | 6 +- analysis/tests/src/DotPipeCompletionSpec.res | 82 +++ .../src/expected/CompletePrioritize1.res.txt | 2 - .../src/expected/CompletePrioritize2.res.txt | 2 - .../tests/src/expected/Completion.res.txt | 22 +- .../expected/CompletionExpressions.res.txt | 3 + .../src/expected/CompletionFromModule.res.txt | 64 ++- .../expected/CompletionFromModule2.res.txt | 60 ++- .../CompletionFunctionArguments.res.txt | 6 - .../expected/CompletionInferValues.res.txt | 47 +- .../tests/src/expected/CompletionJsx.res.txt | 8 - ...mpletionMultipleEditorCompleteFrom.res.txt | 25 +- .../src/expected/CompletionPipeChain.res.txt | 40 +- .../expected/CompletionPipeProperty.res.txt | 24 +- .../expected/CompletionPipeSubmodules.res.txt | 8 - .../expected/DotCompletionEverywhere.res.txt | 54 +- .../expected/DotPipeCompletionSpec.res.txt | 292 ++++++++++ .../src/expected/ExhaustiveSwitch.res.txt | 5 +- analysis/tests/src/expected/Hover.res.txt | 9 + .../src/expected/RecordCompletion.res.txt | 5 +- .../tests/src/expected/RxjsCompletion.res.txt | 88 +++ 27 files changed, 1184 insertions(+), 520 deletions(-) create mode 100644 analysis/src/DotCompletionUtils.ml create mode 100644 analysis/src/PipeCompletionUtils.ml create mode 100644 analysis/tests/src/DotPipeCompletionSpec.res create mode 100644 analysis/tests/src/expected/DotPipeCompletionSpec.res.txt diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 8e41715c2..f96864e47 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -639,13 +639,13 @@ let getCompletionsForPath ~debug ~opens ~full ~pos ~exact ~scope (** Completions intended for piping, from a completion path. *) let completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens ~pos ~scope ~debug ~prefix ~env ~rawOpens ~full completionPath = - let completionPath = + let completionPathWithoutCurrentModule = TypeUtils.removeCurrentModuleIfNeeded ~envCompletionIsMadeFrom completionPath in let completionPathMinusOpens = TypeUtils.removeOpensFromCompletionPath ~rawOpens ~package:full.package - completionPath + completionPathWithoutCurrentModule |> String.concat "." in let completionName name = @@ -660,7 +660,7 @@ let completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens ~pos let completions = completions |> List.map (fun (completion : Completion.t) -> - {completion with name = completionName completion.name; env}) + {completion with name = completionName completion.name}) in completions @@ -1006,186 +1006,147 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact path @ [fieldName] |> getCompletionsForPath ~debug ~opens ~full ~pos ~exact ~completionContext:Field ~env ~scope - | CPField {contextPath = cp; fieldName; fieldNameLoc} -> - if Debug.verbose () then print_endline "[ctx_path]--> dot completion!"; - let completions = + | CPField {contextPath = cp; fieldName; fieldNameLoc} -> ( + if Debug.verbose () then print_endline "[dot_completion]--> Triggered"; + (* This finds completions for the context path of the field completion. + From this, we assume that the first found completion represents the type + of the field parent. So in `someRecordValue.f`, we find the type of `someRecordValue`. + + This procedure is how finding types works in general in the tooling as of + now. *) + let completionsFromCtxPath = cp |> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact:true ~scope in - let completionsForCtxPath = - completions - |> completionsGetCompletionType2 ~debug ~full ~opens ~rawOpens ~pos + (* This extracts the type expr and env from the first found completion, if it's + a type expr. For dot completion, a type expr is the only relevant type we care + about here, since that will point to a type, either record or other type, of which + we can look up the module and any annotations for. + *) + let mainTypeCompletionEnv = + completionsFromCtxPath + |> completionsGetTypeEnv2 ~debug ~full ~opens ~rawOpens ~pos in - (* These are the main completions for the dot. *) - let mainCompletions = - match completionsForCtxPath with - | Some (TypeExpr typ, env) - when typ |> TypeUtils.extractObjectType ~env ~package |> Option.is_some - -> - (* Handle obj completion via dot *) + match mainTypeCompletionEnv with + | None -> ( + if Debug.verbose () then + Printf.printf + "[dot_completion] Could not extract main type completion env. \ + Checking for extracted type.\n"; + + match + completionsFromCtxPath + |> completionsGetCompletionType2 ~debug ~full ~opens ~rawOpens ~pos + with + | Some (ExtractedType typ, env) -> ( if Debug.verbose () then - Printf.printf "[dot_completion]--> Obj type found:\n"; - let objEnv, obj = - typ |> TypeUtils.extractObjectType ~env ~package |> Option.get - in - obj |> TypeUtils.getObjFields - |> Utils.filterMap (fun (field, typ) -> - if Utils.checkName field ~prefix:fieldName ~exact then - let fullObjFieldName = Printf.sprintf "[\"%s\"]" field in - Some - (Completion.create fullObjFieldName ~synthetic:true - ~range:fieldNameLoc ~insertText:fullObjFieldName - ~env:objEnv ~kind:(Completion.ObjLabel typ)) - else None) - | Some (t, env) -> ( - let extracted = - match t with - | TypeExpr typ -> ( - if Debug.verbose () then - Printf.printf - "[dot_completion]--> Found type expr for main completions\n"; - match typ |> TypeUtils.extractRecordType ~env ~package with - | Some (env, fields, typDecl, _path, _attributes) -> - Some - ( env, - fields, - typDecl.item.decl |> Shared.declToString typDecl.name.txt ) - | None -> None) - | ExtractedType typ -> ( - if Debug.verbose () then - Printf.printf - "[dot_completion]--> Found extracted type for main completions\n"; - match typ with - | Trecord {fields} -> - Some (env, fields, typ |> TypeUtils.extractedTypeToString) - | _ -> None) - in - match extracted with - | None -> [] - | Some (envFromExtracted, fields, recordAsString) -> + Printf.printf "[dot_completion] Found extracted type\n"; + + match typ with + | Trecord {fields; definition} -> fields - |> Utils.filterMap (fun field -> - if Utils.checkName field.fname.txt ~prefix:fieldName ~exact - then - Some - (Completion.create field.fname.txt ~env:envFromExtracted - ?deprecated:field.deprecated ~docstring:field.docstring - ~kind:(Completion.Field (field, recordAsString))) - else None)) - | None -> [] - in - let pipeCompletions = - match completionsForCtxPath with - | Some (TypeExpr typ, envFromCompletionItem) -> ( - if Debug.verbose () then - Printf.printf - "[dot_completion]--> Found Type expr when doing pipe completions\n"; - let tPath = - match TypeUtils.pathFromTypeExpr typ with - | None -> None - | Some tPath -> ( - match - TypeUtils.getPathRelativeToEnv ~debug ~env:envCompletionIsMadeFrom - ~envFromItem:envFromCompletionItem (Utils.expandPath tPath) - with - | None -> None - | Some completionPath -> Some (completionPath, tPath)) - in - let env, typ = - typ - |> TypeUtils.resolveTypeForPipeCompletion ~env ~package ~full - ~lhsLoc:Location.none - in - match (typ, tPath) with - | Builtin (builtin, _), _ -> - (* TODO: Unify with existing code *) - let { - arrayModulePath; - optionModulePath; - stringModulePath; - intModulePath; - floatModulePath; - promiseModulePath; - listModulePath; - resultModulePath; - regexpModulePath; - } = - package.builtInCompletionModules - in - let completionPath = - match builtin with - | Array -> arrayModulePath - | Option -> optionModulePath - | String -> stringModulePath - | Int -> intModulePath - | Float -> floatModulePath - | Promise -> promiseModulePath - | List -> listModulePath - | Result -> resultModulePath - | RegExp -> regexpModulePath - | Lazy -> ["Lazy"] - | Char -> ["Char"] - in + |> DotCompletionUtils.filterRecordFields ~env ~prefix:fieldName ~exact + ~recordAsString: + (match definition with + | `NameOnly name -> "type " ^ name + | `TypeExpr t -> Shared.typeToString t) + | _ -> []) + | None | Some (TypeExpr _, _) -> []) + | Some (typ, env) -> + if Debug.verbose () then + Printf.printf "[dot_completion] env module path: %s, type: %s\n" + (TypeUtils.modulePathFromEnv env |> String.concat ".") + (Shared.typeToString typ); + (* Let's first find the actual field completions. *) + let fieldCompletions = + DotCompletionUtils.fieldCompletionsForDotCompletion typ ~env ~package + ~prefix:fieldName ~fieldNameLoc ~exact + in + (* Now, let's get the type id for the type of parent of the dot completion. + The type id is a string that uniquely identifies a type, and that we can use + to filter completions for pipe completion etc.*) + let mainTypeId = TypeUtils.findRootTypeId ~full ~env typ in + let allExtraCompletions = + match mainTypeId with + | None -> if Debug.verbose () then - Printf.printf "[dot_completion] Completing from builtin\n"; - completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens - ~pos ~scope ~debug ~prefix:fieldName ~env ~rawOpens ~full - completionPath - |> List.filter_map (fun completion -> - TypeUtils.transformCompletionToPipeCompletion ~synthetic:true - ~env ~replaceRange:fieldNameLoc completion) - | _, None -> [] - | _, Some (completionPath, tPath) -> + Printf.printf + "[dot_completion] Could not find mainTypeId. Aborting extra pipe \ + completions.\n"; + [] + | Some mainTypeId -> if Debug.verbose () then - Printf.printf "[dot_completion]--> Got completion path %s\n" - (completionPath |> String.concat "."); - if List.length completionPath = 0 then [] - else - let completions = + Printf.printf "[dot_completion] mainTypeId: %s\n" mainTypeId; + + let pipeCompletions = + (* We now need a completion path from where to look up the module for our dot completion type. + This is from where we pull all of the functions we want to complete for the pipe. *) + + (* TODO(in-compiler) No need to have this configurable anymore, just use the new integrated modules from Core..*) + let completionPath = + (* First try completing it as a builtin *) + match mainTypeId with + | "array" -> Some package.builtInCompletionModules.arrayModulePath + | "option" -> + Some package.builtInCompletionModules.optionModulePath + | "string" -> + Some package.builtInCompletionModules.stringModulePath + | "int" -> Some package.builtInCompletionModules.intModulePath + | "float" -> Some package.builtInCompletionModules.floatModulePath + | "promise" -> + Some package.builtInCompletionModules.promiseModulePath + | "list" -> Some package.builtInCompletionModules.listModulePath + | "result" -> + Some package.builtInCompletionModules.resultModulePath + | "Js_re.t" -> + Some package.builtInCompletionModules.regexpModulePath + | "char" -> Some ["Char"] + | _ -> + (* Currently, when completing regular types, we stop at the first module we find that owns the type. + This means that completions will be made not from the root type necessarily, but from the module with + a type alias if it's a type alias. *) + let completionPathForType = + match TypeUtils.pathFromTypeExpr typ with + | None -> None + | Some tPath -> ( + match + TypeUtils.getModulePathRelativeToEnv ~debug + ~env:envCompletionIsMadeFrom ~envFromItem:env + (Utils.expandPath tPath) + with + | None -> Some [env.file.moduleName] + | Some p -> Some p) + in + if Debug.verbose () then + Printf.printf + "[dot_completion] Looked up completion path: %s\n" + (match completionPathForType with + | None -> "-" + | Some p -> p |> String.concat "."); + completionPathForType + in + match completionPath with + | None -> [] + | Some completionPath -> completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens ~pos ~scope ~debug ~prefix:fieldName ~env ~rawOpens ~full completionPath - in - completions - |> TypeUtils.filterPipeableFunctions ~synthetic:true ~env ~full - ~lastPath:(Path.last tPath) ~replaceRange:fieldNameLoc) - | Some (ExtractedType _, _) -> - if Debug.verbose () then - Printf.printf - "[dot_completion]--> PROBLEM: Found extracted type when trying to \ - do pipe completions\n"; - [] - | _ -> [] - in - (* Extra completions from configure extra module(s) *) - let extraCompletions = - match completionsForCtxPath with - | Some (TypeExpr typ, envFromCompletionItem) -> ( - match - TypeUtils.getExtraModuleToCompleteFromForType typ - ~env:envFromCompletionItem ~full - with - | None -> [] - | Some completionPath -> - completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens - ~pos ~scope ~debug ~prefix:fieldName ~env ~rawOpens ~full - completionPath + in + (* TODO: Explain *) + let extraCompletions = + TypeUtils.getExtraModuleTosCompleteFromForType ~env ~full typ + |> List.map (fun completionPath -> + completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom + ~opens ~pos ~scope ~debug ~prefix:fieldName ~env ~rawOpens + ~full completionPath) + |> List.flatten + in + pipeCompletions @ extraCompletions |> TypeUtils.filterPipeableFunctions ~synthetic:true ~env ~full - ~replaceRange:fieldNameLoc - ?lastPath: - (match TypeUtils.pathFromTypeExpr typ with - | None -> None - | Some tPath -> Some (Path.last tPath))) - | Some (ExtractedType _, _) -> - if Debug.verbose () then - Printf.printf - "[dot_completion]--> PROBLEM: Found extracted type when trying to \ - do extra completions\n"; - [] - | _ -> [] - in - pipeCompletions @ extraCompletions @ mainCompletions + ~replaceRange:fieldNameLoc ~targetTypeId:mainTypeId + in + fieldCompletions @ allExtraCompletions) | CPObj (cp, label) -> ( (* TODO: Also needs to support ExtractedType *) if Debug.verbose () then print_endline "[ctx_path]--> CPObj"; @@ -1214,131 +1175,91 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact ~exact:true ~scope ~mode:Pipe |> completionsGetTypeEnv2 ~debug ~full ~opens ~rawOpens ~pos with - | None -> [] - | Some (typ, envFromCompletionItem) -> ( - (* Extract any module to draw extra completions from for the identified type. *) - let extraModuleToCompleteFrom = - TypeUtils.getExtraModuleToCompleteFromForType typ - ~env:envFromCompletionItem ~full - in - let tPath = TypeUtils.pathFromTypeExpr typ in + | None -> + if Debug.verbose () then + print_endline "[CPPipe]--> Could not resolve type env"; + [] + | Some (typ, env) -> ( let env, typ = typ - |> TypeUtils.resolveTypeForPipeCompletion ~env ~package ~full ~lhsLoc + |> TypeUtils.resolveTypeForPipeCompletion2 ~env ~package ~full ~lhsLoc in - if debug then - if env <> envFromCompletionItem then - Printf.printf "CPPipe env:%s envFromCompletionItem:%s\n" - (QueryEnv.toString env) - (QueryEnv.toString envFromCompletionItem) - else Printf.printf "CPPipe env:%s\n" (QueryEnv.toString env); - let completionPath = - match typ with - | Builtin (builtin, _) -> - let { - arrayModulePath; - optionModulePath; - stringModulePath; - intModulePath; - floatModulePath; - promiseModulePath; - listModulePath; - resultModulePath; - regexpModulePath; - } = - package.builtInCompletionModules + let mainTypeId = TypeUtils.findRootTypeId ~full ~env typ in + let typePath = TypeUtils.pathFromTypeExpr typ in + match mainTypeId with + | None -> + if Debug.verbose () then + Printf.printf + "[pipe_completion] Could not find mainTypeId. Aborting pipe \ + completions.\n"; + [] + | Some mainTypeId -> + if Debug.verbose () then + Printf.printf "[pipe_completion] mainTypeId: %s\n" mainTypeId; + let pipeCompletions = + (* We now need a completion path from where to look up the module for our dot completion type. + This is from where we pull all of the functions we want to complete for the pipe. + + A completion path here could be one of two things: + 1. A module path to the main module for the type we've found + 2. A module path to a builtin module, like `Int` for `int`, or `Array` for `array` + + The below code will deliberately _not_ dig into type aliases for the main type when we're looking + for what _module_ to complete from. This is because you should be able to control where completions + come from even if your type is an alias. + *) + let completeAsBuiltin = + match typePath with + | Some t -> TypeUtils.completionPathFromMaybeBuiltin t ~package + | None -> None in - Some - (match builtin with - | Array -> arrayModulePath - | Option -> optionModulePath - | String -> stringModulePath - | Int -> intModulePath - | Float -> floatModulePath - | Promise -> promiseModulePath - | List -> listModulePath - | Result -> resultModulePath - | RegExp -> regexpModulePath - | Lazy -> ["Lazy"] - | Char -> ["Char"]) - | TypExpr t -> ( - match t.Types.desc with - | Tconstr (path, _typeArgs, _) - | Tlink {desc = Tconstr (path, _typeArgs, _)} - | Tsubst {desc = Tconstr (path, _typeArgs, _)} - | Tpoly ({desc = Tconstr (path, _typeArgs, _)}, []) -> - if debug then Printf.printf "CPPipe type path:%s\n" (Path.name path); - TypeUtils.getPathRelativeToEnv ~debug ~env - ~envFromItem:envFromCompletionItem (Utils.expandPath path) - | _ -> None) - in - let completionsFromExtraModule = - match extraModuleToCompleteFrom with - | None -> [] - | Some completionPath -> - if Debug.verbose () then - Printf.printf - "[ctx_path]--> CPPipe --> Found extra module to complete from: %s\n" - (completionPath |> SharedTypes.pathToString); - completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens - ~pos ~scope ~debug ~prefix:funNamePrefix ~env ~rawOpens ~full - completionPath - |> TypeUtils.filterPipeableFunctions ~env ~full - ?lastPath: - (match tPath with - | None -> None - | Some tPath -> Some (Path.last tPath)) - in - match completionPath with - | Some completionPath -> ( - let completionsFromMainFn = - completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens - ~pos ~scope ~debug ~prefix:funNamePrefix ~env ~rawOpens ~full - completionPath - |> TypeUtils.filterPipeableFunctions ~env ~full - ?lastPath: - (match tPath with - | None -> None - | Some tPath -> Some (Path.last tPath)) + (* TODO(in-compiler) No need to have this configurable anymore, just use the new integrated modules from Core..*) + let completionPath = + match (completeAsBuiltin, typePath) with + | Some completionPathForBuiltin, _ -> Some completionPathForBuiltin + | _, Some p -> ( + (* If this isn't a builtin, but we have a path, we try to resolve the + module path relative to the env we're completing from. This ensures that + what we get here is a module path we can find completions for regardless of + of the current scope for the position we're at.*) + match + TypeUtils.getModulePathRelativeToEnv ~debug + ~env:envCompletionIsMadeFrom ~envFromItem:env + (Utils.expandPath p) + with + | None -> Some [env.file.moduleName] + | Some p -> Some p) + | _ -> None + in + match completionPath with + | None -> [] + | Some completionPath -> + completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens + ~pos ~scope ~debug ~prefix:funNamePrefix ~env ~rawOpens ~full + completionPath + |> TypeUtils.filterPipeableFunctions ~env ~full ~synthetic:false + ~targetTypeId:mainTypeId + in + (* Extra completions can be drawn from the @editor.completeFrom attribute. Here we + find and add those completions as well. *) + let extraCompletions = + TypeUtils.getExtraModuleTosCompleteFromForType ~env ~full typ + |> List.map (fun completionPath -> + completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom + ~opens ~pos ~scope ~debug ~prefix:funNamePrefix ~env + ~rawOpens ~full completionPath) + |> List.flatten + |> TypeUtils.filterPipeableFunctions ~synthetic:true ~env ~full + ~targetTypeId:mainTypeId in - let completions = completionsFromMainFn @ completionsFromExtraModule in - (* We add React element functions to the completion if we're in a JSX context *) - let forJsxCompletion = + (* Add JSX completion items if we're in a JSX context. *) + let jsxCompletions = if inJsx then - match typ with - | Builtin (Int, t) -> Some ("int", t) - | Builtin (Float, t) -> Some ("float", t) - | Builtin (String, t) -> Some ("string", t) - | Builtin (Array, t) -> Some ("array", t) - | _ -> None - else None + PipeCompletionUtils.addJsxCompletionItems ~env ~mainTypeId + ~prefix:funNamePrefix ~full ~rawOpens typ + else [] in - match forJsxCompletion with - | Some (builtinNameToComplete, typ) - when Utils.checkName builtinNameToComplete ~prefix:funNamePrefix - ~exact:false -> - let name = - match package.genericJsxModule with - | None -> "React." ^ builtinNameToComplete - | Some g -> - g ^ "." ^ builtinNameToComplete - |> String.split_on_char '.' - |> TypeUtils.removeOpensFromCompletionPath ~rawOpens - ~package:full.package - |> String.concat "." - in - [ - Completion.create name ~synthetic:true ~includesSnippets:true - ~kind:(Value typ) ~env ~sortText:"A" - ~docstring: - [ - "Turns `" ^ builtinNameToComplete - ^ "` into a JSX element so it can be used inside of JSX."; - ]; - ] - @ completions - | _ -> completions) - | None -> completionsFromExtraModule)) + jsxCompletions @ pipeCompletions @ extraCompletions)) | CTuple ctxPaths -> if Debug.verbose () then print_endline "[ctx_path]--> CTuple"; (* Turn a list of context paths into a list of type expressions. *) @@ -1609,7 +1530,7 @@ let rec completeTypedValue ?(typeArgContext : typeArgContext option) ~rawOpens in let getCompletionName exportedValueName = let fnNname = - TypeUtils.getPathRelativeToEnv ~debug:false + TypeUtils.getModulePathRelativeToEnv ~debug:false ~env:(QueryEnv.fromFile full.file) ~envFromItem:env (Utils.expandPath path) in diff --git a/analysis/src/DotCompletionUtils.ml b/analysis/src/DotCompletionUtils.ml new file mode 100644 index 000000000..481772ea9 --- /dev/null +++ b/analysis/src/DotCompletionUtils.ml @@ -0,0 +1,35 @@ +let filterRecordFields ~env ~recordAsString ~prefix ~exact fields = + fields + |> Utils.filterMap (fun (field : SharedTypes.field) -> + if Utils.checkName field.fname.txt ~prefix ~exact then + Some + (SharedTypes.Completion.create field.fname.txt ~env + ?deprecated:field.deprecated ~docstring:field.docstring + ~kind:(SharedTypes.Completion.Field (field, recordAsString))) + else None) + +let fieldCompletionsForDotCompletion typ ~env ~package ~prefix ~fieldNameLoc + ~exact = + let asObject = typ |> TypeUtils.extractObjectType ~env ~package in + match asObject with + | Some (objEnv, obj) -> + (* Handle obj completion via dot *) + if Debug.verbose () then + Printf.printf "[dot_completion]--> Obj type found:\n"; + obj |> TypeUtils.getObjFields + |> Utils.filterMap (fun (field, _typ) -> + if Utils.checkName field ~prefix ~exact then + let fullObjFieldName = Printf.sprintf "[\"%s\"]" field in + Some + (SharedTypes.Completion.create fullObjFieldName ~synthetic:true + ~range:fieldNameLoc ~insertText:fullObjFieldName ~env:objEnv + ~kind:(SharedTypes.Completion.ObjLabel typ)) + else None) + | None -> ( + match typ |> TypeUtils.extractRecordType ~env ~package with + | Some (env, fields, typDecl, _path, _attributes) -> + fields + |> filterRecordFields ~env ~prefix ~exact + ~recordAsString: + (typDecl.item.decl |> Shared.declToString typDecl.name.txt) + | None -> []) diff --git a/analysis/src/PipeCompletionUtils.ml b/analysis/src/PipeCompletionUtils.ml new file mode 100644 index 000000000..f66c93e23 --- /dev/null +++ b/analysis/src/PipeCompletionUtils.ml @@ -0,0 +1,26 @@ +let addJsxCompletionItems ~mainTypeId ~env ~prefix ~(full : SharedTypes.full) + ~rawOpens typ = + match mainTypeId with + | ("array" | "float" | "string" | "int") as builtinNameToComplete -> + if Utils.checkName builtinNameToComplete ~prefix ~exact:false then + let name = + match full.package.genericJsxModule with + | None -> "React." ^ builtinNameToComplete + | Some g -> + g ^ "." ^ builtinNameToComplete + |> String.split_on_char '.' + |> TypeUtils.removeOpensFromCompletionPath ~rawOpens + ~package:full.package + |> String.concat "." + in + [ + SharedTypes.Completion.create name ~synthetic:true + ~includesSnippets:true ~kind:(Value typ) ~env ~sortText:"A" + ~docstring: + [ + "Turns `" ^ builtinNameToComplete + ^ "` into a JSX element so it can be used inside of JSX."; + ]; + ] + else [] + | _ -> [] diff --git a/analysis/src/ProcessAttributes.ml b/analysis/src/ProcessAttributes.ml index 61491f4be..c25853fce 100644 --- a/analysis/src/ProcessAttributes.ml +++ b/analysis/src/ProcessAttributes.ml @@ -64,3 +64,28 @@ let rec findEditorCompleteFromAttribute attributes = :: _ -> Some (Utils.flattenLongIdent path) | _ :: rest -> findEditorCompleteFromAttribute rest + +let rec findEditorCompleteFromAttribute2 ?(modulePaths = []) attributes = + let open Parsetree in + match attributes with + | [] -> modulePaths + | ( {Asttypes.txt = "editor.completeFrom"}, + PStr [{pstr_desc = Pstr_eval (payloadExpr, _)}] ) + :: rest -> + let items = + match payloadExpr with + | {pexp_desc = Pexp_array items} -> items + | p -> [p] + in + let modulePathsFromArray = + items + |> List.filter_map (fun item -> + match item.Parsetree.pexp_desc with + | Pexp_construct ({txt = path}, None) -> + Some (Utils.flattenLongIdent path) + | _ -> None) + in + findEditorCompleteFromAttribute2 + ~modulePaths:(modulePathsFromArray @ modulePaths) + rest + | _ :: rest -> findEditorCompleteFromAttribute2 ~modulePaths rest diff --git a/analysis/src/TypeUtils.ml b/analysis/src/TypeUtils.ml index f8adafac3..b52cd334c 100644 --- a/analysis/src/TypeUtils.ml +++ b/analysis/src/TypeUtils.ml @@ -1,5 +1,18 @@ open SharedTypes +let modulePathFromEnv env = env.QueryEnv.file.moduleName :: List.rev env.pathRev + +let completionPathFromEnvAndPath env ~path = + modulePathFromEnv env @ List.rev (Utils.expandPath path) + |> List.rev |> List.tl |> List.rev + +let getFullTypeId ~env (path : Path.t) = + modulePathFromEnv env @ List.rev (Utils.expandPath path) |> String.concat "." + +let fullTypeIdFromDecl ~env ~name ~modulePath = + env.QueryEnv.file.moduleName :: ModulePath.toPath modulePath name + |> String.concat "." + let debugLogTypeArgContext {env; typeArgs; typeParams} = Printf.sprintf "Type arg context. env: %s, typeArgs: %s, typeParams: %s\n" (Debug.debugPrintEnv env) @@ -259,6 +272,28 @@ let rec extractFunctionType ~env ~package typ = in loop ~env [] typ +let rec extractFunctionTypeWithEnv ~env ~package typ = + let rec loop ~env acc (t : Types.type_expr) = + match t.desc with + | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> loop ~env acc t1 + | Tarrow (label, tArg, tRet, _) -> loop ~env ((label, tArg) :: acc) tRet + | Tconstr (Pident {name = "function$"}, [t; _], _) -> + extractFunctionTypeWithEnv ~env ~package t + | Tconstr (path, typeArgs, _) -> ( + match References.digConstructor ~env ~package path with + | Some + ( env, + { + item = {decl = {type_manifest = Some t1; type_params = typeParams}}; + } ) -> + let t1 = t1 |> instantiateType ~typeParams ~typeArgs in + loop ~env acc t1 + | Some _ -> (List.rev acc, t, env) + | _ -> (List.rev acc, t, env)) + | _ -> (List.rev acc, t, env) + in + loop ~env [] typ + let maybeSetTypeArgCtx ?typeArgContextFromTypeManifest ~typeParams ~typeArgs env = match typeArgContextFromTypeManifest with @@ -567,6 +602,37 @@ let rec resolveTypeForPipeCompletion ~env ~package ~lhsLoc ~full in digToRelevantType ~env ~package t) +let rec resolveTypeForPipeCompletion2 ~env ~package ~lhsLoc ~full + (t : Types.type_expr) = + (* 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 typFromLoc = + match t with + | {Types.desc = Tvar _} -> + findReturnTypeOfFunctionAtLoc lhsLoc ~env ~full ~debug:false + | _ -> None + in + match typFromLoc with + | Some typFromLoc -> + typFromLoc |> resolveTypeForPipeCompletion2 ~lhsLoc ~env ~package ~full + | None -> + let rec digToRelevantType ~env ~package (t : Types.type_expr) = + match t.desc with + | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> + digToRelevantType ~env ~package t1 + (* Don't descend into types named "t". Type t is a convention in the ReScript ecosystem. *) + | Tconstr (path, _, _) when path |> Path.last = "t" -> (env, t) + | Tconstr (path, _, _) -> ( + match References.digConstructor ~env ~package path with + | Some (env, {item = {decl = {type_manifest = Some typ}}}) -> + digToRelevantType ~env ~package typ + | _ -> (env, t)) + | _ -> (env, t) + in + digToRelevantType ~env ~package t + let extractTypeFromResolvedType (typ : Type.t) ~env ~full = match typ.kind with | Tuple items -> Some (Tuple (env, items, Ctype.newty (Ttuple items))) @@ -1074,7 +1140,7 @@ module Codegen = struct Ast_helper.Exp.case pat (mkFailWithExp ()))) end -let getPathRelativeToEnv ~debug ~(env : QueryEnv.t) ~envFromItem path = +let getModulePathRelativeToEnv ~debug ~(env : QueryEnv.t) ~envFromItem path = match path with | _ :: pathRev -> (* type path is relative to the completion environment @@ -1125,49 +1191,89 @@ let pathToElementProps package = | Some g -> (g |> String.split_on_char '.') @ ["Elements"; "props"] (** Extracts module to draw extra completions from for the type, if it has been annotated with @editor.completeFrom. *) -let getExtraModuleToCompleteFromForType ~env ~full (t : Types.type_expr) = +let rec getExtraModuleToCompleteFromForType ~env ~full (t : Types.type_expr) = match t |> Shared.digConstructor with | Some path -> ( match References.digConstructor ~env ~package:full.package path with | None -> None - (*| Some (env, {item = {decl = {type_manifest = Some t}}}) -> + | Some (env, {item = {decl = {type_manifest = Some t}}}) -> getExtraModuleToCompleteFromForType ~env ~full t - - This could be commented back in to traverse type aliases. - Not clear as of now if that makes sense to do or not. - *) | Some (_, {item = {attributes}}) -> ProcessAttributes.findEditorCompleteFromAttribute attributes) | None -> None +module StringSet = Set.Make (String) + +let rec getExtraModuleTosCompleteFromForType ~env ~full (t : Types.type_expr) = + let foundModulePaths = ref StringSet.empty in + let addToModulePaths attributes = + ProcessAttributes.findEditorCompleteFromAttribute2 attributes + |> List.iter (fun e -> + foundModulePaths := + StringSet.add (e |> String.concat ".") !foundModulePaths) + in + let rec inner ~env ~full (t : Types.type_expr) = + match t |> Shared.digConstructor with + | Some path -> ( + match References.digConstructor ~env ~package:full.package path with + | None -> () + | Some (env, {item = {decl = {type_manifest = Some t}; attributes}}) -> + addToModulePaths attributes; + inner ~env ~full t + | Some (_, {item = {attributes}}) -> addToModulePaths attributes) + | None -> () + in + inner ~env ~full t; + !foundModulePaths |> StringSet.elements + |> List.map (fun l -> String.split_on_char '.' l) + (** Checks whether the provided type represents a function that takes the provided path as the first argument (meaning it's pipeable). *) -let rec fnTakesTypeAsFirstArg ~env ~full ~lastPath t = +let rec fnTakesTypeAsFirstArg ~env ~full ~targetTypeId t = + (*if Debug.verbose () then + Printf.printf "[fnTakesTypeAsFirstArg] start env: %s\n" + env.QueryEnv.file.moduleName;*) match t.Types.desc with | Tlink t1 | Tsubst t1 | Tpoly (t1, []) | Tconstr (Pident {name = "function$"}, [t1; _], _) -> - fnTakesTypeAsFirstArg ~env ~full ~lastPath t1 + fnTakesTypeAsFirstArg ~env ~full ~targetTypeId t1 | Tarrow _ -> ( - match extractFunctionType ~env ~package:full.package t with - | (Nolabel, t) :: _, _ -> ( - let p = pathFromTypeExpr t in - match p with + match extractFunctionTypeWithEnv ~env ~package:full.package t with + | (Nolabel, t) :: _, _, env -> ( + (*if Debug.verbose () then + Printf.printf "[fnTakesTypeAsFirstArg] extracted env: %s\n" + env.QueryEnv.file.moduleName;*) + let mainTypeId = + match pathFromTypeExpr t with + | None -> None + | Some tPath -> Some (getFullTypeId ~env tPath) + in + (*if Debug.verbose () then + Printf.printf "[filterPipeableFunctions]--> targetTypeId:%s = %s\n" + targetTypeId + (Option.value ~default:"None" mainTypeId);*) + match mainTypeId with | None -> false - | Some p -> - (* - Rules: - - The path p of the current type in the module we're looking at is relative to the current module. - - The path we're comparing against, `path`, is assumed to belong to this current module, because we're completing from it. - - Therefore, we can safely pluck out just the last part of the `path`, but need to use the entire name of the current type - we're comparing with. - *) - Path.name p = lastPath || Path.name p = "t") + | Some mainTypeId -> mainTypeId = targetTypeId) | _ -> false) | _ -> false +let getFirstFnUnlabelledArgType ~env ~full t = + let labels, _, env = + extractFunctionTypeWithEnv ~env ~package:full.package t + in + let rec findFirstUnlabelledArgType labels = + match labels with + | (Asttypes.Nolabel, t) :: _ -> Some t + | _ :: rest -> findFirstUnlabelledArgType rest + | [] -> None + in + match findFirstUnlabelledArgType labels with + | Some t -> Some (t, env) + | _ -> None + (** Turns a completion into a pipe completion. *) let transformCompletionToPipeCompletion ?(synthetic = false) ~env ~replaceRange (completion : Completion.t) = @@ -1184,16 +1290,70 @@ let transformCompletionToPipeCompletion ?(synthetic = false) ~env ~replaceRange synthetic; } +(** This takes a type expr and the env that type expr was found in, and produces a globally unique + id for that specific type. The globally unique id is the full path to the type as seen from the root + of the project. Example: type x in module SomeModule in file SomeFile would get the globally + unique id `SomeFile.SomeModule.x`.*) +let rec findRootTypeId ~full ~env (t : Types.type_expr) = + let debug = Debug.verbose () in + (* let debug = false in *) + match t.desc with + | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> findRootTypeId ~full ~env t1 + | Tconstr (Pident {name = "function$"}, [t; _], _) -> + findRootTypeId ~full ~env t + | Tconstr (path, _, _) -> ( + (* We have a path. Try to dig to its declaration *) + if debug then + Printf.printf "[findRootTypeId] path %s, dig\n" (Path.name path); + match References.digConstructor ~env ~package:full.package path with + | Some (env, {item = {decl = {type_manifest = Some t1}}}) -> + if debug then + Printf.printf "[findRootTypeId] dug up type alias at module path %s \n" + (modulePathFromEnv env |> String.concat "."); + findRootTypeId ~full ~env t1 + | Some (env, {item = {name}; modulePath}) -> + (* if it's a named type, then we know its name will be its module path from the env + its name.*) + if debug then + Printf.printf + "[findRootTypeId] dug up named type at module path %s, from item: %s \n" + (modulePathFromEnv env |> String.concat ".") + (ModulePath.toPath modulePath name |> String.concat "."); + Some (fullTypeIdFromDecl ~env ~name ~modulePath) + | None -> + (* If we didn't find anything, then it might be a builtin type. Check it.*) + if debug then Printf.printf "[findRootTypeId] dug up non-type alias\n"; + if + Predef.builtin_idents + |> List.find_opt (fun (_, i) -> Ident.same i (Path.head path)) + |> Option.is_some + then + Some + (if debug then Printf.printf "[findRootTypeId] returning builtin\n"; + Path.name path) + else None) + | _ -> None + (** Filters out completions that are not pipeable from a list of completions. *) -let filterPipeableFunctions ~env ~full ?synthetic ?lastPath ?replaceRange +let filterPipeableFunctions ~env ~full ?synthetic ?targetTypeId ?replaceRange completions = - match lastPath with + match targetTypeId with | None -> completions - | Some lastPath -> + | Some targetTypeId -> completions |> List.filter_map (fun (completion : Completion.t) -> - match completion.kind with - | Value t when fnTakesTypeAsFirstArg ~env ~full ~lastPath t -> ( + let thisCompletionItemTypeId = + match completion.kind with + | Value t -> ( + match + getFirstFnUnlabelledArgType ~full ~env:completion.env t + with + | None -> None + | Some (t, envFromLabelledArg) -> + findRootTypeId ~full ~env:envFromLabelledArg t) + | _ -> None + in + match thisCompletionItemTypeId with + | Some mainTypeId when mainTypeId = targetTypeId -> ( match replaceRange with | None -> Some completion | Some replaceRange -> @@ -1216,3 +1376,22 @@ let rec getObjFields (texp : Types.type_expr) = | Tlink te | Tsubst te | Tpoly (te, []) -> te |> getObjFields | Tvar None -> [] | _ -> [] + +let pathToBuiltin path = + Predef.builtin_idents + |> List.find_opt (fun (_, i) -> Ident.same i (Path.head path)) + +let completionPathFromMaybeBuiltin path ~package = + match pathToBuiltin path with + | Some ("array", _) -> Some package.builtInCompletionModules.arrayModulePath + | Some ("option", _) -> Some package.builtInCompletionModules.optionModulePath + | Some ("string", _) -> Some package.builtInCompletionModules.stringModulePath + | Some ("int", _) -> Some package.builtInCompletionModules.intModulePath + | Some ("float", _) -> Some package.builtInCompletionModules.floatModulePath + | Some ("promise", _) -> + Some package.builtInCompletionModules.promiseModulePath + | Some ("list", _) -> Some package.builtInCompletionModules.listModulePath + | Some ("result", _) -> Some package.builtInCompletionModules.resultModulePath + | Some ("dict", _) -> Some ["Dict"] + | Some ("char", _) -> Some ["Char"] + | _ -> None diff --git a/analysis/tests-generic-jsx-transform/src/expected/GenericJsxCompletion.res.txt b/analysis/tests-generic-jsx-transform/src/expected/GenericJsxCompletion.res.txt index 9368a52a3..daf436d52 100644 --- a/analysis/tests-generic-jsx-transform/src/expected/GenericJsxCompletion.res.txt +++ b/analysis/tests-generic-jsx-transform/src/expected/GenericJsxCompletion.res.txt @@ -63,8 +63,8 @@ Resolved opens 1 pervasives ContextPath Value[someString]->st <> ContextPath Value[someString] Path someString -CPPipe env:GenericJsxCompletion -Path Js.String2.st +CPPipe pathFromEnv: found:true +Path GenericJsxCompletion.st [{ "label": "GenericJsx.string", "kind": 12, @@ -73,18 +73,6 @@ Path Js.String2.st "documentation": {"kind": "markdown", "value": "Turns `string` into a JSX element so it can be used inside of JSX."}, "sortText": "A", "insertTextFormat": 2 - }, { - "label": "Js.String2.startsWith", - "kind": 12, - "tags": [], - "detail": "(t, t) => bool", - "documentation": {"kind": "markdown", "value": "\nES2015: `startsWith(str, substr)` returns `true` if the `str` starts with\n`substr`, `false` otherwise.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.startsWith(\"ReScript\", \"Re\") == true\nJs.String2.startsWith(\"ReScript\", \"\") == true\nJs.String2.startsWith(\"JavaScript\", \"Re\") == false\n```\n"} - }, { - "label": "Js.String2.startsWithFrom", - "kind": 12, - "tags": [], - "detail": "(t, t, int) => bool", - "documentation": {"kind": "markdown", "value": "\nES2015: `startsWithFrom(str, substr, n)` returns `true` if the `str` starts\nwith `substr` starting at position `n`, false otherwise. If `n` is negative,\nthe search starts at the beginning of `str`.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.startsWithFrom(\"ReScript\", \"Scri\", 2) == true\nJs.String2.startsWithFrom(\"ReScript\", \"\", 2) == true\nJs.String2.startsWithFrom(\"JavaScript\", \"Scri\", 2) == false\n```\n"} }] Complete src/GenericJsxCompletion.res 20:24 @@ -112,8 +100,8 @@ Resolved opens 2 pervasives GenericJsx.res ContextPath Value[someString]->st <> ContextPath Value[someString] Path someString -CPPipe env:GenericJsxCompletion -Path Js.String2.st +CPPipe pathFromEnv: found:true +Path GenericJsxCompletion.st [{ "label": "string", "kind": 12, @@ -122,17 +110,5 @@ Path Js.String2.st "documentation": {"kind": "markdown", "value": "Turns `string` into a JSX element so it can be used inside of JSX."}, "sortText": "A", "insertTextFormat": 2 - }, { - "label": "Js.String2.startsWith", - "kind": 12, - "tags": [], - "detail": "(t, t) => bool", - "documentation": {"kind": "markdown", "value": "\nES2015: `startsWith(str, substr)` returns `true` if the `str` starts with\n`substr`, `false` otherwise.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.startsWith(\"ReScript\", \"Re\") == true\nJs.String2.startsWith(\"ReScript\", \"\") == true\nJs.String2.startsWith(\"JavaScript\", \"Re\") == false\n```\n"} - }, { - "label": "Js.String2.startsWithFrom", - "kind": 12, - "tags": [], - "detail": "(t, t, int) => bool", - "documentation": {"kind": "markdown", "value": "\nES2015: `startsWithFrom(str, substr, n)` returns `true` if the `str` starts\nwith `substr` starting at position `n`, false otherwise. If `n` is negative,\nthe search starts at the beginning of `str`.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.startsWithFrom(\"ReScript\", \"Scri\", 2) == true\nJs.String2.startsWithFrom(\"ReScript\", \"\", 2) == true\nJs.String2.startsWithFrom(\"JavaScript\", \"Scri\", 2) == false\n```\n"} }] diff --git a/analysis/tests/src/DotCompletionEverywhere.res b/analysis/tests/src/DotCompletionEverywhere.res index 5acd1eebc..d1f52f46a 100644 --- a/analysis/tests/src/DotCompletionEverywhere.res +++ b/analysis/tests/src/DotCompletionEverywhere.res @@ -68,9 +68,9 @@ let arr = [1, 2, 3] // ^com module DOMAPI = { - type htmlElement = {prefix: string } + type htmlElement = {prefix: string} - @editor.completeFrom(HTMLButtonElement) + @editor.completeFrom(DotCompletionEverywhere.HTMLButtonElement) type rec htmlButtonElement = {mutable disabled: bool} } @@ -78,7 +78,7 @@ module HTMLButtonElement = { open DOMAPI @send - external checkValidity: htmlButtonElement => bool = "checkValidity" + external checkValidity: htmlButtonElement => bool = "checkValidity" } let button: DOMAPI.htmlButtonElement = %todo diff --git a/analysis/tests/src/DotPipeCompletionSpec.res b/analysis/tests/src/DotPipeCompletionSpec.res new file mode 100644 index 000000000..2e5dace68 --- /dev/null +++ b/analysis/tests/src/DotPipeCompletionSpec.res @@ -0,0 +1,82 @@ +// +module SomeModule = { + type t = {name: string} + + @get external getName: t => string = "name" + @send + external withUnlabelledArgumentNotFirst: (~name: string=?, t) => unit = + "withUnlabelledArgumentNotFirst" + + let thisShouldNotBeCompletedFor = () => "hi" +} + +let n = {SomeModule.name: "hello"} + +// Type from inside of a module +// n. +// ^com + +@editor.completeFrom(DotPipeCompletionSpec.SomeOtherModule) +type typeOutsideModule = {nname: string} + +let doWithTypeOutsideModule = (_: typeOutsideModule) => "" + +module CompleteFromThisToo = { + external a: typeOutsideModule => string = "a" + external b: unit => typeOutsideModule = "b" +} + +module SomeOtherModule = { + @editor.completeFrom(DotPipeCompletionSpec.CompleteFromThisToo) + type t = typeOutsideModule + + type irrelevantType = string + + @get external getNName: t => string = "nname" + @get external getNName2: typeOutsideModule => string = "nname" + @get external getNName3: irrelevantType => string = "nname" + + let thisShouldNotBeCompletedFor = () => "hi" +} + +let nn: SomeOtherModule.t = {nname: "hello"} + +// Type from module but that's an alias +// nn. +// ^com + +module A = { + @editor.completeFrom(B) + type a + + external withA: a => unit = "withA" + external make: unit => a = "makeA" +} + +module B = { + let b = (_a: A.a) => 1 +} + +external a: A.a = "a" + +// Main type in other module +// a. +// ^com + +let xx: CompletionFromModule.SomeModule.t = {name: "hello"} +// Type from other file +// xx. +// ^com + +type builtinType = array + +let ffff: builtinType = [] + +// A built in type +// ffff.u +// ^com + +// Type outside of module with complete from pointing to other module +let nnn: typeOutsideModule = {nname: "hello"} +// nnn. +// ^com diff --git a/analysis/tests/src/expected/CompletePrioritize1.res.txt b/analysis/tests/src/expected/CompletePrioritize1.res.txt index 33d053eea..6ba73f80e 100644 --- a/analysis/tests/src/expected/CompletePrioritize1.res.txt +++ b/analysis/tests/src/expected/CompletePrioritize1.res.txt @@ -6,8 +6,6 @@ Resolved opens 1 pervasives ContextPath Value[a]-> ContextPath Value[a] Path a -CPPipe env:CompletePrioritize1 -CPPipe type path:Test.t CPPipe pathFromEnv:Test found:true Path Test. [{ diff --git a/analysis/tests/src/expected/CompletePrioritize2.res.txt b/analysis/tests/src/expected/CompletePrioritize2.res.txt index b6bea71f9..b8a2e04ee 100644 --- a/analysis/tests/src/expected/CompletePrioritize2.res.txt +++ b/analysis/tests/src/expected/CompletePrioritize2.res.txt @@ -6,8 +6,6 @@ Resolved opens 1 pervasives ContextPath Value[ax]-> ContextPath Value[ax] Path ax -CPPipe env:CompletePrioritize2 -CPPipe type path:Test.t CPPipe pathFromEnv:Test found:true Path Test. [{ diff --git a/analysis/tests/src/expected/Completion.res.txt b/analysis/tests/src/expected/Completion.res.txt index 22f60609f..47774375b 100644 --- a/analysis/tests/src/expected/Completion.res.txt +++ b/analysis/tests/src/expected/Completion.res.txt @@ -404,7 +404,6 @@ Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives ContextPath array->m ContextPath array -CPPipe env:Completion Path Js.Array2.m [{ "label": "Js.Array2.mapi", @@ -427,7 +426,6 @@ Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives ContextPath string->toU ContextPath string -CPPipe env:Completion Path Js.String2.toU [{ "label": "Js.String2.toUpperCase", @@ -445,7 +443,6 @@ Resolved opens 1 pervasives ContextPath Value[op]->e ContextPath Value[op] Path op -CPPipe env:Completion Path Belt.Option.e [{ "label": "Belt.Option.eqU", @@ -471,8 +468,6 @@ Resolved opens 1 pervasives ContextPath Value[fa]-> ContextPath Value[fa] Path fa -CPPipe env:Completion -CPPipe type path:ForAuto.t CPPipe pathFromEnv:ForAuto found:true Path ForAuto. [{ @@ -729,6 +724,7 @@ ContextPath Value[r]."" ContextPath Value[r] Path r CPPipe pathFromEnv: found:true +Path Completion. [{ "label": "x", "kind": 5, @@ -837,7 +833,9 @@ ContextPath Value[q].aa ContextPath Value[q] Path q CPPipe pathFromEnv: found:true +Path Completion.aa CPPipe pathFromEnv: found:true +Path Completion. [{ "label": "x", "kind": 5, @@ -863,7 +861,9 @@ ContextPath Value[q].aa ContextPath Value[q] Path q CPPipe pathFromEnv: found:true +Path Completion.aa CPPipe pathFromEnv: found:true +Path Completion.n [{ "label": "name", "kind": 5, @@ -1113,8 +1113,6 @@ ContextPath Value[FAO, forAutoObject]["forAutoLabel"].forAuto ContextPath Value[FAO, forAutoObject]["forAutoLabel"] ContextPath Value[FAO, forAutoObject] Path FAO.forAutoObject -CPPipe env:Completion envFromCompletionItem:Completion.FAR -CPPipe type path:ForAuto.t CPPipe pathFromEnv:ForAuto found:false Path ForAuto. [{ @@ -1197,6 +1195,7 @@ ContextPath Value[_z]."" ContextPath Value[_z] Path _z CPPipe pathFromEnv: found:true +Path Completion. [{ "label": "x", "kind": 5, @@ -1356,6 +1355,7 @@ ContextPath Value[funRecord].someFun ContextPath Value[funRecord] Path funRecord CPPipe pathFromEnv: found:true +Path Completion.someFun Found type for function (~name: string) => unit [{ "label": "name", @@ -1377,6 +1377,7 @@ ContextPath Value[retAA](Nolabel) ContextPath Value[retAA] Path retAA CPPipe pathFromEnv: found:true +Path Completion. [{ "label": "x", "kind": 5, @@ -1908,6 +1909,7 @@ ContextPath Value[funRecord]."" ContextPath Value[funRecord] Path funRecord CPPipe pathFromEnv: found:true +Path Completion. [{ "label": "someFun", "kind": 5, @@ -1934,7 +1936,6 @@ Package opens Pervasives.JsxModules.place holder Resolved opens 4 pervasives Completion.res Completion.res js.ml ContextPath array->ma ContextPath array -CPPipe env:Completion Path Js.Array2.ma [{ "label": "Array2.mapi", @@ -2089,7 +2090,6 @@ Package opens Pervasives.JsxModules.place holder Resolved opens 3 pervasives Completion.res Completion.res ContextPath int->t ContextPath int -CPPipe env:Completion Path Belt.Int.t [{ "label": "Belt.Int.toString", @@ -2113,7 +2113,6 @@ Package opens Pervasives.JsxModules.place holder Resolved opens 3 pervasives Completion.res Completion.res ContextPath float->t ContextPath float -CPPipe env:Completion Path Belt.Float.t [{ "label": "Belt.Float.toInt", @@ -2138,7 +2137,6 @@ Resolved opens 3 pervasives Completion.res Completion.res ContextPath Value[ok]->g ContextPath Value[ok] Path ok -CPPipe env:Completion Path Belt.Result.g [{ "label": "Belt.Result.getExn", @@ -2165,6 +2163,7 @@ ContextPath Value[rWithDepr].so ContextPath Value[rWithDepr] Path rWithDepr CPPipe pathFromEnv: found:true +Path Completion.so [{ "label": "someInt", "kind": 5, @@ -2223,7 +2222,6 @@ ContextPath Value[uncurried](Nolabel)->toS ContextPath Value[uncurried](Nolabel) ContextPath Value[uncurried] Path uncurried -CPPipe env:Completion Path Belt.Int.toS [{ "label": "Belt.Int.toString", diff --git a/analysis/tests/src/expected/CompletionExpressions.res.txt b/analysis/tests/src/expected/CompletionExpressions.res.txt index ad7ec6c1a..4338375b6 100644 --- a/analysis/tests/src/expected/CompletionExpressions.res.txt +++ b/analysis/tests/src/expected/CompletionExpressions.res.txt @@ -947,6 +947,7 @@ ContextPath Value[fff].someOpt ContextPath Value[fff] Path fff CPPipe pathFromEnv: found:true +Path CompletionExpressions.someOpt [{ "label": "someOptField", "kind": 5, @@ -1417,6 +1418,7 @@ ContextPath Value[someTyp]."" ContextPath Value[someTyp] Path someTyp CPPipe pathFromEnv: found:true +Path CompletionExpressions. [{ "label": "test", "kind": 5, @@ -1473,6 +1475,7 @@ ContextPath Value[someTyp]."" ContextPath Value[someTyp] Path someTyp CPPipe pathFromEnv: found:true +Path CompletionExpressions. [{ "label": "test", "kind": 5, diff --git a/analysis/tests/src/expected/CompletionFromModule.res.txt b/analysis/tests/src/expected/CompletionFromModule.res.txt index 799cb9c26..480f8472d 100644 --- a/analysis/tests/src/expected/CompletionFromModule.res.txt +++ b/analysis/tests/src/expected/CompletionFromModule.res.txt @@ -10,6 +10,12 @@ Path n CPPipe pathFromEnv:SomeModule found:true Path SomeModule. [{ + "label": "name", + "kind": 5, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype t = {name: string}\n```"} + }, { "label": "->SomeModule.getName", "kind": 12, "tags": [], @@ -20,12 +26,6 @@ Path SomeModule. "range": {"start": {"line": 10, "character": 4}, "end": {"line": 10, "character": 4}}, "newText": "->SomeModule.getName" } - }, { - "label": "name", - "kind": 5, - "tags": [], - "detail": "string", - "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype t = {name: string}\n```"} }] Complete src/CompletionFromModule.res 30:6 @@ -39,7 +39,14 @@ ContextPath Value[nn] Path nn CPPipe pathFromEnv:SomeOtherModule found:true Path SomeOtherModule. +Path CompletionFromModule.SomeOtherModule. [{ + "label": "nname", + "kind": 5, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "```rescript\nnname: string\n```\n\n```rescript\ntype typeOutsideModule = {nname: string}\n```"} + }, { "label": "->SomeOtherModule.getNName", "kind": 12, "tags": [], @@ -51,11 +58,38 @@ Path SomeOtherModule. "newText": "->SomeOtherModule.getNName" } }, { - "label": "nname", - "kind": 5, + "label": "->SomeOtherModule.getNName2", + "kind": 12, "tags": [], - "detail": "string", - "documentation": {"kind": "markdown", "value": "```rescript\nnname: string\n```\n\n```rescript\ntype typeOutsideModule = {nname: string}\n```"} + "detail": "typeOutsideModule => string", + "documentation": null, + "sortText": "getNName2", + "textEdit": { + "range": {"start": {"line": 30, "character": 5}, "end": {"line": 30, "character": 5}}, + "newText": "->SomeOtherModule.getNName2" + } + }, { + "label": "->SomeOtherModule.getNName", + "kind": 12, + "tags": [], + "detail": "t => string", + "documentation": null, + "sortText": "getNName", + "textEdit": { + "range": {"start": {"line": 30, "character": 5}, "end": {"line": 30, "character": 5}}, + "newText": "->SomeOtherModule.getNName" + } + }, { + "label": "->SomeOtherModule.getNName2", + "kind": 12, + "tags": [], + "detail": "typeOutsideModule => string", + "documentation": null, + "sortText": "getNName2", + "textEdit": { + "range": {"start": {"line": 30, "character": 5}, "end": {"line": 30, "character": 5}}, + "newText": "->SomeOtherModule.getNName2" + } }] Complete src/CompletionFromModule.res 33:32 @@ -81,10 +115,9 @@ Resolved opens 1 pervasives ContextPath Value[nnn]-> ContextPath Value[nnn] Path nnn -CPPipe env:CompletionFromModule -CPPipe type path:typeOutsideModule CPPipe pathFromEnv: found:true -Path SomeOtherModule. +Path CompletionFromModule. +Path CompletionFromModule.SomeOtherModule. [{ "label": "SomeOtherModule.getNName", "kind": 12, @@ -108,10 +141,9 @@ Resolved opens 2 pervasives CompletionFromModule.res ContextPath Value[nnn]-> ContextPath Value[nnn] Path nnn -CPPipe env:CompletionFromModule -CPPipe type path:typeOutsideModule CPPipe pathFromEnv: found:true -Path SomeOtherModule. +Path CompletionFromModule. +Path CompletionFromModule.SomeOtherModule. [{ "label": "getNName", "kind": 12, diff --git a/analysis/tests/src/expected/CompletionFromModule2.res.txt b/analysis/tests/src/expected/CompletionFromModule2.res.txt index b4c123e7a..f4c9f4ed7 100644 --- a/analysis/tests/src/expected/CompletionFromModule2.res.txt +++ b/analysis/tests/src/expected/CompletionFromModule2.res.txt @@ -10,6 +10,12 @@ Path CompletionFromModule.n CPPipe pathFromEnv:SomeModule found:true Path CompletionFromModule.SomeModule. [{ + "label": "name", + "kind": 5, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype t = {name: string}\n```"} + }, { "label": "->CompletionFromModule.SomeModule.getName", "kind": 12, "tags": [], @@ -20,12 +26,6 @@ Path CompletionFromModule.SomeModule. "range": {"start": {"line": 2, "character": 25}, "end": {"line": 2, "character": 25}}, "newText": "->CompletionFromModule.SomeModule.getName" } - }, { - "label": "name", - "kind": 5, - "tags": [], - "detail": "string", - "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype t = {name: string}\n```"} }] Complete src/CompletionFromModule2.res 5:27 @@ -39,7 +39,14 @@ ContextPath Value[CompletionFromModule, nn] Path CompletionFromModule.nn CPPipe pathFromEnv:SomeOtherModule found:true Path CompletionFromModule.SomeOtherModule. +Path CompletionFromModule.SomeOtherModule. [{ + "label": "nname", + "kind": 5, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "```rescript\nnname: string\n```\n\n```rescript\ntype typeOutsideModule = {nname: string}\n```"} + }, { "label": "->CompletionFromModule.SomeOtherModule.getNName", "kind": 12, "tags": [], @@ -51,11 +58,38 @@ Path CompletionFromModule.SomeOtherModule. "newText": "->CompletionFromModule.SomeOtherModule.getNName" } }, { - "label": "nname", - "kind": 5, + "label": "->CompletionFromModule.SomeOtherModule.getNName2", + "kind": 12, "tags": [], - "detail": "string", - "documentation": {"kind": "markdown", "value": "```rescript\nnname: string\n```\n\n```rescript\ntype typeOutsideModule = {nname: string}\n```"} + "detail": "typeOutsideModule => string", + "documentation": null, + "sortText": "getNName2", + "textEdit": { + "range": {"start": {"line": 5, "character": 26}, "end": {"line": 5, "character": 26}}, + "newText": "->CompletionFromModule.SomeOtherModule.getNName2" + } + }, { + "label": "->CompletionFromModule.SomeOtherModule.getNName", + "kind": 12, + "tags": [], + "detail": "t => string", + "documentation": null, + "sortText": "getNName", + "textEdit": { + "range": {"start": {"line": 5, "character": 26}, "end": {"line": 5, "character": 26}}, + "newText": "->CompletionFromModule.SomeOtherModule.getNName" + } + }, { + "label": "->CompletionFromModule.SomeOtherModule.getNName2", + "kind": 12, + "tags": [], + "detail": "typeOutsideModule => string", + "documentation": null, + "sortText": "getNName2", + "textEdit": { + "range": {"start": {"line": 5, "character": 26}, "end": {"line": 5, "character": 26}}, + "newText": "->CompletionFromModule.SomeOtherModule.getNName2" + } }] Complete src/CompletionFromModule2.res 8:29 @@ -66,9 +100,8 @@ Resolved opens 1 pervasives ContextPath Value[CompletionFromModule, nnn]-> ContextPath Value[CompletionFromModule, nnn] Path CompletionFromModule.nnn -CPPipe env:CompletionFromModule2 envFromCompletionItem:CompletionFromModule -CPPipe type path:typeOutsideModule CPPipe pathFromEnv: found:true +Path CompletionFromModule. Path CompletionFromModule.SomeOtherModule. [{ "label": "CompletionFromModule.SomeOtherModule.getNName", @@ -93,9 +126,8 @@ Resolved opens 2 pervasives CompletionFromModule.res ContextPath Value[CompletionFromModule, nnn]-> ContextPath Value[CompletionFromModule, nnn] Path CompletionFromModule.nnn -CPPipe env:CompletionFromModule2 envFromCompletionItem:CompletionFromModule -CPPipe type path:typeOutsideModule CPPipe pathFromEnv: found:true +Path CompletionFromModule. Path CompletionFromModule.SomeOtherModule. [{ "label": "getNName", diff --git a/analysis/tests/src/expected/CompletionFunctionArguments.res.txt b/analysis/tests/src/expected/CompletionFunctionArguments.res.txt index b84d25a27..003691cdd 100644 --- a/analysis/tests/src/expected/CompletionFunctionArguments.res.txt +++ b/analysis/tests/src/expected/CompletionFunctionArguments.res.txt @@ -400,8 +400,6 @@ Resolved opens 1 pervasives ContextPath Value[thisGetsBrokenLoc]->a <> ContextPath Value[thisGetsBrokenLoc] Path thisGetsBrokenLoc -CPPipe env:CompletionFunctionArguments -CPPipe type path:ReactEvent.Mouse.t CPPipe pathFromEnv:ReactEvent.Mouse found:false Path ReactEvent.Mouse.a [{ @@ -426,8 +424,6 @@ Resolved opens 1 pervasives ContextPath Value[reassignedWorks]->a <> ContextPath Value[reassignedWorks] Path reassignedWorks -CPPipe env:CompletionFunctionArguments -CPPipe type path:ReactEvent.Mouse.t CPPipe pathFromEnv:ReactEvent.Mouse found:false Path ReactEvent.Mouse.a [{ @@ -449,8 +445,6 @@ Resolved opens 1 pervasives ContextPath Value[fineModuleVal]-> ContextPath Value[fineModuleVal] Path fineModuleVal -CPPipe env:CompletionFunctionArguments -CPPipe type path:FineModule.t CPPipe pathFromEnv:FineModule found:true Path FineModule. [{ diff --git a/analysis/tests/src/expected/CompletionInferValues.res.txt b/analysis/tests/src/expected/CompletionInferValues.res.txt index 8bbaf98c1..4a04b2cee 100644 --- a/analysis/tests/src/expected/CompletionInferValues.res.txt +++ b/analysis/tests/src/expected/CompletionInferValues.res.txt @@ -9,7 +9,6 @@ Path aliased ContextPath Value[x] Path x ContextPath int -CPPipe env:CompletionInferValues Path Belt.Int.t [{ "label": "Belt.Int.toString", @@ -38,6 +37,7 @@ ContextPath Value[getSomeRecord](Nolabel) ContextPath Value[getSomeRecord] Path getSomeRecord CPPipe pathFromEnv: found:true +Path CompletionInferValues. [{ "label": "name", "kind": 5, @@ -67,6 +67,7 @@ ContextPath Value[getSomeRecord](Nolabel) ContextPath Value[getSomeRecord] Path getSomeRecord CPPipe pathFromEnv: found:true +Path CompletionInferValues. [{ "label": "name", "kind": 5, @@ -101,6 +102,7 @@ ContextPath CArgument Value[someFnWithCallback]($0) ContextPath Value[someFnWithCallback] Path someFnWithCallback CPPipe pathFromEnv: found:true +Path CompletionInferValues. [{ "label": "name", "kind": 5, @@ -137,6 +139,7 @@ Path aliasedFn ContextPath Value[someFnWithCallback] Path someFnWithCallback CPPipe pathFromEnv: found:true +Path CompletionInferValues. [{ "label": "name", "kind": 5, @@ -167,8 +170,6 @@ ContextPath CArgument CArgument Value[reactEventFn]($0)($0) ContextPath CArgument Value[reactEventFn]($0) ContextPath Value[reactEventFn] Path reactEventFn -CPPipe env:CompletionInferValues -CPPipe type path:ReactEvent.Mouse.t CPPipe pathFromEnv:ReactEvent.Mouse found:false Path ReactEvent.Mouse.pr [{ @@ -195,8 +196,6 @@ ContextPath CArgument CJsxPropValue [div] onMouseEnter($0) ContextPath CJsxPropValue [div] onMouseEnter Path ReactDOM.domProps Path PervasivesU.JsxDOM.domProps -CPPipe env:CompletionInferValues -CPPipe type path:JsxEventU.Mouse.t CPPipe pathFromEnv:JsxEventU.Mouse found:false Path JsxEventU.Mouse.pr [{ @@ -222,8 +221,6 @@ Path event ContextPath CArgument CJsxPropValue [Div] onMouseEnter($0) ContextPath CJsxPropValue [Div] onMouseEnter Path Div.make -CPPipe env:CompletionInferValues envFromCompletionItem:CompletionInferValues.Div -CPPipe type path:PervasivesU.JsxEvent.Mouse.t CPPipe pathFromEnv:PervasivesU.JsxEvent.Mouse found:false Path PervasivesU.JsxEvent.Mouse.pr [{ @@ -250,7 +247,6 @@ Path btn ContextPath Value[JsxEvent, Mouse, button](Nolabel) ContextPath Value[JsxEvent, Mouse, button] Path JsxEvent.Mouse.button -CPPipe env:CompletionInferValues envFromCompletionItem:JsxEventU.Mouse Path Belt.Int.t [{ "label": "Belt.Int.toString", @@ -282,7 +278,6 @@ Path btn ContextPath Value[Belt, Int, toString](Nolabel) ContextPath Value[Belt, Int, toString] Path Belt.Int.toString -CPPipe env:CompletionInferValues envFromCompletionItem:Belt_Int Path Js.String2.spl [{ "label": "Js.String2.splitAtMost", @@ -326,7 +321,6 @@ Path btn ContextPath Value[Js, String2, split](Nolabel, Nolabel) ContextPath Value[Js, String2, split] Path Js.String2.split -CPPipe env:CompletionInferValues envFromCompletionItem:Js_string2 Path Js.Array2.ma [{ "label": "Js.Array2.mapi", @@ -353,18 +347,20 @@ ContextPath Value[x] Path x ContextPath Type[someRecord] Path someRecord +ContextPath Type[someRecord] +Path someRecord [{ "label": "name", "kind": 5, "tags": [], "detail": "string", - "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype someRecord = {name: string, age: int}\n```"} + "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype someRecord\n```"} }, { "label": "age", "kind": 5, "tags": [], "detail": "int", - "documentation": {"kind": "markdown", "value": "```rescript\nage: int\n```\n\n```rescript\ntype someRecord = {name: string, age: int}\n```"} + "documentation": {"kind": "markdown", "value": "```rescript\nage: int\n```\n\n```rescript\ntype someRecord\n```"} }] Complete src/CompletionInferValues.res 78:78 @@ -382,6 +378,7 @@ Path x ContextPath Type[someRecordWithNestedStuff] Path someRecordWithNestedStuff CPPipe pathFromEnv: found:true +Path CompletionInferValues. [{ "label": "name", "kind": 5, @@ -411,6 +408,7 @@ Path x ContextPath Type[someRecordWithNestedStuff] Path someRecordWithNestedStuff CPPipe pathFromEnv: found:true +Path CompletionInferValues. [{ "label": "someRecord", "kind": 5, @@ -434,6 +432,7 @@ Path x ContextPath Type[someRecordWithNestedStuff] Path someRecordWithNestedStuff CPPipe pathFromEnv: found:true +Path CompletionInferValues. [{ "label": "name", "kind": 5, @@ -461,7 +460,6 @@ ContextPath Value[x] Path x ContextPath Type[someRecordWithNestedStuff] Path someRecordWithNestedStuff -CPPipe env:CompletionInferValues Path Js.String2.slic [{ "label": "Js.String2.sliceToEnd", @@ -490,7 +488,6 @@ ContextPath Value[x] Path x ContextPath Type[someRecordWithNestedStuff] Path someRecordWithNestedStuff -CPPipe env:CompletionInferValues Path Belt.Int.toS [{ "label": "Belt.Int.toString", @@ -516,7 +513,6 @@ ContextPath Value[x] Path x ContextPath Type[otherNestedRecord] Path otherNestedRecord -CPPipe env:CompletionInferValues Path Belt.Int.toS [{ "label": "Belt.Int.toString", @@ -543,7 +539,6 @@ ContextPath Value[x] Path x ContextPath Type[otherNestedRecord] Path otherNestedRecord -CPPipe env:CompletionInferValues Path Belt.Int.toS [{ "label": "Belt.Int.toString", @@ -566,7 +561,6 @@ ContextPath Value[x] Path x ContextPath Type[otherNestedRecord] Path otherNestedRecord -CPPipe env:CompletionInferValues Path Js.String2.slic [{ "label": "Js.String2.sliceToEnd", @@ -595,7 +589,6 @@ ContextPath Value[x] Path x ContextPath Type[otherNestedRecord] Path otherNestedRecord -CPPipe env:CompletionInferValues Path Js.String2.slic [{ "label": "Js.String2.sliceToEnd", @@ -624,7 +617,6 @@ ContextPath Value[x] Path x ContextPath Type[otherNestedRecord] Path otherNestedRecord -CPPipe env:CompletionInferValues Path Js.String2.slic [{ "label": "Js.String2.sliceToEnd", @@ -655,18 +647,24 @@ Path x ContextPath array ContextPath Type[otherNestedRecord] Path otherNestedRecord +ContextPath CPatternPath(Value[x])->array +ContextPath Value[x] +Path x +ContextPath array +ContextPath Type[otherNestedRecord] +Path otherNestedRecord [{ "label": "someRecord", "kind": 5, "tags": [], "detail": "someRecord", - "documentation": {"kind": "markdown", "value": "```rescript\nsomeRecord: someRecord\n```\n\n```rescript\ntype otherNestedRecord = {someRecord: someRecord, someTuple: (someVariant, int, somePolyVariant), optRecord: option}\n```"} + "documentation": {"kind": "markdown", "value": "```rescript\nsomeRecord: someRecord\n```\n\n```rescript\ntype otherNestedRecord\n```"} }, { "label": "someTuple", "kind": 5, "tags": [], "detail": "(someVariant, int, somePolyVariant)", - "documentation": {"kind": "markdown", "value": "```rescript\nsomeTuple: (someVariant, int, somePolyVariant)\n```\n\n```rescript\ntype otherNestedRecord = {someRecord: someRecord, someTuple: (someVariant, int, somePolyVariant), optRecord: option}\n```"} + "documentation": {"kind": "markdown", "value": "```rescript\nsomeTuple: (someVariant, int, somePolyVariant)\n```\n\n```rescript\ntype otherNestedRecord\n```"} }] Complete src/CompletionInferValues.res 122:53 @@ -680,7 +678,6 @@ Path v ContextPath Value[x] Path x ContextPath int -CPPipe env:CompletionInferValues Path Belt.Int.toSt [{ "label": "Belt.Int.toString", @@ -736,8 +733,6 @@ ContextPath CArgument CArgument Value[fn2](~cb)($0) ContextPath CArgument Value[fn2](~cb) ContextPath Value[fn2] Path fn2 -CPPipe env:CompletionInferValues -CPPipe type path:ReactDOM.Client.Root.t CPPipe pathFromEnv:ReactDOM.Client.Root found:false Path ReactDOM.Client.Root. [{ @@ -772,8 +767,6 @@ ContextPath CArgument CArgument Value[fn3](~cb)($0) ContextPath CArgument Value[fn3](~cb) ContextPath Value[fn3] Path fn3 -CPPipe env:CompletionInferValues -CPPipe type path:CompletionSupport.Test.t CPPipe pathFromEnv:CompletionSupport.Test found:false Path CompletionSupport.Test. [{ @@ -874,8 +867,6 @@ ContextPath CArgument CArgument Value[CompletionSupport2, makeRenderer](~render) ContextPath CArgument Value[CompletionSupport2, makeRenderer](~render) ContextPath Value[CompletionSupport2, makeRenderer] Path CompletionSupport2.makeRenderer -CPPipe env:CompletionInferValues envFromCompletionItem:CompletionSupport2.Internal -CPPipe type path:ReactDOM.Client.Root.t CPPipe pathFromEnv:ReactDOM.Client.Root found:false Path ReactDOM.Client.Root. [{ diff --git a/analysis/tests/src/expected/CompletionJsx.res.txt b/analysis/tests/src/expected/CompletionJsx.res.txt index 8c668e969..31041216d 100644 --- a/analysis/tests/src/expected/CompletionJsx.res.txt +++ b/analysis/tests/src/expected/CompletionJsx.res.txt @@ -6,7 +6,6 @@ Resolved opens 1 pervasives ContextPath Value[someString]->st ContextPath Value[someString] Path someString -CPPipe env:CompletionJsx Path Js.String2.st [{ "label": "Js.String2.startsWith", @@ -37,7 +36,6 @@ Resolved opens 1 pervasives ContextPath Value[someString]->st <> ContextPath Value[someString] Path someString -CPPipe env:CompletionJsx Path Js.String2.st [{ "label": "React.string", @@ -83,7 +81,6 @@ Resolved opens 1 pervasives ContextPath Value[someString]->st <> ContextPath Value[someString] Path someString -CPPipe env:CompletionJsx Path Js.String2.st [{ "label": "React.string", @@ -128,7 +125,6 @@ Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives ContextPath string->st <> ContextPath string -CPPipe env:CompletionJsx Path Js.String2.st [{ "label": "React.string", @@ -175,7 +171,6 @@ ContextPath Value[Js, String2, trim](Nolabel)->st <> ContextPath Value[Js, String2, trim](Nolabel) ContextPath Value[Js, String2, trim] Path Js.String2.trim -CPPipe env:CompletionJsx envFromCompletionItem:Js_string2 Path Js.String2.st [{ "label": "React.string", @@ -221,7 +216,6 @@ Resolved opens 1 pervasives ContextPath Value[someInt]-> <> ContextPath Value[someInt] Path someInt -CPPipe env:CompletionJsx Path Belt.Int. [{ "label": "React.int", @@ -290,7 +284,6 @@ Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives ContextPath int-> <> ContextPath int -CPPipe env:CompletionJsx Path Belt.Int. [{ "label": "React.int", @@ -360,7 +353,6 @@ Resolved opens 1 pervasives ContextPath Value[someArr]->a <> ContextPath Value[someArr] Path someArr -CPPipe env:CompletionJsx Path Js.Array2.a [{ "label": "React.array", diff --git a/analysis/tests/src/expected/CompletionMultipleEditorCompleteFrom.res.txt b/analysis/tests/src/expected/CompletionMultipleEditorCompleteFrom.res.txt index 8476ebfc1..f3afb8883 100644 --- a/analysis/tests/src/expected/CompletionMultipleEditorCompleteFrom.res.txt +++ b/analysis/tests/src/expected/CompletionMultipleEditorCompleteFrom.res.txt @@ -10,5 +10,28 @@ Path a CPPipe pathFromEnv:A found:true Path A. Path B. -[] +Path C. +[{ + "label": "->B.b", + "kind": 12, + "tags": [], + "detail": "A.a => int", + "documentation": null, + "sortText": "b", + "textEdit": { + "range": {"start": {"line": 19, "character": 4}, "end": {"line": 19, "character": 4}}, + "newText": "->B.b" + } + }, { + "label": "->C.c", + "kind": 12, + "tags": [], + "detail": "A.a => char", + "documentation": null, + "sortText": "c", + "textEdit": { + "range": {"start": {"line": 19, "character": 4}, "end": {"line": 19, "character": 4}}, + "newText": "->C.c" + } + }] diff --git a/analysis/tests/src/expected/CompletionPipeChain.res.txt b/analysis/tests/src/expected/CompletionPipeChain.res.txt index f8aea750b..e3cb6ae63 100644 --- a/analysis/tests/src/expected/CompletionPipeChain.res.txt +++ b/analysis/tests/src/expected/CompletionPipeChain.res.txt @@ -6,8 +6,6 @@ Resolved opens 1 pervasives ContextPath Value[int]-> ContextPath Value[int] Path int -CPPipe env:CompletionPipeChain -CPPipe type path:Integer.t CPPipe pathFromEnv:Integer found:true Path Integer. [{ @@ -39,8 +37,6 @@ ContextPath Value[toFlt](Nolabel)-> ContextPath Value[toFlt](Nolabel) ContextPath Value[toFlt] Path toFlt -CPPipe env:CompletionPipeChain -CPPipe type path:SuperFloat.t CPPipe pathFromEnv:SuperFloat found:true Path SuperFloat. [{ @@ -60,8 +56,6 @@ ContextPath Value[Integer, increment](Nolabel, Nolabel)-> ContextPath Value[Integer, increment](Nolabel, Nolabel) ContextPath Value[Integer, increment] Path Integer.increment -CPPipe env:CompletionPipeChain envFromCompletionItem:CompletionPipeChain.Integer -CPPipe type path:t CPPipe pathFromEnv:Integer found:true Path Integer. [{ @@ -93,8 +87,6 @@ ContextPath Value[Integer, increment](Nolabel, Nolabel)-> ContextPath Value[Integer, increment](Nolabel, Nolabel) ContextPath Value[Integer, increment] Path Integer.increment -CPPipe env:CompletionPipeChain envFromCompletionItem:CompletionPipeChain.Integer -CPPipe type path:t CPPipe pathFromEnv:Integer found:true Path Integer. [{ @@ -126,8 +118,6 @@ ContextPath Value[Integer, decrement](Nolabel, Nolabel)-> ContextPath Value[Integer, decrement](Nolabel, Nolabel) ContextPath Value[Integer, decrement] Path Integer.decrement -CPPipe env:CompletionPipeChain envFromCompletionItem:CompletionPipeChain.Integer -CPPipe type path:t CPPipe pathFromEnv:Integer found:true Path Integer. [{ @@ -159,8 +149,6 @@ ContextPath Value[Integer, decrement](Nolabel, Nolabel)-> ContextPath Value[Integer, decrement](Nolabel, Nolabel) ContextPath Value[Integer, decrement] Path Integer.decrement -CPPipe env:CompletionPipeChain envFromCompletionItem:CompletionPipeChain.Integer -CPPipe type path:t CPPipe pathFromEnv:Integer found:true Path Integer. [{ @@ -192,8 +180,6 @@ ContextPath Value[SuperFloat, fromInteger](Nolabel)-> ContextPath Value[SuperFloat, fromInteger](Nolabel) ContextPath Value[SuperFloat, fromInteger] Path SuperFloat.fromInteger -CPPipe env:CompletionPipeChain envFromCompletionItem:CompletionPipeChain.SuperFloat -CPPipe type path:t CPPipe pathFromEnv:SuperFloat found:true Path SuperFloat. [{ @@ -213,8 +199,6 @@ ContextPath Value[SuperFloat, fromInteger](Nolabel)->t ContextPath Value[SuperFloat, fromInteger](Nolabel) ContextPath Value[SuperFloat, fromInteger] Path SuperFloat.fromInteger -CPPipe env:CompletionPipeChain envFromCompletionItem:CompletionPipeChain.SuperFloat -CPPipe type path:t CPPipe pathFromEnv:SuperFloat found:true Path SuperFloat.t [{ @@ -234,8 +218,6 @@ ContextPath Value[CompletionSupport, Test, make](Nolabel)-> ContextPath Value[CompletionSupport, Test, make](Nolabel) ContextPath Value[CompletionSupport, Test, make] Path CompletionSupport.Test.make -CPPipe env:CompletionPipeChain envFromCompletionItem:CompletionSupport.Test -CPPipe type path:t CPPipe pathFromEnv:Test found:true Path CompletionSupport.Test. [{ @@ -261,8 +243,6 @@ ContextPath Value[CompletionSupport, Test, addSelf](Nolabel, Nolabel)-> ContextPath Value[CompletionSupport, Test, addSelf](Nolabel, Nolabel) ContextPath Value[CompletionSupport, Test, addSelf] Path CompletionSupport.Test.addSelf -CPPipe env:CompletionPipeChain envFromCompletionItem:CompletionSupport.Test -CPPipe type path:t CPPipe pathFromEnv:Test found:true Path CompletionSupport.Test. [{ @@ -288,9 +268,8 @@ ContextPath Value[Js, Array2, forEach](Nolabel, Nolabel)-> ContextPath Value[Js, Array2, forEach](Nolabel, Nolabel) ContextPath Value[Js, Array2, forEach] Path Js.Array2.forEach -CPPipe env:CompletionPipeChain envFromCompletionItem:Js_array2 -CPPipe type path:unit CPPipe pathFromEnv: found:true +Path Js_array2. [] Complete src/CompletionPipeChain.res 62:6 @@ -302,7 +281,6 @@ ContextPath Value[Belt, Array, reduce](Nolabel, Nolabel, Nolabel)->t ContextPath Value[Belt, Array, reduce](Nolabel, Nolabel, Nolabel) ContextPath Value[Belt, Array, reduce] Path Belt.Array.reduce -CPPipe env:CompletionPipeChain envFromCompletionItem:Belt_Array Path Belt.Int.t [{ "label": "Belt.Int.toString", @@ -326,8 +304,6 @@ Resolved opens 1 pervasives ContextPath Value[aliased]-> ContextPath Value[aliased] Path aliased -CPPipe env:CompletionPipeChain -CPPipe type path:CompletionSupport.Test.t CPPipe pathFromEnv:CompletionSupport.Test found:false Path CompletionSupport.Test. [{ @@ -352,8 +328,6 @@ Resolved opens 1 pervasives ContextPath Value[notAliased]-> ContextPath Value[notAliased] Path notAliased -CPPipe env:CompletionPipeChain -CPPipe type path:CompletionSupport.Test.t CPPipe pathFromEnv:CompletionSupport.Test found:false Path CompletionSupport.Test. [{ @@ -390,8 +364,6 @@ CPPipe pathFromEnv:CompletionSupport2.Internal found:false Path CompletionSupport2.Internal.support CPPipe pathFromEnv:CompletionSupport.Nested found:false Path CompletionSupport.Nested.root -CPPipe env:CompletionPipeChain envFromCompletionItem:CompletionSupport.Nested -CPPipe type path:ReactDOM.Client.Root.t CPPipe pathFromEnv:ReactDOM.Client.Root found:false Path ReactDOM.Client.Root.ren [{ @@ -418,8 +390,6 @@ Resolved opens 1 pervasives ContextPath Value[root]->ren ContextPath Value[root] Path root -CPPipe env:CompletionPipeChain -CPPipe type path:ReactDOM.Client.Root.t CPPipe pathFromEnv:ReactDOM.Client.Root found:false Path ReactDOM.Client.Root.ren [{ @@ -441,8 +411,6 @@ Resolved opens 1 pervasives ContextPath Value[int]-> ContextPath Value[int] Path int -CPPipe env:CompletionPipeChain -CPPipe type path:Integer.t CPPipe pathFromEnv:Integer found:true Path Integer. [{ @@ -475,8 +443,6 @@ Resolved opens 1 pervasives ContextPath Value[int]->t ContextPath Value[int] Path int -CPPipe env:CompletionPipeChain -CPPipe type path:Integer.t CPPipe pathFromEnv:Integer found:true Path Integer.t [{ @@ -495,7 +461,7 @@ Resolved opens 1 pervasives ContextPath Value[r]->la ContextPath Value[r] Path r -CPPipe env:CompletionPipeChain +CPPipe pathFromEnv:Js.Re found:false Path Js.Re.la [{ "label": "Js.Re.lastIndex", @@ -513,8 +479,6 @@ Resolved opens 1 pervasives ContextPath Value[xx]-> ContextPath Value[xx] Path xx -CPPipe env:CompletionPipeChain -CPPipe type path:Xyz.xx CPPipe pathFromEnv:Xyz found:true Path Xyz. [{ diff --git a/analysis/tests/src/expected/CompletionPipeProperty.res.txt b/analysis/tests/src/expected/CompletionPipeProperty.res.txt index 7cfbd37c9..7fd14d555 100644 --- a/analysis/tests/src/expected/CompletionPipeProperty.res.txt +++ b/analysis/tests/src/expected/CompletionPipeProperty.res.txt @@ -13,6 +13,18 @@ Path Sprite.anchor CPPipe pathFromEnv:ObservablePoint found:true Path ObservablePoint. [{ + "label": "x", + "kind": 5, + "tags": [], + "detail": "int", + "documentation": {"kind": "markdown", "value": "```rescript\nx: int\n```\n\n```rescript\ntype op = {mutable x: int, mutable y: int}\n```"} + }, { + "label": "y", + "kind": 5, + "tags": [], + "detail": "int", + "documentation": {"kind": "markdown", "value": "```rescript\ny: int\n```\n\n```rescript\ntype op = {mutable x: int, mutable y: int}\n```"} + }, { "label": "->ObservablePoint.setBoth", "kind": 12, "tags": [], @@ -34,17 +46,5 @@ Path ObservablePoint. "range": {"start": {"line": 21, "character": 16}, "end": {"line": 21, "character": 16}}, "newText": "->ObservablePoint.set" } - }, { - "label": "x", - "kind": 5, - "tags": [], - "detail": "int", - "documentation": {"kind": "markdown", "value": "```rescript\nx: int\n```\n\n```rescript\ntype op = {mutable x: int, mutable y: int}\n```"} - }, { - "label": "y", - "kind": 5, - "tags": [], - "detail": "int", - "documentation": {"kind": "markdown", "value": "```rescript\ny: int\n```\n\n```rescript\ntype op = {mutable x: int, mutable y: int}\n```"} }] diff --git a/analysis/tests/src/expected/CompletionPipeSubmodules.res.txt b/analysis/tests/src/expected/CompletionPipeSubmodules.res.txt index 08b0bfbe8..ec0f0e47b 100644 --- a/analysis/tests/src/expected/CompletionPipeSubmodules.res.txt +++ b/analysis/tests/src/expected/CompletionPipeSubmodules.res.txt @@ -6,8 +6,6 @@ Resolved opens 1 pervasives ContextPath Value[A, B1, xx]-> ContextPath Value[A, B1, xx] Path A.B1.xx -CPPipe env:CompletionPipeSubmodules envFromCompletionItem:CompletionPipeSubmodules.A.B1 -CPPipe type path:b1 CPPipe pathFromEnv:A.B1 found:true Path A.B1. [{ @@ -29,8 +27,6 @@ ContextPath Value[A, x] Path A.x CPPipe pathFromEnv:A found:true Path A.v -CPPipe env:CompletionPipeSubmodules envFromCompletionItem:CompletionPipeSubmodules.A -CPPipe type path:B1.b1 CPPipe pathFromEnv:A.B1 found:true Path A.B1. [{ @@ -55,8 +51,6 @@ CPPipe pathFromEnv:E found:true Path E.v CPPipe pathFromEnv:D found:true Path D.v -CPPipe env:CompletionPipeSubmodules envFromCompletionItem:CompletionPipeSubmodules.D -CPPipe type path:C.t CPPipe pathFromEnv:C found:false Path C. [{ @@ -81,8 +75,6 @@ CPPipe pathFromEnv:E found:true Path E.v CPPipe pathFromEnv:D found:true Path D.v2 -CPPipe env:CompletionPipeSubmodules envFromCompletionItem:CompletionPipeSubmodules.D -CPPipe type path:C2.t2 CPPipe pathFromEnv:D.C2 found:true Path D.C2. [{ diff --git a/analysis/tests/src/expected/DotCompletionEverywhere.res.txt b/analysis/tests/src/expected/DotCompletionEverywhere.res.txt index 089c35f6c..ad7f42c7b 100644 --- a/analysis/tests/src/expected/DotCompletionEverywhere.res.txt +++ b/analysis/tests/src/expected/DotCompletionEverywhere.res.txt @@ -11,7 +11,7 @@ Path someObj "label": "[\"age\"]", "kind": 4, "tags": [], - "detail": "int", + "detail": "{\"age\": int, \"name\": string}", "documentation": null, "textEdit": { "range": {"start": {"line": 5, "character": 10}, "end": {"line": 5, "character": 10}}, @@ -21,7 +21,7 @@ Path someObj "label": "[\"name\"]", "kind": 4, "tags": [], - "detail": "string", + "detail": "{\"age\": int, \"name\": string}", "documentation": null, "textEdit": { "range": {"start": {"line": 5, "character": 10}, "end": {"line": 5, "character": 10}}, @@ -42,7 +42,7 @@ Path someObj "label": "[\"name\"]", "kind": 4, "tags": [], - "detail": "string", + "detail": "{\"age\": int, \"name\": string}", "documentation": null, "textEdit": { "range": {"start": {"line": 8, "character": 11}, "end": {"line": 8, "character": 13}}, @@ -60,6 +60,7 @@ ContextPath Value[rrr].n ContextPath Value[rrr] Path rrr CPPipe pathFromEnv: found:true +Path DotCompletionEverywhere.n [{ "label": "name", "kind": 5, @@ -128,6 +129,12 @@ Path Sss.rrr CPPipe pathFromEnv:Sss found:true Path Sss. [{ + "label": "name", + "kind": 5, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype rrr = {name: string}\n```"} + }, { "label": "->Sss.do", "kind": 12, "tags": [], @@ -138,12 +145,6 @@ Path Sss. "range": {"start": {"line": 41, "character": 10}, "end": {"line": 41, "character": 10}}, "newText": "->Sss.do" } - }, { - "label": "name", - "kind": 5, - "tags": [], - "detail": "string", - "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype rrr = {name: string}\n```"} }] Complete src/DotCompletionEverywhere.res 52:8 @@ -156,8 +157,15 @@ ContextPath Value[x2x2]."" ContextPath Value[x2x2] Path x2x2 CPPipe pathFromEnv: found:true -Path X2. +Path DotCompletionEverywhere. +Path DotCompletionEverywhere.X2. [{ + "label": "namee", + "kind": 5, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "```rescript\nnamee: string\n```\n\n```rescript\ntype x2x2 = {namee: string}\n```"} + }, { "label": "->X2.stuff", "kind": 12, "tags": [], @@ -168,12 +176,6 @@ Path X2. "range": {"start": {"line": 52, "character": 7}, "end": {"line": 52, "character": 7}}, "newText": "->X2.stuff" } - }, { - "label": "namee", - "kind": 5, - "tags": [], - "detail": "string", - "documentation": {"kind": "markdown", "value": "```rescript\nnamee: string\n```\n\n```rescript\ntype x2x2 = {namee: string}\n```"} }] Complete src/DotCompletionEverywhere.res 61:7 @@ -189,7 +191,7 @@ Path obj "label": "[\"name\"]", "kind": 4, "tags": [], - "detail": "string", + "detail": "{\"name\": string, \"nothing\": bool, \"number\": int}", "documentation": null, "textEdit": { "range": {"start": {"line": 61, "character": 6}, "end": {"line": 61, "character": 6}}, @@ -199,7 +201,7 @@ Path obj "label": "[\"nothing\"]", "kind": 4, "tags": [], - "detail": "bool", + "detail": "{\"name\": string, \"nothing\": bool, \"number\": int}", "documentation": null, "textEdit": { "range": {"start": {"line": 61, "character": 6}, "end": {"line": 61, "character": 6}}, @@ -209,7 +211,7 @@ Path obj "label": "[\"number\"]", "kind": 4, "tags": [], - "detail": "int", + "detail": "{\"name\": string, \"nothing\": bool, \"number\": int}", "documentation": null, "textEdit": { "range": {"start": {"line": 61, "character": 6}, "end": {"line": 61, "character": 6}}, @@ -226,7 +228,6 @@ Resolved opens 1 pervasives ContextPath Value[arr].m ContextPath Value[arr] Path arr -CPPipe pathFromEnv: found:true Path Js.Array2.m [{ "label": "->Js.Array2.mapi", @@ -263,12 +264,23 @@ ContextPath Value[button] Path button CPPipe pathFromEnv:DOMAPI found:true Path DOMAPI. -Path HTMLButtonElement. +Path DotCompletionEverywhere.HTMLButtonElement. [{ "label": "disabled", "kind": 5, "tags": [], "detail": "bool", "documentation": {"kind": "markdown", "value": "```rescript\ndisabled: bool\n```\n\n```rescript\ntype htmlButtonElement = {mutable disabled: bool}\n```"} + }, { + "label": "->HTMLButtonElement.checkValidity", + "kind": 12, + "tags": [], + "detail": "DOMAPI.htmlButtonElement => bool", + "documentation": null, + "sortText": "checkValidity", + "textEdit": { + "range": {"start": {"line": 85, "character": 9}, "end": {"line": 85, "character": 9}}, + "newText": "->HTMLButtonElement.checkValidity" + } }] diff --git a/analysis/tests/src/expected/DotPipeCompletionSpec.res.txt b/analysis/tests/src/expected/DotPipeCompletionSpec.res.txt new file mode 100644 index 000000000..1d84ecd16 --- /dev/null +++ b/analysis/tests/src/expected/DotPipeCompletionSpec.res.txt @@ -0,0 +1,292 @@ +Complete src/DotPipeCompletionSpec.res 15:5 +posCursor:[15:5] posNoWhite:[15:4] Found expr:[15:3->15:5] +Pexp_field [15:3->15:4] _:[18:0->15:5] +Completable: Cpath Value[n]."" +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[n]."" +ContextPath Value[n] +Path n +CPPipe pathFromEnv:SomeModule found:true +Path SomeModule. +[{ + "label": "name", + "kind": 5, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype t = {name: string}\n```"} + }, { + "label": "->SomeModule.withUnlabelledArgumentNotFirst", + "kind": 12, + "tags": [], + "detail": "(~name: string=?, t) => unit", + "documentation": null, + "sortText": "withUnlabelledArgumentNotFirst", + "textEdit": { + "range": {"start": {"line": 15, "character": 4}, "end": {"line": 15, "character": 4}}, + "newText": "->SomeModule.withUnlabelledArgumentNotFirst" + } + }, { + "label": "->SomeModule.getName", + "kind": 12, + "tags": [], + "detail": "t => string", + "documentation": null, + "sortText": "getName", + "textEdit": { + "range": {"start": {"line": 15, "character": 4}, "end": {"line": 15, "character": 4}}, + "newText": "->SomeModule.getName" + } + }] + +Complete src/DotPipeCompletionSpec.res 44:6 +posCursor:[44:6] posNoWhite:[44:5] Found expr:[44:3->44:6] +Pexp_field [44:3->44:5] _:[47:0->44:6] +Completable: Cpath Value[nn]."" +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[nn]."" +ContextPath Value[nn] +Path nn +CPPipe pathFromEnv:SomeOtherModule found:true +Path SomeOtherModule. +Path DotPipeCompletionSpec.CompleteFromThisToo. +Path DotPipeCompletionSpec.SomeOtherModule. +[{ + "label": "nname", + "kind": 5, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "```rescript\nnname: string\n```\n\n```rescript\ntype typeOutsideModule = {nname: string}\n```"} + }, { + "label": "->SomeOtherModule.getNName", + "kind": 12, + "tags": [], + "detail": "t => string", + "documentation": null, + "sortText": "getNName", + "textEdit": { + "range": {"start": {"line": 44, "character": 5}, "end": {"line": 44, "character": 5}}, + "newText": "->SomeOtherModule.getNName" + } + }, { + "label": "->SomeOtherModule.getNName2", + "kind": 12, + "tags": [], + "detail": "typeOutsideModule => string", + "documentation": null, + "sortText": "getNName2", + "textEdit": { + "range": {"start": {"line": 44, "character": 5}, "end": {"line": 44, "character": 5}}, + "newText": "->SomeOtherModule.getNName2" + } + }, { + "label": "->CompleteFromThisToo.a", + "kind": 12, + "tags": [], + "detail": "typeOutsideModule => string", + "documentation": null, + "sortText": "a", + "textEdit": { + "range": {"start": {"line": 44, "character": 5}, "end": {"line": 44, "character": 5}}, + "newText": "->CompleteFromThisToo.a" + } + }, { + "label": "->SomeOtherModule.getNName", + "kind": 12, + "tags": [], + "detail": "t => string", + "documentation": null, + "sortText": "getNName", + "textEdit": { + "range": {"start": {"line": 44, "character": 5}, "end": {"line": 44, "character": 5}}, + "newText": "->SomeOtherModule.getNName" + } + }, { + "label": "->SomeOtherModule.getNName2", + "kind": 12, + "tags": [], + "detail": "typeOutsideModule => string", + "documentation": null, + "sortText": "getNName2", + "textEdit": { + "range": {"start": {"line": 44, "character": 5}, "end": {"line": 44, "character": 5}}, + "newText": "->SomeOtherModule.getNName2" + } + }] + +Complete src/DotPipeCompletionSpec.res 62:5 +posCursor:[62:5] posNoWhite:[62:4] Found expr:[62:3->62:5] +Pexp_field [62:3->62:4] _:[65:0->62:5] +Completable: Cpath Value[a]."" +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[a]."" +ContextPath Value[a] +Path a +CPPipe pathFromEnv:A found:true +Path A. +Path B. +[{ + "label": "->A.withA", + "kind": 12, + "tags": [], + "detail": "a => unit", + "documentation": null, + "sortText": "withA", + "textEdit": { + "range": {"start": {"line": 62, "character": 4}, "end": {"line": 62, "character": 4}}, + "newText": "->A.withA" + } + }, { + "label": "->B.b", + "kind": 12, + "tags": [], + "detail": "A.a => int", + "documentation": null, + "sortText": "b", + "textEdit": { + "range": {"start": {"line": 62, "character": 4}, "end": {"line": 62, "character": 4}}, + "newText": "->B.b" + } + }] + +Complete src/DotPipeCompletionSpec.res 67:6 +posCursor:[67:6] posNoWhite:[67:5] Found expr:[67:3->67:6] +Pexp_field [67:3->67:5] _:[70:0->67:6] +Completable: Cpath Value[xx]."" +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[xx]."" +ContextPath Value[xx] +Path xx +CPPipe pathFromEnv:CompletionFromModule.SomeModule found:false +Path CompletionFromModule.SomeModule. +[{ + "label": "name", + "kind": 5, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype t = {name: string}\n```"} + }, { + "label": "->CompletionFromModule.SomeModule.getName", + "kind": 12, + "tags": [], + "detail": "t => string", + "documentation": null, + "sortText": "getName", + "textEdit": { + "range": {"start": {"line": 67, "character": 5}, "end": {"line": 67, "character": 5}}, + "newText": "->CompletionFromModule.SomeModule.getName" + } + }] + +Complete src/DotPipeCompletionSpec.res 75:9 +posCursor:[75:9] posNoWhite:[75:8] Found expr:[75:3->75:9] +Pexp_field [75:3->75:7] u:[75:8->75:9] +Completable: Cpath Value[ffff].u +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[ffff].u +ContextPath Value[ffff] +Path ffff +Path Js.Array2.u +[{ + "label": "->Js.Array2.unshiftMany", + "kind": 12, + "tags": [], + "detail": "(t<'a>, array<'a>) => int", + "documentation": {"kind": "markdown", "value": "\nAdds the elements in the second array argument at the beginning of the first\narray argument, returning the new number of elements in the array. *This\nfunction modifies the original array.* See\n[`Array.unshift`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift)\non MDN.\n\n## Examples\n\n```rescript\nlet arr = [\"d\", \"e\"]\nJs.Array2.unshiftMany(arr, [\"a\", \"b\", \"c\"]) == 5\narr == [\"a\", \"b\", \"c\", \"d\", \"e\"]\n```\n"}, + "sortText": "unshiftMany", + "textEdit": { + "range": {"start": {"line": 75, "character": 8}, "end": {"line": 75, "character": 9}}, + "newText": "->Js.Array2.unshiftMany" + } + }, { + "label": "->Js.Array2.unshift", + "kind": 12, + "tags": [], + "detail": "(t<'a>, 'a) => int", + "documentation": {"kind": "markdown", "value": "\nAdds the given element to the array, returning the new number of elements in\nthe array. *This function modifies the original array.* See\n[`Array.unshift`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift)\non MDN.\n\n## Examples\n\n```rescript\nlet arr = [\"b\", \"c\", \"d\"]\nJs.Array2.unshift(arr, \"a\") == 4\narr == [\"a\", \"b\", \"c\", \"d\"]\n```\n"}, + "sortText": "unshift", + "textEdit": { + "range": {"start": {"line": 75, "character": 8}, "end": {"line": 75, "character": 9}}, + "newText": "->Js.Array2.unshift" + } + }, { + "label": "->Js.Array2.unsafe_get", + "kind": 12, + "tags": [], + "detail": "(array<'a>, int) => 'a", + "documentation": {"kind": "markdown", "value": "\nReturns the value at the given position in the array if the position is in\nbounds; returns the JavaScript value `undefined` otherwise.\n\n## Examples\n\n```rescript\nlet arr = [100, 101, 102, 103]\nJs.Array2.unsafe_get(arr, 3) == 103\nJs.Array2.unsafe_get(arr, 4) // returns undefined\n```\n"}, + "sortText": "unsafe_get", + "textEdit": { + "range": {"start": {"line": 75, "character": 8}, "end": {"line": 75, "character": 9}}, + "newText": "->Js.Array2.unsafe_get" + } + }, { + "label": "->Js.Array2.unsafe_set", + "kind": 12, + "tags": [], + "detail": "(array<'a>, int, 'a) => unit", + "documentation": {"kind": "markdown", "value": "\nSets the value at the given position in the array if the position is in bounds.\nIf the index is out of bounds, well, “here there be dragons.“\n\n*This function modifies the original array.*\n\n## Examples\n\n```rescript\nlet arr = [100, 101, 102, 103]\nJs.Array2.unsafe_set(arr, 3, 99)\n// result is [100, 101, 102, 99];\n\nJs.Array2.unsafe_set(arr, 4, 88)\n// result is [100, 101, 102, 99, 88]\n\nJs.Array2.unsafe_set(arr, 6, 77)\n// result is [100, 101, 102, 99, 88, <1 empty item>, 77]\n\nJs.Array2.unsafe_set(arr, -1, 66)\n// you don't want to know.\n```\n"}, + "sortText": "unsafe_set", + "textEdit": { + "range": {"start": {"line": 75, "character": 8}, "end": {"line": 75, "character": 9}}, + "newText": "->Js.Array2.unsafe_set" + } + }] + +Complete src/DotPipeCompletionSpec.res 80:7 +posCursor:[80:7] posNoWhite:[80:6] Found expr:[80:3->80:7] +Pexp_field [80:3->80:6] _:[83:0->80:7] +Completable: Cpath Value[nnn]."" +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[nnn]."" +ContextPath Value[nnn] +Path nnn +CPPipe pathFromEnv: found:true +Path DotPipeCompletionSpec. +Path DotPipeCompletionSpec.SomeOtherModule. +[{ + "label": "nname", + "kind": 5, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "```rescript\nnname: string\n```\n\n```rescript\ntype typeOutsideModule = {nname: string}\n```"} + }, { + "label": "->doWithTypeOutsideModule", + "kind": 12, + "tags": [], + "detail": "typeOutsideModule => string", + "documentation": null, + "sortText": "doWithTypeOutsideModule", + "textEdit": { + "range": {"start": {"line": 80, "character": 6}, "end": {"line": 80, "character": 6}}, + "newText": "->doWithTypeOutsideModule" + } + }, { + "label": "->SomeOtherModule.getNName", + "kind": 12, + "tags": [], + "detail": "t => string", + "documentation": null, + "sortText": "getNName", + "textEdit": { + "range": {"start": {"line": 80, "character": 6}, "end": {"line": 80, "character": 6}}, + "newText": "->SomeOtherModule.getNName" + } + }, { + "label": "->SomeOtherModule.getNName2", + "kind": 12, + "tags": [], + "detail": "typeOutsideModule => string", + "documentation": null, + "sortText": "getNName2", + "textEdit": { + "range": {"start": {"line": 80, "character": 6}, "end": {"line": 80, "character": 6}}, + "newText": "->SomeOtherModule.getNName2" + } + }] + diff --git a/analysis/tests/src/expected/ExhaustiveSwitch.res.txt b/analysis/tests/src/expected/ExhaustiveSwitch.res.txt index bf1cc8447..b89a5ce19 100644 --- a/analysis/tests/src/expected/ExhaustiveSwitch.res.txt +++ b/analysis/tests/src/expected/ExhaustiveSwitch.res.txt @@ -103,9 +103,10 @@ Resolved opens 1 pervasives ContextPath Value[x]-> ContextPath Value[x] Path x -CPPipe env:ExhaustiveSwitch -CPPipe type path:rcrd CPPipe pathFromEnv: found:true +Path ExhaustiveSwitch. +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives Xform src/ExhaustiveSwitch.res start: 33:3, end: 33:10 found selection: [33:3->33:10] -> [33:6->33:10] diff --git a/analysis/tests/src/expected/Hover.res.txt b/analysis/tests/src/expected/Hover.res.txt index 9d88941c3..7a314d52b 100644 --- a/analysis/tests/src/expected/Hover.res.txt +++ b/analysis/tests/src/expected/Hover.res.txt @@ -125,7 +125,9 @@ ContextPath Value[x1].content ContextPath Value[x1] Path x1 CPPipe pathFromEnv: found:true +Path Hover.content CPPipe pathFromEnv: found:true +Path Hover. [{ "label": "age", "kind": 5, @@ -145,7 +147,9 @@ ContextPath Value[x2].content ContextPath Value[x2] Path x2 CPPipe pathFromEnv: found:true +Path Hover.content CPPipe pathFromEnv: found:true +Path Hover. [{ "label": "age", "kind": 5, @@ -165,7 +169,9 @@ ContextPath Value[y1].content ContextPath Value[y1] Path y1 CPPipe pathFromEnv: found:true +Path Hover.content CPPipe pathFromEnv: found:true +Path Hover. [{ "label": "age", "kind": 5, @@ -185,7 +191,9 @@ ContextPath Value[y2].content ContextPath Value[y2] Path y2 CPPipe pathFromEnv: found:true +Path Hover.content CPPipe pathFromEnv: found:true +Path Hover. [{ "label": "age", "kind": 5, @@ -230,6 +238,7 @@ ContextPath Value[x].someField ContextPath Value[x] Path x CPPipe pathFromEnv: found:true +Path Hover.someField Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives {"contents": {"kind": "markdown", "value": " Mighty fine field here. \n\n```rescript\nbool\n```"}} diff --git a/analysis/tests/src/expected/RecordCompletion.res.txt b/analysis/tests/src/expected/RecordCompletion.res.txt index ccd548237..851c5fa92 100644 --- a/analysis/tests/src/expected/RecordCompletion.res.txt +++ b/analysis/tests/src/expected/RecordCompletion.res.txt @@ -8,7 +8,7 @@ ContextPath Value[t].n ContextPath Value[t] Path t CPPipe pathFromEnv: found:true -CPPipe env:RecordCompletion +Path RecordCompletion.n Path Js.Array2.m [{ "label": "Js.Array2.mapi", @@ -35,8 +35,9 @@ ContextPath Value[t2].n2 ContextPath Value[t2] Path t2 CPPipe pathFromEnv: found:true +Path RecordCompletion.n2 CPPipe pathFromEnv: found:true -CPPipe env:RecordCompletion +Path RecordCompletion.n Path Js.Array2.m [{ "label": "Js.Array2.mapi", diff --git a/analysis/tests/src/expected/RxjsCompletion.res.txt b/analysis/tests/src/expected/RxjsCompletion.res.txt index f07e1dd72..7f2f2f4cc 100644 --- a/analysis/tests/src/expected/RxjsCompletion.res.txt +++ b/analysis/tests/src/expected/RxjsCompletion.res.txt @@ -28,6 +28,50 @@ Path Rxjs. "range": {"start": {"line": 26, "character": 28}, "end": {"line": 26, "character": 28}}, "newText": "->Observable.subscribe" } + }, { + "label": "->pipe", + "kind": 12, + "tags": [], + "detail": "(Observable.t<'t>, operation<'t, 'u>) => Observable.t<'u>", + "documentation": null, + "sortText": "pipe", + "textEdit": { + "range": {"start": {"line": 26, "character": 28}, "end": {"line": 26, "character": 28}}, + "newText": "->pipe" + } + }, { + "label": "->combineLatest", + "kind": 12, + "tags": [], + "detail": "(\n Observable.t<'a>,\n Observable.t<'b>,\n) => Observable.t<('a, 'b)>", + "documentation": null, + "sortText": "combineLatest", + "textEdit": { + "range": {"start": {"line": 26, "character": 28}, "end": {"line": 26, "character": 28}}, + "newText": "->combineLatest" + } + }, { + "label": "->merge", + "kind": 12, + "tags": [], + "detail": "(Observable.t<'t>, Observable.t<'t>) => Observable.t<'t>", + "documentation": null, + "sortText": "merge", + "textEdit": { + "range": {"start": {"line": 26, "character": 28}, "end": {"line": 26, "character": 28}}, + "newText": "->merge" + } + }, { + "label": "->pipe2", + "kind": 12, + "tags": [], + "detail": "(\n Observable.t<'t>,\n operation<'t, 'u>,\n operation<'u, 'i>,\n) => Observable.t<'i>", + "documentation": null, + "sortText": "pipe2", + "textEdit": { + "range": {"start": {"line": 26, "character": 28}, "end": {"line": 26, "character": 28}}, + "newText": "->pipe2" + } }] Complete src/RxjsCompletion.res 34:30 @@ -58,5 +102,49 @@ Path Rxjs. "range": {"start": {"line": 34, "character": 29}, "end": {"line": 34, "character": 29}}, "newText": "->Rxjs.Observable.subscribe" } + }, { + "label": "->Rxjs.pipe", + "kind": 12, + "tags": [], + "detail": "(Observable.t<'t>, operation<'t, 'u>) => Observable.t<'u>", + "documentation": null, + "sortText": "pipe", + "textEdit": { + "range": {"start": {"line": 34, "character": 29}, "end": {"line": 34, "character": 29}}, + "newText": "->Rxjs.pipe" + } + }, { + "label": "->Rxjs.combineLatest", + "kind": 12, + "tags": [], + "detail": "(\n Observable.t<'a>,\n Observable.t<'b>,\n) => Observable.t<('a, 'b)>", + "documentation": null, + "sortText": "combineLatest", + "textEdit": { + "range": {"start": {"line": 34, "character": 29}, "end": {"line": 34, "character": 29}}, + "newText": "->Rxjs.combineLatest" + } + }, { + "label": "->Rxjs.merge", + "kind": 12, + "tags": [], + "detail": "(Observable.t<'t>, Observable.t<'t>) => Observable.t<'t>", + "documentation": null, + "sortText": "merge", + "textEdit": { + "range": {"start": {"line": 34, "character": 29}, "end": {"line": 34, "character": 29}}, + "newText": "->Rxjs.merge" + } + }, { + "label": "->Rxjs.pipe2", + "kind": 12, + "tags": [], + "detail": "(\n Observable.t<'t>,\n operation<'t, 'u>,\n operation<'u, 'i>,\n) => Observable.t<'i>", + "documentation": null, + "sortText": "pipe2", + "textEdit": { + "range": {"start": {"line": 34, "character": 29}, "end": {"line": 34, "character": 29}}, + "newText": "->Rxjs.pipe2" + } }] From 415eea92633540b8f46d203585c0b2a2c3bd3946 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Wed, 25 Dec 2024 18:20:54 +0100 Subject: [PATCH 20/34] do not care about ExtractedType now that we have incremental type checking --- analysis/src/CompletionBackEnd.ml | 25 +-- analysis/tests/src/CompletionInferValues.res | 7 - .../expected/CompletionInferValues.res.txt | 194 ++++++------------ 3 files changed, 69 insertions(+), 157 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index f96864e47..d52c19ff4 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -1029,30 +1029,11 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact |> completionsGetTypeEnv2 ~debug ~full ~opens ~rawOpens ~pos in match mainTypeCompletionEnv with - | None -> ( + | None -> if Debug.verbose () then Printf.printf - "[dot_completion] Could not extract main type completion env. \ - Checking for extracted type.\n"; - - match - completionsFromCtxPath - |> completionsGetCompletionType2 ~debug ~full ~opens ~rawOpens ~pos - with - | Some (ExtractedType typ, env) -> ( - if Debug.verbose () then - Printf.printf "[dot_completion] Found extracted type\n"; - - match typ with - | Trecord {fields; definition} -> - fields - |> DotCompletionUtils.filterRecordFields ~env ~prefix:fieldName ~exact - ~recordAsString: - (match definition with - | `NameOnly name -> "type " ^ name - | `TypeExpr t -> Shared.typeToString t) - | _ -> []) - | None | Some (TypeExpr _, _) -> []) + "[dot_completion] Could not extract main type completion env.\n"; + [] | Some (typ, env) -> if Debug.verbose () then Printf.printf "[dot_completion] env module path: %s, type: %s\n" diff --git a/analysis/tests/src/CompletionInferValues.res b/analysis/tests/src/CompletionInferValues.res index 1f0d1e242..48ce0e253 100644 --- a/analysis/tests/src/CompletionInferValues.res +++ b/analysis/tests/src/CompletionInferValues.res @@ -54,9 +54,6 @@ module Div = { // let _ =
{ let btn = event->JsxEvent.Mouse.button->Belt.Int.toString->Js.String2.split("/"); btn->ma }} /> // ^com -// let x: someRecord = {name: "Hello", age: 123}; x. -// ^com - type someVariant = One | Two | Three(int, string) type somePolyVariant = [#one | #two | #three(int, string)] type someNestedRecord = {someRecord: someRecord} @@ -115,10 +112,6 @@ type otherNestedRecord = { // let x: otherNestedRecord; switch x { | {optRecord:Some({name})} => name->slic } // ^com -// Follow arrays -// let x: array; switch x { | [inner] => inner.s } -// ^com - // Infer top level return // let x = 123; switch x { | 123 => () | v => v->toSt } // ^com diff --git a/analysis/tests/src/expected/CompletionInferValues.res.txt b/analysis/tests/src/expected/CompletionInferValues.res.txt index 4a04b2cee..86454171d 100644 --- a/analysis/tests/src/expected/CompletionInferValues.res.txt +++ b/analysis/tests/src/expected/CompletionInferValues.res.txt @@ -336,36 +336,9 @@ Path Js.Array2.ma "documentation": {"kind": "markdown", "value": "\nApplies the function (the second argument) to each item in the array, returning\na new array. The result array does not have to have elements of the same type\nas the input array. See\n[`Array.map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)\non MDN.\n\n## Examples\n\n```rescript\nJs.Array2.map([12, 4, 8], x => x * x) == [144, 16, 64]\nJs.Array2.map([\"animal\", \"vegetable\", \"mineral\"], Js.String.length) == [6, 9, 7]\n```\n"} }] -Complete src/CompletionInferValues.res 56:52 -posCursor:[56:52] posNoWhite:[56:51] Found expr:[56:50->56:52] -Pexp_field [56:50->56:51] _:[59:0->56:52] -Completable: Cpath Value[x]."" -Package opens Pervasives.JsxModules.place holder -Resolved opens 1 pervasives -ContextPath Value[x]."" -ContextPath Value[x] -Path x -ContextPath Type[someRecord] -Path someRecord -ContextPath Type[someRecord] -Path someRecord -[{ - "label": "name", - "kind": 5, - "tags": [], - "detail": "string", - "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype someRecord\n```"} - }, { - "label": "age", - "kind": 5, - "tags": [], - "detail": "int", - "documentation": {"kind": "markdown", "value": "```rescript\nage: int\n```\n\n```rescript\ntype someRecord\n```"} - }] - -Complete src/CompletionInferValues.res 78:78 -posCursor:[78:78] posNoWhite:[78:77] Found expr:[78:70->78:78] -Pexp_field [78:70->78:77] _:[125:0->78:78] +Complete src/CompletionInferValues.res 75:78 +posCursor:[75:78] posNoWhite:[75:77] Found expr:[75:70->75:78] +Pexp_field [75:70->75:77] _:[118:0->75:78] Completable: Cpath Value[srecord]."" Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives @@ -393,9 +366,9 @@ Path CompletionInferValues. "documentation": {"kind": "markdown", "value": "```rescript\nage: int\n```\n\n```rescript\ntype someRecord = {name: string, age: int}\n```"} }] -Complete src/CompletionInferValues.res 82:86 -posCursor:[82:86] posNoWhite:[82:85] Found expr:[82:78->82:86] -Pexp_field [82:78->82:85] _:[125:0->82:86] +Complete src/CompletionInferValues.res 79:86 +posCursor:[79:86] posNoWhite:[79:85] Found expr:[79:78->79:86] +Pexp_field [79:78->79:85] _:[118:0->79:86] Completable: Cpath Value[aliased]."" Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives @@ -417,9 +390,9 @@ Path CompletionInferValues. "documentation": {"kind": "markdown", "value": "```rescript\nsomeRecord: someRecord\n```\n\n```rescript\ntype someNestedRecord = {someRecord: someRecord}\n```"} }] -Complete src/CompletionInferValues.res 86:103 -posCursor:[86:103] posNoWhite:[86:102] Found expr:[86:92->86:103] -Pexp_field [86:92->86:102] _:[125:0->86:103] +Complete src/CompletionInferValues.res 83:103 +posCursor:[83:103] posNoWhite:[83:102] Found expr:[83:92->83:103] +Pexp_field [83:92->83:102] _:[118:0->83:103] Completable: Cpath Value[someRecord]."" Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives @@ -447,8 +420,8 @@ Path CompletionInferValues. "documentation": {"kind": "markdown", "value": "```rescript\nage: int\n```\n\n```rescript\ntype someRecord = {name: string, age: int}\n```"} }] -Complete src/CompletionInferValues.res 90:81 -posCursor:[90:81] posNoWhite:[90:80] Found expr:[90:69->90:81] +Complete src/CompletionInferValues.res 87:81 +posCursor:[87:81] posNoWhite:[87:80] Found expr:[87:69->87:81] Completable: Cpath Value[things]->slic Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives @@ -475,8 +448,8 @@ Path Js.String2.slic "documentation": {"kind": "markdown", "value": "\n`slice(str, from:n1, to_:n2)` returns the substring of `str` starting at\ncharacter `n1` up to but not including `n2`.\n- If either `n1` or `n2` is negative, then it is evaluated as `length(str - n1)` or `length(str - n2)`.\n- If `n2` is greater than the length of `str`, then it is treated as `length(str)`.\n- If `n1` is greater than `n2`, slice returns the empty string.\n\nSee [`String.slice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice) on MDN.\n\n## Examples\n\n```rescript\nJs.String2.slice(\"abcdefg\", ~from=2, ~to_=5) == \"cde\"\nJs.String2.slice(\"abcdefg\", ~from=2, ~to_=9) == \"cdefg\"\nJs.String2.slice(\"abcdefg\", ~from=-4, ~to_=-2) == \"de\"\nJs.String2.slice(\"abcdefg\", ~from=5, ~to_=1) == \"\"\n```\n"} }] -Complete src/CompletionInferValues.res 94:82 -posCursor:[94:82] posNoWhite:[94:81] Found expr:[94:70->94:82] +Complete src/CompletionInferValues.res 91:82 +posCursor:[91:82] posNoWhite:[91:81] Found expr:[91:70->91:82] Completable: Cpath Value[someInt]->toS Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives @@ -497,8 +470,8 @@ Path Belt.Int.toS "documentation": {"kind": "markdown", "value": "\nConverts a given `int` to a `string`. Uses the JavaScript `String` constructor under the hood.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.toString(1) === \"1\") /* true */\n```\n"} }] -Complete src/CompletionInferValues.res 98:109 -posCursor:[98:109] posNoWhite:[98:108] Found expr:[98:97->98:109] +Complete src/CompletionInferValues.res 95:109 +posCursor:[95:109] posNoWhite:[95:108] Found expr:[95:97->95:109] Completable: Cpath Value[someInt]->toS Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives @@ -522,9 +495,9 @@ Path Belt.Int.toS "documentation": {"kind": "markdown", "value": "\nConverts a given `int` to a `string`. Uses the JavaScript `String` constructor under the hood.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.toString(1) === \"1\") /* true */\n```\n"} }] -Complete src/CompletionInferValues.res 102:102 -posCursor:[102:102] posNoWhite:[102:101] Found expr:[102:57->102:102] -posCursor:[102:102] posNoWhite:[102:101] Found expr:[102:90->102:102] +Complete src/CompletionInferValues.res 99:102 +posCursor:[99:102] posNoWhite:[99:101] Found expr:[99:57->99:102] +posCursor:[99:102] posNoWhite:[99:101] Found expr:[99:90->99:102] Completable: Cpath Value[someInt]->toS Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives @@ -548,8 +521,8 @@ Path Belt.Int.toS "documentation": {"kind": "markdown", "value": "\nConverts a given `int` to a `string`. Uses the JavaScript `String` constructor under the hood.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.toString(1) === \"1\") /* true */\n```\n"} }] -Complete src/CompletionInferValues.res 106:88 -posCursor:[106:88] posNoWhite:[106:87] Found expr:[106:79->106:88] +Complete src/CompletionInferValues.res 103:88 +posCursor:[103:88] posNoWhite:[103:87] Found expr:[103:79->103:88] Completable: Cpath Value[str]->slic Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives @@ -576,8 +549,8 @@ Path Js.String2.slic "documentation": {"kind": "markdown", "value": "\n`slice(str, from:n1, to_:n2)` returns the substring of `str` starting at\ncharacter `n1` up to but not including `n2`.\n- If either `n1` or `n2` is negative, then it is evaluated as `length(str - n1)` or `length(str - n2)`.\n- If `n2` is greater than the length of `str`, then it is treated as `length(str)`.\n- If `n1` is greater than `n2`, slice returns the empty string.\n\nSee [`String.slice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice) on MDN.\n\n## Examples\n\n```rescript\nJs.String2.slice(\"abcdefg\", ~from=2, ~to_=5) == \"cde\"\nJs.String2.slice(\"abcdefg\", ~from=2, ~to_=9) == \"cdefg\"\nJs.String2.slice(\"abcdefg\", ~from=-4, ~to_=-2) == \"de\"\nJs.String2.slice(\"abcdefg\", ~from=5, ~to_=1) == \"\"\n```\n"} }] -Complete src/CompletionInferValues.res 110:89 -posCursor:[110:89] posNoWhite:[110:88] Found expr:[110:80->110:89] +Complete src/CompletionInferValues.res 107:89 +posCursor:[107:89] posNoWhite:[107:88] Found expr:[107:80->107:89] Completable: Cpath Value[str]->slic Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives @@ -604,8 +577,8 @@ Path Js.String2.slic "documentation": {"kind": "markdown", "value": "\n`slice(str, from:n1, to_:n2)` returns the substring of `str` starting at\ncharacter `n1` up to but not including `n2`.\n- If either `n1` or `n2` is negative, then it is evaluated as `length(str - n1)` or `length(str - n2)`.\n- If `n2` is greater than the length of `str`, then it is treated as `length(str)`.\n- If `n1` is greater than `n2`, slice returns the empty string.\n\nSee [`String.slice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice) on MDN.\n\n## Examples\n\n```rescript\nJs.String2.slice(\"abcdefg\", ~from=2, ~to_=5) == \"cde\"\nJs.String2.slice(\"abcdefg\", ~from=2, ~to_=9) == \"cdefg\"\nJs.String2.slice(\"abcdefg\", ~from=-4, ~to_=-2) == \"de\"\nJs.String2.slice(\"abcdefg\", ~from=5, ~to_=1) == \"\"\n```\n"} }] -Complete src/CompletionInferValues.res 114:80 -posCursor:[114:80] posNoWhite:[114:79] Found expr:[114:70->114:80] +Complete src/CompletionInferValues.res 111:80 +posCursor:[111:80] posNoWhite:[111:79] Found expr:[111:70->111:80] Completable: Cpath Value[name]->slic Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives @@ -632,43 +605,8 @@ Path Js.String2.slic "documentation": {"kind": "markdown", "value": "\n`slice(str, from:n1, to_:n2)` returns the substring of `str` starting at\ncharacter `n1` up to but not including `n2`.\n- If either `n1` or `n2` is negative, then it is evaluated as `length(str - n1)` or `length(str - n2)`.\n- If `n2` is greater than the length of `str`, then it is treated as `length(str)`.\n- If `n1` is greater than `n2`, slice returns the empty string.\n\nSee [`String.slice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice) on MDN.\n\n## Examples\n\n```rescript\nJs.String2.slice(\"abcdefg\", ~from=2, ~to_=5) == \"cde\"\nJs.String2.slice(\"abcdefg\", ~from=2, ~to_=9) == \"cdefg\"\nJs.String2.slice(\"abcdefg\", ~from=-4, ~to_=-2) == \"de\"\nJs.String2.slice(\"abcdefg\", ~from=5, ~to_=1) == \"\"\n```\n"} }] -Complete src/CompletionInferValues.res 118:67 -posCursor:[118:67] posNoWhite:[118:66] Found expr:[118:60->118:67] -Pexp_field [118:60->118:65] s:[118:66->118:67] -Completable: Cpath Value[inner].s -Package opens Pervasives.JsxModules.place holder -Resolved opens 1 pervasives -ContextPath Value[inner].s -ContextPath Value[inner] -Path inner -ContextPath CPatternPath(Value[x])->array -ContextPath Value[x] -Path x -ContextPath array -ContextPath Type[otherNestedRecord] -Path otherNestedRecord -ContextPath CPatternPath(Value[x])->array -ContextPath Value[x] -Path x -ContextPath array -ContextPath Type[otherNestedRecord] -Path otherNestedRecord -[{ - "label": "someRecord", - "kind": 5, - "tags": [], - "detail": "someRecord", - "documentation": {"kind": "markdown", "value": "```rescript\nsomeRecord: someRecord\n```\n\n```rescript\ntype otherNestedRecord\n```"} - }, { - "label": "someTuple", - "kind": 5, - "tags": [], - "detail": "(someVariant, int, somePolyVariant)", - "documentation": {"kind": "markdown", "value": "```rescript\nsomeTuple: (someVariant, int, somePolyVariant)\n```\n\n```rescript\ntype otherNestedRecord\n```"} - }] - -Complete src/CompletionInferValues.res 122:53 -posCursor:[122:53] posNoWhite:[122:52] Found expr:[122:46->122:53] +Complete src/CompletionInferValues.res 115:53 +posCursor:[115:53] posNoWhite:[115:52] Found expr:[115:46->115:53] Completable: Cpath Value[v]->toSt Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives @@ -687,13 +625,13 @@ Path Belt.Int.toSt "documentation": {"kind": "markdown", "value": "\nConverts a given `int` to a `string`. Uses the JavaScript `String` constructor under the hood.\n\n## Examples\n\n```rescript\nJs.log(Belt.Int.toString(1) === \"1\") /* true */\n```\n"} }] -Complete src/CompletionInferValues.res 130:26 -posCursor:[130:26] posNoWhite:[130:25] Found expr:[130:3->130:37] -Pexp_apply ...[130:3->130:23] (...[130:24->130:36]) -posCursor:[130:26] posNoWhite:[130:25] Found expr:[130:24->130:36] -posCursor:[130:26] posNoWhite:[130:25] Found expr:[130:25->130:36] -posCursor:[130:26] posNoWhite:[130:25] Found pattern:[130:25->130:27] -posCursor:[130:26] posNoWhite:[130:25] Found pattern:[130:25->130:27] +Complete src/CompletionInferValues.res 123:26 +posCursor:[123:26] posNoWhite:[123:25] Found expr:[123:3->123:37] +Pexp_apply ...[123:3->123:23] (...[123:24->123:36]) +posCursor:[123:26] posNoWhite:[123:25] Found expr:[123:24->123:36] +posCursor:[123:26] posNoWhite:[123:25] Found expr:[123:25->123:36] +posCursor:[123:26] posNoWhite:[123:25] Found pattern:[123:25->123:27] +posCursor:[123:26] posNoWhite:[123:25] Found pattern:[123:25->123:27] Completable: Cpattern CArgument CArgument Value[fnWithRecordCallback]($0)($0)->recordBody Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives @@ -715,13 +653,13 @@ Path fnWithRecordCallback "documentation": {"kind": "markdown", "value": "```rescript\nage: int\n```\n\n```rescript\ntype someRecord = {name: string, age: int}\n```"} }] -Complete src/CompletionInferValues.res 137:30 -posCursor:[137:30] posNoWhite:[137:29] Found expr:[137:3->137:33] -Pexp_apply ...[137:3->137:6] (~cb137:8->137:10=...[137:11->137:32]) -posCursor:[137:30] posNoWhite:[137:29] Found expr:[137:11->137:32] -posCursor:[137:30] posNoWhite:[137:29] Found expr:[137:12->137:32] -posCursor:[137:30] posNoWhite:[137:29] Found expr:[137:24->0:-1] -posCursor:[137:30] posNoWhite:[137:29] Found expr:[137:24->0:-1] +Complete src/CompletionInferValues.res 130:30 +posCursor:[130:30] posNoWhite:[130:29] Found expr:[130:3->130:33] +Pexp_apply ...[130:3->130:6] (~cb130:8->130:10=...[130:11->130:32]) +posCursor:[130:30] posNoWhite:[130:29] Found expr:[130:11->130:32] +posCursor:[130:30] posNoWhite:[130:29] Found expr:[130:12->130:32] +posCursor:[130:30] posNoWhite:[130:29] Found expr:[130:24->0:-1] +posCursor:[130:30] posNoWhite:[130:29] Found expr:[130:24->0:-1] Completable: Cpath Value[root]-> Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives @@ -749,13 +687,13 @@ Path ReactDOM.Client.Root. "documentation": null }] -Complete src/CompletionInferValues.res 146:30 -posCursor:[146:30] posNoWhite:[146:29] Found expr:[146:3->146:33] -Pexp_apply ...[146:3->146:6] (~cb146:8->146:10=...[146:11->146:32]) -posCursor:[146:30] posNoWhite:[146:29] Found expr:[146:11->146:32] -posCursor:[146:30] posNoWhite:[146:29] Found expr:[146:12->146:32] -posCursor:[146:30] posNoWhite:[146:29] Found expr:[146:24->0:-1] -posCursor:[146:30] posNoWhite:[146:29] Found expr:[146:24->0:-1] +Complete src/CompletionInferValues.res 139:30 +posCursor:[139:30] posNoWhite:[139:29] Found expr:[139:3->139:33] +Pexp_apply ...[139:3->139:6] (~cb139:8->139:10=...[139:11->139:32]) +posCursor:[139:30] posNoWhite:[139:29] Found expr:[139:11->139:32] +posCursor:[139:30] posNoWhite:[139:29] Found expr:[139:12->139:32] +posCursor:[139:30] posNoWhite:[139:29] Found expr:[139:24->0:-1] +posCursor:[139:30] posNoWhite:[139:29] Found expr:[139:24->0:-1] Completable: Cpath Value[root]-> Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives @@ -783,7 +721,7 @@ Path CompletionSupport.Test. "documentation": null }] -Complete src/CompletionInferValues.res 150:47 +Complete src/CompletionInferValues.res 143:47 XXX Not found! Completable: Cpattern Value[Belt, Int, toString](Nolabel) Package opens Pervasives.JsxModules.place holder @@ -802,7 +740,7 @@ Path Belt.Int.toString "insertTextFormat": 2 }] -Complete src/CompletionInferValues.res 154:70 +Complete src/CompletionInferValues.res 147:70 XXX Not found! Completable: Cpattern Value[Js, String2, split](Nolabel, Nolabel) Package opens Pervasives.JsxModules.place holder @@ -821,13 +759,13 @@ Path Js.String2.split "insertTextFormat": 2 }] -Complete src/CompletionInferValues.res 158:105 -posCursor:[158:105] posNoWhite:[158:104] Found expr:[158:18->158:110] -Pexp_apply ...[158:18->158:49] (~prepare158:51->158:58=...[158:59->158:72], ~render158:74->158:80=...[158:81->158:106], ...[158:107->158:109]) -posCursor:[158:105] posNoWhite:[158:104] Found expr:[158:81->158:106] -posCursor:[158:105] posNoWhite:[158:104] Found expr:[158:82->158:106] -posCursor:[158:105] posNoWhite:[158:104] Found expr:[158:97->158:105] -Pexp_field [158:97->158:104] _:[158:105->158:105] +Complete src/CompletionInferValues.res 151:105 +posCursor:[151:105] posNoWhite:[151:104] Found expr:[151:18->151:110] +Pexp_apply ...[151:18->151:49] (~prepare151:51->151:58=...[151:59->151:72], ~render151:74->151:80=...[151:81->151:106], ...[151:107->151:109]) +posCursor:[151:105] posNoWhite:[151:104] Found expr:[151:81->151:106] +posCursor:[151:105] posNoWhite:[151:104] Found expr:[151:82->151:106] +posCursor:[151:105] posNoWhite:[151:104] Found expr:[151:97->151:105] +Pexp_field [151:97->151:104] _:[151:105->151:105] Completable: Cpath Value[support]."" Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives @@ -849,13 +787,13 @@ Path CompletionSupport.Nested. "documentation": {"kind": "markdown", "value": "```rescript\nroot: ReactDOM.Client.Root.t\n```\n\n```rescript\ntype config = {root: ReactDOM.Client.Root.t}\n```"} }] -Complete src/CompletionInferValues.res 162:110 -posCursor:[162:110] posNoWhite:[162:109] Found expr:[162:18->162:115] -Pexp_apply ...[162:18->162:49] (~prepare162:51->162:58=...[162:59->162:72], ~render162:74->162:80=...[162:81->162:111], ...[162:112->162:114]) -posCursor:[162:110] posNoWhite:[162:109] Found expr:[162:81->162:111] -posCursor:[162:110] posNoWhite:[162:109] Found expr:[162:82->162:111] -posCursor:[162:110] posNoWhite:[162:109] Found expr:[162:104->0:-1] -posCursor:[162:110] posNoWhite:[162:109] Found expr:[162:104->0:-1] +Complete src/CompletionInferValues.res 155:110 +posCursor:[155:110] posNoWhite:[155:109] Found expr:[155:18->155:115] +Pexp_apply ...[155:18->155:49] (~prepare155:51->155:58=...[155:59->155:72], ~render155:74->155:80=...[155:81->155:111], ...[155:112->155:114]) +posCursor:[155:110] posNoWhite:[155:109] Found expr:[155:81->155:111] +posCursor:[155:110] posNoWhite:[155:109] Found expr:[155:82->155:111] +posCursor:[155:110] posNoWhite:[155:109] Found expr:[155:104->0:-1] +posCursor:[155:110] posNoWhite:[155:109] Found expr:[155:104->0:-1] Completable: Cpath Value[root]-> Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives @@ -883,10 +821,10 @@ Path ReactDOM.Client.Root. "documentation": null }] -Hover src/CompletionInferValues.res 167:27 +Hover src/CompletionInferValues.res 160:27 Nothing at that position. Now trying to use completion. -posCursor:[167:27] posNoWhite:[167:26] Found expr:[167:25->167:28] -Pexp_ident res:[167:25->167:28] +posCursor:[160:27] posNoWhite:[160:26] Found expr:[160:25->160:28] +Pexp_ident res:[160:25->160:28] Completable: Cpath Value[res] Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives From c715071e7f7ca7df61b13ac0859ef008044656da Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Wed, 25 Dec 2024 19:06:59 +0100 Subject: [PATCH 21/34] refactor to share pipe completion code logic --- analysis/src/CompletionBackEnd.ml | 271 +++++++----------- .../tests/src/expected/Completion.res.txt | 4 + 2 files changed, 105 insertions(+), 170 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index d52c19ff4..c1e1c7da6 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -664,6 +664,97 @@ let completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens ~pos in completions +let getPipeCompletions ~env ~full ~identifierLoc ~debug ~envCompletionIsMadeFrom + ~opens ~pos ~scope ~prefix ~rawOpens ~inJsx + ?(mainCompletionsAreSynthetic = false) ?(formatCompletionsWithPipes = false) + typ = + let env, typ = + typ + |> TypeUtils.resolveTypeForPipeCompletion2 ~env ~package:full.package ~full + ~lhsLoc:identifierLoc + in + let mainTypeId = TypeUtils.findRootTypeId ~full ~env typ in + let typePath = TypeUtils.pathFromTypeExpr typ in + match mainTypeId with + | None -> + if Debug.verbose () then + Printf.printf + "[pipe_completion] Could not find mainTypeId. Aborting pipe completions.\n"; + [] + | Some mainTypeId -> + if Debug.verbose () then + Printf.printf "[pipe_completion] mainTypeId: %s\n" mainTypeId; + let pipeCompletions = + (* We now need a completion path from where to look up the module for our dot completion type. + This is from where we pull all of the functions we want to complete for the pipe. + + A completion path here could be one of two things: + 1. A module path to the main module for the type we've found + 2. A module path to a builtin module, like `Int` for `int`, or `Array` for `array` + + The below code will deliberately _not_ dig into type aliases for the main type when we're looking + for what _module_ to complete from. This is because you should be able to control where completions + come from even if your type is an alias. + *) + let completeAsBuiltin = + match typePath with + | Some t -> + TypeUtils.completionPathFromMaybeBuiltin t ~package:full.package + | None -> None + in + (* TODO(in-compiler) No need to have this configurable anymore, just use the new integrated modules from Core..*) + let completionPath = + match (completeAsBuiltin, typePath) with + | Some completionPathForBuiltin, _ -> Some completionPathForBuiltin + | _, Some p -> ( + (* If this isn't a builtin, but we have a path, we try to resolve the + module path relative to the env we're completing from. This ensures that + what we get here is a module path we can find completions for regardless of + of the current scope for the position we're at.*) + match + TypeUtils.getModulePathRelativeToEnv ~debug + ~env:envCompletionIsMadeFrom ~envFromItem:env (Utils.expandPath p) + with + | None -> Some [env.file.moduleName] + | Some p -> Some p) + | _ -> None + in + match completionPath with + | None -> [] + | Some completionPath -> + completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens + ~pos ~scope ~debug ~prefix ~env ~rawOpens ~full completionPath + |> TypeUtils.filterPipeableFunctions ~env ~full + ~synthetic:mainCompletionsAreSynthetic ~targetTypeId:mainTypeId + in + (* Extra completions can be drawn from the @editor.completeFrom attribute. Here we + find and add those completions as well. *) + let extraCompletions = + TypeUtils.getExtraModuleTosCompleteFromForType ~env ~full typ + |> List.map (fun completionPath -> + completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom + ~opens ~pos ~scope ~debug ~prefix ~env ~rawOpens ~full + completionPath) + |> List.flatten + |> TypeUtils.filterPipeableFunctions ~synthetic:true ~env ~full + ~targetTypeId:mainTypeId + in + (* Add JSX completion items if we're in a JSX context. *) + let jsxCompletions = + if inJsx then + PipeCompletionUtils.addJsxCompletionItems ~env ~mainTypeId ~prefix ~full + ~rawOpens typ + else [] + in + let allCompletions = jsxCompletions @ pipeCompletions @ extraCompletions in + if formatCompletionsWithPipes then + allCompletions + |> List.filter_map (fun (c : Completion.t) -> + c + |> TypeUtils.transformCompletionToPipeCompletion ~env + ~replaceRange:identifierLoc ~synthetic:c.synthetic) + else allCompletions + let rec digToRecordFieldsForCompletion ~debug ~package ~opens ~full ~pos ~env ~scope path = match @@ -1035,99 +1126,16 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact "[dot_completion] Could not extract main type completion env.\n"; [] | Some (typ, env) -> - if Debug.verbose () then - Printf.printf "[dot_completion] env module path: %s, type: %s\n" - (TypeUtils.modulePathFromEnv env |> String.concat ".") - (Shared.typeToString typ); - (* Let's first find the actual field completions. *) let fieldCompletions = DotCompletionUtils.fieldCompletionsForDotCompletion typ ~env ~package ~prefix:fieldName ~fieldNameLoc ~exact in - (* Now, let's get the type id for the type of parent of the dot completion. - The type id is a string that uniquely identifies a type, and that we can use - to filter completions for pipe completion etc.*) - let mainTypeId = TypeUtils.findRootTypeId ~full ~env typ in - let allExtraCompletions = - match mainTypeId with - | None -> - if Debug.verbose () then - Printf.printf - "[dot_completion] Could not find mainTypeId. Aborting extra pipe \ - completions.\n"; - [] - | Some mainTypeId -> - if Debug.verbose () then - Printf.printf "[dot_completion] mainTypeId: %s\n" mainTypeId; - - let pipeCompletions = - (* We now need a completion path from where to look up the module for our dot completion type. - This is from where we pull all of the functions we want to complete for the pipe. *) - - (* TODO(in-compiler) No need to have this configurable anymore, just use the new integrated modules from Core..*) - let completionPath = - (* First try completing it as a builtin *) - match mainTypeId with - | "array" -> Some package.builtInCompletionModules.arrayModulePath - | "option" -> - Some package.builtInCompletionModules.optionModulePath - | "string" -> - Some package.builtInCompletionModules.stringModulePath - | "int" -> Some package.builtInCompletionModules.intModulePath - | "float" -> Some package.builtInCompletionModules.floatModulePath - | "promise" -> - Some package.builtInCompletionModules.promiseModulePath - | "list" -> Some package.builtInCompletionModules.listModulePath - | "result" -> - Some package.builtInCompletionModules.resultModulePath - | "Js_re.t" -> - Some package.builtInCompletionModules.regexpModulePath - | "char" -> Some ["Char"] - | _ -> - (* Currently, when completing regular types, we stop at the first module we find that owns the type. - This means that completions will be made not from the root type necessarily, but from the module with - a type alias if it's a type alias. *) - let completionPathForType = - match TypeUtils.pathFromTypeExpr typ with - | None -> None - | Some tPath -> ( - match - TypeUtils.getModulePathRelativeToEnv ~debug - ~env:envCompletionIsMadeFrom ~envFromItem:env - (Utils.expandPath tPath) - with - | None -> Some [env.file.moduleName] - | Some p -> Some p) - in - if Debug.verbose () then - Printf.printf - "[dot_completion] Looked up completion path: %s\n" - (match completionPathForType with - | None -> "-" - | Some p -> p |> String.concat "."); - completionPathForType - in - match completionPath with - | None -> [] - | Some completionPath -> - completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom - ~opens ~pos ~scope ~debug ~prefix:fieldName ~env ~rawOpens ~full - completionPath - in - (* TODO: Explain *) - let extraCompletions = - TypeUtils.getExtraModuleTosCompleteFromForType ~env ~full typ - |> List.map (fun completionPath -> - completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom - ~opens ~pos ~scope ~debug ~prefix:fieldName ~env ~rawOpens - ~full completionPath) - |> List.flatten - in - pipeCompletions @ extraCompletions - |> TypeUtils.filterPipeableFunctions ~synthetic:true ~env ~full - ~replaceRange:fieldNameLoc ~targetTypeId:mainTypeId + let pipeCompletions = + getPipeCompletions ~env ~full ~identifierLoc:fieldNameLoc + ~envCompletionIsMadeFrom ~debug ~opens ~rawOpens ~scope ~pos + ~inJsx:false ~prefix:fieldName ~formatCompletionsWithPipes:true typ in - fieldCompletions @ allExtraCompletions) + fieldCompletions @ pipeCompletions) | CPObj (cp, label) -> ( (* TODO: Also needs to support ExtractedType *) if Debug.verbose () then print_endline "[ctx_path]--> CPObj"; @@ -1148,7 +1156,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact else None) | None -> []) | None -> []) - | CPPipe {contextPath = cp; id = funNamePrefix; lhsLoc; inJsx} -> ( + | CPPipe {contextPath = cp; id = prefix; lhsLoc; inJsx} -> ( if Debug.verbose () then print_endline "[ctx_path]--> CPPipe"; match cp @@ -1160,87 +1168,10 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact if Debug.verbose () then print_endline "[CPPipe]--> Could not resolve type env"; [] - | Some (typ, env) -> ( - let env, typ = - typ - |> TypeUtils.resolveTypeForPipeCompletion2 ~env ~package ~full ~lhsLoc - in - let mainTypeId = TypeUtils.findRootTypeId ~full ~env typ in - let typePath = TypeUtils.pathFromTypeExpr typ in - match mainTypeId with - | None -> - if Debug.verbose () then - Printf.printf - "[pipe_completion] Could not find mainTypeId. Aborting pipe \ - completions.\n"; - [] - | Some mainTypeId -> - if Debug.verbose () then - Printf.printf "[pipe_completion] mainTypeId: %s\n" mainTypeId; - let pipeCompletions = - (* We now need a completion path from where to look up the module for our dot completion type. - This is from where we pull all of the functions we want to complete for the pipe. - - A completion path here could be one of two things: - 1. A module path to the main module for the type we've found - 2. A module path to a builtin module, like `Int` for `int`, or `Array` for `array` - - The below code will deliberately _not_ dig into type aliases for the main type when we're looking - for what _module_ to complete from. This is because you should be able to control where completions - come from even if your type is an alias. - *) - let completeAsBuiltin = - match typePath with - | Some t -> TypeUtils.completionPathFromMaybeBuiltin t ~package - | None -> None - in - (* TODO(in-compiler) No need to have this configurable anymore, just use the new integrated modules from Core..*) - let completionPath = - match (completeAsBuiltin, typePath) with - | Some completionPathForBuiltin, _ -> Some completionPathForBuiltin - | _, Some p -> ( - (* If this isn't a builtin, but we have a path, we try to resolve the - module path relative to the env we're completing from. This ensures that - what we get here is a module path we can find completions for regardless of - of the current scope for the position we're at.*) - match - TypeUtils.getModulePathRelativeToEnv ~debug - ~env:envCompletionIsMadeFrom ~envFromItem:env - (Utils.expandPath p) - with - | None -> Some [env.file.moduleName] - | Some p -> Some p) - | _ -> None - in - match completionPath with - | None -> [] - | Some completionPath -> - completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens - ~pos ~scope ~debug ~prefix:funNamePrefix ~env ~rawOpens ~full - completionPath - |> TypeUtils.filterPipeableFunctions ~env ~full ~synthetic:false - ~targetTypeId:mainTypeId - in - (* Extra completions can be drawn from the @editor.completeFrom attribute. Here we - find and add those completions as well. *) - let extraCompletions = - TypeUtils.getExtraModuleTosCompleteFromForType ~env ~full typ - |> List.map (fun completionPath -> - completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom - ~opens ~pos ~scope ~debug ~prefix:funNamePrefix ~env - ~rawOpens ~full completionPath) - |> List.flatten - |> TypeUtils.filterPipeableFunctions ~synthetic:true ~env ~full - ~targetTypeId:mainTypeId - in - (* Add JSX completion items if we're in a JSX context. *) - let jsxCompletions = - if inJsx then - PipeCompletionUtils.addJsxCompletionItems ~env ~mainTypeId - ~prefix:funNamePrefix ~full ~rawOpens typ - else [] - in - jsxCompletions @ pipeCompletions @ extraCompletions)) + | Some (typ, env) -> + getPipeCompletions ~env ~full ~identifierLoc:lhsLoc + ~envCompletionIsMadeFrom ~debug ~opens ~rawOpens ~scope ~pos ~inJsx + ~prefix typ) | CTuple ctxPaths -> if Debug.verbose () then print_endline "[ctx_path]--> CTuple"; (* Turn a list of context paths into a list of type expressions. *) diff --git a/analysis/tests/src/expected/Completion.res.txt b/analysis/tests/src/expected/Completion.res.txt index 47774375b..b1ef0f6d6 100644 --- a/analysis/tests/src/expected/Completion.res.txt +++ b/analysis/tests/src/expected/Completion.res.txt @@ -1088,6 +1088,8 @@ ContextPath Value[FAO, forAutoObject]["forAutoLabel"]."" ContextPath Value[FAO, forAutoObject]["forAutoLabel"] ContextPath Value[FAO, forAutoObject] Path FAO.forAutoObject +CPPipe pathFromEnv:FAR found:true +Path FAR. [{ "label": "forAuto", "kind": 5, @@ -1113,6 +1115,8 @@ ContextPath Value[FAO, forAutoObject]["forAutoLabel"].forAuto ContextPath Value[FAO, forAutoObject]["forAutoLabel"] ContextPath Value[FAO, forAutoObject] Path FAO.forAutoObject +CPPipe pathFromEnv:FAR found:true +Path FAR.forAuto CPPipe pathFromEnv:ForAuto found:false Path ForAuto. [{ From dfc3eb00eabc3b5ebbecd6bf72e76082d7bb98bd Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Wed, 25 Dec 2024 19:12:52 +0100 Subject: [PATCH 22/34] cleanup --- analysis/src/CompletionBackEnd.ml | 4 +- analysis/src/TypeUtils.ml | 136 +----------------------------- 2 files changed, 4 insertions(+), 136 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index c1e1c7da6..1c8913174 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -670,7 +670,7 @@ let getPipeCompletions ~env ~full ~identifierLoc ~debug ~envCompletionIsMadeFrom typ = let env, typ = typ - |> TypeUtils.resolveTypeForPipeCompletion2 ~env ~package:full.package ~full + |> TypeUtils.resolveTypeForPipeCompletion ~env ~package:full.package ~full ~lhsLoc:identifierLoc in let mainTypeId = TypeUtils.findRootTypeId ~full ~env typ in @@ -730,7 +730,7 @@ let getPipeCompletions ~env ~full ~identifierLoc ~debug ~envCompletionIsMadeFrom (* Extra completions can be drawn from the @editor.completeFrom attribute. Here we find and add those completions as well. *) let extraCompletions = - TypeUtils.getExtraModuleTosCompleteFromForType ~env ~full typ + TypeUtils.getExtraModulesToCompleteFromForType ~env ~full typ |> List.map (fun completionPath -> completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens ~pos ~scope ~debug ~prefix ~env ~rawOpens ~full diff --git a/analysis/src/TypeUtils.ml b/analysis/src/TypeUtils.ml index b52cd334c..f18616ae9 100644 --- a/analysis/src/TypeUtils.ml +++ b/analysis/src/TypeUtils.ml @@ -2,13 +2,6 @@ open SharedTypes let modulePathFromEnv env = env.QueryEnv.file.moduleName :: List.rev env.pathRev -let completionPathFromEnvAndPath env ~path = - modulePathFromEnv env @ List.rev (Utils.expandPath path) - |> List.rev |> List.tl |> List.rev - -let getFullTypeId ~env (path : Path.t) = - modulePathFromEnv env @ List.rev (Utils.expandPath path) |> String.concat "." - let fullTypeIdFromDecl ~env ~name ~modulePath = env.QueryEnv.file.moduleName :: ModulePath.toPath modulePath name |> String.concat "." @@ -506,45 +499,6 @@ let findReturnTypeOfFunctionAtLoc loc ~(env : QueryEnv.t) ~full ~debug = | _ -> None) | _ -> None -type builtinType = - | Array - | Option - | String - | Int - | Float - | Promise - | List - | Result - | Lazy - | Char - | RegExp - -type pipeCompletionType = - | Builtin of builtinType * Types.type_expr - | TypExpr of Types.type_expr - -let getBuiltinFromTypePath path = - match path with - | Path.Pident _ -> ( - match Path.name path with - | "array" -> Some Array - | "option" -> Some Option - | "string" -> Some String - | "int" -> Some Int - | "float" -> Some Float - | "promise" -> Some Promise - | "list" -> Some List - | "result" -> Some Result - | "lazy_t" -> Some Lazy - | "char" -> Some Char - | _ -> None) - | Pdot (Pdot (Pident m, "Re", _), "t", _) when Ident.name m = "Js" -> - Some RegExp - | Pdot (Pident id, "result", _) - when Ident.name id = "Pervasives" || Ident.name id = "PervasivesU" -> - Some Result - | _ -> None - let rec digToRelevantTemplateNameType ~env ~package ?(suffix = "") (t : Types.type_expr) = match t.desc with @@ -563,47 +517,6 @@ let rec digToRelevantTemplateNameType ~env ~package ?(suffix = "") let rec resolveTypeForPipeCompletion ~env ~package ~lhsLoc ~full (t : Types.type_expr) = - let builtin = - match t |> pathFromTypeExpr with - | Some path -> path |> getBuiltinFromTypePath - | None -> None - in - match builtin with - | Some builtin -> (env, Builtin (builtin, t)) - | None -> ( - (* 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 typFromLoc = - match t with - | {Types.desc = Tvar _} -> ( - match findReturnTypeOfFunctionAtLoc lhsLoc ~env ~full ~debug:false with - | None -> None - | Some typFromLoc -> Some typFromLoc) - | _ -> None - in - match typFromLoc with - | Some typFromLoc -> - typFromLoc |> resolveTypeForPipeCompletion ~lhsLoc ~env ~package ~full - | None -> - let rec digToRelevantType ~env ~package (t : Types.type_expr) = - match t.desc with - | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> - digToRelevantType ~env ~package t1 - (* Don't descend into types named "t". Type t is a convention in the ReScript ecosystem. *) - | Tconstr (path, _, _) when path |> Path.last = "t" -> (env, TypExpr t) - | Tconstr (path, _, _) -> ( - match References.digConstructor ~env ~package path with - | Some (env, {item = {decl = {type_manifest = Some typ}}}) -> - digToRelevantType ~env ~package typ - | _ -> (env, TypExpr t)) - | _ -> (env, TypExpr t) - in - digToRelevantType ~env ~package t) - -let rec resolveTypeForPipeCompletion2 ~env ~package ~lhsLoc ~full - (t : Types.type_expr) = (* 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 @@ -616,7 +529,7 @@ let rec resolveTypeForPipeCompletion2 ~env ~package ~lhsLoc ~full in match typFromLoc with | Some typFromLoc -> - typFromLoc |> resolveTypeForPipeCompletion2 ~lhsLoc ~env ~package ~full + typFromLoc |> resolveTypeForPipeCompletion ~lhsLoc ~env ~package ~full | None -> let rec digToRelevantType ~env ~package (t : Types.type_expr) = match t.desc with @@ -1190,21 +1103,9 @@ let pathToElementProps package = | None -> ["ReactDOM"; "domProps"] | Some g -> (g |> String.split_on_char '.') @ ["Elements"; "props"] -(** Extracts module to draw extra completions from for the type, if it has been annotated with @editor.completeFrom. *) -let rec getExtraModuleToCompleteFromForType ~env ~full (t : Types.type_expr) = - match t |> Shared.digConstructor with - | Some path -> ( - match References.digConstructor ~env ~package:full.package path with - | None -> None - | Some (env, {item = {decl = {type_manifest = Some t}}}) -> - getExtraModuleToCompleteFromForType ~env ~full t - | Some (_, {item = {attributes}}) -> - ProcessAttributes.findEditorCompleteFromAttribute attributes) - | None -> None - module StringSet = Set.Make (String) -let rec getExtraModuleTosCompleteFromForType ~env ~full (t : Types.type_expr) = +let getExtraModulesToCompleteFromForType ~env ~full (t : Types.type_expr) = let foundModulePaths = ref StringSet.empty in let addToModulePaths attributes = ProcessAttributes.findEditorCompleteFromAttribute2 attributes @@ -1227,39 +1128,6 @@ let rec getExtraModuleTosCompleteFromForType ~env ~full (t : Types.type_expr) = !foundModulePaths |> StringSet.elements |> List.map (fun l -> String.split_on_char '.' l) -(** Checks whether the provided type represents a function that takes the provided path - as the first argument (meaning it's pipeable). *) -let rec fnTakesTypeAsFirstArg ~env ~full ~targetTypeId t = - (*if Debug.verbose () then - Printf.printf "[fnTakesTypeAsFirstArg] start env: %s\n" - env.QueryEnv.file.moduleName;*) - match t.Types.desc with - | Tlink t1 - | Tsubst t1 - | Tpoly (t1, []) - | Tconstr (Pident {name = "function$"}, [t1; _], _) -> - fnTakesTypeAsFirstArg ~env ~full ~targetTypeId t1 - | Tarrow _ -> ( - match extractFunctionTypeWithEnv ~env ~package:full.package t with - | (Nolabel, t) :: _, _, env -> ( - (*if Debug.verbose () then - Printf.printf "[fnTakesTypeAsFirstArg] extracted env: %s\n" - env.QueryEnv.file.moduleName;*) - let mainTypeId = - match pathFromTypeExpr t with - | None -> None - | Some tPath -> Some (getFullTypeId ~env tPath) - in - (*if Debug.verbose () then - Printf.printf "[filterPipeableFunctions]--> targetTypeId:%s = %s\n" - targetTypeId - (Option.value ~default:"None" mainTypeId);*) - match mainTypeId with - | None -> false - | Some mainTypeId -> mainTypeId = targetTypeId) - | _ -> false) - | _ -> false - let getFirstFnUnlabelledArgType ~env ~full t = let labels, _, env = extractFunctionTypeWithEnv ~env ~package:full.package t From 4e4d03c224f0f16850a3e1209a6db8c465f42897 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Wed, 25 Dec 2024 22:40:15 +0100 Subject: [PATCH 23/34] cleanup --- analysis/src/CompletionBackEnd.ml | 12 ------------ analysis/src/DotCompletionUtils.ml | 2 +- analysis/src/ProcessAttributes.ml | 22 +++------------------- analysis/src/TypeUtils.ml | 6 +++--- 4 files changed, 7 insertions(+), 35 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 1c8913174..3350ef183 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -702,7 +702,6 @@ let getPipeCompletions ~env ~full ~identifierLoc ~debug ~envCompletionIsMadeFrom TypeUtils.completionPathFromMaybeBuiltin t ~package:full.package | None -> None in - (* TODO(in-compiler) No need to have this configurable anymore, just use the new integrated modules from Core..*) let completionPath = match (completeAsBuiltin, typePath) with | Some completionPathForBuiltin, _ -> Some completionPathForBuiltin @@ -1099,22 +1098,11 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact ~completionContext:Field ~env ~scope | CPField {contextPath = cp; fieldName; fieldNameLoc} -> ( if Debug.verbose () then print_endline "[dot_completion]--> Triggered"; - (* This finds completions for the context path of the field completion. - From this, we assume that the first found completion represents the type - of the field parent. So in `someRecordValue.f`, we find the type of `someRecordValue`. - - This procedure is how finding types works in general in the tooling as of - now. *) let completionsFromCtxPath = cp |> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact:true ~scope in - (* This extracts the type expr and env from the first found completion, if it's - a type expr. For dot completion, a type expr is the only relevant type we care - about here, since that will point to a type, either record or other type, of which - we can look up the module and any annotations for. - *) let mainTypeCompletionEnv = completionsFromCtxPath |> completionsGetTypeEnv2 ~debug ~full ~opens ~rawOpens ~pos diff --git a/analysis/src/DotCompletionUtils.ml b/analysis/src/DotCompletionUtils.ml index 481772ea9..00e518e63 100644 --- a/analysis/src/DotCompletionUtils.ml +++ b/analysis/src/DotCompletionUtils.ml @@ -27,7 +27,7 @@ let fieldCompletionsForDotCompletion typ ~env ~package ~prefix ~fieldNameLoc else None) | None -> ( match typ |> TypeUtils.extractRecordType ~env ~package with - | Some (env, fields, typDecl, _path, _attributes) -> + | Some (env, fields, typDecl) -> fields |> filterRecordFields ~env ~prefix ~exact ~recordAsString: diff --git a/analysis/src/ProcessAttributes.ml b/analysis/src/ProcessAttributes.ml index c25853fce..10e43d511 100644 --- a/analysis/src/ProcessAttributes.ml +++ b/analysis/src/ProcessAttributes.ml @@ -49,23 +49,7 @@ let newDeclared ~item ~extent ~name ~stamp ~modulePath isExported attributes = item; } -let rec findEditorCompleteFromAttribute attributes = - let open Parsetree in - match attributes with - | [] -> None - | ( {Asttypes.txt = "editor.completeFrom"}, - PStr - [ - { - pstr_desc = - Pstr_eval ({pexp_desc = Pexp_construct ({txt = path}, None)}, _); - }; - ] ) - :: _ -> - Some (Utils.flattenLongIdent path) - | _ :: rest -> findEditorCompleteFromAttribute rest - -let rec findEditorCompleteFromAttribute2 ?(modulePaths = []) attributes = +let rec findEditorCompleteFromAttribute ?(modulePaths = []) attributes = let open Parsetree in match attributes with | [] -> modulePaths @@ -85,7 +69,7 @@ let rec findEditorCompleteFromAttribute2 ?(modulePaths = []) attributes = Some (Utils.flattenLongIdent path) | _ -> None) in - findEditorCompleteFromAttribute2 + findEditorCompleteFromAttribute ~modulePaths:(modulePathsFromArray @ modulePaths) rest - | _ :: rest -> findEditorCompleteFromAttribute2 ~modulePaths rest + | _ :: rest -> findEditorCompleteFromAttribute ~modulePaths rest diff --git a/analysis/src/TypeUtils.ml b/analysis/src/TypeUtils.ml index f18616ae9..62636097f 100644 --- a/analysis/src/TypeUtils.ml +++ b/analysis/src/TypeUtils.ml @@ -209,7 +209,7 @@ let rec extractRecordType ~env ~package (t : Types.type_expr) = | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> extractRecordType ~env ~package t1 | Tconstr (path, typeArgs, _) -> ( match References.digConstructor ~env ~package path with - | Some (env, ({item = {kind = Record fields; attributes}} as typ)) -> + | Some (env, ({item = {kind = Record fields}} as typ)) -> let typeParams = typ.item.decl.type_params in let fields = fields @@ -219,7 +219,7 @@ let rec extractRecordType ~env ~package (t : Types.type_expr) = in {field with typ = fieldTyp}) in - Some (env, fields, typ, path, attributes) + Some (env, fields, typ) | Some ( env, {item = {decl = {type_manifest = Some t1; type_params = typeParams}}} @@ -1108,7 +1108,7 @@ module StringSet = Set.Make (String) let getExtraModulesToCompleteFromForType ~env ~full (t : Types.type_expr) = let foundModulePaths = ref StringSet.empty in let addToModulePaths attributes = - ProcessAttributes.findEditorCompleteFromAttribute2 attributes + ProcessAttributes.findEditorCompleteFromAttribute attributes |> List.iter (fun e -> foundModulePaths := StringSet.add (e |> String.concat ".") !foundModulePaths) From 0fa56aa4d2fa4fc5b8da997930a6062cbb88a859 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Sat, 28 Dec 2024 22:09:32 +0100 Subject: [PATCH 24/34] fix debug command --- client/src/commands/dump_debug.ts | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/client/src/commands/dump_debug.ts b/client/src/commands/dump_debug.ts index 168d87a22..9b41c4a9b 100644 --- a/client/src/commands/dump_debug.ts +++ b/client/src/commands/dump_debug.ts @@ -200,13 +200,7 @@ export const dumpDebug = async ( switch (callType.command) { case "completion": { - opts.push( - filePath, - line.toString(), - character.toString(), - currentFile, - "true" - ); + opts.push(filePath, line.toString(), character.toString(), currentFile); break; } case "definition": { @@ -222,13 +216,7 @@ export const dumpDebug = async ( break; } case "hover": { - opts.push( - filePath, - line.toString(), - character.toString(), - currentFile, - "true" - ); + opts.push(filePath, line.toString(), character.toString(), currentFile); break; } case "signatureHelp": { From b6f7cccb3dc8bfd15e7219b1d7b262e9a0f24b8f Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Sat, 28 Dec 2024 22:09:44 +0100 Subject: [PATCH 25/34] up rescript in test project --- analysis/tests/package-lock.json | 14 +++++++------- analysis/tests/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/analysis/tests/package-lock.json b/analysis/tests/package-lock.json index aa28542ff..cd47acceb 100644 --- a/analysis/tests/package-lock.json +++ b/analysis/tests/package-lock.json @@ -5,7 +5,7 @@ "packages": { "": { "dependencies": { - "rescript": "^11.1.0" + "rescript": "11.1.4" }, "devDependencies": { "@rescript/react": "0.12.0" @@ -69,9 +69,9 @@ } }, "node_modules/rescript": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/rescript/-/rescript-11.1.0.tgz", - "integrity": "sha512-9la2Dv+ACylQ77I8s4spPu1JnLZXbH5WgxcLHLLUBWgFFSiv0wXqgzWztrBIZqwFgVX5BYcwldUqUVcEzdCyHg==", + "version": "11.1.4", + "resolved": "https://registry.npmjs.org/rescript/-/rescript-11.1.4.tgz", + "integrity": "sha512-0bGU0bocihjSC6MsE3TMjHjY0EUpchyrREquLS8VsZ3ohSMD+VHUEwimEfB3kpBI1vYkw3UFZ3WD8R28guz/Vw==", "hasInstallScript": true, "bin": { "bsc": "bsc", @@ -140,9 +140,9 @@ } }, "rescript": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/rescript/-/rescript-11.1.0.tgz", - "integrity": "sha512-9la2Dv+ACylQ77I8s4spPu1JnLZXbH5WgxcLHLLUBWgFFSiv0wXqgzWztrBIZqwFgVX5BYcwldUqUVcEzdCyHg==" + "version": "11.1.4", + "resolved": "https://registry.npmjs.org/rescript/-/rescript-11.1.4.tgz", + "integrity": "sha512-0bGU0bocihjSC6MsE3TMjHjY0EUpchyrREquLS8VsZ3ohSMD+VHUEwimEfB3kpBI1vYkw3UFZ3WD8R28guz/Vw==" }, "scheduler": { "version": "0.23.0", diff --git a/analysis/tests/package.json b/analysis/tests/package.json index c34977ca6..3d9b9a107 100644 --- a/analysis/tests/package.json +++ b/analysis/tests/package.json @@ -8,6 +8,6 @@ "@rescript/react": "0.12.0" }, "dependencies": { - "rescript": "^11.1.0" + "rescript": "11.1.4" } } From eb4cea5e443ab83bd727e331a7fbe82ae20744c4 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Sun, 29 Dec 2024 22:38:00 +0100 Subject: [PATCH 26/34] change strategy for removing dot on completion --- analysis/src/Codemod.ml | 2 +- analysis/src/CompletionBackEnd.ml | 40 ++--- analysis/src/CompletionFrontEnd.ml | 9 +- analysis/src/DotCompletionUtils.ml | 15 +- analysis/src/Loc.ml | 9 ++ analysis/src/Pos.ml | 16 ++ analysis/src/Protocol.ml | 43 ++--- analysis/src/SharedTypes.ml | 7 +- analysis/src/TypeUtils.ml | 27 +++- analysis/src/Xform.ml | 33 ++-- .../src/expected/CompletionFromModule.res.txt | 45 +++--- .../expected/CompletionFromModule2.res.txt | 45 +++--- ...mpletionMultipleEditorCompleteFrom.res.txt | 18 ++- .../expected/CompletionPipeProperty.res.txt | 18 ++- analysis/tests/src/expected/Cross.res.txt | 36 ++--- .../expected/DotCompletionEverywhere.res.txt | 117 ++++++++------ .../expected/DotPipeCompletionSpec.res.txt | 153 ++++++++++-------- analysis/tests/src/expected/Rename.res.txt | 30 ++-- .../src/expected/RenameWithInterface.res.txt | 24 +-- .../src/expected/RenameWithInterface.resi.txt | 24 +-- .../tests/src/expected/RxjsCompletion.res.txt | 90 ++++++----- 21 files changed, 437 insertions(+), 364 deletions(-) diff --git a/analysis/src/Codemod.ml b/analysis/src/Codemod.ml index 61d7318cc..5c273637d 100644 --- a/analysis/src/Codemod.ml +++ b/analysis/src/Codemod.ml @@ -41,7 +41,7 @@ let transform ~path ~pos ~debug ~typ ~hint = if debug then print_endline "Found no result"; exit 1 | Some switchExpr -> - printExpr ~range:(Xform.rangeOfLoc switchExpr.pexp_loc) switchExpr) + printExpr ~range:(Loc.rangeOfLoc switchExpr.pexp_loc) switchExpr) | _ -> if debug then print_endline "Mismatch in expected structure"; exit 1) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 3350ef183..bf1b674ba 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -664,8 +664,8 @@ let completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens ~pos in completions -let getPipeCompletions ~env ~full ~identifierLoc ~debug ~envCompletionIsMadeFrom - ~opens ~pos ~scope ~prefix ~rawOpens ~inJsx +let getPipeCompletions ~env ~full ~identifierLoc ?posOfDot ~debug + ~envCompletionIsMadeFrom ~opens ~pos ~scope ~prefix ~rawOpens ~inJsx ?(mainCompletionsAreSynthetic = false) ?(formatCompletionsWithPipes = false) typ = let env, typ = @@ -750,8 +750,8 @@ let getPipeCompletions ~env ~full ~identifierLoc ~debug ~envCompletionIsMadeFrom allCompletions |> List.filter_map (fun (c : Completion.t) -> c - |> TypeUtils.transformCompletionToPipeCompletion ~env - ~replaceRange:identifierLoc ~synthetic:c.synthetic) + |> TypeUtils.transformCompletionToPipeCompletion ~env ?posOfDot + ~synthetic:c.synthetic) else allCompletions let rec digToRecordFieldsForCompletion ~debug ~package ~opens ~full ~pos ~env @@ -772,8 +772,8 @@ let rec digToRecordFieldsForCompletion ~debug ~package ~opens ~full ~pos ~env | {kind = Type {kind = Record fields}} :: _ -> Some fields | _ -> None -let mkItem ?data ?(range : Location.t option) name ~kind ~detail ~deprecated - ~docstring = +let mkItem ?data ?additionalTextEdits name ~kind ~detail ~deprecated ~docstring + = let docContent = (match deprecated with | None -> "" @@ -802,23 +802,7 @@ let mkItem ?data ?(range : Location.t option) name ~kind ~detail ~deprecated insertTextFormat = None; filterText = None; data; - range = - (match range with - | None -> None - | Some range -> - Some - { - start = - { - line = range.loc_start.pos_lnum - 1; - character = range.loc_start.pos_cnum - range.loc_start.pos_bol; - }; - end_ = - { - line = range.loc_end.pos_lnum - 1; - character = range.loc_end.pos_cnum - range.loc_end.pos_bol; - }; - }); + additionalTextEdits; } let completionToItem @@ -833,10 +817,10 @@ let completionToItem filterText; detail; env; - range; + additionalTextEdits; } ~full = let item = - mkItem name ?range + mkItem name ?additionalTextEdits ?data:(kindToData (full.file.uri |> Uri.toPath) kind) ~kind:(Completion.kindToInt kind) ~deprecated @@ -1096,7 +1080,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact path @ [fieldName] |> getCompletionsForPath ~debug ~opens ~full ~pos ~exact ~completionContext:Field ~env ~scope - | CPField {contextPath = cp; fieldName; fieldNameLoc} -> ( + | CPField {contextPath = cp; fieldName; fieldNameLoc; posOfDot} -> ( if Debug.verbose () then print_endline "[dot_completion]--> Triggered"; let completionsFromCtxPath = cp @@ -1116,10 +1100,10 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact | Some (typ, env) -> let fieldCompletions = DotCompletionUtils.fieldCompletionsForDotCompletion typ ~env ~package - ~prefix:fieldName ~fieldNameLoc ~exact + ~prefix:fieldName ?posOfDot ~exact in let pipeCompletions = - getPipeCompletions ~env ~full ~identifierLoc:fieldNameLoc + getPipeCompletions ~env ~full ~identifierLoc:fieldNameLoc ?posOfDot ~envCompletionIsMadeFrom ~debug ~opens ~rawOpens ~scope ~pos ~inJsx:false ~prefix:fieldName ~formatCompletionsWithPipes:true typ in diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index ecb1d7954..e05741a92 100644 --- a/analysis/src/CompletionFrontEnd.ml +++ b/analysis/src/CompletionFrontEnd.ml @@ -225,7 +225,9 @@ let rec exprToContextPathInner (e : Parsetree.expression) = | Pexp_field (e1, {txt = Lident name; loc}) -> ( match exprToContextPath e1 with | Some contextPath -> - Some (CPField {contextPath; fieldName = name; fieldNameLoc = loc}) + Some + (CPField + {contextPath; fieldName = name; fieldNameLoc = loc; posOfDot = None}) | _ -> None) | Pexp_field (_, {loc; txt = Ldot (lid, name)}) -> (* Case x.M.field ignore the x part *) @@ -241,6 +243,7 @@ let rec exprToContextPathInner (e : Parsetree.expression) = }; fieldName = name; fieldNameLoc = loc; + posOfDot = None; }) | Pexp_send (e1, {txt}) -> ( match exprToContextPath e1 with @@ -332,6 +335,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor Some text.[offsetNoWhite] else None in + let posOfDot = Pos.posOfDot text ~pos:posCursor ~offset in let charAtCursor = if offset < String.length text then text.[offset] else '\n' in @@ -1167,6 +1171,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor contextPath; fieldName = name; fieldNameLoc = fieldName.loc; + posOfDot; } in setResult (Cpath contextPath) @@ -1189,6 +1194,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor else if name = "_" then "" else name); fieldNameLoc = fieldName.loc; + posOfDot; } in setResult (Cpath contextPath) @@ -1208,6 +1214,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor loc_end = e.pexp_loc.loc_end; loc_ghost = false; }; + posOfDot; })) | None -> ()) | Pexp_apply ({pexp_desc = Pexp_ident compName}, args) diff --git a/analysis/src/DotCompletionUtils.ml b/analysis/src/DotCompletionUtils.ml index 00e518e63..fc2574279 100644 --- a/analysis/src/DotCompletionUtils.ml +++ b/analysis/src/DotCompletionUtils.ml @@ -8,8 +8,8 @@ let filterRecordFields ~env ~recordAsString ~prefix ~exact fields = ~kind:(SharedTypes.Completion.Field (field, recordAsString))) else None) -let fieldCompletionsForDotCompletion typ ~env ~package ~prefix ~fieldNameLoc - ~exact = +let fieldCompletionsForDotCompletion ?posOfDot typ ~env ~package ~prefix ~exact + = let asObject = typ |> TypeUtils.extractObjectType ~env ~package in match asObject with | Some (objEnv, obj) -> @@ -22,8 +22,15 @@ let fieldCompletionsForDotCompletion typ ~env ~package ~prefix ~fieldNameLoc let fullObjFieldName = Printf.sprintf "[\"%s\"]" field in Some (SharedTypes.Completion.create fullObjFieldName ~synthetic:true - ~range:fieldNameLoc ~insertText:fullObjFieldName ~env:objEnv - ~kind:(SharedTypes.Completion.ObjLabel typ)) + ~insertText:fullObjFieldName ~env:objEnv + ~kind:(SharedTypes.Completion.ObjLabel typ) + ?additionalTextEdits: + (match posOfDot with + | None -> None + | Some posOfDot -> + Some + (TypeUtils.makeAdditionalTextEditsForRemovingDot + posOfDot))) else None) | None -> ( match typ |> TypeUtils.extractRecordType ~env ~package with diff --git a/analysis/src/Loc.ml b/analysis/src/Loc.ml index a9e42979c..2ab1d8fbd 100644 --- a/analysis/src/Loc.ml +++ b/analysis/src/Loc.ml @@ -12,3 +12,12 @@ let hasPos ~pos loc = start loc <= pos && pos < end_ loc (** Allows the character after the end to be included. Ie when the cursor is at the end of the word, like `someIdentifier`. Useful in some scenarios. *) let hasPosInclusiveEnd ~pos loc = start loc <= pos && pos <= end_ loc + +let mkPosition (pos : Pos.t) = + let line, character = pos in + {Protocol.line; character} + +let rangeOfLoc (loc : t) = + let start = loc |> start |> mkPosition in + let end_ = loc |> end_ |> mkPosition in + {Protocol.start; end_} diff --git a/analysis/src/Pos.ml b/analysis/src/Pos.ml index 081c575ea..e20af11e5 100644 --- a/analysis/src/Pos.ml +++ b/analysis/src/Pos.ml @@ -26,3 +26,19 @@ let positionToOffset text (line, character) = else None let posBeforeCursor pos = (fst pos, max 0 (snd pos - 1)) + +let posOfDot text ~(pos : int * int) ~offset = + let rec loop i = + if i < 0 then None + else + match text.[i] with + | '.' -> Some (i + 1) + | '\n' -> None + | _ -> loop (i - 1) + in + match loop (offset - 1) with + | None -> None + | Some offsetBeforeDot -> + let line, col = pos in + let newCol = max 0 (col - (offset - offsetBeforeDot)) in + Some (line, newCol) diff --git a/analysis/src/Protocol.ml b/analysis/src/Protocol.ml index 7e42b2964..3631192cb 100644 --- a/analysis/src/Protocol.ml +++ b/analysis/src/Protocol.ml @@ -40,6 +40,8 @@ let insertTextFormatToInt f = match f with | Snippet -> 2 +type textEdit = {range: range; newText: string} + type completionItem = { label: string; kind: int; @@ -51,7 +53,7 @@ type completionItem = { insertText: string option; documentation: markupContent option; data: (string * string) list option; - range: range option; + additionalTextEdits: textEdit list option; } type location = {uri: string; range: range} @@ -62,8 +64,6 @@ type documentSymbolItem = { children: documentSymbolItem list; } type renameFile = {oldUri: string; newUri: string} -type textEdit = {range: range; newText: string} - type diagnostic = {range: range; message: string; severity: int} type optionalVersionedTextDocumentIdentifier = { @@ -105,6 +105,14 @@ let stringifyRange r = (stringifyPosition r.start) (stringifyPosition r.end_) +let stringifyTextEdit (te : textEdit) = + Printf.sprintf + {|{ + "range": %s, + "newText": %s + }|} + (stringifyRange te.range) (wrapInQuotes te.newText) + let stringifyMarkupContent (m : markupContent) = Printf.sprintf {|{"kind": %s, "value": %s}|} (wrapInQuotes m.kind) (wrapInQuotes m.value) @@ -143,10 +151,7 @@ let stringifyCompletionItem c = | Some doc -> stringifyMarkupContent doc) ); ("sortText", optWrapInQuotes c.sortText); ("filterText", optWrapInQuotes c.filterText); - ( "insertText", - match c.range with - | Some _ -> None - | None -> optWrapInQuotes c.insertText ); + ("insertText", optWrapInQuotes c.insertText); ( "insertTextFormat", match c.insertTextFormat with | None -> None @@ -160,19 +165,10 @@ let stringifyCompletionItem c = (fields |> List.map (fun (key, value) -> (key, Some (wrapInQuotes value))) |> stringifyObject ~indentation:2) ); - ( "textEdit", - match c.range with - | Some range -> - Some - (stringifyObject - [ - ("range", Some (stringifyRange range)); - ( "newText", - optWrapInQuotes - (match c.insertText with - | None -> Some c.label - | v -> v) ); - ]) + ( "additionalTextEdits", + match c.additionalTextEdits with + | Some additionalTextEdits -> + Some (additionalTextEdits |> List.map stringifyTextEdit |> array) | None -> None ); ] @@ -233,13 +229,6 @@ let stringifyRenameFile {oldUri; newUri} = }|} (wrapInQuotes oldUri) (wrapInQuotes newUri) -let stringifyTextEdit (te : textEdit) = - Printf.sprintf {|{ - "range": %s, - "newText": %s - }|} - (stringifyRange te.range) (wrapInQuotes te.newText) - let stringifyoptionalVersionedTextDocumentIdentifier td = Printf.sprintf {|{ "version": %s, diff --git a/analysis/src/SharedTypes.ml b/analysis/src/SharedTypes.ml index 83c324ba0..e3214701b 100644 --- a/analysis/src/SharedTypes.ml +++ b/analysis/src/SharedTypes.ml @@ -614,6 +614,7 @@ module Completable = struct | CPField of { contextPath: contextPath; fieldName: string; + posOfDot: (int * int) option; fieldNameLoc: Location.t; } | CPObj of contextPath * string @@ -812,12 +813,12 @@ module Completion = struct detail: string option; typeArgContext: typeArgContext option; data: (string * string) list option; - range: Location.t option; + additionalTextEdits: Protocol.textEdit list option; synthetic: bool; (** Whether this item is an made up, synthetic item or not. *) } - let create ?(synthetic = false) ?range ?data ?typeArgContext + let create ?(synthetic = false) ?additionalTextEdits ?data ?typeArgContext ?(includesSnippets = false) ?insertText ~kind ~env ?sortText ?deprecated ?filterText ?detail ?(docstring = []) name = { @@ -834,7 +835,7 @@ module Completion = struct detail; typeArgContext; data; - range; + additionalTextEdits; synthetic; } diff --git a/analysis/src/TypeUtils.ml b/analysis/src/TypeUtils.ml index 62636097f..c563f9788 100644 --- a/analysis/src/TypeUtils.ml +++ b/analysis/src/TypeUtils.ml @@ -1142,8 +1142,20 @@ let getFirstFnUnlabelledArgType ~env ~full t = | Some t -> Some (t, env) | _ -> None +let makeAdditionalTextEditsForRemovingDot posOfDot = + [ + { + Protocol.range = + { + start = {line = fst posOfDot; character = snd posOfDot - 1}; + end_ = {line = fst posOfDot; character = snd posOfDot}; + }; + newText = ""; + }; + ] + (** Turns a completion into a pipe completion. *) -let transformCompletionToPipeCompletion ?(synthetic = false) ~env ~replaceRange +let transformCompletionToPipeCompletion ?(synthetic = false) ~env ?posOfDot (completion : Completion.t) = let name = completion.name in let nameWithPipe = "->" ^ name in @@ -1154,8 +1166,11 @@ let transformCompletionToPipeCompletion ?(synthetic = false) ~env ~replaceRange sortText = Some (name |> String.split_on_char '.' |> List.rev |> List.hd); insertText = Some nameWithPipe; env; - range = Some replaceRange; synthetic; + additionalTextEdits = + (match posOfDot with + | None -> None + | Some posOfDot -> Some (makeAdditionalTextEditsForRemovingDot posOfDot)); } (** This takes a type expr and the env that type expr was found in, and produces a globally unique @@ -1202,7 +1217,7 @@ let rec findRootTypeId ~full ~env (t : Types.type_expr) = | _ -> None (** Filters out completions that are not pipeable from a list of completions. *) -let filterPipeableFunctions ~env ~full ?synthetic ?targetTypeId ?replaceRange +let filterPipeableFunctions ~env ~full ?synthetic ?targetTypeId ?posOfDot completions = match targetTypeId with | None -> completions @@ -1222,10 +1237,10 @@ let filterPipeableFunctions ~env ~full ?synthetic ?targetTypeId ?replaceRange in match thisCompletionItemTypeId with | Some mainTypeId when mainTypeId = targetTypeId -> ( - match replaceRange with + match posOfDot with | None -> Some completion - | Some replaceRange -> - transformCompletionToPipeCompletion ?synthetic ~env ~replaceRange + | Some posOfDot -> + transformCompletionToPipeCompletion ?synthetic ~env ~posOfDot completion) | _ -> None) diff --git a/analysis/src/Xform.ml b/analysis/src/Xform.ml index 64c90be18..904fa5bf8 100644 --- a/analysis/src/Xform.ml +++ b/analysis/src/Xform.ml @@ -2,15 +2,6 @@ let isBracedExpr = Res_parsetree_viewer.is_braced_expr -let mkPosition (pos : Pos.t) = - let line, character = pos in - {Protocol.line; character} - -let rangeOfLoc (loc : Location.t) = - let start = loc |> Loc.start |> mkPosition in - let end_ = loc |> Loc.end_ |> mkPosition in - {Protocol.start; end_} - let extractTypeFromExpr expr ~debug ~path ~currentFile ~full ~pos = match expr.Parsetree.pexp_loc @@ -144,7 +135,7 @@ module IfThenElse = struct match !changed with | None -> () | Some newExpr -> - let range = rangeOfLoc newExpr.pexp_loc in + let range = Loc.rangeOfLoc newExpr.pexp_loc in let newText = printExpr ~range newExpr in let codeAction = CodeActions.make ~title:"Replace with switch" ~kind:RefactorRewrite @@ -161,7 +152,7 @@ module ModuleToFile = struct | Pstr_module {pmb_loc; pmb_name; pmb_expr = {pmod_desc = Pmod_structure structure}} when structure_item.pstr_loc |> Loc.hasPos ~pos -> - let range = rangeOfLoc structure_item.pstr_loc in + let range = Loc.rangeOfLoc structure_item.pstr_loc in let newTextInCurrentFile = "" in let textForExtractedFile = printStandaloneStructure ~loc:pmb_loc structure @@ -280,7 +271,7 @@ module AddBracesToFn = struct match !changed with | None -> () | Some newStructureItem -> - let range = rangeOfLoc newStructureItem.pstr_loc in + let range = Loc.rangeOfLoc newStructureItem.pstr_loc in let newText = printStructureItem ~range newStructureItem in let codeAction = CodeActions.make ~title:"Add braces to function" ~kind:RefactorRewrite @@ -349,10 +340,10 @@ module AddTypeAnnotation = struct let range, newText = match annotation with | Plain -> - ( rangeOfLoc {locItem.loc with loc_start = locItem.loc.loc_end}, + ( Loc.rangeOfLoc {locItem.loc with loc_start = locItem.loc.loc_end}, ": " ^ (typ |> Shared.typeToString) ) | WithParens -> - ( rangeOfLoc locItem.loc, + ( Loc.rangeOfLoc locItem.loc, "(" ^ name ^ ": " ^ (typ |> Shared.typeToString) ^ ")" ) in let codeAction = @@ -439,7 +430,7 @@ module ExpandCatchAllForVariants = struct | Args _ | InlineRecord _ -> "(_)") |> String.concat " | " in - let range = rangeOfLoc catchAllCase.pc_lhs.ppat_loc in + let range = Loc.rangeOfLoc catchAllCase.pc_lhs.ppat_loc in let codeAction = CodeActions.make ~title:"Expand catch-all" ~kind:RefactorRewrite ~uri:path ~newText ~range @@ -463,7 +454,7 @@ module ExpandCatchAllForVariants = struct | _ -> "(_)") |> String.concat " | " in - let range = rangeOfLoc catchAllCase.pc_lhs.ppat_loc in + let range = Loc.rangeOfLoc catchAllCase.pc_lhs.ppat_loc in let codeAction = CodeActions.make ~title:"Expand catch-all" ~kind:RefactorRewrite ~uri:path ~newText ~range @@ -532,7 +523,7 @@ module ExpandCatchAllForVariants = struct let newText = if hasNoneCase then newText else newText ^ " | None" in - let range = rangeOfLoc catchAllCase.pc_lhs.ppat_loc in + let range = Loc.rangeOfLoc catchAllCase.pc_lhs.ppat_loc in let codeAction = CodeActions.make ~title:"Expand catch-all" ~kind:RefactorRewrite ~uri:path ~newText ~range @@ -627,7 +618,7 @@ module ExhaustiveSwitch = struct match exhaustiveSwitch with | None -> () | Some cases -> - let range = rangeOfLoc expr.pexp_loc in + let range = Loc.rangeOfLoc expr.pexp_loc in let newText = printExpr ~range {expr with pexp_desc = Pexp_match (expr, cases)} in @@ -652,7 +643,7 @@ module ExhaustiveSwitch = struct match exhaustiveSwitch with | None -> () | Some cases -> - let range = rangeOfLoc switchExpr.pexp_loc in + let range = Loc.rangeOfLoc switchExpr.pexp_loc in let newText = printExpr ~range {switchExpr with pexp_desc = Pexp_match (completionExpr, cases)} @@ -752,7 +743,7 @@ module AddDocTemplate = struct match newSignatureItem with | Some signatureItem -> - let range = rangeOfLoc signatureItem.psig_loc in + let range = Loc.rangeOfLoc signatureItem.psig_loc in let newText = printSignatureItem ~range signatureItem in let codeAction = CodeActions.make ~title:"Add Documentation template" @@ -837,7 +828,7 @@ module AddDocTemplate = struct match newStructureItem with | Some structureItem -> - let range = rangeOfLoc structureItem.pstr_loc in + let range = Loc.rangeOfLoc structureItem.pstr_loc in let newText = printStructureItem ~range structureItem in let codeAction = CodeActions.make ~title:"Add Documentation template" diff --git a/analysis/tests/src/expected/CompletionFromModule.res.txt b/analysis/tests/src/expected/CompletionFromModule.res.txt index 480f8472d..4439f9f20 100644 --- a/analysis/tests/src/expected/CompletionFromModule.res.txt +++ b/analysis/tests/src/expected/CompletionFromModule.res.txt @@ -22,10 +22,11 @@ Path SomeModule. "detail": "t => string", "documentation": null, "sortText": "getName", - "textEdit": { - "range": {"start": {"line": 10, "character": 4}, "end": {"line": 10, "character": 4}}, - "newText": "->SomeModule.getName" - } + "insertText": "->SomeModule.getName", + "additionalTextEdits": [{ + "range": {"start": {"line": 10, "character": 4}, "end": {"line": 10, "character": 5}}, + "newText": "" + }] }] Complete src/CompletionFromModule.res 30:6 @@ -53,10 +54,11 @@ Path CompletionFromModule.SomeOtherModule. "detail": "t => string", "documentation": null, "sortText": "getNName", - "textEdit": { - "range": {"start": {"line": 30, "character": 5}, "end": {"line": 30, "character": 5}}, - "newText": "->SomeOtherModule.getNName" - } + "insertText": "->SomeOtherModule.getNName", + "additionalTextEdits": [{ + "range": {"start": {"line": 30, "character": 5}, "end": {"line": 30, "character": 6}}, + "newText": "" + }] }, { "label": "->SomeOtherModule.getNName2", "kind": 12, @@ -64,10 +66,11 @@ Path CompletionFromModule.SomeOtherModule. "detail": "typeOutsideModule => string", "documentation": null, "sortText": "getNName2", - "textEdit": { - "range": {"start": {"line": 30, "character": 5}, "end": {"line": 30, "character": 5}}, - "newText": "->SomeOtherModule.getNName2" - } + "insertText": "->SomeOtherModule.getNName2", + "additionalTextEdits": [{ + "range": {"start": {"line": 30, "character": 5}, "end": {"line": 30, "character": 6}}, + "newText": "" + }] }, { "label": "->SomeOtherModule.getNName", "kind": 12, @@ -75,10 +78,11 @@ Path CompletionFromModule.SomeOtherModule. "detail": "t => string", "documentation": null, "sortText": "getNName", - "textEdit": { - "range": {"start": {"line": 30, "character": 5}, "end": {"line": 30, "character": 5}}, - "newText": "->SomeOtherModule.getNName" - } + "insertText": "->SomeOtherModule.getNName", + "additionalTextEdits": [{ + "range": {"start": {"line": 30, "character": 5}, "end": {"line": 30, "character": 6}}, + "newText": "" + }] }, { "label": "->SomeOtherModule.getNName2", "kind": 12, @@ -86,10 +90,11 @@ Path CompletionFromModule.SomeOtherModule. "detail": "typeOutsideModule => string", "documentation": null, "sortText": "getNName2", - "textEdit": { - "range": {"start": {"line": 30, "character": 5}, "end": {"line": 30, "character": 5}}, - "newText": "->SomeOtherModule.getNName2" - } + "insertText": "->SomeOtherModule.getNName2", + "additionalTextEdits": [{ + "range": {"start": {"line": 30, "character": 5}, "end": {"line": 30, "character": 6}}, + "newText": "" + }] }] Complete src/CompletionFromModule.res 33:32 diff --git a/analysis/tests/src/expected/CompletionFromModule2.res.txt b/analysis/tests/src/expected/CompletionFromModule2.res.txt index f4c9f4ed7..1f91137dc 100644 --- a/analysis/tests/src/expected/CompletionFromModule2.res.txt +++ b/analysis/tests/src/expected/CompletionFromModule2.res.txt @@ -22,10 +22,11 @@ Path CompletionFromModule.SomeModule. "detail": "t => string", "documentation": null, "sortText": "getName", - "textEdit": { - "range": {"start": {"line": 2, "character": 25}, "end": {"line": 2, "character": 25}}, - "newText": "->CompletionFromModule.SomeModule.getName" - } + "insertText": "->CompletionFromModule.SomeModule.getName", + "additionalTextEdits": [{ + "range": {"start": {"line": 2, "character": 25}, "end": {"line": 2, "character": 26}}, + "newText": "" + }] }] Complete src/CompletionFromModule2.res 5:27 @@ -53,10 +54,11 @@ Path CompletionFromModule.SomeOtherModule. "detail": "t => string", "documentation": null, "sortText": "getNName", - "textEdit": { - "range": {"start": {"line": 5, "character": 26}, "end": {"line": 5, "character": 26}}, - "newText": "->CompletionFromModule.SomeOtherModule.getNName" - } + "insertText": "->CompletionFromModule.SomeOtherModule.getNName", + "additionalTextEdits": [{ + "range": {"start": {"line": 5, "character": 26}, "end": {"line": 5, "character": 27}}, + "newText": "" + }] }, { "label": "->CompletionFromModule.SomeOtherModule.getNName2", "kind": 12, @@ -64,10 +66,11 @@ Path CompletionFromModule.SomeOtherModule. "detail": "typeOutsideModule => string", "documentation": null, "sortText": "getNName2", - "textEdit": { - "range": {"start": {"line": 5, "character": 26}, "end": {"line": 5, "character": 26}}, - "newText": "->CompletionFromModule.SomeOtherModule.getNName2" - } + "insertText": "->CompletionFromModule.SomeOtherModule.getNName2", + "additionalTextEdits": [{ + "range": {"start": {"line": 5, "character": 26}, "end": {"line": 5, "character": 27}}, + "newText": "" + }] }, { "label": "->CompletionFromModule.SomeOtherModule.getNName", "kind": 12, @@ -75,10 +78,11 @@ Path CompletionFromModule.SomeOtherModule. "detail": "t => string", "documentation": null, "sortText": "getNName", - "textEdit": { - "range": {"start": {"line": 5, "character": 26}, "end": {"line": 5, "character": 26}}, - "newText": "->CompletionFromModule.SomeOtherModule.getNName" - } + "insertText": "->CompletionFromModule.SomeOtherModule.getNName", + "additionalTextEdits": [{ + "range": {"start": {"line": 5, "character": 26}, "end": {"line": 5, "character": 27}}, + "newText": "" + }] }, { "label": "->CompletionFromModule.SomeOtherModule.getNName2", "kind": 12, @@ -86,10 +90,11 @@ Path CompletionFromModule.SomeOtherModule. "detail": "typeOutsideModule => string", "documentation": null, "sortText": "getNName2", - "textEdit": { - "range": {"start": {"line": 5, "character": 26}, "end": {"line": 5, "character": 26}}, - "newText": "->CompletionFromModule.SomeOtherModule.getNName2" - } + "insertText": "->CompletionFromModule.SomeOtherModule.getNName2", + "additionalTextEdits": [{ + "range": {"start": {"line": 5, "character": 26}, "end": {"line": 5, "character": 27}}, + "newText": "" + }] }] Complete src/CompletionFromModule2.res 8:29 diff --git a/analysis/tests/src/expected/CompletionMultipleEditorCompleteFrom.res.txt b/analysis/tests/src/expected/CompletionMultipleEditorCompleteFrom.res.txt index f3afb8883..43ea9a439 100644 --- a/analysis/tests/src/expected/CompletionMultipleEditorCompleteFrom.res.txt +++ b/analysis/tests/src/expected/CompletionMultipleEditorCompleteFrom.res.txt @@ -18,10 +18,11 @@ Path C. "detail": "A.a => int", "documentation": null, "sortText": "b", - "textEdit": { - "range": {"start": {"line": 19, "character": 4}, "end": {"line": 19, "character": 4}}, - "newText": "->B.b" - } + "insertText": "->B.b", + "additionalTextEdits": [{ + "range": {"start": {"line": 19, "character": 4}, "end": {"line": 19, "character": 5}}, + "newText": "" + }] }, { "label": "->C.c", "kind": 12, @@ -29,9 +30,10 @@ Path C. "detail": "A.a => char", "documentation": null, "sortText": "c", - "textEdit": { - "range": {"start": {"line": 19, "character": 4}, "end": {"line": 19, "character": 4}}, - "newText": "->C.c" - } + "insertText": "->C.c", + "additionalTextEdits": [{ + "range": {"start": {"line": 19, "character": 4}, "end": {"line": 19, "character": 5}}, + "newText": "" + }] }] diff --git a/analysis/tests/src/expected/CompletionPipeProperty.res.txt b/analysis/tests/src/expected/CompletionPipeProperty.res.txt index 7fd14d555..4532daa15 100644 --- a/analysis/tests/src/expected/CompletionPipeProperty.res.txt +++ b/analysis/tests/src/expected/CompletionPipeProperty.res.txt @@ -31,10 +31,11 @@ Path ObservablePoint. "detail": "(op, float) => unit", "documentation": null, "sortText": "setBoth", - "textEdit": { - "range": {"start": {"line": 21, "character": 16}, "end": {"line": 21, "character": 16}}, - "newText": "->ObservablePoint.setBoth" - } + "insertText": "->ObservablePoint.setBoth", + "additionalTextEdits": [{ + "range": {"start": {"line": 21, "character": 16}, "end": {"line": 21, "character": 17}}, + "newText": "" + }] }, { "label": "->ObservablePoint.set", "kind": 12, @@ -42,9 +43,10 @@ Path ObservablePoint. "detail": "(op, float, float) => unit", "documentation": null, "sortText": "set", - "textEdit": { - "range": {"start": {"line": 21, "character": 16}, "end": {"line": 21, "character": 16}}, - "newText": "->ObservablePoint.set" - } + "insertText": "->ObservablePoint.set", + "additionalTextEdits": [{ + "range": {"start": {"line": 21, "character": 16}, "end": {"line": 21, "character": 17}}, + "newText": "" + }] }] diff --git a/analysis/tests/src/expected/Cross.res.txt b/analysis/tests/src/expected/Cross.res.txt index 6f5ad3e43..a4c058b6a 100644 --- a/analysis/tests/src/expected/Cross.res.txt +++ b/analysis/tests/src/expected/Cross.res.txt @@ -35,12 +35,12 @@ Rename src/Cross.res 18:13 RenameWithInterfacePrime "uri": "Cross.res" }, "edits": [{ - "range": {"start": {"line": 18, "character": 8}, "end": {"line": 18, "character": 27}}, - "newText": "RenameWithInterfacePrime" - }, { - "range": {"start": {"line": 21, "character": 8}, "end": {"line": 21, "character": 27}}, - "newText": "RenameWithInterfacePrime" - }] + "range": {"start": {"line": 18, "character": 8}, "end": {"line": 18, "character": 27}}, + "newText": "RenameWithInterfacePrime" + }, { + "range": {"start": {"line": 21, "character": 8}, "end": {"line": 21, "character": 27}}, + "newText": "RenameWithInterfacePrime" + }] } ] @@ -52,9 +52,9 @@ Rename src/Cross.res 21:28 xPrime "uri": "RenameWithInterface.resi" }, "edits": [{ - "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}}, - "newText": "xPrime" - }] + "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}}, + "newText": "xPrime" + }] }, { "textDocument": { @@ -62,9 +62,9 @@ Rename src/Cross.res 21:28 xPrime "uri": "RenameWithInterface.res" }, "edits": [{ - "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}}, - "newText": "xPrime" - }] + "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}}, + "newText": "xPrime" + }] }, { "textDocument": { @@ -72,12 +72,12 @@ Rename src/Cross.res 21:28 xPrime "uri": "Cross.res" }, "edits": [{ - "range": {"start": {"line": 18, "character": 28}, "end": {"line": 18, "character": 29}}, - "newText": "xPrime" - }, { - "range": {"start": {"line": 21, "character": 28}, "end": {"line": 21, "character": 29}}, - "newText": "xPrime" - }] + "range": {"start": {"line": 18, "character": 28}, "end": {"line": 18, "character": 29}}, + "newText": "xPrime" + }, { + "range": {"start": {"line": 21, "character": 28}, "end": {"line": 21, "character": 29}}, + "newText": "xPrime" + }] } ] diff --git a/analysis/tests/src/expected/DotCompletionEverywhere.res.txt b/analysis/tests/src/expected/DotCompletionEverywhere.res.txt index ad7f42c7b..c418f580c 100644 --- a/analysis/tests/src/expected/DotCompletionEverywhere.res.txt +++ b/analysis/tests/src/expected/DotCompletionEverywhere.res.txt @@ -13,20 +13,22 @@ Path someObj "tags": [], "detail": "{\"age\": int, \"name\": string}", "documentation": null, - "textEdit": { - "range": {"start": {"line": 5, "character": 10}, "end": {"line": 5, "character": 10}}, - "newText": "[\"age\"]" - } + "insertText": "[\"age\"]", + "additionalTextEdits": [{ + "range": {"start": {"line": 5, "character": 10}, "end": {"line": 5, "character": 11}}, + "newText": "" + }] }, { "label": "[\"name\"]", "kind": 4, "tags": [], "detail": "{\"age\": int, \"name\": string}", "documentation": null, - "textEdit": { - "range": {"start": {"line": 5, "character": 10}, "end": {"line": 5, "character": 10}}, - "newText": "[\"name\"]" - } + "insertText": "[\"name\"]", + "additionalTextEdits": [{ + "range": {"start": {"line": 5, "character": 10}, "end": {"line": 5, "character": 11}}, + "newText": "" + }] }] Complete src/DotCompletionEverywhere.res 8:13 @@ -44,10 +46,11 @@ Path someObj "tags": [], "detail": "{\"age\": int, \"name\": string}", "documentation": null, - "textEdit": { - "range": {"start": {"line": 8, "character": 11}, "end": {"line": 8, "character": 13}}, - "newText": "[\"name\"]" - } + "insertText": "[\"name\"]", + "additionalTextEdits": [{ + "range": {"start": {"line": 8, "character": 10}, "end": {"line": 8, "character": 11}}, + "newText": "" + }] }] Complete src/DotCompletionEverywhere.res 14:8 @@ -87,10 +90,11 @@ Path SomeMod.SomeOtherMod. "detail": "x => unit", "documentation": null, "sortText": "do", - "textEdit": { - "range": {"start": {"line": 29, "character": 4}, "end": {"line": 29, "character": 4}}, - "newText": "->SomeMod.SomeOtherMod.do" - } + "insertText": "->SomeMod.SomeOtherMod.do", + "additionalTextEdits": [{ + "range": {"start": {"line": 29, "character": 4}, "end": {"line": 29, "character": 5}}, + "newText": "" + }] }] Complete src/DotCompletionEverywhere.res 32:27 @@ -111,10 +115,11 @@ Path SomeMod.SomeOtherMod. "detail": "x => unit", "documentation": null, "sortText": "do", - "textEdit": { - "range": {"start": {"line": 32, "character": 26}, "end": {"line": 32, "character": 26}}, - "newText": "->SomeMod.SomeOtherMod.do" - } + "insertText": "->SomeMod.SomeOtherMod.do", + "additionalTextEdits": [{ + "range": {"start": {"line": 32, "character": 26}, "end": {"line": 32, "character": 27}}, + "newText": "" + }] }] Complete src/DotCompletionEverywhere.res 41:11 @@ -141,10 +146,11 @@ Path Sss. "detail": "rrr => string", "documentation": null, "sortText": "do", - "textEdit": { - "range": {"start": {"line": 41, "character": 10}, "end": {"line": 41, "character": 10}}, - "newText": "->Sss.do" - } + "insertText": "->Sss.do", + "additionalTextEdits": [{ + "range": {"start": {"line": 41, "character": 10}, "end": {"line": 41, "character": 11}}, + "newText": "" + }] }] Complete src/DotCompletionEverywhere.res 52:8 @@ -172,10 +178,11 @@ Path DotCompletionEverywhere.X2. "detail": "x2x2 => string", "documentation": null, "sortText": "stuff", - "textEdit": { - "range": {"start": {"line": 52, "character": 7}, "end": {"line": 52, "character": 7}}, - "newText": "->X2.stuff" - } + "insertText": "->X2.stuff", + "additionalTextEdits": [{ + "range": {"start": {"line": 52, "character": 7}, "end": {"line": 52, "character": 8}}, + "newText": "" + }] }] Complete src/DotCompletionEverywhere.res 61:7 @@ -193,30 +200,33 @@ Path obj "tags": [], "detail": "{\"name\": string, \"nothing\": bool, \"number\": int}", "documentation": null, - "textEdit": { - "range": {"start": {"line": 61, "character": 6}, "end": {"line": 61, "character": 6}}, - "newText": "[\"name\"]" - } + "insertText": "[\"name\"]", + "additionalTextEdits": [{ + "range": {"start": {"line": 61, "character": 6}, "end": {"line": 61, "character": 7}}, + "newText": "" + }] }, { "label": "[\"nothing\"]", "kind": 4, "tags": [], "detail": "{\"name\": string, \"nothing\": bool, \"number\": int}", "documentation": null, - "textEdit": { - "range": {"start": {"line": 61, "character": 6}, "end": {"line": 61, "character": 6}}, - "newText": "[\"nothing\"]" - } + "insertText": "[\"nothing\"]", + "additionalTextEdits": [{ + "range": {"start": {"line": 61, "character": 6}, "end": {"line": 61, "character": 7}}, + "newText": "" + }] }, { "label": "[\"number\"]", "kind": 4, "tags": [], "detail": "{\"name\": string, \"nothing\": bool, \"number\": int}", "documentation": null, - "textEdit": { - "range": {"start": {"line": 61, "character": 6}, "end": {"line": 61, "character": 6}}, - "newText": "[\"number\"]" - } + "insertText": "[\"number\"]", + "additionalTextEdits": [{ + "range": {"start": {"line": 61, "character": 6}, "end": {"line": 61, "character": 7}}, + "newText": "" + }] }] Complete src/DotCompletionEverywhere.res 66:8 @@ -236,10 +246,11 @@ Path Js.Array2.m "detail": "(t<'a>, ('a, int) => 'b) => t<'b>", "documentation": {"kind": "markdown", "value": "\nApplies the function (the second argument) to each item in the array, returning\na new array. The function acceps two arguments: an item from the array and its\nindex number. The result array does not have to have elements of the same type\nas the input array. See\n[`Array.map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)\non MDN.\n\n## Examples\n\n```rescript\n// multiply each item in array by its position\nlet product = (item, index) => item * index\nJs.Array2.mapi([10, 11, 12], product) == [0, 11, 24]\n```\n"}, "sortText": "mapi", - "textEdit": { - "range": {"start": {"line": 66, "character": 7}, "end": {"line": 66, "character": 8}}, - "newText": "->Js.Array2.mapi" - } + "insertText": "->Js.Array2.mapi", + "additionalTextEdits": [{ + "range": {"start": {"line": 66, "character": 6}, "end": {"line": 66, "character": 7}}, + "newText": "" + }] }, { "label": "->Js.Array2.map", "kind": 12, @@ -247,10 +258,11 @@ Path Js.Array2.m "detail": "(t<'a>, 'a => 'b) => t<'b>", "documentation": {"kind": "markdown", "value": "\nApplies the function (the second argument) to each item in the array, returning\na new array. The result array does not have to have elements of the same type\nas the input array. See\n[`Array.map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)\non MDN.\n\n## Examples\n\n```rescript\nJs.Array2.map([12, 4, 8], x => x * x) == [144, 16, 64]\nJs.Array2.map([\"animal\", \"vegetable\", \"mineral\"], Js.String.length) == [6, 9, 7]\n```\n"}, "sortText": "map", - "textEdit": { - "range": {"start": {"line": 66, "character": 7}, "end": {"line": 66, "character": 8}}, - "newText": "->Js.Array2.map" - } + "insertText": "->Js.Array2.map", + "additionalTextEdits": [{ + "range": {"start": {"line": 66, "character": 6}, "end": {"line": 66, "character": 7}}, + "newText": "" + }] }] Complete src/DotCompletionEverywhere.res 85:10 @@ -278,9 +290,10 @@ Path DotCompletionEverywhere.HTMLButtonElement. "detail": "DOMAPI.htmlButtonElement => bool", "documentation": null, "sortText": "checkValidity", - "textEdit": { - "range": {"start": {"line": 85, "character": 9}, "end": {"line": 85, "character": 9}}, - "newText": "->HTMLButtonElement.checkValidity" - } + "insertText": "->HTMLButtonElement.checkValidity", + "additionalTextEdits": [{ + "range": {"start": {"line": 85, "character": 9}, "end": {"line": 85, "character": 10}}, + "newText": "" + }] }] diff --git a/analysis/tests/src/expected/DotPipeCompletionSpec.res.txt b/analysis/tests/src/expected/DotPipeCompletionSpec.res.txt index 1d84ecd16..fa229c17a 100644 --- a/analysis/tests/src/expected/DotPipeCompletionSpec.res.txt +++ b/analysis/tests/src/expected/DotPipeCompletionSpec.res.txt @@ -22,10 +22,11 @@ Path SomeModule. "detail": "(~name: string=?, t) => unit", "documentation": null, "sortText": "withUnlabelledArgumentNotFirst", - "textEdit": { - "range": {"start": {"line": 15, "character": 4}, "end": {"line": 15, "character": 4}}, - "newText": "->SomeModule.withUnlabelledArgumentNotFirst" - } + "insertText": "->SomeModule.withUnlabelledArgumentNotFirst", + "additionalTextEdits": [{ + "range": {"start": {"line": 15, "character": 4}, "end": {"line": 15, "character": 5}}, + "newText": "" + }] }, { "label": "->SomeModule.getName", "kind": 12, @@ -33,10 +34,11 @@ Path SomeModule. "detail": "t => string", "documentation": null, "sortText": "getName", - "textEdit": { - "range": {"start": {"line": 15, "character": 4}, "end": {"line": 15, "character": 4}}, - "newText": "->SomeModule.getName" - } + "insertText": "->SomeModule.getName", + "additionalTextEdits": [{ + "range": {"start": {"line": 15, "character": 4}, "end": {"line": 15, "character": 5}}, + "newText": "" + }] }] Complete src/DotPipeCompletionSpec.res 44:6 @@ -65,10 +67,11 @@ Path DotPipeCompletionSpec.SomeOtherModule. "detail": "t => string", "documentation": null, "sortText": "getNName", - "textEdit": { - "range": {"start": {"line": 44, "character": 5}, "end": {"line": 44, "character": 5}}, - "newText": "->SomeOtherModule.getNName" - } + "insertText": "->SomeOtherModule.getNName", + "additionalTextEdits": [{ + "range": {"start": {"line": 44, "character": 5}, "end": {"line": 44, "character": 6}}, + "newText": "" + }] }, { "label": "->SomeOtherModule.getNName2", "kind": 12, @@ -76,10 +79,11 @@ Path DotPipeCompletionSpec.SomeOtherModule. "detail": "typeOutsideModule => string", "documentation": null, "sortText": "getNName2", - "textEdit": { - "range": {"start": {"line": 44, "character": 5}, "end": {"line": 44, "character": 5}}, - "newText": "->SomeOtherModule.getNName2" - } + "insertText": "->SomeOtherModule.getNName2", + "additionalTextEdits": [{ + "range": {"start": {"line": 44, "character": 5}, "end": {"line": 44, "character": 6}}, + "newText": "" + }] }, { "label": "->CompleteFromThisToo.a", "kind": 12, @@ -87,10 +91,11 @@ Path DotPipeCompletionSpec.SomeOtherModule. "detail": "typeOutsideModule => string", "documentation": null, "sortText": "a", - "textEdit": { - "range": {"start": {"line": 44, "character": 5}, "end": {"line": 44, "character": 5}}, - "newText": "->CompleteFromThisToo.a" - } + "insertText": "->CompleteFromThisToo.a", + "additionalTextEdits": [{ + "range": {"start": {"line": 44, "character": 5}, "end": {"line": 44, "character": 6}}, + "newText": "" + }] }, { "label": "->SomeOtherModule.getNName", "kind": 12, @@ -98,10 +103,11 @@ Path DotPipeCompletionSpec.SomeOtherModule. "detail": "t => string", "documentation": null, "sortText": "getNName", - "textEdit": { - "range": {"start": {"line": 44, "character": 5}, "end": {"line": 44, "character": 5}}, - "newText": "->SomeOtherModule.getNName" - } + "insertText": "->SomeOtherModule.getNName", + "additionalTextEdits": [{ + "range": {"start": {"line": 44, "character": 5}, "end": {"line": 44, "character": 6}}, + "newText": "" + }] }, { "label": "->SomeOtherModule.getNName2", "kind": 12, @@ -109,10 +115,11 @@ Path DotPipeCompletionSpec.SomeOtherModule. "detail": "typeOutsideModule => string", "documentation": null, "sortText": "getNName2", - "textEdit": { - "range": {"start": {"line": 44, "character": 5}, "end": {"line": 44, "character": 5}}, - "newText": "->SomeOtherModule.getNName2" - } + "insertText": "->SomeOtherModule.getNName2", + "additionalTextEdits": [{ + "range": {"start": {"line": 44, "character": 5}, "end": {"line": 44, "character": 6}}, + "newText": "" + }] }] Complete src/DotPipeCompletionSpec.res 62:5 @@ -134,10 +141,11 @@ Path B. "detail": "a => unit", "documentation": null, "sortText": "withA", - "textEdit": { - "range": {"start": {"line": 62, "character": 4}, "end": {"line": 62, "character": 4}}, - "newText": "->A.withA" - } + "insertText": "->A.withA", + "additionalTextEdits": [{ + "range": {"start": {"line": 62, "character": 4}, "end": {"line": 62, "character": 5}}, + "newText": "" + }] }, { "label": "->B.b", "kind": 12, @@ -145,10 +153,11 @@ Path B. "detail": "A.a => int", "documentation": null, "sortText": "b", - "textEdit": { - "range": {"start": {"line": 62, "character": 4}, "end": {"line": 62, "character": 4}}, - "newText": "->B.b" - } + "insertText": "->B.b", + "additionalTextEdits": [{ + "range": {"start": {"line": 62, "character": 4}, "end": {"line": 62, "character": 5}}, + "newText": "" + }] }] Complete src/DotPipeCompletionSpec.res 67:6 @@ -175,10 +184,11 @@ Path CompletionFromModule.SomeModule. "detail": "t => string", "documentation": null, "sortText": "getName", - "textEdit": { - "range": {"start": {"line": 67, "character": 5}, "end": {"line": 67, "character": 5}}, - "newText": "->CompletionFromModule.SomeModule.getName" - } + "insertText": "->CompletionFromModule.SomeModule.getName", + "additionalTextEdits": [{ + "range": {"start": {"line": 67, "character": 5}, "end": {"line": 67, "character": 6}}, + "newText": "" + }] }] Complete src/DotPipeCompletionSpec.res 75:9 @@ -198,10 +208,11 @@ Path Js.Array2.u "detail": "(t<'a>, array<'a>) => int", "documentation": {"kind": "markdown", "value": "\nAdds the elements in the second array argument at the beginning of the first\narray argument, returning the new number of elements in the array. *This\nfunction modifies the original array.* See\n[`Array.unshift`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift)\non MDN.\n\n## Examples\n\n```rescript\nlet arr = [\"d\", \"e\"]\nJs.Array2.unshiftMany(arr, [\"a\", \"b\", \"c\"]) == 5\narr == [\"a\", \"b\", \"c\", \"d\", \"e\"]\n```\n"}, "sortText": "unshiftMany", - "textEdit": { - "range": {"start": {"line": 75, "character": 8}, "end": {"line": 75, "character": 9}}, - "newText": "->Js.Array2.unshiftMany" - } + "insertText": "->Js.Array2.unshiftMany", + "additionalTextEdits": [{ + "range": {"start": {"line": 75, "character": 7}, "end": {"line": 75, "character": 8}}, + "newText": "" + }] }, { "label": "->Js.Array2.unshift", "kind": 12, @@ -209,10 +220,11 @@ Path Js.Array2.u "detail": "(t<'a>, 'a) => int", "documentation": {"kind": "markdown", "value": "\nAdds the given element to the array, returning the new number of elements in\nthe array. *This function modifies the original array.* See\n[`Array.unshift`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift)\non MDN.\n\n## Examples\n\n```rescript\nlet arr = [\"b\", \"c\", \"d\"]\nJs.Array2.unshift(arr, \"a\") == 4\narr == [\"a\", \"b\", \"c\", \"d\"]\n```\n"}, "sortText": "unshift", - "textEdit": { - "range": {"start": {"line": 75, "character": 8}, "end": {"line": 75, "character": 9}}, - "newText": "->Js.Array2.unshift" - } + "insertText": "->Js.Array2.unshift", + "additionalTextEdits": [{ + "range": {"start": {"line": 75, "character": 7}, "end": {"line": 75, "character": 8}}, + "newText": "" + }] }, { "label": "->Js.Array2.unsafe_get", "kind": 12, @@ -220,10 +232,11 @@ Path Js.Array2.u "detail": "(array<'a>, int) => 'a", "documentation": {"kind": "markdown", "value": "\nReturns the value at the given position in the array if the position is in\nbounds; returns the JavaScript value `undefined` otherwise.\n\n## Examples\n\n```rescript\nlet arr = [100, 101, 102, 103]\nJs.Array2.unsafe_get(arr, 3) == 103\nJs.Array2.unsafe_get(arr, 4) // returns undefined\n```\n"}, "sortText": "unsafe_get", - "textEdit": { - "range": {"start": {"line": 75, "character": 8}, "end": {"line": 75, "character": 9}}, - "newText": "->Js.Array2.unsafe_get" - } + "insertText": "->Js.Array2.unsafe_get", + "additionalTextEdits": [{ + "range": {"start": {"line": 75, "character": 7}, "end": {"line": 75, "character": 8}}, + "newText": "" + }] }, { "label": "->Js.Array2.unsafe_set", "kind": 12, @@ -231,10 +244,11 @@ Path Js.Array2.u "detail": "(array<'a>, int, 'a) => unit", "documentation": {"kind": "markdown", "value": "\nSets the value at the given position in the array if the position is in bounds.\nIf the index is out of bounds, well, “here there be dragons.“\n\n*This function modifies the original array.*\n\n## Examples\n\n```rescript\nlet arr = [100, 101, 102, 103]\nJs.Array2.unsafe_set(arr, 3, 99)\n// result is [100, 101, 102, 99];\n\nJs.Array2.unsafe_set(arr, 4, 88)\n// result is [100, 101, 102, 99, 88]\n\nJs.Array2.unsafe_set(arr, 6, 77)\n// result is [100, 101, 102, 99, 88, <1 empty item>, 77]\n\nJs.Array2.unsafe_set(arr, -1, 66)\n// you don't want to know.\n```\n"}, "sortText": "unsafe_set", - "textEdit": { - "range": {"start": {"line": 75, "character": 8}, "end": {"line": 75, "character": 9}}, - "newText": "->Js.Array2.unsafe_set" - } + "insertText": "->Js.Array2.unsafe_set", + "additionalTextEdits": [{ + "range": {"start": {"line": 75, "character": 7}, "end": {"line": 75, "character": 8}}, + "newText": "" + }] }] Complete src/DotPipeCompletionSpec.res 80:7 @@ -262,10 +276,11 @@ Path DotPipeCompletionSpec.SomeOtherModule. "detail": "typeOutsideModule => string", "documentation": null, "sortText": "doWithTypeOutsideModule", - "textEdit": { - "range": {"start": {"line": 80, "character": 6}, "end": {"line": 80, "character": 6}}, - "newText": "->doWithTypeOutsideModule" - } + "insertText": "->doWithTypeOutsideModule", + "additionalTextEdits": [{ + "range": {"start": {"line": 80, "character": 6}, "end": {"line": 80, "character": 7}}, + "newText": "" + }] }, { "label": "->SomeOtherModule.getNName", "kind": 12, @@ -273,10 +288,11 @@ Path DotPipeCompletionSpec.SomeOtherModule. "detail": "t => string", "documentation": null, "sortText": "getNName", - "textEdit": { - "range": {"start": {"line": 80, "character": 6}, "end": {"line": 80, "character": 6}}, - "newText": "->SomeOtherModule.getNName" - } + "insertText": "->SomeOtherModule.getNName", + "additionalTextEdits": [{ + "range": {"start": {"line": 80, "character": 6}, "end": {"line": 80, "character": 7}}, + "newText": "" + }] }, { "label": "->SomeOtherModule.getNName2", "kind": 12, @@ -284,9 +300,10 @@ Path DotPipeCompletionSpec.SomeOtherModule. "detail": "typeOutsideModule => string", "documentation": null, "sortText": "getNName2", - "textEdit": { - "range": {"start": {"line": 80, "character": 6}, "end": {"line": 80, "character": 6}}, - "newText": "->SomeOtherModule.getNName2" - } + "insertText": "->SomeOtherModule.getNName2", + "additionalTextEdits": [{ + "range": {"start": {"line": 80, "character": 6}, "end": {"line": 80, "character": 7}}, + "newText": "" + }] }] diff --git a/analysis/tests/src/expected/Rename.res.txt b/analysis/tests/src/expected/Rename.res.txt index 5cd2adfee..92c381452 100644 --- a/analysis/tests/src/expected/Rename.res.txt +++ b/analysis/tests/src/expected/Rename.res.txt @@ -6,15 +6,15 @@ Rename src/Rename.res 0:4 y "uri": "Rename.res" }, "edits": [{ - "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}}, - "newText": "y" - }, { - "range": {"start": {"line": 3, "character": 8}, "end": {"line": 3, "character": 9}}, - "newText": "y" - }, { - "range": {"start": {"line": 7, "character": 8}, "end": {"line": 7, "character": 9}}, - "newText": "y" - }] + "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}}, + "newText": "y" + }, { + "range": {"start": {"line": 3, "character": 8}, "end": {"line": 3, "character": 9}}, + "newText": "y" + }, { + "range": {"start": {"line": 7, "character": 8}, "end": {"line": 7, "character": 9}}, + "newText": "y" + }] } ] @@ -26,12 +26,12 @@ Rename src/Rename.res 9:19 yy "uri": "Rename.res" }, "edits": [{ - "range": {"start": {"line": 9, "character": 11}, "end": {"line": 9, "character": 14}}, - "newText": "yy" - }, { - "range": {"start": {"line": 9, "character": 19}, "end": {"line": 9, "character": 21}}, - "newText": "yy" - }] + "range": {"start": {"line": 9, "character": 11}, "end": {"line": 9, "character": 14}}, + "newText": "yy" + }, { + "range": {"start": {"line": 9, "character": 19}, "end": {"line": 9, "character": 21}}, + "newText": "yy" + }] } ] diff --git a/analysis/tests/src/expected/RenameWithInterface.res.txt b/analysis/tests/src/expected/RenameWithInterface.res.txt index a13988fa9..48ecdabc5 100644 --- a/analysis/tests/src/expected/RenameWithInterface.res.txt +++ b/analysis/tests/src/expected/RenameWithInterface.res.txt @@ -6,9 +6,9 @@ Rename src/RenameWithInterface.res 0:4 y "uri": "RenameWithInterface.resi" }, "edits": [{ - "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}}, - "newText": "y" - }] + "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}}, + "newText": "y" + }] }, { "textDocument": { @@ -16,9 +16,9 @@ Rename src/RenameWithInterface.res 0:4 y "uri": "RenameWithInterface.res" }, "edits": [{ - "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}}, - "newText": "y" - }] + "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}}, + "newText": "y" + }] }, { "textDocument": { @@ -26,12 +26,12 @@ Rename src/RenameWithInterface.res 0:4 y "uri": "Cross.res" }, "edits": [{ - "range": {"start": {"line": 18, "character": 28}, "end": {"line": 18, "character": 29}}, - "newText": "y" - }, { - "range": {"start": {"line": 21, "character": 28}, "end": {"line": 21, "character": 29}}, - "newText": "y" - }] + "range": {"start": {"line": 18, "character": 28}, "end": {"line": 18, "character": 29}}, + "newText": "y" + }, { + "range": {"start": {"line": 21, "character": 28}, "end": {"line": 21, "character": 29}}, + "newText": "y" + }] } ] diff --git a/analysis/tests/src/expected/RenameWithInterface.resi.txt b/analysis/tests/src/expected/RenameWithInterface.resi.txt index 2a1dabb44..3c5d52909 100644 --- a/analysis/tests/src/expected/RenameWithInterface.resi.txt +++ b/analysis/tests/src/expected/RenameWithInterface.resi.txt @@ -6,9 +6,9 @@ Rename src/RenameWithInterface.resi 0:4 y "uri": "RenameWithInterface.resi" }, "edits": [{ - "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}}, - "newText": "y" - }] + "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}}, + "newText": "y" + }] }, { "textDocument": { @@ -16,9 +16,9 @@ Rename src/RenameWithInterface.resi 0:4 y "uri": "RenameWithInterface.res" }, "edits": [{ - "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}}, - "newText": "y" - }] + "range": {"start": {"line": 0, "character": 4}, "end": {"line": 0, "character": 5}}, + "newText": "y" + }] }, { "textDocument": { @@ -26,12 +26,12 @@ Rename src/RenameWithInterface.resi 0:4 y "uri": "Cross.res" }, "edits": [{ - "range": {"start": {"line": 18, "character": 28}, "end": {"line": 18, "character": 29}}, - "newText": "y" - }, { - "range": {"start": {"line": 21, "character": 28}, "end": {"line": 21, "character": 29}}, - "newText": "y" - }] + "range": {"start": {"line": 18, "character": 28}, "end": {"line": 18, "character": 29}}, + "newText": "y" + }, { + "range": {"start": {"line": 21, "character": 28}, "end": {"line": 21, "character": 29}}, + "newText": "y" + }] } ] diff --git a/analysis/tests/src/expected/RxjsCompletion.res.txt b/analysis/tests/src/expected/RxjsCompletion.res.txt index 7f2f2f4cc..2164d14e7 100644 --- a/analysis/tests/src/expected/RxjsCompletion.res.txt +++ b/analysis/tests/src/expected/RxjsCompletion.res.txt @@ -24,10 +24,11 @@ Path Rxjs. "detail": "(t<'t>, 't => unit) => subscription", "documentation": null, "sortText": "subscribe", - "textEdit": { - "range": {"start": {"line": 26, "character": 28}, "end": {"line": 26, "character": 28}}, - "newText": "->Observable.subscribe" - } + "insertText": "->Observable.subscribe", + "additionalTextEdits": [{ + "range": {"start": {"line": 26, "character": 28}, "end": {"line": 26, "character": 29}}, + "newText": "" + }] }, { "label": "->pipe", "kind": 12, @@ -35,10 +36,11 @@ Path Rxjs. "detail": "(Observable.t<'t>, operation<'t, 'u>) => Observable.t<'u>", "documentation": null, "sortText": "pipe", - "textEdit": { - "range": {"start": {"line": 26, "character": 28}, "end": {"line": 26, "character": 28}}, - "newText": "->pipe" - } + "insertText": "->pipe", + "additionalTextEdits": [{ + "range": {"start": {"line": 26, "character": 28}, "end": {"line": 26, "character": 29}}, + "newText": "" + }] }, { "label": "->combineLatest", "kind": 12, @@ -46,10 +48,11 @@ Path Rxjs. "detail": "(\n Observable.t<'a>,\n Observable.t<'b>,\n) => Observable.t<('a, 'b)>", "documentation": null, "sortText": "combineLatest", - "textEdit": { - "range": {"start": {"line": 26, "character": 28}, "end": {"line": 26, "character": 28}}, - "newText": "->combineLatest" - } + "insertText": "->combineLatest", + "additionalTextEdits": [{ + "range": {"start": {"line": 26, "character": 28}, "end": {"line": 26, "character": 29}}, + "newText": "" + }] }, { "label": "->merge", "kind": 12, @@ -57,10 +60,11 @@ Path Rxjs. "detail": "(Observable.t<'t>, Observable.t<'t>) => Observable.t<'t>", "documentation": null, "sortText": "merge", - "textEdit": { - "range": {"start": {"line": 26, "character": 28}, "end": {"line": 26, "character": 28}}, - "newText": "->merge" - } + "insertText": "->merge", + "additionalTextEdits": [{ + "range": {"start": {"line": 26, "character": 28}, "end": {"line": 26, "character": 29}}, + "newText": "" + }] }, { "label": "->pipe2", "kind": 12, @@ -68,10 +72,11 @@ Path Rxjs. "detail": "(\n Observable.t<'t>,\n operation<'t, 'u>,\n operation<'u, 'i>,\n) => Observable.t<'i>", "documentation": null, "sortText": "pipe2", - "textEdit": { - "range": {"start": {"line": 26, "character": 28}, "end": {"line": 26, "character": 28}}, - "newText": "->pipe2" - } + "insertText": "->pipe2", + "additionalTextEdits": [{ + "range": {"start": {"line": 26, "character": 28}, "end": {"line": 26, "character": 29}}, + "newText": "" + }] }] Complete src/RxjsCompletion.res 34:30 @@ -98,10 +103,11 @@ Path Rxjs. "detail": "(t<'t>, 't => unit) => subscription", "documentation": null, "sortText": "subscribe", - "textEdit": { - "range": {"start": {"line": 34, "character": 29}, "end": {"line": 34, "character": 29}}, - "newText": "->Rxjs.Observable.subscribe" - } + "insertText": "->Rxjs.Observable.subscribe", + "additionalTextEdits": [{ + "range": {"start": {"line": 34, "character": 29}, "end": {"line": 34, "character": 30}}, + "newText": "" + }] }, { "label": "->Rxjs.pipe", "kind": 12, @@ -109,10 +115,11 @@ Path Rxjs. "detail": "(Observable.t<'t>, operation<'t, 'u>) => Observable.t<'u>", "documentation": null, "sortText": "pipe", - "textEdit": { - "range": {"start": {"line": 34, "character": 29}, "end": {"line": 34, "character": 29}}, - "newText": "->Rxjs.pipe" - } + "insertText": "->Rxjs.pipe", + "additionalTextEdits": [{ + "range": {"start": {"line": 34, "character": 29}, "end": {"line": 34, "character": 30}}, + "newText": "" + }] }, { "label": "->Rxjs.combineLatest", "kind": 12, @@ -120,10 +127,11 @@ Path Rxjs. "detail": "(\n Observable.t<'a>,\n Observable.t<'b>,\n) => Observable.t<('a, 'b)>", "documentation": null, "sortText": "combineLatest", - "textEdit": { - "range": {"start": {"line": 34, "character": 29}, "end": {"line": 34, "character": 29}}, - "newText": "->Rxjs.combineLatest" - } + "insertText": "->Rxjs.combineLatest", + "additionalTextEdits": [{ + "range": {"start": {"line": 34, "character": 29}, "end": {"line": 34, "character": 30}}, + "newText": "" + }] }, { "label": "->Rxjs.merge", "kind": 12, @@ -131,10 +139,11 @@ Path Rxjs. "detail": "(Observable.t<'t>, Observable.t<'t>) => Observable.t<'t>", "documentation": null, "sortText": "merge", - "textEdit": { - "range": {"start": {"line": 34, "character": 29}, "end": {"line": 34, "character": 29}}, - "newText": "->Rxjs.merge" - } + "insertText": "->Rxjs.merge", + "additionalTextEdits": [{ + "range": {"start": {"line": 34, "character": 29}, "end": {"line": 34, "character": 30}}, + "newText": "" + }] }, { "label": "->Rxjs.pipe2", "kind": 12, @@ -142,9 +151,10 @@ Path Rxjs. "detail": "(\n Observable.t<'t>,\n operation<'t, 'u>,\n operation<'u, 'i>,\n) => Observable.t<'i>", "documentation": null, "sortText": "pipe2", - "textEdit": { - "range": {"start": {"line": 34, "character": 29}, "end": {"line": 34, "character": 29}}, - "newText": "->Rxjs.pipe2" - } + "insertText": "->Rxjs.pipe2", + "additionalTextEdits": [{ + "range": {"start": {"line": 34, "character": 29}, "end": {"line": 34, "character": 30}}, + "newText": "" + }] }] From b1c68509f4cd825c5e98bd6280e43af9331b1ba7 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Tue, 31 Dec 2024 09:36:10 +0100 Subject: [PATCH 27/34] contonous dot completion --- analysis/src/CompletionBackEnd.ml | 26 ++++- analysis/src/CompletionFrontEnd.ml | 24 ++-- analysis/src/SharedTypes.ml | 2 +- analysis/tests/src/DotPipeCompletionSpec.res | 9 ++ .../tests/src/expected/Completion.res.txt | 54 +++++++++ .../expected/CompletionExpressions.res.txt | 9 ++ .../src/expected/CompletionFromModule.res.txt | 6 + .../expected/CompletionFromModule2.res.txt | 6 + .../expected/CompletionInferValues.res.txt | 62 +++++++++++ ...mpletionMultipleEditorCompleteFrom.res.txt | 3 + .../src/expected/CompletionPipeChain.res.txt | 12 ++ .../expected/CompletionPipeProperty.res.txt | 12 ++ .../expected/CompletionPipeSubmodules.res.txt | 27 +++++ .../expected/DotCompletionEverywhere.res.txt | 30 +++++ .../expected/DotPipeCompletionSpec.res.txt | 104 +++++++++++++++++- analysis/tests/src/expected/Hover.res.txt | 51 +++++++++ .../src/expected/RecordCompletion.res.txt | 15 +++ .../tests/src/expected/RxjsCompletion.res.txt | 8 ++ 18 files changed, 442 insertions(+), 18 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index bf1b674ba..fc718588c 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -1080,7 +1080,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact path @ [fieldName] |> getCompletionsForPath ~debug ~opens ~full ~pos ~exact ~completionContext:Field ~env ~scope - | CPField {contextPath = cp; fieldName; fieldNameLoc; posOfDot} -> ( + | CPField {contextPath = cp; fieldName; posOfDot; exprLoc} -> ( if Debug.verbose () then print_endline "[dot_completion]--> Triggered"; let completionsFromCtxPath = cp @@ -1102,10 +1102,28 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact DotCompletionUtils.fieldCompletionsForDotCompletion typ ~env ~package ~prefix:fieldName ?posOfDot ~exact in + let cpAsPipeCompletion = + Completable.CPPipe + { + contextPath = + (match cp with + | CPApply (c, args) -> CPApply (c, args @ [Asttypes.Nolabel]) + | c -> c); + id = fieldName; + inJsx = false; + lhsLoc = exprLoc; + } + in + let completionsFromPipeCtxPath = + cpAsPipeCompletion + |> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos + ~env:envCompletionIsMadeFrom ~exact ~scope + in let pipeCompletions = - getPipeCompletions ~env ~full ~identifierLoc:fieldNameLoc ?posOfDot - ~envCompletionIsMadeFrom ~debug ~opens ~rawOpens ~scope ~pos - ~inJsx:false ~prefix:fieldName ~formatCompletionsWithPipes:true typ + completionsFromPipeCtxPath + |> List.filter_map (fun c -> + TypeUtils.transformCompletionToPipeCompletion ~synthetic:true + ~env ?posOfDot c) in fieldCompletions @ pipeCompletions) | CPObj (cp, label) -> ( diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index e05741a92..ab224285a 100644 --- a/analysis/src/CompletionFrontEnd.ml +++ b/analysis/src/CompletionFrontEnd.ml @@ -222,14 +222,19 @@ let rec exprToContextPathInner (e : Parsetree.expression) = | Pexp_ident {txt; loc} -> Some (CPId {path = Utils.flattenLongIdent txt; completionContext = Value; loc}) - | Pexp_field (e1, {txt = Lident name; loc}) -> ( + | Pexp_field (e1, {txt = Lident name}) -> ( match exprToContextPath e1 with | Some contextPath -> Some (CPField - {contextPath; fieldName = name; fieldNameLoc = loc; posOfDot = None}) + { + contextPath; + fieldName = name; + posOfDot = None; + exprLoc = e1.pexp_loc; + }) | _ -> None) - | Pexp_field (_, {loc; txt = Ldot (lid, name)}) -> + | Pexp_field (e1, {loc; txt = Ldot (lid, name)}) -> (* Case x.M.field ignore the x part *) Some (CPField @@ -242,8 +247,8 @@ let rec exprToContextPathInner (e : Parsetree.expression) = loc; }; fieldName = name; - fieldNameLoc = loc; posOfDot = None; + exprLoc = e1.pexp_loc; }) | Pexp_send (e1, {txt}) -> ( match exprToContextPath e1 with @@ -1170,8 +1175,8 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor { contextPath; fieldName = name; - fieldNameLoc = fieldName.loc; posOfDot; + exprLoc = e.pexp_loc; } in setResult (Cpath contextPath) @@ -1193,8 +1198,8 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor (* x.M. field ---> M. *) "" else if name = "_" then "" else name); - fieldNameLoc = fieldName.loc; posOfDot; + exprLoc = e.pexp_loc; } in setResult (Cpath contextPath) @@ -1208,13 +1213,8 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor { contextPath; fieldName = ""; - fieldNameLoc = - { - loc_start = e.pexp_loc.loc_end; - loc_end = e.pexp_loc.loc_end; - loc_ghost = false; - }; posOfDot; + exprLoc = e.pexp_loc; })) | None -> ()) | Pexp_apply ({pexp_desc = Pexp_ident compName}, args) diff --git a/analysis/src/SharedTypes.ml b/analysis/src/SharedTypes.ml index e3214701b..0a2443e93 100644 --- a/analysis/src/SharedTypes.ml +++ b/analysis/src/SharedTypes.ml @@ -615,7 +615,7 @@ module Completable = struct contextPath: contextPath; fieldName: string; posOfDot: (int * int) option; - fieldNameLoc: Location.t; + exprLoc: Location.t; } | CPObj of contextPath * string | CPAwait of contextPath diff --git a/analysis/tests/src/DotPipeCompletionSpec.res b/analysis/tests/src/DotPipeCompletionSpec.res index 2e5dace68..ae9a94de9 100644 --- a/analysis/tests/src/DotPipeCompletionSpec.res +++ b/analysis/tests/src/DotPipeCompletionSpec.res @@ -80,3 +80,12 @@ let ffff: builtinType = [] let nnn: typeOutsideModule = {nname: "hello"} // nnn. // ^com + +// Continuous completion +let xxxx = [1, 2] + +// xxxx->Js.Array2.filter(v => v > 10).filt +// ^com + +// xxxx->Js.Array2.filter(v => v > 10)->Js.Array2.joinWith(",").includ +// ^com diff --git a/analysis/tests/src/expected/Completion.res.txt b/analysis/tests/src/expected/Completion.res.txt index b1ef0f6d6..6b8c8577a 100644 --- a/analysis/tests/src/expected/Completion.res.txt +++ b/analysis/tests/src/expected/Completion.res.txt @@ -723,6 +723,9 @@ Resolved opens 1 pervasives ContextPath Value[r]."" ContextPath Value[r] Path r +ContextPath Value[r]-> +ContextPath Value[r] +Path r CPPipe pathFromEnv: found:true Path Completion. [{ @@ -748,6 +751,9 @@ Resolved opens 1 pervasives ContextPath Value[Objects, Rec, recordVal]."" ContextPath Value[Objects, Rec, recordVal] Path Objects.Rec.recordVal +ContextPath Value[Objects, Rec, recordVal]-> +ContextPath Value[Objects, Rec, recordVal] +Path Objects.Rec.recordVal CPPipe pathFromEnv:Rec found:true Path Objects.Rec. [{ @@ -832,6 +838,18 @@ ContextPath Value[q].aa."" ContextPath Value[q].aa ContextPath Value[q] Path q +ContextPath Value[q]->aa +ContextPath Value[q] +Path q +CPPipe pathFromEnv: found:true +Path Completion.aa +ContextPath Value[q].aa-> +ContextPath Value[q].aa +ContextPath Value[q] +Path q +ContextPath Value[q]->aa +ContextPath Value[q] +Path q CPPipe pathFromEnv: found:true Path Completion.aa CPPipe pathFromEnv: found:true @@ -860,6 +878,18 @@ ContextPath Value[q].aa.n ContextPath Value[q].aa ContextPath Value[q] Path q +ContextPath Value[q]->aa +ContextPath Value[q] +Path q +CPPipe pathFromEnv: found:true +Path Completion.aa +ContextPath Value[q].aa->n +ContextPath Value[q].aa +ContextPath Value[q] +Path q +ContextPath Value[q]->aa +ContextPath Value[q] +Path q CPPipe pathFromEnv: found:true Path Completion.aa CPPipe pathFromEnv: found:true @@ -1088,6 +1118,10 @@ ContextPath Value[FAO, forAutoObject]["forAutoLabel"]."" ContextPath Value[FAO, forAutoObject]["forAutoLabel"] ContextPath Value[FAO, forAutoObject] Path FAO.forAutoObject +ContextPath Value[FAO, forAutoObject]["forAutoLabel"]-> +ContextPath Value[FAO, forAutoObject]["forAutoLabel"] +ContextPath Value[FAO, forAutoObject] +Path FAO.forAutoObject CPPipe pathFromEnv:FAR found:true Path FAR. [{ @@ -1115,6 +1149,10 @@ ContextPath Value[FAO, forAutoObject]["forAutoLabel"].forAuto ContextPath Value[FAO, forAutoObject]["forAutoLabel"] ContextPath Value[FAO, forAutoObject] Path FAO.forAutoObject +ContextPath Value[FAO, forAutoObject]["forAutoLabel"]->forAuto +ContextPath Value[FAO, forAutoObject]["forAutoLabel"] +ContextPath Value[FAO, forAutoObject] +Path FAO.forAutoObject CPPipe pathFromEnv:FAR found:true Path FAR.forAuto CPPipe pathFromEnv:ForAuto found:false @@ -1198,6 +1236,9 @@ Resolved opens 3 pervasives Completion.res Completion.res ContextPath Value[_z]."" ContextPath Value[_z] Path _z +ContextPath Value[_z]-> +ContextPath Value[_z] +Path _z CPPipe pathFromEnv: found:true Path Completion. [{ @@ -1358,6 +1399,9 @@ Resolved opens 3 pervasives Completion.res Completion.res ContextPath Value[funRecord].someFun ContextPath Value[funRecord] Path funRecord +ContextPath Value[funRecord]->someFun +ContextPath Value[funRecord] +Path funRecord CPPipe pathFromEnv: found:true Path Completion.someFun Found type for function (~name: string) => unit @@ -1380,6 +1424,10 @@ ContextPath Value[retAA](Nolabel)."" ContextPath Value[retAA](Nolabel) ContextPath Value[retAA] Path retAA +ContextPath Value[retAA](Nolabel, Nolabel)-> +ContextPath Value[retAA](Nolabel, Nolabel) +ContextPath Value[retAA] +Path retAA CPPipe pathFromEnv: found:true Path Completion. [{ @@ -1912,6 +1960,9 @@ Resolved opens 3 pervasives Completion.res Completion.res ContextPath Value[funRecord]."" ContextPath Value[funRecord] Path funRecord +ContextPath Value[funRecord]-> +ContextPath Value[funRecord] +Path funRecord CPPipe pathFromEnv: found:true Path Completion. [{ @@ -2166,6 +2217,9 @@ Resolved opens 3 pervasives Completion.res Completion.res ContextPath Value[rWithDepr].so ContextPath Value[rWithDepr] Path rWithDepr +ContextPath Value[rWithDepr]->so +ContextPath Value[rWithDepr] +Path rWithDepr CPPipe pathFromEnv: found:true Path Completion.so [{ diff --git a/analysis/tests/src/expected/CompletionExpressions.res.txt b/analysis/tests/src/expected/CompletionExpressions.res.txt index 4338375b6..1065df396 100644 --- a/analysis/tests/src/expected/CompletionExpressions.res.txt +++ b/analysis/tests/src/expected/CompletionExpressions.res.txt @@ -946,6 +946,9 @@ Resolved opens 1 pervasives ContextPath Value[fff].someOpt ContextPath Value[fff] Path fff +ContextPath Value[fff]->someOpt +ContextPath Value[fff] +Path fff CPPipe pathFromEnv: found:true Path CompletionExpressions.someOpt [{ @@ -1417,6 +1420,9 @@ Resolved opens 2 pervasives CompletionSupport.res ContextPath Value[someTyp]."" ContextPath Value[someTyp] Path someTyp +ContextPath Value[someTyp]-> +ContextPath Value[someTyp] +Path someTyp CPPipe pathFromEnv: found:true Path CompletionExpressions. [{ @@ -1474,6 +1480,9 @@ Resolved opens 2 pervasives CompletionSupport.res ContextPath Value[someTyp]."" ContextPath Value[someTyp] Path someTyp +ContextPath Value[someTyp]-> +ContextPath Value[someTyp] +Path someTyp CPPipe pathFromEnv: found:true Path CompletionExpressions. [{ diff --git a/analysis/tests/src/expected/CompletionFromModule.res.txt b/analysis/tests/src/expected/CompletionFromModule.res.txt index 4439f9f20..01512adcc 100644 --- a/analysis/tests/src/expected/CompletionFromModule.res.txt +++ b/analysis/tests/src/expected/CompletionFromModule.res.txt @@ -7,6 +7,9 @@ Resolved opens 1 pervasives ContextPath Value[n]."" ContextPath Value[n] Path n +ContextPath Value[n]-> +ContextPath Value[n] +Path n CPPipe pathFromEnv:SomeModule found:true Path SomeModule. [{ @@ -38,6 +41,9 @@ Resolved opens 1 pervasives ContextPath Value[nn]."" ContextPath Value[nn] Path nn +ContextPath Value[nn]-> +ContextPath Value[nn] +Path nn CPPipe pathFromEnv:SomeOtherModule found:true Path SomeOtherModule. Path CompletionFromModule.SomeOtherModule. diff --git a/analysis/tests/src/expected/CompletionFromModule2.res.txt b/analysis/tests/src/expected/CompletionFromModule2.res.txt index 1f91137dc..0f60e0158 100644 --- a/analysis/tests/src/expected/CompletionFromModule2.res.txt +++ b/analysis/tests/src/expected/CompletionFromModule2.res.txt @@ -7,6 +7,9 @@ Resolved opens 1 pervasives ContextPath Value[CompletionFromModule, n]."" ContextPath Value[CompletionFromModule, n] Path CompletionFromModule.n +ContextPath Value[CompletionFromModule, n]-> +ContextPath Value[CompletionFromModule, n] +Path CompletionFromModule.n CPPipe pathFromEnv:SomeModule found:true Path CompletionFromModule.SomeModule. [{ @@ -38,6 +41,9 @@ Resolved opens 1 pervasives ContextPath Value[CompletionFromModule, nn]."" ContextPath Value[CompletionFromModule, nn] Path CompletionFromModule.nn +ContextPath Value[CompletionFromModule, nn]-> +ContextPath Value[CompletionFromModule, nn] +Path CompletionFromModule.nn CPPipe pathFromEnv:SomeOtherModule found:true Path CompletionFromModule.SomeOtherModule. Path CompletionFromModule.SomeOtherModule. diff --git a/analysis/tests/src/expected/CompletionInferValues.res.txt b/analysis/tests/src/expected/CompletionInferValues.res.txt index 86454171d..57fc82343 100644 --- a/analysis/tests/src/expected/CompletionInferValues.res.txt +++ b/analysis/tests/src/expected/CompletionInferValues.res.txt @@ -36,6 +36,12 @@ Path x ContextPath Value[getSomeRecord](Nolabel) ContextPath Value[getSomeRecord] Path getSomeRecord +ContextPath Value[x]-> +ContextPath Value[x] +Path x +ContextPath Value[getSomeRecord](Nolabel) +ContextPath Value[getSomeRecord] +Path getSomeRecord CPPipe pathFromEnv: found:true Path CompletionInferValues. [{ @@ -66,6 +72,14 @@ Path x ContextPath Value[getSomeRecord](Nolabel) ContextPath Value[getSomeRecord] Path getSomeRecord +ContextPath Value[aliased]-> +ContextPath Value[aliased] +Path aliased +ContextPath Value[x] +Path x +ContextPath Value[getSomeRecord](Nolabel) +ContextPath Value[getSomeRecord] +Path getSomeRecord CPPipe pathFromEnv: found:true Path CompletionInferValues. [{ @@ -101,6 +115,13 @@ ContextPath CArgument CArgument Value[someFnWithCallback]($0)(~someRecord) ContextPath CArgument Value[someFnWithCallback]($0) ContextPath Value[someFnWithCallback] Path someFnWithCallback +ContextPath Value[someRecord]-> +ContextPath Value[someRecord] +Path someRecord +ContextPath CArgument CArgument Value[someFnWithCallback]($0)(~someRecord) +ContextPath CArgument Value[someFnWithCallback]($0) +ContextPath Value[someFnWithCallback] +Path someFnWithCallback CPPipe pathFromEnv: found:true Path CompletionInferValues. [{ @@ -138,6 +159,15 @@ ContextPath Value[aliasedFn] Path aliasedFn ContextPath Value[someFnWithCallback] Path someFnWithCallback +ContextPath Value[someRecord]-> +ContextPath Value[someRecord] +Path someRecord +ContextPath CArgument CArgument Value[aliasedFn]($0)(~someRecord) +ContextPath CArgument Value[aliasedFn]($0) +ContextPath Value[aliasedFn] +Path aliasedFn +ContextPath Value[someFnWithCallback] +Path someFnWithCallback CPPipe pathFromEnv: found:true Path CompletionInferValues. [{ @@ -350,6 +380,14 @@ ContextPath Value[x] Path x ContextPath Type[someRecordWithNestedStuff] Path someRecordWithNestedStuff +ContextPath Value[srecord]-> +ContextPath Value[srecord] +Path srecord +ContextPath CPatternPath(Value[x])->recordField(srecord) +ContextPath Value[x] +Path x +ContextPath Type[someRecordWithNestedStuff] +Path someRecordWithNestedStuff CPPipe pathFromEnv: found:true Path CompletionInferValues. [{ @@ -380,6 +418,14 @@ ContextPath Value[x] Path x ContextPath Type[someRecordWithNestedStuff] Path someRecordWithNestedStuff +ContextPath Value[aliased]-> +ContextPath Value[aliased] +Path aliased +ContextPath CPatternPath(Value[x])->recordField(nested) +ContextPath Value[x] +Path x +ContextPath Type[someRecordWithNestedStuff] +Path someRecordWithNestedStuff CPPipe pathFromEnv: found:true Path CompletionInferValues. [{ @@ -404,6 +450,14 @@ ContextPath Value[x] Path x ContextPath Type[someRecordWithNestedStuff] Path someRecordWithNestedStuff +ContextPath Value[someRecord]-> +ContextPath Value[someRecord] +Path someRecord +ContextPath CPatternPath(Value[x])->recordField(nested)->recordField(someRecord) +ContextPath Value[x] +Path x +ContextPath Type[someRecordWithNestedStuff] +Path someRecordWithNestedStuff CPPipe pathFromEnv: found:true Path CompletionInferValues. [{ @@ -777,6 +831,14 @@ ContextPath CArgument CArgument Value[CompletionSupport2, makeRenderer](~render) ContextPath CArgument Value[CompletionSupport2, makeRenderer](~render) ContextPath Value[CompletionSupport2, makeRenderer] Path CompletionSupport2.makeRenderer +ContextPath Value[support]-> +ContextPath Value[support] +Path support +ContextPath CPatternPath(CArgument CArgument Value[CompletionSupport2, makeRenderer](~render)($0))->recordField(support) +ContextPath CArgument CArgument Value[CompletionSupport2, makeRenderer](~render)($0) +ContextPath CArgument Value[CompletionSupport2, makeRenderer](~render) +ContextPath Value[CompletionSupport2, makeRenderer] +Path CompletionSupport2.makeRenderer CPPipe pathFromEnv:CompletionSupport.Nested found:false Path CompletionSupport.Nested. [{ diff --git a/analysis/tests/src/expected/CompletionMultipleEditorCompleteFrom.res.txt b/analysis/tests/src/expected/CompletionMultipleEditorCompleteFrom.res.txt index 43ea9a439..76504e198 100644 --- a/analysis/tests/src/expected/CompletionMultipleEditorCompleteFrom.res.txt +++ b/analysis/tests/src/expected/CompletionMultipleEditorCompleteFrom.res.txt @@ -7,6 +7,9 @@ Resolved opens 1 pervasives ContextPath Value[a]."" ContextPath Value[a] Path a +ContextPath Value[a]-> +ContextPath Value[a] +Path a CPPipe pathFromEnv:A found:true Path A. Path B. diff --git a/analysis/tests/src/expected/CompletionPipeChain.res.txt b/analysis/tests/src/expected/CompletionPipeChain.res.txt index e3cb6ae63..d1eb333af 100644 --- a/analysis/tests/src/expected/CompletionPipeChain.res.txt +++ b/analysis/tests/src/expected/CompletionPipeChain.res.txt @@ -360,6 +360,18 @@ ContextPath Value[props].support.root ContextPath Value[props].support ContextPath Value[props] Path props +ContextPath Value[props]->support +ContextPath Value[props] +Path props +CPPipe pathFromEnv:CompletionSupport2.Internal found:false +Path CompletionSupport2.Internal.support +ContextPath Value[props].support->root +ContextPath Value[props].support +ContextPath Value[props] +Path props +ContextPath Value[props]->support +ContextPath Value[props] +Path props CPPipe pathFromEnv:CompletionSupport2.Internal found:false Path CompletionSupport2.Internal.support CPPipe pathFromEnv:CompletionSupport.Nested found:false diff --git a/analysis/tests/src/expected/CompletionPipeProperty.res.txt b/analysis/tests/src/expected/CompletionPipeProperty.res.txt index 4532daa15..ac83d3192 100644 --- a/analysis/tests/src/expected/CompletionPipeProperty.res.txt +++ b/analysis/tests/src/expected/CompletionPipeProperty.res.txt @@ -8,6 +8,18 @@ ContextPath Value[sprite].anchor."" ContextPath Value[sprite].anchor ContextPath Value[sprite] Path sprite +ContextPath Value[sprite]->anchor +ContextPath Value[sprite] +Path sprite +CPPipe pathFromEnv:Sprite found:true +Path Sprite.anchor +ContextPath Value[sprite].anchor-> +ContextPath Value[sprite].anchor +ContextPath Value[sprite] +Path sprite +ContextPath Value[sprite]->anchor +ContextPath Value[sprite] +Path sprite CPPipe pathFromEnv:Sprite found:true Path Sprite.anchor CPPipe pathFromEnv:ObservablePoint found:true diff --git a/analysis/tests/src/expected/CompletionPipeSubmodules.res.txt b/analysis/tests/src/expected/CompletionPipeSubmodules.res.txt index ec0f0e47b..90f0e089e 100644 --- a/analysis/tests/src/expected/CompletionPipeSubmodules.res.txt +++ b/analysis/tests/src/expected/CompletionPipeSubmodules.res.txt @@ -25,6 +25,9 @@ ContextPath Value[A, x].v-> ContextPath Value[A, x].v ContextPath Value[A, x] Path A.x +ContextPath Value[A, x]->v +ContextPath Value[A, x] +Path A.x CPPipe pathFromEnv:A found:true Path A.v CPPipe pathFromEnv:A.B1 found:true @@ -47,6 +50,18 @@ ContextPath Value[E, e].v.v ContextPath Value[E, e].v ContextPath Value[E, e] Path E.e +ContextPath Value[E, e]->v +ContextPath Value[E, e] +Path E.e +CPPipe pathFromEnv:E found:true +Path E.v +ContextPath Value[E, e].v->v +ContextPath Value[E, e].v +ContextPath Value[E, e] +Path E.e +ContextPath Value[E, e]->v +ContextPath Value[E, e] +Path E.e CPPipe pathFromEnv:E found:true Path E.v CPPipe pathFromEnv:D found:true @@ -71,6 +86,18 @@ ContextPath Value[E, e].v.v2 ContextPath Value[E, e].v ContextPath Value[E, e] Path E.e +ContextPath Value[E, e]->v +ContextPath Value[E, e] +Path E.e +CPPipe pathFromEnv:E found:true +Path E.v +ContextPath Value[E, e].v->v2 +ContextPath Value[E, e].v +ContextPath Value[E, e] +Path E.e +ContextPath Value[E, e]->v +ContextPath Value[E, e] +Path E.e CPPipe pathFromEnv:E found:true Path E.v CPPipe pathFromEnv:D found:true diff --git a/analysis/tests/src/expected/DotCompletionEverywhere.res.txt b/analysis/tests/src/expected/DotCompletionEverywhere.res.txt index c418f580c..37a7df9e5 100644 --- a/analysis/tests/src/expected/DotCompletionEverywhere.res.txt +++ b/analysis/tests/src/expected/DotCompletionEverywhere.res.txt @@ -7,6 +7,9 @@ Resolved opens 1 pervasives ContextPath Value[someObj]."" ContextPath Value[someObj] Path someObj +ContextPath Value[someObj]-> +ContextPath Value[someObj] +Path someObj [{ "label": "[\"age\"]", "kind": 4, @@ -40,6 +43,9 @@ Resolved opens 1 pervasives ContextPath Value[someObj].na ContextPath Value[someObj] Path someObj +ContextPath Value[someObj]->na +ContextPath Value[someObj] +Path someObj [{ "label": "[\"name\"]", "kind": 4, @@ -62,6 +68,9 @@ Resolved opens 1 pervasives ContextPath Value[rrr].n ContextPath Value[rrr] Path rrr +ContextPath Value[rrr]->n +ContextPath Value[rrr] +Path rrr CPPipe pathFromEnv: found:true Path DotCompletionEverywhere.n [{ @@ -81,6 +90,9 @@ Resolved opens 1 pervasives ContextPath Value[x]."" ContextPath Value[x] Path x +ContextPath Value[x]-> +ContextPath Value[x] +Path x CPPipe pathFromEnv:SomeMod.SomeOtherMod found:true Path SomeMod.SomeOtherMod. [{ @@ -106,6 +118,9 @@ Resolved opens 1 pervasives ContextPath Value[SomeMod, SomeOtherMod, xx]."" ContextPath Value[SomeMod, SomeOtherMod, xx] Path SomeMod.SomeOtherMod.xx +ContextPath Value[SomeMod, SomeOtherMod, xx]-> +ContextPath Value[SomeMod, SomeOtherMod, xx] +Path SomeMod.SomeOtherMod.xx CPPipe pathFromEnv:SomeMod.SomeOtherMod found:true Path SomeMod.SomeOtherMod. [{ @@ -131,6 +146,9 @@ Resolved opens 1 pervasives ContextPath Value[Sss, rrr]."" ContextPath Value[Sss, rrr] Path Sss.rrr +ContextPath Value[Sss, rrr]-> +ContextPath Value[Sss, rrr] +Path Sss.rrr CPPipe pathFromEnv:Sss found:true Path Sss. [{ @@ -162,6 +180,9 @@ Resolved opens 1 pervasives ContextPath Value[x2x2]."" ContextPath Value[x2x2] Path x2x2 +ContextPath Value[x2x2]-> +ContextPath Value[x2x2] +Path x2x2 CPPipe pathFromEnv: found:true Path DotCompletionEverywhere. Path DotCompletionEverywhere.X2. @@ -194,6 +215,9 @@ Resolved opens 1 pervasives ContextPath Value[obj]."" ContextPath Value[obj] Path obj +ContextPath Value[obj]-> +ContextPath Value[obj] +Path obj [{ "label": "[\"name\"]", "kind": 4, @@ -238,6 +262,9 @@ Resolved opens 1 pervasives ContextPath Value[arr].m ContextPath Value[arr] Path arr +ContextPath Value[arr]->m +ContextPath Value[arr] +Path arr Path Js.Array2.m [{ "label": "->Js.Array2.mapi", @@ -274,6 +301,9 @@ Resolved opens 1 pervasives ContextPath Value[button]."" ContextPath Value[button] Path button +ContextPath Value[button]-> +ContextPath Value[button] +Path button CPPipe pathFromEnv:DOMAPI found:true Path DOMAPI. Path DotCompletionEverywhere.HTMLButtonElement. diff --git a/analysis/tests/src/expected/DotPipeCompletionSpec.res.txt b/analysis/tests/src/expected/DotPipeCompletionSpec.res.txt index fa229c17a..3ec510d2c 100644 --- a/analysis/tests/src/expected/DotPipeCompletionSpec.res.txt +++ b/analysis/tests/src/expected/DotPipeCompletionSpec.res.txt @@ -7,6 +7,9 @@ Resolved opens 1 pervasives ContextPath Value[n]."" ContextPath Value[n] Path n +ContextPath Value[n]-> +ContextPath Value[n] +Path n CPPipe pathFromEnv:SomeModule found:true Path SomeModule. [{ @@ -50,6 +53,9 @@ Resolved opens 1 pervasives ContextPath Value[nn]."" ContextPath Value[nn] Path nn +ContextPath Value[nn]-> +ContextPath Value[nn] +Path nn CPPipe pathFromEnv:SomeOtherModule found:true Path SomeOtherModule. Path DotPipeCompletionSpec.CompleteFromThisToo. @@ -131,6 +137,9 @@ Resolved opens 1 pervasives ContextPath Value[a]."" ContextPath Value[a] Path a +ContextPath Value[a]-> +ContextPath Value[a] +Path a CPPipe pathFromEnv:A found:true Path A. Path B. @@ -169,6 +178,9 @@ Resolved opens 1 pervasives ContextPath Value[xx]."" ContextPath Value[xx] Path xx +ContextPath Value[xx]-> +ContextPath Value[xx] +Path xx CPPipe pathFromEnv:CompletionFromModule.SomeModule found:false Path CompletionFromModule.SomeModule. [{ @@ -200,6 +212,9 @@ Resolved opens 1 pervasives ContextPath Value[ffff].u ContextPath Value[ffff] Path ffff +ContextPath Value[ffff]->u +ContextPath Value[ffff] +Path ffff Path Js.Array2.u [{ "label": "->Js.Array2.unshiftMany", @@ -253,13 +268,16 @@ Path Js.Array2.u Complete src/DotPipeCompletionSpec.res 80:7 posCursor:[80:7] posNoWhite:[80:6] Found expr:[80:3->80:7] -Pexp_field [80:3->80:6] _:[83:0->80:7] +Pexp_field [80:3->80:6] _:[84:0->80:7] Completable: Cpath Value[nnn]."" Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives ContextPath Value[nnn]."" ContextPath Value[nnn] Path nnn +ContextPath Value[nnn]-> +ContextPath Value[nnn] +Path nnn CPPipe pathFromEnv: found:true Path DotPipeCompletionSpec. Path DotPipeCompletionSpec.SomeOtherModule. @@ -307,3 +325,87 @@ Path DotPipeCompletionSpec.SomeOtherModule. }] }] +Complete src/DotPipeCompletionSpec.res 86:43 +posCursor:[86:43] posNoWhite:[86:42] Found expr:[86:3->86:43] +posCursor:[86:43] posNoWhite:[86:42] Found expr:[86:9->86:43] +Pexp_field [86:9->86:38] filt:[86:39->86:43] +Completable: Cpath Value[Js, Array2, filter](Nolabel).filt +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[Js, Array2, filter](Nolabel).filt +ContextPath Value[Js, Array2, filter](Nolabel) +ContextPath Value[Js, Array2, filter] +Path Js.Array2.filter +ContextPath Value[Js, Array2, filter](Nolabel, Nolabel)->filt +ContextPath Value[Js, Array2, filter](Nolabel, Nolabel) +ContextPath Value[Js, Array2, filter] +Path Js.Array2.filter +Path Js.Array2.filt +[{ + "label": "->Js.Array2.filter", + "kind": 12, + "tags": [], + "detail": "(t<'a>, 'a => bool) => t<'a>", + "documentation": {"kind": "markdown", "value": "\nApplies the given predicate function (the second argument) to each element in\nthe array; the result is an array of those elements for which the predicate\nfunction returned `true`. See\n[`Array.filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)\non MDN.\n\n## Examples\n\n```rescript\nlet nonEmpty = s => s != \"\"\nJs.Array2.filter([\"abc\", \"\", \"\", \"def\", \"ghi\"], nonEmpty) == [\"abc\", \"def\", \"ghi\"]\n```\n"}, + "sortText": "filter", + "insertText": "->Js.Array2.filter", + "additionalTextEdits": [{ + "range": {"start": {"line": 86, "character": 38}, "end": {"line": 86, "character": 39}}, + "newText": "" + }] + }, { + "label": "->Js.Array2.filteri", + "kind": 12, + "tags": [], + "detail": "(t<'a>, ('a, int) => bool) => t<'a>", + "documentation": {"kind": "markdown", "value": "\nEach element of the given array are passed to the predicate function. The\nreturn value is an array of all those elements for which the predicate function\nreturned `true`.\n\nSee\n[`Array.filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)\non MDN.\n\n## Examples\n\n```rescript\n// keep only positive elements at odd indices\nlet positiveOddElement = (item, index) => mod(index, 2) == 1 && item > 0\n\nJs.Array2.filteri([6, 3, 5, 8, 7, -4, 1], positiveOddElement) == [3, 8]\n```\n"}, + "sortText": "filteri", + "insertText": "->Js.Array2.filteri", + "additionalTextEdits": [{ + "range": {"start": {"line": 86, "character": 38}, "end": {"line": 86, "character": 39}}, + "newText": "" + }] + }] + +Complete src/DotPipeCompletionSpec.res 89:70 +posCursor:[89:70] posNoWhite:[89:69] Found expr:[89:3->89:70] +posCursor:[89:70] posNoWhite:[89:69] Found expr:[89:40->89:70] +Pexp_field [89:40->89:63] includ:[89:64->89:70] +Completable: Cpath Value[Js, Array2, joinWith](Nolabel).includ +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[Js, Array2, joinWith](Nolabel).includ +ContextPath Value[Js, Array2, joinWith](Nolabel) +ContextPath Value[Js, Array2, joinWith] +Path Js.Array2.joinWith +ContextPath Value[Js, Array2, joinWith](Nolabel, Nolabel)->includ +ContextPath Value[Js, Array2, joinWith](Nolabel, Nolabel) +ContextPath Value[Js, Array2, joinWith] +Path Js.Array2.joinWith +Path Js.String2.includ +[{ + "label": "->Js.String2.includesFrom", + "kind": 12, + "tags": [], + "detail": "(t, t, int) => bool", + "documentation": {"kind": "markdown", "value": "\nES2015: `includes(str, searchValue start)` returns `true` if `searchValue` is\nfound anywhere within `str` starting at character number `start` (where 0 is\nthe first character), `false` otherwise.\n\nSee [`String.includes`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.includesFrom(\"programmer\", \"gram\", 1) == true\nJs.String2.includesFrom(\"programmer\", \"gram\", 4) == false\nJs.String2.includesFrom(`대한민국`, `한`, 1) == true\n```\n"}, + "sortText": "includesFrom", + "insertText": "->Js.String2.includesFrom", + "additionalTextEdits": [{ + "range": {"start": {"line": 89, "character": 63}, "end": {"line": 89, "character": 64}}, + "newText": "" + }] + }, { + "label": "->Js.String2.includes", + "kind": 12, + "tags": [], + "detail": "(t, t) => bool", + "documentation": {"kind": "markdown", "value": "\nES2015: `includes(str, searchValue)` returns `true` if `searchValue` is found\nanywhere within `str`, false otherwise.\n\nSee [`String.includes`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.includes(\"programmer\", \"gram\") == true\nJs.String2.includes(\"programmer\", \"er\") == true\nJs.String2.includes(\"programmer\", \"pro\") == true\nJs.String2.includes(\"programmer.dat\", \"xyz\") == false\n```\n"}, + "sortText": "includes", + "insertText": "->Js.String2.includes", + "additionalTextEdits": [{ + "range": {"start": {"line": 89, "character": 63}, "end": {"line": 89, "character": 64}}, + "newText": "" + }] + }] + diff --git a/analysis/tests/src/expected/Hover.res.txt b/analysis/tests/src/expected/Hover.res.txt index 7a314d52b..07aa4ca22 100644 --- a/analysis/tests/src/expected/Hover.res.txt +++ b/analysis/tests/src/expected/Hover.res.txt @@ -124,6 +124,18 @@ ContextPath Value[x1].content."" ContextPath Value[x1].content ContextPath Value[x1] Path x1 +ContextPath Value[x1]->content +ContextPath Value[x1] +Path x1 +CPPipe pathFromEnv: found:true +Path Hover.content +ContextPath Value[x1].content-> +ContextPath Value[x1].content +ContextPath Value[x1] +Path x1 +ContextPath Value[x1]->content +ContextPath Value[x1] +Path x1 CPPipe pathFromEnv: found:true Path Hover.content CPPipe pathFromEnv: found:true @@ -146,6 +158,18 @@ ContextPath Value[x2].content."" ContextPath Value[x2].content ContextPath Value[x2] Path x2 +ContextPath Value[x2]->content +ContextPath Value[x2] +Path x2 +CPPipe pathFromEnv: found:true +Path Hover.content +ContextPath Value[x2].content-> +ContextPath Value[x2].content +ContextPath Value[x2] +Path x2 +ContextPath Value[x2]->content +ContextPath Value[x2] +Path x2 CPPipe pathFromEnv: found:true Path Hover.content CPPipe pathFromEnv: found:true @@ -168,6 +192,18 @@ ContextPath Value[y1].content."" ContextPath Value[y1].content ContextPath Value[y1] Path y1 +ContextPath Value[y1]->content +ContextPath Value[y1] +Path y1 +CPPipe pathFromEnv: found:true +Path Hover.content +ContextPath Value[y1].content-> +ContextPath Value[y1].content +ContextPath Value[y1] +Path y1 +ContextPath Value[y1]->content +ContextPath Value[y1] +Path y1 CPPipe pathFromEnv: found:true Path Hover.content CPPipe pathFromEnv: found:true @@ -190,6 +226,18 @@ ContextPath Value[y2].content."" ContextPath Value[y2].content ContextPath Value[y2] Path y2 +ContextPath Value[y2]->content +ContextPath Value[y2] +Path y2 +CPPipe pathFromEnv: found:true +Path Hover.content +ContextPath Value[y2].content-> +ContextPath Value[y2].content +ContextPath Value[y2] +Path y2 +ContextPath Value[y2]->content +ContextPath Value[y2] +Path y2 CPPipe pathFromEnv: found:true Path Hover.content CPPipe pathFromEnv: found:true @@ -237,6 +285,9 @@ Resolved opens 1 pervasives ContextPath Value[x].someField ContextPath Value[x] Path x +ContextPath Value[x]->someField +ContextPath Value[x] +Path x CPPipe pathFromEnv: found:true Path Hover.someField Package opens Pervasives.JsxModules.place holder diff --git a/analysis/tests/src/expected/RecordCompletion.res.txt b/analysis/tests/src/expected/RecordCompletion.res.txt index 851c5fa92..d0cefde7e 100644 --- a/analysis/tests/src/expected/RecordCompletion.res.txt +++ b/analysis/tests/src/expected/RecordCompletion.res.txt @@ -7,6 +7,9 @@ ContextPath Value[t].n->m ContextPath Value[t].n ContextPath Value[t] Path t +ContextPath Value[t]->n +ContextPath Value[t] +Path t CPPipe pathFromEnv: found:true Path RecordCompletion.n Path Js.Array2.m @@ -34,6 +37,18 @@ ContextPath Value[t2].n2.n ContextPath Value[t2].n2 ContextPath Value[t2] Path t2 +ContextPath Value[t2]->n2 +ContextPath Value[t2] +Path t2 +CPPipe pathFromEnv: found:true +Path RecordCompletion.n2 +ContextPath Value[t2].n2->n +ContextPath Value[t2].n2 +ContextPath Value[t2] +Path t2 +ContextPath Value[t2]->n2 +ContextPath Value[t2] +Path t2 CPPipe pathFromEnv: found:true Path RecordCompletion.n2 CPPipe pathFromEnv: found:true diff --git a/analysis/tests/src/expected/RxjsCompletion.res.txt b/analysis/tests/src/expected/RxjsCompletion.res.txt index 2164d14e7..f8a17be58 100644 --- a/analysis/tests/src/expected/RxjsCompletion.res.txt +++ b/analysis/tests/src/expected/RxjsCompletion.res.txt @@ -14,6 +14,10 @@ ContextPath Value[merge](Nolabel, Nolabel)."" ContextPath Value[merge](Nolabel, Nolabel) ContextPath Value[merge] Path merge +ContextPath Value[merge](Nolabel, Nolabel, Nolabel)-> +ContextPath Value[merge](Nolabel, Nolabel, Nolabel) +ContextPath Value[merge] +Path merge CPPipe pathFromEnv:Observable found:true Path Rxjs.Observable. Path Rxjs. @@ -93,6 +97,10 @@ ContextPath Value[Rxjs, combineLatest](Nolabel, Nolabel)."" ContextPath Value[Rxjs, combineLatest](Nolabel, Nolabel) ContextPath Value[Rxjs, combineLatest] Path Rxjs.combineLatest +ContextPath Value[Rxjs, combineLatest](Nolabel, Nolabel, Nolabel)-> +ContextPath Value[Rxjs, combineLatest](Nolabel, Nolabel, Nolabel) +ContextPath Value[Rxjs, combineLatest] +Path Rxjs.combineLatest CPPipe pathFromEnv:Observable found:true Path Rxjs.Observable. Path Rxjs. From 9c29710314797f9cd64e77250e7ba8fd464f80e7 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Tue, 31 Dec 2024 20:52:39 +0100 Subject: [PATCH 28/34] handle dot completions on piped idents --- analysis/src/CompletionBackEnd.ml | 5 +- analysis/src/TypeUtils.ml | 5 ++ analysis/tests/src/DotPipeCompletionSpec.res | 8 +++ .../expected/DotPipeCompletionSpec.res.txt | 58 +++++++++++++++++++ 4 files changed, 75 insertions(+), 1 deletion(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index fc718588c..4b0740b5e 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -1102,13 +1102,16 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact DotCompletionUtils.fieldCompletionsForDotCompletion typ ~env ~package ~prefix:fieldName ?posOfDot ~exact in + (* Get additional completions acting as if this field completion was actually a pipe completion. *) let cpAsPipeCompletion = Completable.CPPipe { contextPath = (match cp with | CPApply (c, args) -> CPApply (c, args @ [Asttypes.Nolabel]) - | c -> c); + | CPId _ when TypeUtils.isFunctionType ~env ~package typ -> + CPApply (cp, [Asttypes.Nolabel]) + | _ -> cp); id = fieldName; inJsx = false; lhsLoc = exprLoc; diff --git a/analysis/src/TypeUtils.ml b/analysis/src/TypeUtils.ml index c563f9788..1a185f0bc 100644 --- a/analysis/src/TypeUtils.ml +++ b/analysis/src/TypeUtils.ml @@ -491,6 +491,11 @@ let rec extractType ?(printOpeningDebug = true) if Debug.verbose () then print_endline "[extract_type]--> miss"; None +let isFunctionType ~env ~package t = + match extractType ~env ~package t with + | Some (Tfunction _, _) -> true + | _ -> false + let findReturnTypeOfFunctionAtLoc loc ~(env : QueryEnv.t) ~full ~debug = match References.getLocItem ~full ~pos:(loc |> Loc.end_) ~debug with | Some {locType = Typed (_, typExpr, _)} -> ( diff --git a/analysis/tests/src/DotPipeCompletionSpec.res b/analysis/tests/src/DotPipeCompletionSpec.res index ae9a94de9..94bd1599c 100644 --- a/analysis/tests/src/DotPipeCompletionSpec.res +++ b/analysis/tests/src/DotPipeCompletionSpec.res @@ -89,3 +89,11 @@ let xxxx = [1, 2] // xxxx->Js.Array2.filter(v => v > 10)->Js.Array2.joinWith(",").includ // ^com + +let str = "hello" + +// str->Js.String2.toLowerCase.toUpperCa +// ^com + +// str->Js.String2.toLowerCase->Js.String2.toUpperCase.toLowerC +// ^com diff --git a/analysis/tests/src/expected/DotPipeCompletionSpec.res.txt b/analysis/tests/src/expected/DotPipeCompletionSpec.res.txt index 3ec510d2c..2a2a0b6fd 100644 --- a/analysis/tests/src/expected/DotPipeCompletionSpec.res.txt +++ b/analysis/tests/src/expected/DotPipeCompletionSpec.res.txt @@ -409,3 +409,61 @@ Path Js.String2.includ }] }] +Complete src/DotPipeCompletionSpec.res 94:40 +posCursor:[94:40] posNoWhite:[94:39] Found expr:[94:3->94:40] +posCursor:[94:40] posNoWhite:[94:39] Found expr:[94:8->94:40] +Pexp_field [94:8->94:30] toUpperCa:[94:31->94:40] +Completable: Cpath Value[Js, String2, toLowerCase].toUpperCa +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[Js, String2, toLowerCase].toUpperCa +ContextPath Value[Js, String2, toLowerCase] +Path Js.String2.toLowerCase +ContextPath Value[Js, String2, toLowerCase](Nolabel)->toUpperCa +ContextPath Value[Js, String2, toLowerCase](Nolabel) +ContextPath Value[Js, String2, toLowerCase] +Path Js.String2.toLowerCase +Path Js.String2.toUpperCa +[{ + "label": "->Js.String2.toUpperCase", + "kind": 12, + "tags": [], + "detail": "t => t", + "documentation": {"kind": "markdown", "value": "\n`toUpperCase(str)` converts `str` to upper case using the locale-insensitive\ncase mappings in the Unicode Character Database. Notice that the conversion can\nexpand the number of letters in the result; for example the German ß\ncapitalizes to two Ses in a row.\n\nSee [`String.toUpperCase`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.toUpperCase(\"abc\") == \"ABC\"\nJs.String2.toUpperCase(`Straße`) == `STRASSE`\nJs.String2.toUpperCase(`πς`) == `ΠΣ`\n```\n"}, + "sortText": "toUpperCase", + "insertText": "->Js.String2.toUpperCase", + "additionalTextEdits": [{ + "range": {"start": {"line": 94, "character": 30}, "end": {"line": 94, "character": 31}}, + "newText": "" + }] + }] + +Complete src/DotPipeCompletionSpec.res 97:63 +posCursor:[97:63] posNoWhite:[97:62] Found expr:[97:3->97:63] +posCursor:[97:63] posNoWhite:[97:62] Found expr:[97:32->97:63] +Pexp_field [97:32->97:54] toLowerC:[97:55->97:63] +Completable: Cpath Value[Js, String2, toUpperCase].toLowerC +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[Js, String2, toUpperCase].toLowerC +ContextPath Value[Js, String2, toUpperCase] +Path Js.String2.toUpperCase +ContextPath Value[Js, String2, toUpperCase](Nolabel)->toLowerC +ContextPath Value[Js, String2, toUpperCase](Nolabel) +ContextPath Value[Js, String2, toUpperCase] +Path Js.String2.toUpperCase +Path Js.String2.toLowerC +[{ + "label": "->Js.String2.toLowerCase", + "kind": 12, + "tags": [], + "detail": "t => t", + "documentation": {"kind": "markdown", "value": "\n`toLowerCase(str)` converts `str` to lower case using the locale-insensitive\ncase mappings in the Unicode Character Database. Notice that the conversion can\ngive different results depending upon context, for example with the Greek\nletter sigma, which has two different lower case forms; one when it is the last\ncharacter in a string and another when it is not.\n\nSee [`String.toLowerCase`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toLowerCase)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.toLowerCase(\"ABC\") == \"abc\"\nJs.String2.toLowerCase(`ΣΠ`) == `σπ`\nJs.String2.toLowerCase(`ΠΣ`) == `πς`\n```\n"}, + "sortText": "toLowerCase", + "insertText": "->Js.String2.toLowerCase", + "additionalTextEdits": [{ + "range": {"start": {"line": 97, "character": 54}, "end": {"line": 97, "character": 55}}, + "newText": "" + }] + }] + From 9ac0817012c41e66e973e6e3215852e4b40d31f2 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Wed, 1 Jan 2025 00:26:13 +0100 Subject: [PATCH 29/34] handle scope --- analysis/src/CompletionBackEnd.ml | 24 ++++++-- analysis/tests/src/DotPipeCompletionSpec.res | 8 +++ .../expected/DotPipeCompletionSpec.res.txt | 61 +++++++++++++++++++ 3 files changed, 89 insertions(+), 4 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 4b0740b5e..91ba7c202 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -704,7 +704,8 @@ let getPipeCompletions ~env ~full ~identifierLoc ?posOfDot ~debug in let completionPath = match (completeAsBuiltin, typePath) with - | Some completionPathForBuiltin, _ -> Some completionPathForBuiltin + | Some completionPathForBuiltin, _ -> + Some (false, completionPathForBuiltin) | _, Some p -> ( (* If this isn't a builtin, but we have a path, we try to resolve the module path relative to the env we're completing from. This ensures that @@ -714,17 +715,32 @@ let getPipeCompletions ~env ~full ~identifierLoc ?posOfDot ~debug TypeUtils.getModulePathRelativeToEnv ~debug ~env:envCompletionIsMadeFrom ~envFromItem:env (Utils.expandPath p) with - | None -> Some [env.file.moduleName] - | Some p -> Some p) + | None -> Some (true, [env.file.moduleName]) + | Some p -> Some (false, p)) | _ -> None in match completionPath with | None -> [] - | Some completionPath -> + | Some (isFromCurrentModule, completionPath) -> completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens ~pos ~scope ~debug ~prefix ~env ~rawOpens ~full completionPath |> TypeUtils.filterPipeableFunctions ~env ~full ~synthetic:mainCompletionsAreSynthetic ~targetTypeId:mainTypeId + |> List.filter (fun (c : Completion.t) -> + (* If we're completing from the current module then we need to care about scope. + This is automatically taken care of in other cases. *) + if isFromCurrentModule then + match c.kind with + | Value _ -> + scope + |> List.find_opt (fun (item : ScopeTypes.item) -> + match item with + | Value (scopeItemName, _, _, _) -> + scopeItemName = c.name + | _ -> false) + |> Option.is_some + | _ -> false + else true) in (* Extra completions can be drawn from the @editor.completeFrom attribute. Here we find and add those completions as well. *) diff --git a/analysis/tests/src/DotPipeCompletionSpec.res b/analysis/tests/src/DotPipeCompletionSpec.res index 94bd1599c..81f89fdab 100644 --- a/analysis/tests/src/DotPipeCompletionSpec.res +++ b/analysis/tests/src/DotPipeCompletionSpec.res @@ -97,3 +97,11 @@ let str = "hello" // str->Js.String2.toLowerCase->Js.String2.toUpperCase.toLowerC // ^com + +let cc = (t: typeOutsideModule) => { + // t. + // ^com + t +} + +let outOfScope = (t: typeOutsideModule) => t diff --git a/analysis/tests/src/expected/DotPipeCompletionSpec.res.txt b/analysis/tests/src/expected/DotPipeCompletionSpec.res.txt index 2a2a0b6fd..e8919c866 100644 --- a/analysis/tests/src/expected/DotPipeCompletionSpec.res.txt +++ b/analysis/tests/src/expected/DotPipeCompletionSpec.res.txt @@ -467,3 +467,64 @@ Path Js.String2.toLowerC }] }] +Complete src/DotPipeCompletionSpec.res 101:7 +posCursor:[101:7] posNoWhite:[101:6] Found expr:[100:9->104:1] +posCursor:[101:7] posNoWhite:[101:6] Found expr:[100:10->104:1] +posCursor:[101:7] posNoWhite:[101:6] Found expr:[101:5->103:3] +Pexp_field [101:5->101:6] t:[103:2->103:3] +Completable: Cpath Value[t]."" +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[t]."" +ContextPath Value[t] +Path t +ContextPath Value[t]-> +ContextPath Value[t] +Path t +CPPipe pathFromEnv: found:true +Path DotPipeCompletionSpec. +Path DotPipeCompletionSpec.SomeOtherModule. +[{ + "label": "nname", + "kind": 5, + "tags": [], + "detail": "string", + "documentation": {"kind": "markdown", "value": "```rescript\nnname: string\n```\n\n```rescript\ntype typeOutsideModule = {nname: string}\n```"} + }, { + "label": "->doWithTypeOutsideModule", + "kind": 12, + "tags": [], + "detail": "typeOutsideModule => string", + "documentation": null, + "sortText": "doWithTypeOutsideModule", + "insertText": "->doWithTypeOutsideModule", + "additionalTextEdits": [{ + "range": {"start": {"line": 101, "character": 6}, "end": {"line": 101, "character": 7}}, + "newText": "" + }] + }, { + "label": "->SomeOtherModule.getNName", + "kind": 12, + "tags": [], + "detail": "t => string", + "documentation": null, + "sortText": "getNName", + "insertText": "->SomeOtherModule.getNName", + "additionalTextEdits": [{ + "range": {"start": {"line": 101, "character": 6}, "end": {"line": 101, "character": 7}}, + "newText": "" + }] + }, { + "label": "->SomeOtherModule.getNName2", + "kind": 12, + "tags": [], + "detail": "typeOutsideModule => string", + "documentation": null, + "sortText": "getNName2", + "insertText": "->SomeOtherModule.getNName2", + "additionalTextEdits": [{ + "range": {"start": {"line": 101, "character": 6}, "end": {"line": 101, "character": 7}}, + "newText": "" + }] + }] + From 3d0a1c817a3be4615b134e9fd9d428a1de530598 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Thu, 2 Jan 2025 09:55:37 +0100 Subject: [PATCH 30/34] inline pipe completion logic again --- analysis/src/CompletionBackEnd.ml | 210 ++++++++++++++--------------- analysis/src/CompletionFrontEnd.ml | 10 +- analysis/src/SharedTypes.ml | 1 + 3 files changed, 109 insertions(+), 112 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 91ba7c202..ee1348e51 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -664,112 +664,6 @@ let completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens ~pos in completions -let getPipeCompletions ~env ~full ~identifierLoc ?posOfDot ~debug - ~envCompletionIsMadeFrom ~opens ~pos ~scope ~prefix ~rawOpens ~inJsx - ?(mainCompletionsAreSynthetic = false) ?(formatCompletionsWithPipes = false) - typ = - let env, typ = - typ - |> TypeUtils.resolveTypeForPipeCompletion ~env ~package:full.package ~full - ~lhsLoc:identifierLoc - in - let mainTypeId = TypeUtils.findRootTypeId ~full ~env typ in - let typePath = TypeUtils.pathFromTypeExpr typ in - match mainTypeId with - | None -> - if Debug.verbose () then - Printf.printf - "[pipe_completion] Could not find mainTypeId. Aborting pipe completions.\n"; - [] - | Some mainTypeId -> - if Debug.verbose () then - Printf.printf "[pipe_completion] mainTypeId: %s\n" mainTypeId; - let pipeCompletions = - (* We now need a completion path from where to look up the module for our dot completion type. - This is from where we pull all of the functions we want to complete for the pipe. - - A completion path here could be one of two things: - 1. A module path to the main module for the type we've found - 2. A module path to a builtin module, like `Int` for `int`, or `Array` for `array` - - The below code will deliberately _not_ dig into type aliases for the main type when we're looking - for what _module_ to complete from. This is because you should be able to control where completions - come from even if your type is an alias. - *) - let completeAsBuiltin = - match typePath with - | Some t -> - TypeUtils.completionPathFromMaybeBuiltin t ~package:full.package - | None -> None - in - let completionPath = - match (completeAsBuiltin, typePath) with - | Some completionPathForBuiltin, _ -> - Some (false, completionPathForBuiltin) - | _, Some p -> ( - (* If this isn't a builtin, but we have a path, we try to resolve the - module path relative to the env we're completing from. This ensures that - what we get here is a module path we can find completions for regardless of - of the current scope for the position we're at.*) - match - TypeUtils.getModulePathRelativeToEnv ~debug - ~env:envCompletionIsMadeFrom ~envFromItem:env (Utils.expandPath p) - with - | None -> Some (true, [env.file.moduleName]) - | Some p -> Some (false, p)) - | _ -> None - in - match completionPath with - | None -> [] - | Some (isFromCurrentModule, completionPath) -> - completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens - ~pos ~scope ~debug ~prefix ~env ~rawOpens ~full completionPath - |> TypeUtils.filterPipeableFunctions ~env ~full - ~synthetic:mainCompletionsAreSynthetic ~targetTypeId:mainTypeId - |> List.filter (fun (c : Completion.t) -> - (* If we're completing from the current module then we need to care about scope. - This is automatically taken care of in other cases. *) - if isFromCurrentModule then - match c.kind with - | Value _ -> - scope - |> List.find_opt (fun (item : ScopeTypes.item) -> - match item with - | Value (scopeItemName, _, _, _) -> - scopeItemName = c.name - | _ -> false) - |> Option.is_some - | _ -> false - else true) - in - (* Extra completions can be drawn from the @editor.completeFrom attribute. Here we - find and add those completions as well. *) - let extraCompletions = - TypeUtils.getExtraModulesToCompleteFromForType ~env ~full typ - |> List.map (fun completionPath -> - completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom - ~opens ~pos ~scope ~debug ~prefix ~env ~rawOpens ~full - completionPath) - |> List.flatten - |> TypeUtils.filterPipeableFunctions ~synthetic:true ~env ~full - ~targetTypeId:mainTypeId - in - (* Add JSX completion items if we're in a JSX context. *) - let jsxCompletions = - if inJsx then - PipeCompletionUtils.addJsxCompletionItems ~env ~mainTypeId ~prefix ~full - ~rawOpens typ - else [] - in - let allCompletions = jsxCompletions @ pipeCompletions @ extraCompletions in - if formatCompletionsWithPipes then - allCompletions - |> List.filter_map (fun (c : Completion.t) -> - c - |> TypeUtils.transformCompletionToPipeCompletion ~env ?posOfDot - ~synthetic:c.synthetic) - else allCompletions - let rec digToRecordFieldsForCompletion ~debug ~package ~opens ~full ~pos ~env ~scope path = match @@ -1122,6 +1016,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact let cpAsPipeCompletion = Completable.CPPipe { + synthetic = true; contextPath = (match cp with | CPApply (c, args) -> CPApply (c, args @ [Asttypes.Nolabel]) @@ -1165,7 +1060,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact else None) | None -> []) | None -> []) - | CPPipe {contextPath = cp; id = prefix; lhsLoc; inJsx} -> ( + | CPPipe {contextPath = cp; id = prefix; lhsLoc; inJsx; synthetic} -> ( if Debug.verbose () then print_endline "[ctx_path]--> CPPipe"; match cp @@ -1177,10 +1072,103 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact if Debug.verbose () then print_endline "[CPPipe]--> Could not resolve type env"; [] - | Some (typ, env) -> - getPipeCompletions ~env ~full ~identifierLoc:lhsLoc - ~envCompletionIsMadeFrom ~debug ~opens ~rawOpens ~scope ~pos ~inJsx - ~prefix typ) + | Some (typ, env) -> ( + let env, typ = + typ + |> TypeUtils.resolveTypeForPipeCompletion ~env ~package:full.package + ~full ~lhsLoc + in + let mainTypeId = TypeUtils.findRootTypeId ~full ~env typ in + let typePath = TypeUtils.pathFromTypeExpr typ in + match mainTypeId with + | None -> + if Debug.verbose () then + Printf.printf + "[pipe_completion] Could not find mainTypeId. Aborting pipe \ + completions.\n"; + [] + | Some mainTypeId -> + if Debug.verbose () then + Printf.printf "[pipe_completion] mainTypeId: %s\n" mainTypeId; + let pipeCompletions = + (* We now need a completion path from where to look up the module for our dot completion type. + This is from where we pull all of the functions we want to complete for the pipe. + + A completion path here could be one of two things: + 1. A module path to the main module for the type we've found + 2. A module path to a builtin module, like `Int` for `int`, or `Array` for `array` + + The below code will deliberately _not_ dig into type aliases for the main type when we're looking + for what _module_ to complete from. This is because you should be able to control where completions + come from even if your type is an alias. + *) + let completeAsBuiltin = + match typePath with + | Some t -> + TypeUtils.completionPathFromMaybeBuiltin t ~package:full.package + | None -> None + in + let completionPath = + match (completeAsBuiltin, typePath) with + | Some completionPathForBuiltin, _ -> + Some (false, completionPathForBuiltin) + | _, Some p -> ( + (* If this isn't a builtin, but we have a path, we try to resolve the + module path relative to the env we're completing from. This ensures that + what we get here is a module path we can find completions for regardless of + of the current scope for the position we're at.*) + match + TypeUtils.getModulePathRelativeToEnv ~debug + ~env:envCompletionIsMadeFrom ~envFromItem:env + (Utils.expandPath p) + with + | None -> Some (true, [env.file.moduleName]) + | Some p -> Some (false, p)) + | _ -> None + in + match completionPath with + | None -> [] + | Some (isFromCurrentModule, completionPath) -> + completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens + ~pos ~scope ~debug ~prefix ~env ~rawOpens ~full completionPath + |> TypeUtils.filterPipeableFunctions ~env ~full ~synthetic + ~targetTypeId:mainTypeId + |> List.filter (fun (c : Completion.t) -> + (* If we're completing from the current module then we need to care about scope. + This is automatically taken care of in other cases. *) + if isFromCurrentModule then + match c.kind with + | Value _ -> + scope + |> List.find_opt (fun (item : ScopeTypes.item) -> + match item with + | Value (scopeItemName, _, _, _) -> + scopeItemName = c.name + | _ -> false) + |> Option.is_some + | _ -> false + else true) + in + (* Extra completions can be drawn from the @editor.completeFrom attribute. Here we + find and add those completions as well. *) + let extraCompletions = + TypeUtils.getExtraModulesToCompleteFromForType ~env ~full typ + |> List.map (fun completionPath -> + completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom + ~opens ~pos ~scope ~debug ~prefix ~env ~rawOpens ~full + completionPath) + |> List.flatten + |> TypeUtils.filterPipeableFunctions ~synthetic:true ~env ~full + ~targetTypeId:mainTypeId + in + (* Add JSX completion items if we're in a JSX context. *) + let jsxCompletions = + if inJsx then + PipeCompletionUtils.addJsxCompletionItems ~env ~mainTypeId ~prefix + ~full ~rawOpens typ + else [] + in + jsxCompletions @ pipeCompletions @ extraCompletions)) | CTuple ctxPaths -> if Debug.verbose () then print_endline "[ctx_path]--> CTuple"; (* Turn a list of context paths into a list of type expressions. *) diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index ab224285a..00cd2cb66 100644 --- a/analysis/src/CompletionFrontEnd.ml +++ b/analysis/src/CompletionFrontEnd.ml @@ -1050,6 +1050,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor (Cpath (CPPipe { + synthetic = false; contextPath = pipe; id; lhsLoc = lhs.pexp_loc; @@ -1060,7 +1061,14 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor | Some (pipe, lhsLoc) -> setResult (Cpath - (CPPipe {contextPath = pipe; id; lhsLoc; inJsx = !inJsxContext})); + (CPPipe + { + synthetic = false; + contextPath = pipe; + id; + lhsLoc; + inJsx = !inJsxContext; + })); true in typedCompletionExpr expr; diff --git a/analysis/src/SharedTypes.ml b/analysis/src/SharedTypes.ml index 0a2443e93..59675f181 100644 --- a/analysis/src/SharedTypes.ml +++ b/analysis/src/SharedTypes.ml @@ -620,6 +620,7 @@ module Completable = struct | CPObj of contextPath * string | CPAwait of contextPath | CPPipe of { + synthetic: bool; (** Whether this pipe completion is synthetic. *) contextPath: contextPath; id: string; inJsx: bool; (** Whether this pipe was found in a JSX context. *) From bf7eebac2d93f633d40a6f08b394ee259763f8da Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Thu, 2 Jan 2025 10:01:23 +0100 Subject: [PATCH 31/34] refactor --- analysis/src/CompletionBackEnd.ml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index ee1348e51..7589e45f9 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -1028,13 +1028,10 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact lhsLoc = exprLoc; } in - let completionsFromPipeCtxPath = + let pipeCompletions = cpAsPipeCompletion |> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env:envCompletionIsMadeFrom ~exact ~scope - in - let pipeCompletions = - completionsFromPipeCtxPath |> List.filter_map (fun c -> TypeUtils.transformCompletionToPipeCompletion ~synthetic:true ~env ?posOfDot c) From 380e8a922011251ee390a181682679d2d56240a0 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Thu, 2 Jan 2025 10:05:25 +0100 Subject: [PATCH 32/34] more compl spec --- analysis/tests/src/DotPipeCompletionSpec.res | 9 ++ .../expected/DotPipeCompletionSpec.res.txt | 101 ++++++++++++++++++ 2 files changed, 110 insertions(+) diff --git a/analysis/tests/src/DotPipeCompletionSpec.res b/analysis/tests/src/DotPipeCompletionSpec.res index 81f89fdab..a6ac244bb 100644 --- a/analysis/tests/src/DotPipeCompletionSpec.res +++ b/analysis/tests/src/DotPipeCompletionSpec.res @@ -105,3 +105,12 @@ let cc = (t: typeOutsideModule) => { } let outOfScope = (t: typeOutsideModule) => t + +// @editor.completeFrom(Dot) type t +// ^com + +// @editor.completeFrom([CompletionPipe]) type t +// ^com + +// @editor.completeFrom([CompletionPipe, Dot]) type t +// ^com diff --git a/analysis/tests/src/expected/DotPipeCompletionSpec.res.txt b/analysis/tests/src/expected/DotPipeCompletionSpec.res.txt index e8919c866..736806d72 100644 --- a/analysis/tests/src/expected/DotPipeCompletionSpec.res.txt +++ b/analysis/tests/src/expected/DotPipeCompletionSpec.res.txt @@ -528,3 +528,104 @@ Path DotPipeCompletionSpec.SomeOtherModule. }] }] +Complete src/DotPipeCompletionSpec.res 108:27 +XXX Not found! +Completable: Cpath Module[Dot] +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Module[Dot] +Path Dot +[{ + "label": "DotCompletionEverywhere", + "kind": 9, + "tags": [], + "detail": "module DotCompletionEverywhere", + "documentation": null, + "data": { + "modulePath": "DotCompletionEverywhere", + "filePath": "src/DotPipeCompletionSpec.res" + } + }, { + "label": "DotPipeCompletionSpec", + "kind": 9, + "tags": [], + "detail": "module DotPipeCompletionSpec", + "documentation": null, + "data": { + "modulePath": "DotPipeCompletionSpec", + "filePath": "src/DotPipeCompletionSpec.res" + } + }] + +Complete src/DotPipeCompletionSpec.res 111:39 +posCursor:[111:39] posNoWhite:[111:38] Found expr:[111:24->111:40] +posCursor:[111:39] posNoWhite:[111:38] Found expr:[111:25->111:39] +Pexp_construct CompletionPipe:[111:25->111:39] None +Completable: Cpath Value[CompletionPipe] +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[CompletionPipe] +Path CompletionPipe +[{ + "label": "CompletionPipeChain", + "kind": 9, + "tags": [], + "detail": "module CompletionPipeChain", + "documentation": null, + "data": { + "modulePath": "CompletionPipeChain", + "filePath": "src/DotPipeCompletionSpec.res" + } + }, { + "label": "CompletionPipeProperty", + "kind": 9, + "tags": [], + "detail": "module CompletionPipeProperty", + "documentation": null, + "data": { + "modulePath": "CompletionPipeProperty", + "filePath": "src/DotPipeCompletionSpec.res" + } + }, { + "label": "CompletionPipeSubmodules", + "kind": 9, + "tags": [], + "detail": "module CompletionPipeSubmodules", + "documentation": null, + "data": { + "modulePath": "CompletionPipeSubmodules", + "filePath": "src/DotPipeCompletionSpec.res" + } + }] + +Complete src/DotPipeCompletionSpec.res 114:44 +posCursor:[114:44] posNoWhite:[114:43] Found expr:[114:24->114:45] +posCursor:[114:44] posNoWhite:[114:43] Found expr:[114:41->114:44] +Pexp_construct Dot:[114:41->114:44] None +Completable: Cpath Value[Dot] +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[Dot] +Path Dot +[{ + "label": "DotCompletionEverywhere", + "kind": 9, + "tags": [], + "detail": "module DotCompletionEverywhere", + "documentation": null, + "data": { + "modulePath": "DotCompletionEverywhere", + "filePath": "src/DotPipeCompletionSpec.res" + } + }, { + "label": "DotPipeCompletionSpec", + "kind": 9, + "tags": [], + "detail": "module DotPipeCompletionSpec", + "documentation": null, + "data": { + "modulePath": "DotPipeCompletionSpec", + "filePath": "src/DotPipeCompletionSpec.res" + } + }] + From 603b8d9f81db63c03b40a822c5f6c29249444ed5 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Thu, 2 Jan 2025 10:15:50 +0100 Subject: [PATCH 33/34] refactor --- analysis/src/TypeUtils.ml | 3 +- .../tests/src/DotCompletionEverywhere.res | 87 ----- analysis/tests/src/DotPipeCompletionSpec.res | 30 ++ .../expected/DotCompletionEverywhere.res.txt | 329 ------------------ .../expected/DotPipeCompletionSpec.res.txt | 116 ++++-- 5 files changed, 127 insertions(+), 438 deletions(-) delete mode 100644 analysis/tests/src/DotCompletionEverywhere.res delete mode 100644 analysis/tests/src/expected/DotCompletionEverywhere.res.txt diff --git a/analysis/src/TypeUtils.ml b/analysis/src/TypeUtils.ml index 1a185f0bc..2394f2803 100644 --- a/analysis/src/TypeUtils.ml +++ b/analysis/src/TypeUtils.ml @@ -1183,8 +1183,7 @@ let transformCompletionToPipeCompletion ?(synthetic = false) ~env ?posOfDot of the project. Example: type x in module SomeModule in file SomeFile would get the globally unique id `SomeFile.SomeModule.x`.*) let rec findRootTypeId ~full ~env (t : Types.type_expr) = - let debug = Debug.verbose () in - (* let debug = false in *) + let debug = false in match t.desc with | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> findRootTypeId ~full ~env t1 | Tconstr (Pident {name = "function$"}, [t; _], _) -> diff --git a/analysis/tests/src/DotCompletionEverywhere.res b/analysis/tests/src/DotCompletionEverywhere.res deleted file mode 100644 index d1f52f46a..000000000 --- a/analysis/tests/src/DotCompletionEverywhere.res +++ /dev/null @@ -1,87 +0,0 @@ -let someObj = { - "name": "hello", - "age": 123, -} - -// someObj. -// ^com - -// someObj.na -// ^com - -type rrr = {name: string} -let rrr = {name: "hello"} - -// rrr.n -// ^com - -module SomeMod = { - module SomeOtherMod = { - type x - - @send external do: x => unit = "do" - - external xx: x = "xx" - } -} - -external x: SomeMod.SomeOtherMod.x = "x" - -// x. -// ^com - -// SomeMod.SomeOtherMod.xx. -// ^com - -module Sss = { - type rrr = {name: string} - let rrr = {name: "hello"} - let do = rrr => rrr.name -} - -// Sss.rrr. -// ^com - -@editor.completeFrom(DotCompletionEverywhere.X2) -type x2x2 = {namee: string} -let x2x2 = {namee: "hello"} - -module X2 = { - let stuff = x => x.namee -} - -// x2x2. -// ^com - -let obj = { - "name": "ReScript", - "number": 1, - "nothing": true, -} - -// obj. -// ^com - -let arr = [1, 2, 3] - -// arr.m -// ^com - -module DOMAPI = { - type htmlElement = {prefix: string} - - @editor.completeFrom(DotCompletionEverywhere.HTMLButtonElement) - type rec htmlButtonElement = {mutable disabled: bool} -} - -module HTMLButtonElement = { - open DOMAPI - - @send - external checkValidity: htmlButtonElement => bool = "checkValidity" -} - -let button: DOMAPI.htmlButtonElement = %todo - -// button. -// ^com diff --git a/analysis/tests/src/DotPipeCompletionSpec.res b/analysis/tests/src/DotPipeCompletionSpec.res index a6ac244bb..9e141cb2c 100644 --- a/analysis/tests/src/DotPipeCompletionSpec.res +++ b/analysis/tests/src/DotPipeCompletionSpec.res @@ -114,3 +114,33 @@ let outOfScope = (t: typeOutsideModule) => t // @editor.completeFrom([CompletionPipe, Dot]) type t // ^com + +let someObj = { + "name": "hello", + "age": 123, +} + +// someObj. +// ^com + +// someObj.na +// ^com + +module DOMAPI = { + type htmlElement = {prefix: string} + + @editor.completeFrom(DotPipeCompletionSpec.HTMLButtonElement) + type rec htmlButtonElement = {mutable disabled: bool} +} + +module HTMLButtonElement = { + open DOMAPI + + @send + external checkValidity: htmlButtonElement => bool = "checkValidity" +} + +external button: DOMAPI.htmlButtonElement = "button" + +// button. +// ^com diff --git a/analysis/tests/src/expected/DotCompletionEverywhere.res.txt b/analysis/tests/src/expected/DotCompletionEverywhere.res.txt deleted file mode 100644 index 37a7df9e5..000000000 --- a/analysis/tests/src/expected/DotCompletionEverywhere.res.txt +++ /dev/null @@ -1,329 +0,0 @@ -Complete src/DotCompletionEverywhere.res 5:11 -posCursor:[5:11] posNoWhite:[5:10] Found expr:[5:3->5:11] -Pexp_field [5:3->5:10] _:[11:0->5:11] -Completable: Cpath Value[someObj]."" -Package opens Pervasives.JsxModules.place holder -Resolved opens 1 pervasives -ContextPath Value[someObj]."" -ContextPath Value[someObj] -Path someObj -ContextPath Value[someObj]-> -ContextPath Value[someObj] -Path someObj -[{ - "label": "[\"age\"]", - "kind": 4, - "tags": [], - "detail": "{\"age\": int, \"name\": string}", - "documentation": null, - "insertText": "[\"age\"]", - "additionalTextEdits": [{ - "range": {"start": {"line": 5, "character": 10}, "end": {"line": 5, "character": 11}}, - "newText": "" - }] - }, { - "label": "[\"name\"]", - "kind": 4, - "tags": [], - "detail": "{\"age\": int, \"name\": string}", - "documentation": null, - "insertText": "[\"name\"]", - "additionalTextEdits": [{ - "range": {"start": {"line": 5, "character": 10}, "end": {"line": 5, "character": 11}}, - "newText": "" - }] - }] - -Complete src/DotCompletionEverywhere.res 8:13 -posCursor:[8:13] posNoWhite:[8:12] Found expr:[8:3->8:13] -Pexp_field [8:3->8:10] na:[8:11->8:13] -Completable: Cpath Value[someObj].na -Package opens Pervasives.JsxModules.place holder -Resolved opens 1 pervasives -ContextPath Value[someObj].na -ContextPath Value[someObj] -Path someObj -ContextPath Value[someObj]->na -ContextPath Value[someObj] -Path someObj -[{ - "label": "[\"name\"]", - "kind": 4, - "tags": [], - "detail": "{\"age\": int, \"name\": string}", - "documentation": null, - "insertText": "[\"name\"]", - "additionalTextEdits": [{ - "range": {"start": {"line": 8, "character": 10}, "end": {"line": 8, "character": 11}}, - "newText": "" - }] - }] - -Complete src/DotCompletionEverywhere.res 14:8 -posCursor:[14:8] posNoWhite:[14:7] Found expr:[14:3->14:8] -Pexp_field [14:3->14:6] n:[14:7->14:8] -Completable: Cpath Value[rrr].n -Package opens Pervasives.JsxModules.place holder -Resolved opens 1 pervasives -ContextPath Value[rrr].n -ContextPath Value[rrr] -Path rrr -ContextPath Value[rrr]->n -ContextPath Value[rrr] -Path rrr -CPPipe pathFromEnv: found:true -Path DotCompletionEverywhere.n -[{ - "label": "name", - "kind": 5, - "tags": [], - "detail": "string", - "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype rrr = {name: string}\n```"} - }] - -Complete src/DotCompletionEverywhere.res 29:5 -posCursor:[29:5] posNoWhite:[29:4] Found expr:[29:3->29:5] -Pexp_field [29:3->29:4] _:[35:0->29:5] -Completable: Cpath Value[x]."" -Package opens Pervasives.JsxModules.place holder -Resolved opens 1 pervasives -ContextPath Value[x]."" -ContextPath Value[x] -Path x -ContextPath Value[x]-> -ContextPath Value[x] -Path x -CPPipe pathFromEnv:SomeMod.SomeOtherMod found:true -Path SomeMod.SomeOtherMod. -[{ - "label": "->SomeMod.SomeOtherMod.do", - "kind": 12, - "tags": [], - "detail": "x => unit", - "documentation": null, - "sortText": "do", - "insertText": "->SomeMod.SomeOtherMod.do", - "additionalTextEdits": [{ - "range": {"start": {"line": 29, "character": 4}, "end": {"line": 29, "character": 5}}, - "newText": "" - }] - }] - -Complete src/DotCompletionEverywhere.res 32:27 -posCursor:[32:27] posNoWhite:[32:26] Found expr:[32:3->32:27] -Pexp_field [32:3->32:26] _:[35:0->32:27] -Completable: Cpath Value[SomeMod, SomeOtherMod, xx]."" -Package opens Pervasives.JsxModules.place holder -Resolved opens 1 pervasives -ContextPath Value[SomeMod, SomeOtherMod, xx]."" -ContextPath Value[SomeMod, SomeOtherMod, xx] -Path SomeMod.SomeOtherMod.xx -ContextPath Value[SomeMod, SomeOtherMod, xx]-> -ContextPath Value[SomeMod, SomeOtherMod, xx] -Path SomeMod.SomeOtherMod.xx -CPPipe pathFromEnv:SomeMod.SomeOtherMod found:true -Path SomeMod.SomeOtherMod. -[{ - "label": "->SomeMod.SomeOtherMod.do", - "kind": 12, - "tags": [], - "detail": "x => unit", - "documentation": null, - "sortText": "do", - "insertText": "->SomeMod.SomeOtherMod.do", - "additionalTextEdits": [{ - "range": {"start": {"line": 32, "character": 26}, "end": {"line": 32, "character": 27}}, - "newText": "" - }] - }] - -Complete src/DotCompletionEverywhere.res 41:11 -posCursor:[41:11] posNoWhite:[41:10] Found expr:[41:3->41:11] -Pexp_field [41:3->41:10] _:[44:0->41:11] -Completable: Cpath Value[Sss, rrr]."" -Package opens Pervasives.JsxModules.place holder -Resolved opens 1 pervasives -ContextPath Value[Sss, rrr]."" -ContextPath Value[Sss, rrr] -Path Sss.rrr -ContextPath Value[Sss, rrr]-> -ContextPath Value[Sss, rrr] -Path Sss.rrr -CPPipe pathFromEnv:Sss found:true -Path Sss. -[{ - "label": "name", - "kind": 5, - "tags": [], - "detail": "string", - "documentation": {"kind": "markdown", "value": "```rescript\nname: string\n```\n\n```rescript\ntype rrr = {name: string}\n```"} - }, { - "label": "->Sss.do", - "kind": 12, - "tags": [], - "detail": "rrr => string", - "documentation": null, - "sortText": "do", - "insertText": "->Sss.do", - "additionalTextEdits": [{ - "range": {"start": {"line": 41, "character": 10}, "end": {"line": 41, "character": 11}}, - "newText": "" - }] - }] - -Complete src/DotCompletionEverywhere.res 52:8 -posCursor:[52:8] posNoWhite:[52:7] Found expr:[52:3->52:8] -Pexp_field [52:3->52:7] _:[55:0->52:8] -Completable: Cpath Value[x2x2]."" -Package opens Pervasives.JsxModules.place holder -Resolved opens 1 pervasives -ContextPath Value[x2x2]."" -ContextPath Value[x2x2] -Path x2x2 -ContextPath Value[x2x2]-> -ContextPath Value[x2x2] -Path x2x2 -CPPipe pathFromEnv: found:true -Path DotCompletionEverywhere. -Path DotCompletionEverywhere.X2. -[{ - "label": "namee", - "kind": 5, - "tags": [], - "detail": "string", - "documentation": {"kind": "markdown", "value": "```rescript\nnamee: string\n```\n\n```rescript\ntype x2x2 = {namee: string}\n```"} - }, { - "label": "->X2.stuff", - "kind": 12, - "tags": [], - "detail": "x2x2 => string", - "documentation": null, - "sortText": "stuff", - "insertText": "->X2.stuff", - "additionalTextEdits": [{ - "range": {"start": {"line": 52, "character": 7}, "end": {"line": 52, "character": 8}}, - "newText": "" - }] - }] - -Complete src/DotCompletionEverywhere.res 61:7 -posCursor:[61:7] posNoWhite:[61:6] Found expr:[61:3->61:7] -Pexp_field [61:3->61:6] _:[64:0->61:7] -Completable: Cpath Value[obj]."" -Package opens Pervasives.JsxModules.place holder -Resolved opens 1 pervasives -ContextPath Value[obj]."" -ContextPath Value[obj] -Path obj -ContextPath Value[obj]-> -ContextPath Value[obj] -Path obj -[{ - "label": "[\"name\"]", - "kind": 4, - "tags": [], - "detail": "{\"name\": string, \"nothing\": bool, \"number\": int}", - "documentation": null, - "insertText": "[\"name\"]", - "additionalTextEdits": [{ - "range": {"start": {"line": 61, "character": 6}, "end": {"line": 61, "character": 7}}, - "newText": "" - }] - }, { - "label": "[\"nothing\"]", - "kind": 4, - "tags": [], - "detail": "{\"name\": string, \"nothing\": bool, \"number\": int}", - "documentation": null, - "insertText": "[\"nothing\"]", - "additionalTextEdits": [{ - "range": {"start": {"line": 61, "character": 6}, "end": {"line": 61, "character": 7}}, - "newText": "" - }] - }, { - "label": "[\"number\"]", - "kind": 4, - "tags": [], - "detail": "{\"name\": string, \"nothing\": bool, \"number\": int}", - "documentation": null, - "insertText": "[\"number\"]", - "additionalTextEdits": [{ - "range": {"start": {"line": 61, "character": 6}, "end": {"line": 61, "character": 7}}, - "newText": "" - }] - }] - -Complete src/DotCompletionEverywhere.res 66:8 -posCursor:[66:8] posNoWhite:[66:7] Found expr:[66:3->66:8] -Pexp_field [66:3->66:6] m:[66:7->66:8] -Completable: Cpath Value[arr].m -Package opens Pervasives.JsxModules.place holder -Resolved opens 1 pervasives -ContextPath Value[arr].m -ContextPath Value[arr] -Path arr -ContextPath Value[arr]->m -ContextPath Value[arr] -Path arr -Path Js.Array2.m -[{ - "label": "->Js.Array2.mapi", - "kind": 12, - "tags": [], - "detail": "(t<'a>, ('a, int) => 'b) => t<'b>", - "documentation": {"kind": "markdown", "value": "\nApplies the function (the second argument) to each item in the array, returning\na new array. The function acceps two arguments: an item from the array and its\nindex number. The result array does not have to have elements of the same type\nas the input array. See\n[`Array.map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)\non MDN.\n\n## Examples\n\n```rescript\n// multiply each item in array by its position\nlet product = (item, index) => item * index\nJs.Array2.mapi([10, 11, 12], product) == [0, 11, 24]\n```\n"}, - "sortText": "mapi", - "insertText": "->Js.Array2.mapi", - "additionalTextEdits": [{ - "range": {"start": {"line": 66, "character": 6}, "end": {"line": 66, "character": 7}}, - "newText": "" - }] - }, { - "label": "->Js.Array2.map", - "kind": 12, - "tags": [], - "detail": "(t<'a>, 'a => 'b) => t<'b>", - "documentation": {"kind": "markdown", "value": "\nApplies the function (the second argument) to each item in the array, returning\na new array. The result array does not have to have elements of the same type\nas the input array. See\n[`Array.map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)\non MDN.\n\n## Examples\n\n```rescript\nJs.Array2.map([12, 4, 8], x => x * x) == [144, 16, 64]\nJs.Array2.map([\"animal\", \"vegetable\", \"mineral\"], Js.String.length) == [6, 9, 7]\n```\n"}, - "sortText": "map", - "insertText": "->Js.Array2.map", - "additionalTextEdits": [{ - "range": {"start": {"line": 66, "character": 6}, "end": {"line": 66, "character": 7}}, - "newText": "" - }] - }] - -Complete src/DotCompletionEverywhere.res 85:10 -posCursor:[85:10] posNoWhite:[85:9] Found expr:[85:3->85:10] -Pexp_field [85:3->85:9] _:[88:0->85:10] -Completable: Cpath Value[button]."" -Package opens Pervasives.JsxModules.place holder -Resolved opens 1 pervasives -ContextPath Value[button]."" -ContextPath Value[button] -Path button -ContextPath Value[button]-> -ContextPath Value[button] -Path button -CPPipe pathFromEnv:DOMAPI found:true -Path DOMAPI. -Path DotCompletionEverywhere.HTMLButtonElement. -[{ - "label": "disabled", - "kind": 5, - "tags": [], - "detail": "bool", - "documentation": {"kind": "markdown", "value": "```rescript\ndisabled: bool\n```\n\n```rescript\ntype htmlButtonElement = {mutable disabled: bool}\n```"} - }, { - "label": "->HTMLButtonElement.checkValidity", - "kind": 12, - "tags": [], - "detail": "DOMAPI.htmlButtonElement => bool", - "documentation": null, - "sortText": "checkValidity", - "insertText": "->HTMLButtonElement.checkValidity", - "additionalTextEdits": [{ - "range": {"start": {"line": 85, "character": 9}, "end": {"line": 85, "character": 10}}, - "newText": "" - }] - }] - diff --git a/analysis/tests/src/expected/DotPipeCompletionSpec.res.txt b/analysis/tests/src/expected/DotPipeCompletionSpec.res.txt index 736806d72..4126787a3 100644 --- a/analysis/tests/src/expected/DotPipeCompletionSpec.res.txt +++ b/analysis/tests/src/expected/DotPipeCompletionSpec.res.txt @@ -536,16 +536,6 @@ Resolved opens 1 pervasives ContextPath Module[Dot] Path Dot [{ - "label": "DotCompletionEverywhere", - "kind": 9, - "tags": [], - "detail": "module DotCompletionEverywhere", - "documentation": null, - "data": { - "modulePath": "DotCompletionEverywhere", - "filePath": "src/DotPipeCompletionSpec.res" - } - }, { "label": "DotPipeCompletionSpec", "kind": 9, "tags": [], @@ -608,16 +598,6 @@ Resolved opens 1 pervasives ContextPath Value[Dot] Path Dot [{ - "label": "DotCompletionEverywhere", - "kind": 9, - "tags": [], - "detail": "module DotCompletionEverywhere", - "documentation": null, - "data": { - "modulePath": "DotCompletionEverywhere", - "filePath": "src/DotPipeCompletionSpec.res" - } - }, { "label": "DotPipeCompletionSpec", "kind": 9, "tags": [], @@ -629,3 +609,99 @@ Path Dot } }] +Complete src/DotPipeCompletionSpec.res 122:11 +posCursor:[122:11] posNoWhite:[122:10] Found expr:[122:3->122:11] +Pexp_field [122:3->122:10] _:[128:0->122:11] +Completable: Cpath Value[someObj]."" +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[someObj]."" +ContextPath Value[someObj] +Path someObj +ContextPath Value[someObj]-> +ContextPath Value[someObj] +Path someObj +[{ + "label": "[\"age\"]", + "kind": 4, + "tags": [], + "detail": "{\"age\": int, \"name\": string}", + "documentation": null, + "insertText": "[\"age\"]", + "additionalTextEdits": [{ + "range": {"start": {"line": 122, "character": 10}, "end": {"line": 122, "character": 11}}, + "newText": "" + }] + }, { + "label": "[\"name\"]", + "kind": 4, + "tags": [], + "detail": "{\"age\": int, \"name\": string}", + "documentation": null, + "insertText": "[\"name\"]", + "additionalTextEdits": [{ + "range": {"start": {"line": 122, "character": 10}, "end": {"line": 122, "character": 11}}, + "newText": "" + }] + }] + +Complete src/DotPipeCompletionSpec.res 125:13 +posCursor:[125:13] posNoWhite:[125:12] Found expr:[125:3->125:13] +Pexp_field [125:3->125:10] na:[125:11->125:13] +Completable: Cpath Value[someObj].na +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[someObj].na +ContextPath Value[someObj] +Path someObj +ContextPath Value[someObj]->na +ContextPath Value[someObj] +Path someObj +[{ + "label": "[\"name\"]", + "kind": 4, + "tags": [], + "detail": "{\"age\": int, \"name\": string}", + "documentation": null, + "insertText": "[\"name\"]", + "additionalTextEdits": [{ + "range": {"start": {"line": 125, "character": 10}, "end": {"line": 125, "character": 11}}, + "newText": "" + }] + }] + +Complete src/DotPipeCompletionSpec.res 144:10 +posCursor:[144:10] posNoWhite:[144:9] Found expr:[144:3->144:10] +Pexp_field [144:3->144:9] _:[147:0->144:10] +Completable: Cpath Value[button]."" +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath Value[button]."" +ContextPath Value[button] +Path button +ContextPath Value[button]-> +ContextPath Value[button] +Path button +CPPipe pathFromEnv:DOMAPI found:true +Path DOMAPI. +Path DotPipeCompletionSpec.HTMLButtonElement. +[{ + "label": "disabled", + "kind": 5, + "tags": [], + "detail": "bool", + "documentation": {"kind": "markdown", "value": "```rescript\ndisabled: bool\n```\n\n```rescript\ntype htmlButtonElement = {mutable disabled: bool}\n```"} + }, { + "label": "->HTMLButtonElement.checkValidity", + "kind": 12, + "tags": [], + "detail": "DOMAPI.htmlButtonElement => bool", + "documentation": null, + "sortText": "checkValidity", + "insertText": "->HTMLButtonElement.checkValidity", + "additionalTextEdits": [{ + "range": {"start": {"line": 144, "character": 9}, "end": {"line": 144, "character": 10}}, + "newText": "" + }] + }] + From b5053bcf1690d71a7be0b5396fba597871ab7fb6 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Thu, 2 Jan 2025 10:18:11 +0100 Subject: [PATCH 34/34] changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 411d38b1d..e4301e619 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ ## master +#### :rocket: New Feature + +- Add support for "dot completion everywhere". In addition to record fields, dots will now complete for object fields, and pipe completions applicable to the type the dot is on. You can also configure where the editor draws extra pipe completions from via the `@editor.completeFrom` attribute. https://github.com/rescript-lang/rescript-vscode/pull/1054 + ## 1.60.0 #### :rocket: New Feature