From dcd2e1804e9bdc76b8f84aca41294ecd9abd91f7 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Sat, 6 Jan 2024 19:11:32 +0100 Subject: [PATCH 1/2] complete lowercase JSX labels from the domProps type --- analysis/src/CompletionBackEnd.ml | 90 +++++++++++++------ analysis/tests/src/CompletionJsx.res | 3 + .../tests/src/expected/Completion.res.txt | 2 + .../tests/src/expected/CompletionJsx.res.txt | 16 ++++ analysis/tests/src/expected/Div.res.txt | 2 + 5 files changed, 86 insertions(+), 27 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 550deebe9..3bc309959 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -548,6 +548,24 @@ let getCompletionsForPath ~debug ~package ~opens ~full ~pos ~exact ~scope findAllCompletions ~env ~prefix ~exact ~namesUsed ~completionContext | None -> []) +let rec digToRecordFieldsForCompletion ~debug ~package ~opens ~full ~pos ~env + ~scope path = + match + path + |> getCompletionsForPath ~debug ~completionContext:Type ~exact:true ~package + ~opens ~full ~pos ~env ~scope + with + | {kind = Type {kind = Abstract (Some (p, _))}} :: _ -> + (* This case happens when what we're looking for is a type alias. + This is the case in newer rescript-react versions where + ReactDOM.domProps is an alias for JsxEvent.t. *) + let pathRev = p |> Utils.expandPath in + pathRev |> List.rev + |> digToRecordFieldsForCompletion ~debug ~package ~opens ~full ~pos ~env + ~scope + | {kind = Type {kind = Record fields}} :: _ -> Some fields + | _ -> None + let mkItem ~name ~kind ~detail ~deprecated ~docstring = let docContent = (match deprecated with @@ -571,7 +589,7 @@ let mkItem ~name ~kind ~detail ~deprecated ~docstring = detail; documentation = (if docContent = "" then None - else Some {kind = "markdown"; value = docContent}); + else Some {kind = "markdown"; value = docContent}); sortText = None; insertText = None; insertTextFormat = None; @@ -1043,25 +1061,16 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact in let targetLabel = if lowercaseComponent then - let rec digToTypeForCompletion path = - match - path - |> getCompletionsForPath ~debug ~completionContext:Type ~exact:true - ~package ~opens ~full ~pos ~env ~scope - with - | {kind = Type {kind = Abstract (Some (p, _))}} :: _ -> - (* This case happens when what we're looking for is a type alias. - This is the case in newer rescript-react versions where - ReactDOM.domProps is an alias for JsxEvent.t. *) - let pathRev = p |> Utils.expandPath in - pathRev |> List.rev |> digToTypeForCompletion - | {kind = Type {kind = Record fields}} :: _ -> ( - match fields |> List.find_opt (fun f -> f.fname.txt = propName) with - | None -> None - | Some f -> Some (f.fname.txt, f.typ, env)) - | _ -> None - in - ["ReactDOM"; "domProps"] |> digToTypeForCompletion + match + ["ReactDOM"; "domProps"] + |> digToRecordFieldsForCompletion ~debug ~package ~opens ~full ~pos + ~env ~scope + with + | None -> None + | Some fields -> ( + match fields |> List.find_opt (fun f -> f.fname.txt = propName) with + | None -> None + | Some f -> Some (f.fname.txt, f.typ, env)) else CompletionJsx.getJsxLabels ~componentPath:pathToComponent ~findTypeOfValue ~package @@ -1541,7 +1550,7 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover completable = contextPath |> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact:forHover ~scope - | Cjsx ([id], prefix, identsSeen) when String.uncapitalize_ascii id = id -> + | 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 @@ -1549,12 +1558,39 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover completable = let keyLabels = if Utils.startsWith "key" prefix then [mkLabel ("key", "string")] else [] in - (CompletionJsx.domLabels - |> List.filter (fun (name, _t) -> - Utils.startsWith name prefix - && (forHover || not (List.mem name identsSeen))) - |> List.map mkLabel) - @ keyLabels + (* We always try to look up completion from the actual domProps type first. + This works in JSXv4. For JSXv3, we have a backup hardcoded list of dom + labels we can use for completion. *) + let fromDomProps = + match + ["ReactDOM"; "domProps"] + |> digToRecordFieldsForCompletion ~debug ~package ~opens ~full ~pos ~env + ~scope + with + | None -> None + | Some fields -> + Some + (fields + |> List.filter_map (fun (f : field) -> + if + Utils.startsWith f.fname.txt prefix + && (forHover || not (List.mem f.fname.txt identsSeen)) + then + Some + ( f.fname.txt, + Shared.typeToString (Utils.unwrapIfOption f.typ) ) + else None) + |> List.map mkLabel) + in + match fromDomProps with + | Some domProps -> domProps + | None -> + (CompletionJsx.domLabels + |> List.filter (fun (name, _t) -> + Utils.startsWith name prefix + && (forHover || not (List.mem name identsSeen))) + |> List.map mkLabel) + @ keyLabels) | Cjsx (componentPath, prefix, identsSeen) -> let labels = CompletionJsx.getJsxLabels ~componentPath ~findTypeOfValue ~package diff --git a/analysis/tests/src/CompletionJsx.res b/analysis/tests/src/CompletionJsx.res index 5e966180f..dca147687 100644 --- a/analysis/tests/src/CompletionJsx.res +++ b/analysis/tests/src/CompletionJsx.res @@ -48,3 +48,6 @@ module CompWithoutJsxPpx = { // // ^com + +//

51:11] +JSX 51:6] hidd[51:7->51:11]=...[51:7->51:11]> _children:None +Completable: Cjsx([h1], hidd, [hidd]) +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +Path ReactDOM.domProps +Path Pervasives.JsxDOM.domProps +[{ + "label": "hidden", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + diff --git a/analysis/tests/src/expected/Div.res.txt b/analysis/tests/src/expected/Div.res.txt index 29048516a..6f0086a44 100644 --- a/analysis/tests/src/expected/Div.res.txt +++ b/analysis/tests/src/expected/Div.res.txt @@ -8,6 +8,8 @@ JSX 3:7] dangerous[3:8->3:17]=...[3:8->3:17]> _children:None Completable: Cjsx([div], dangerous, [dangerous]) Package opens Pervasives.JsxModules.place holder Resolved opens 1 pervasives +Path ReactDOM.domProps +Path Pervasives.JsxDOM.domProps [{ "label": "dangerouslySetInnerHTML", "kind": 4, From 7220e9d5d34a9f49f7b67bf9bc329526fee50379 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Sat, 6 Jan 2024 19:19:04 +0100 Subject: [PATCH 2/2] changelog --- CHANGELOG.md | 1 + analysis/src/CompletionBackEnd.ml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 971a0d4c4..8b181179b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - Better error recovery when analysis fails. https://github.com/rescript-lang/rescript-vscode/pull/880 - Expand type aliases in hovers. https://github.com/rescript-lang/rescript-vscode/pull/881 - Include fields when completing a braced expr that's an ID, where it the path likely starts with a module. https://github.com/rescript-lang/rescript-vscode/pull/882 +- Complete domProps for lowercase JSX components from `ReactDOM.domProps` if possible. https://github.com/rescript-lang/rescript-vscode/pull/883 ## 1.32.0 diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 3bc309959..021c5cc89 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -1585,6 +1585,8 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover completable = match fromDomProps with | Some domProps -> domProps | None -> + if debug then + Printf.printf "Could not find ReactDOM.domProps to complete from.\n"; (CompletionJsx.domLabels |> List.filter (fun (name, _t) -> Utils.startsWith name prefix