diff --git a/CHANGELOG.md b/CHANGELOG.md index a054d487c..35dc2d2f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ - Prefer opened `Belt` modules in autocomplete when `-open Belt` is detected in `bsconfig`. https://github.com/rescript-lang/rescript-vscode/pull/673 - Improve precision in signature help. You now do not need to type anything into the argument for it to highlight. https://github.com/rescript-lang/rescript-vscode/pull/675 - Remove redundant function name in signature help, to clean up what's shown to the user some. https://github.com/rescript-lang/rescript-vscode/pull/678 +- Show docstrings in hover for record fields and variant constructors. https://github.com/rescript-lang/rescript-vscode/pull/694 #### :bug: Bug Fix diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index ac8dc013b..34ee9afed 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -557,7 +557,7 @@ let completionForExporteds iterExported getDeclared ~prefix ~exact ~env Hashtbl.add namesUsed declared.name.txt (); res := { - (Completion.create ~name:declared.name.txt ~env + (Completion.create declared.name.txt ~env ~kind:(transformContents declared.item)) with deprecated = declared.deprecated; @@ -597,7 +597,7 @@ let completionsForExportedConstructors ~(env : QueryEnv.t) ~prefix ~exact if not (Hashtbl.mem namesUsed name) then let () = Hashtbl.add namesUsed name () in Some - (Completion.create ~name ~env + (Completion.create name ~env ~docstring:c.docstring ~kind: (Completion.Constructor (c, t.item.decl |> Shared.declToString t.name.txt))) @@ -619,7 +619,7 @@ let completionForExportedFields ~(env : QueryEnv.t) ~prefix ~exact ~namesUsed = if not (Hashtbl.mem namesUsed name) then let () = Hashtbl.add namesUsed name () in Some - (Completion.create ~name ~env + (Completion.create name ~env ~docstring:f.docstring ~kind: (Completion.Field (f, t.item.decl |> Shared.declToString t.name.txt))) @@ -800,8 +800,7 @@ let processLocalValue name loc ~prefix ~exact ~env Hashtbl.add localTables.namesUsed name (); localTables.resultRev <- { - (Completion.create ~name:declared.name.txt ~env - ~kind:(Value declared.item)) + (Completion.create declared.name.txt ~env ~kind:(Value declared.item)) with deprecated = declared.deprecated; docstring = declared.docstring; @@ -812,7 +811,7 @@ let processLocalValue name loc ~prefix ~exact ~env (Printf.sprintf "Completion Value Not Found %s loc:%s\n" name (Loc.toString loc)); localTables.resultRev <- - Completion.create ~name ~env + Completion.create name ~env ~kind: (Value (Ctype.newconstr @@ -831,7 +830,7 @@ let processLocalConstructor name loc ~prefix ~exact ~env Hashtbl.add localTables.namesUsed name (); localTables.resultRev <- { - (Completion.create ~name:declared.name.txt ~env + (Completion.create declared.name.txt ~env ~kind: (Constructor ( declared.item, @@ -856,8 +855,7 @@ let processLocalType name loc ~prefix ~exact ~env ~(localTables : LocalTables.t) Hashtbl.add localTables.namesUsed name (); localTables.resultRev <- { - (Completion.create ~name:declared.name.txt ~env - ~kind:(Type declared.item)) + (Completion.create declared.name.txt ~env ~kind:(Type declared.item)) with deprecated = declared.deprecated; docstring = declared.docstring; @@ -877,7 +875,7 @@ let processLocalModule name loc ~prefix ~exact ~env Hashtbl.add localTables.namesUsed name (); localTables.resultRev <- { - (Completion.create ~name:declared.name.txt ~env + (Completion.create declared.name.txt ~env ~kind:(Module declared.item)) with deprecated = declared.deprecated; @@ -1139,7 +1137,7 @@ let getComplementaryCompletionsForTypedValue ~opens ~allFiles ~scope ~env prefix (String.contains name '-') then Some - (Completion.create ~name ~env ~kind:(Completion.FileModule name)) + (Completion.create name ~env ~kind:(Completion.FileModule name)) else None) in localCompletionsWithOpens @ fileModules @@ -1163,8 +1161,7 @@ let getCompletionsForPath ~package ~opens ~allFiles ~pos ~exact ~scope (String.contains name '-') then Some - (Completion.create ~name ~env - ~kind:(Completion.FileModule name)) + (Completion.create name ~env ~kind:(Completion.FileModule name)) else None) in localCompletionsWithOpens @ fileModules @@ -1350,28 +1347,28 @@ let rec getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env match contextPath with | CPString -> [ - Completion.create ~name:"string" ~env + Completion.create "string" ~env ~kind: (Completion.Value (Ctype.newconstr (Path.Pident (Ident.create "string")) [])); ] | CPInt -> [ - Completion.create ~name:"int" ~env + Completion.create "int" ~env ~kind: (Completion.Value (Ctype.newconstr (Path.Pident (Ident.create "int")) [])); ] | CPFloat -> [ - Completion.create ~name:"float" ~env + Completion.create "float" ~env ~kind: (Completion.Value (Ctype.newconstr (Path.Pident (Ident.create "float")) [])); ] | CPArray -> [ - Completion.create ~name:"array" ~env + Completion.create "array" ~env ~kind: (Completion.Value (Ctype.newconstr (Path.Pident (Ident.create "array")) [])); @@ -1418,7 +1415,7 @@ let rec getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env | args, tRet when args <> [] -> let args = processApply args labels in let retType = reconstructFunctionType args tRet in - [Completion.create ~name:"dummy" ~env ~kind:(Completion.Value retType)] + [Completion.create "dummy" ~env ~kind:(Completion.Value retType)] | _ -> []) | None -> []) | CPField (CPId (path, Module), fieldName) -> @@ -1440,7 +1437,8 @@ let rec getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env |> Utils.filterMap (fun field -> if checkName field.fname.txt ~prefix:fieldName ~exact then Some - (Completion.create ~name:field.fname.txt ~env + (Completion.create field.fname.txt ~env + ~docstring:field.docstring ~kind: (Completion.Field ( field, @@ -1472,8 +1470,7 @@ let rec getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env |> Utils.filterMap (fun (field, typ) -> if checkName field ~prefix:label ~exact then Some - (Completion.create ~name:field ~env - ~kind:(Completion.ObjLabel typ)) + (Completion.create field ~env ~kind:(Completion.ObjLabel typ)) else None) | None -> []) | None -> []) @@ -1655,7 +1652,7 @@ let rec getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env in if List.length ctxPaths = List.length typeExrps then [ - Completion.create ~name:"dummy" ~env + Completion.create "dummy" ~env ~kind:(Completion.Value (Ctype.newty (Ttuple typeExrps))); ] else [] @@ -1674,7 +1671,7 @@ let rec getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env | None -> [] | Some (_, typ, env) -> [ - Completion.create ~name:"dummy" ~env + Completion.create "dummy" ~env ~kind:(Completion.Value (Utils.unwrapIfOption typ)); ]) | CArgument {functionContextPath; argumentLabel} -> ( @@ -1707,7 +1704,7 @@ let rec getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env | None -> [] | Some (_, typ) -> [ - Completion.create ~name:"dummy" ~env + Completion.create "dummy" ~env ~kind: (Completion.Value (if expandOption then Utils.unwrapIfOption typ else typ)); @@ -1801,12 +1798,8 @@ let rec completeTypedValue (t : Types.type_expr) ~env ~full ~prefix match t |> extractType ~env ~package:full.package with | Some (Tbool env) -> [ - Completion.create ~name:"true" - ~kind:(Label (t |> Shared.typeToString)) - ~env; - Completion.create ~name:"false" - ~kind:(Label (t |> Shared.typeToString)) - ~env; + Completion.create "true" ~kind:(Label (t |> Shared.typeToString)) ~env; + Completion.create "false" ~kind:(Label (t |> Shared.typeToString)) ~env; ] |> filterItems ~prefix | Some (Tvariant {env; constructors; variantDecl; variantName}) -> @@ -1865,9 +1858,7 @@ let rec completeTypedValue (t : Types.type_expr) ~env ~full ~prefix }) in [ - Completion.create ~name:"None" - ~kind:(Label (t |> Shared.typeToString)) - ~env; + Completion.create "None" ~kind:(Label (t |> Shared.typeToString)) ~env; Completion.createWithSnippet ~name:"Some(_)" ~kind:(Label (t |> Shared.typeToString)) ~env ~insertText:"Some(${1:_})" (); @@ -1892,7 +1883,7 @@ let rec completeTypedValue (t : Types.type_expr) ~env ~full ~prefix |> List.filter (fun (field : field) -> List.mem field.fname.txt seenFields = false) |> List.map (fun (field : field) -> - Completion.create ~name:field.fname.txt + Completion.create field.fname.txt ~kind:(Field (field, typeExpr |> Shared.typeToString)) ~env) |> filterItems ~prefix @@ -1998,7 +1989,7 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover | Cjsx ([id], prefix, identsSeen) when String.uncapitalize_ascii id = id -> (* Lowercase JSX tag means builtin *) let mkLabel (name, typString) = - Completion.create ~name ~kind:(Label typString) ~env + Completion.create name ~kind:(Label typString) ~env in let keyLabels = if Utils.startsWith "key" prefix then [mkLabel ("key", "string")] else [] @@ -2012,7 +2003,7 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover | Cjsx (componentPath, prefix, identsSeen) -> let labels = getJsxLabels ~componentPath ~findTypeOfValue ~package in let mkLabel_ name typString = - Completion.create ~name ~kind:(Label typString) ~env + Completion.create name ~kind:(Label typString) ~env in let mkLabel (name, typ, _env) = mkLabel_ name (typ |> Shared.typeToString) @@ -2031,7 +2022,7 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover @ keyLabels | Cdecorator prefix -> let mkDecorator (name, docstring) = - {(Completion.create ~name ~kind:(Label "") ~env) with docstring} + {(Completion.create name ~kind:(Label "") ~env) with docstring} in [ ( "as", @@ -2288,7 +2279,7 @@ Note: The `@react.component` decorator requires the react-jsx config to be set i | None -> [] in let mkLabel (name, typ) = - Completion.create ~name ~kind:(Label (typ |> Shared.typeToString)) ~env + Completion.create name ~kind:(Label (typ |> Shared.typeToString)) ~env in labels |> List.filter (fun (name, _t) -> diff --git a/analysis/src/Hover.ml b/analysis/src/Hover.ml index 2cfeb8348..deef54ebf 100644 --- a/analysis/src/Hover.ml +++ b/analysis/src/Hover.ml @@ -94,8 +94,7 @@ let findRelevantTypesFromType ~file ~package typ = constructors |> List.filter_map (fromConstructorPath ~env:envToSearch) (* Produces a hover with relevant types expanded in the main type being hovered. *) -let hoverWithExpandedTypes ~docstring ~file ~package ~supportsMarkdownLinks typ - = +let hoverWithExpandedTypes ~file ~package ~supportsMarkdownLinks typ = let typeString = Markdown.codeBlock (typ |> Shared.typeToString) in let types = findRelevantTypesFromType typ ~file ~package in let typeDefinitions = @@ -114,7 +113,7 @@ let hoverWithExpandedTypes ~docstring ~file ~package ~supportsMarkdownLinks typ (SharedTypes.pathIdentToString path)) ^ linkToTypeDefinitionStr ^ "\n") in - (typeString :: typeDefinitions |> String.concat "\n", docstring) + typeString :: typeDefinitions |> String.concat "\n" (* Leverages autocomplete functionality to produce a hover for a position. This makes it (most often) work with unsaved content. *) @@ -151,12 +150,20 @@ let getHoverViaCompletions ~debug ~path ~pos ~currentFile ~forHover @ docstring in Some (Protocol.stringifyHover (String.concat "\n\n" parts)) + | {kind = Field _; docstring} :: _ -> ( + match CompletionBackEnd.completionsGetTypeEnv completions with + | Some (typ, _env) -> + let typeString = + hoverWithExpandedTypes ~file ~package ~supportsMarkdownLinks typ + in + let parts = typeString :: docstring in + Some (Protocol.stringifyHover (String.concat "\n\n" parts)) + | None -> None) | _ -> ( match CompletionBackEnd.completionsGetTypeEnv completions with | Some (typ, _env) -> - let typeString, _docstring = - hoverWithExpandedTypes ~docstring:"" ~file ~package - ~supportsMarkdownLinks typ + let typeString = + hoverWithExpandedTypes ~file ~package ~supportsMarkdownLinks typ in Some (Protocol.stringifyHover typeString) | None -> None)))) @@ -221,8 +228,8 @@ let newHover ~full:{file; package} ~supportsMarkdownLinks locItem = | Const_nativeint _ -> "int")) | Typed (_, t, locKind) -> let fromType ~docstring typ = - hoverWithExpandedTypes ~docstring ~file ~package ~supportsMarkdownLinks - typ + ( hoverWithExpandedTypes ~file ~package ~supportsMarkdownLinks typ, + docstring ) in let parts = match References.definedForLoc ~file ~package locKind with @@ -234,7 +241,7 @@ let newHover ~full:{file; package} ~supportsMarkdownLinks locItem = | `Declared -> let typeString, docstring = t |> fromType ~docstring in typeString :: docstring - | `Constructor {cname = {txt}; args} -> + | `Constructor {cname = {txt}; args; docstring} -> let typeString, docstring = t |> fromType ~docstring in let argsString = match args with @@ -244,7 +251,7 @@ let newHover ~full:{file; package} ~supportsMarkdownLinks locItem = |> List.map (fun (t, _) -> Shared.typeToString t) |> String.concat ", " |> Printf.sprintf "(%s)" in - typeString :: Markdown.codeBlock (txt ^ argsString) :: docstring + (Markdown.codeBlock (txt ^ argsString) :: docstring) @ [typeString] | `Field -> let typeString, docstring = t |> fromType ~docstring in typeString :: docstring) diff --git a/analysis/src/ProcessCmt.ml b/analysis/src/ProcessCmt.ml index 901c19a12..96f3b15e9 100644 --- a/analysis/src/ProcessCmt.ml +++ b/analysis/src/ProcessCmt.ml @@ -10,6 +10,11 @@ let addDeclared ~(name : string Location.loc) ~extent ~stamp ~(env : Env.t) addStamp env.stamps stamp declared; declared +let attrsToDocstring attrs = + match ProcessAttributes.findDocAttribute attrs with + | None -> [] + | Some docstring -> [docstring] + let rec forTypeSignatureItem ~(env : SharedTypes.Env.t) ~(exported : Exported.t) (item : Types.signature_item) = match item with @@ -76,6 +81,7 @@ let rec forTypeSignatureItem ~(env : SharedTypes.Env.t) ~(exported : Exported.t) | Cstr_record _ -> []); res = cd_res; typeDecl = (name, decl); + docstring = attrsToDocstring cd_attributes; } in let declared = @@ -99,6 +105,7 @@ let rec forTypeSignatureItem ~(env : SharedTypes.Env.t) ~(exported : Exported.t) optional = Res_parsetree_viewer.hasOptionalAttribute ld_attributes; + docstring = attrsToDocstring ld_attributes; }))); } ~name ~stamp:(Ident.binding_time ident) ~env type_attributes @@ -201,6 +208,7 @@ let forTypeDeclaration ~env ~(exported : Exported.t) | None -> None | Some t -> Some t.ctyp_type); typeDecl = (name.txt, typ_type); + docstring = attrsToDocstring cd_attributes; } in let declared = @@ -230,6 +238,7 @@ let forTypeDeclaration ~env ~(exported : Exported.t) optional = Res_parsetree_viewer.hasOptionalAttribute ld_attributes; + docstring = attrsToDocstring ld_attributes; }))); } ~name ~stamp ~env typ_attributes @@ -309,11 +318,7 @@ let forSignature ~name ~env sigItems = | {sig_desc = Tsig_attribute attribute} :: _ -> [attribute] | _ -> [] in - let docstring = - match ProcessAttributes.findDocAttribute attributes with - | None -> [] - | Some d -> [d] - in + let docstring = attrsToDocstring attributes in {Module.name; docstring; exported; items} let forTreeModuleType ~name ~env {Typedtree.mty_desc} = @@ -483,11 +488,7 @@ and forStructure ~name ~env strItems = | {str_desc = Tstr_attribute attribute} :: _ -> [attribute] | _ -> [] in - let docstring = - match ProcessAttributes.findDocAttribute attributes with - | None -> [] - | Some d -> [d] - in + let docstring = attrsToDocstring attributes in {Module.name; docstring; exported; items} let fileForCmtInfos ~moduleName ~uri diff --git a/analysis/src/References.ml b/analysis/src/References.ml index 7ef538cb9..e19439a20 100644 --- a/analysis/src/References.ml +++ b/analysis/src/References.ml @@ -169,8 +169,14 @@ let definedForLoc ~file ~package locKind = | Constructor name -> ( match getConstructor file stamp name with | None -> None - | Some constructor -> Some ([], `Constructor constructor)) - | Field _name -> Some ([], `Field) + | Some constructor -> + Some (constructor.docstring, `Constructor constructor)) + | Field name -> + Some + ( (match getField file stamp name with + | None -> [] + | Some field -> field.docstring), + `Field ) | _ -> ( maybeLog ("Trying for declared " ^ Tip.toString tip ^ " " ^ string_of_int stamp diff --git a/analysis/src/SharedTypes.ml b/analysis/src/SharedTypes.ml index 768a7bb7e..7cc7196c5 100644 --- a/analysis/src/SharedTypes.ml +++ b/analysis/src/SharedTypes.ml @@ -29,6 +29,7 @@ type field = { fname: string Location.loc; typ: Types.type_expr; optional: bool; + docstring: string list; } module Constructor = struct @@ -38,6 +39,7 @@ module Constructor = struct args: (Types.type_expr * Location.t) list; res: Types.type_expr option; typeDecl: string * Types.type_declaration; + docstring: string list; } end @@ -309,12 +311,12 @@ module Completion = struct kind: kind; } - let create ~name ~kind ~env = + let create ~kind ~env ?(docstring = []) name = { name; env; deprecated = None; - docstring = []; + docstring; kind; sortText = None; insertText = None; diff --git a/analysis/tests/src/Hover.res b/analysis/tests/src/Hover.res index 51c31fec5..739482f9a 100644 --- a/analysis/tests/src/Hover.res +++ b/analysis/tests/src/Hover.res @@ -233,3 +233,23 @@ let _ = NotShadowed.xx let _ = Shadowed.xx // ^hov + +type recordWithDocstringField = { + /** Mighty fine field here. */ + someField: bool, +} + +let x: recordWithDocstringField = { + someField: true, +} + +// x.someField +// ^hov + +let someField = x.someField +// ^hov + +type variant = | /** Cool variant! */ CoolVariant | /** Other cool variant */ OtherCoolVariant + +let coolVariant = CoolVariant +// ^hov diff --git a/analysis/tests/src/expected/Hover.res.txt b/analysis/tests/src/expected/Hover.res.txt index b1a12b118..5b47e8f22 100644 --- a/analysis/tests/src/expected/Hover.res.txt +++ b/analysis/tests/src/expected/Hover.res.txt @@ -179,3 +179,16 @@ Hover src/Hover.res 230:20 Hover src/Hover.res 233:17 {"contents": {"kind": "markdown", "value": "```rescript\nint\n```\n\n More Stuff "}} +Hover src/Hover.res 245:6 +Nothing at that position. Now trying to use completion. +posCursor:[245:6] posNoWhite:[245:5] Found expr:[245:3->245:14] +Pexp_field [245:3->245:4] someField:[245:5->245:14] +Completable: Cpath Value[x].someField +{"contents": {"kind": "markdown", "value": "```rescript\nbool\n```\n\n Mighty fine field here. "}} + +Hover src/Hover.res 248:19 +{"contents": {"kind": "markdown", "value": "```rescript\nbool\n```\n\n Mighty fine field here. "}} + +Hover src/Hover.res 253:20 +{"contents": {"kind": "markdown", "value": "```rescript\nCoolVariant\n```\n\n Cool variant! \n\n```rescript\nvariant\n```\n\n---\n\n```\n \n```\n```rescript\ntype variant = CoolVariant | OtherCoolVariant\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Hover.res%22%2C251%2C0%5D)\n"}} +