Skip to content

Complete lowercase JSX labels from the domProps type #883

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
92 changes: 65 additions & 27 deletions analysis/src/CompletionBackEnd.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1541,20 +1550,49 @@ 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
in
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 ->
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
&& (forHover || not (List.mem name identsSeen)))
|> List.map mkLabel)
@ keyLabels)
| Cjsx (componentPath, prefix, identsSeen) ->
let labels =
CompletionJsx.getJsxLabels ~componentPath ~findTypeOfValue ~package
Expand Down
3 changes: 3 additions & 0 deletions analysis/tests/src/CompletionJsx.res
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,6 @@ module CompWithoutJsxPpx = {

// <SomeComponent someProp=>
// ^com

// <h1 hidd
// ^com
2 changes: 2 additions & 0 deletions analysis/tests/src/expected/Completion.res.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1684,6 +1684,8 @@ Completable: Cjsx([div], name, [name])
Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
Package opens Pervasives.JsxModules.place holder
Resolved opens 3 pervasives Completion.res Completion.res
Path ReactDOM.domProps
Path Pervasives.JsxDOM.domProps
{"contents": {"kind": "markdown", "value": "```rescript\nstring\n```"}}

Hover src/Completion.res 349:17
Expand Down
16 changes: 16 additions & 0 deletions analysis/tests/src/expected/CompletionJsx.res.txt
Original file line number Diff line number Diff line change
Expand Up @@ -492,3 +492,19 @@ Path SomeComponent.make
"insertTextFormat": 2
}]

Complete src/CompletionJsx.res 51:11
posCursor:[51:11] posNoWhite:[51:10] Found expr:[51:4->51:11]
JSX <h1:[51:4->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
}]

2 changes: 2 additions & 0 deletions analysis/tests/src/expected/Div.res.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ JSX <div:[3:4->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,
Expand Down