From 6cd56f3dda4f4bb1b3bf74be4771c51598831607 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Wed, 18 Jan 2023 21:38:03 +0100 Subject: [PATCH 1/3] typed expression compltion for lowercase JSX tags --- analysis/src/CompletionBackEnd.ml | 37 ++++++++++++++----- analysis/src/Utils.ml | 6 +++ analysis/tests/src/CompletionJsxProps.res | 3 ++ .../src/expected/CompletionJsxProps.res.txt | 18 +++++++++ 4 files changed, 54 insertions(+), 10 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index d63e74420..8d8a3776f 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -756,18 +756,12 @@ let rec getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env | Lazy -> ["Lazy"] | Char -> ["Char"]) | TypExpr t -> ( - let rec expandPath (path : Path.t) = - match path with - | Pident id -> [Ident.name id] - | Pdot (p, s, _) -> s :: expandPath p - | Papply _ -> [] - in match t.Types.desc with | Tconstr (path, _typeArgs, _) | Tlink {desc = Tconstr (path, _typeArgs, _)} | Tsubst {desc = Tconstr (path, _typeArgs, _)} | Tpoly ({desc = Tconstr (path, _typeArgs, _)}, []) -> ( - match expandPath path with + match Utils.expandPath path with | _ :: pathRev -> (* type path is relative to the completion environment express it from the root of the file *) @@ -882,10 +876,33 @@ let rec getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env ~opens ~allFiles ~pos ~env ~scope |> completionsGetTypeEnv in + let isBuiltin = + match pathToComponent with + | [elName] when Char.lowercase_ascii elName.[0] = elName.[0] -> true + | _ -> false + in let targetLabel = - CompletionJsx.getJsxLabels ~componentPath:pathToComponent ~findTypeOfValue - ~package - |> List.find_opt (fun (label, _, _) -> label = propName) + if isBuiltin then + let rec digToTypeForCompletion path ~env = + match + path + |> getCompletionsForPath ~completionContext:Type ~exact:true + ~package ~opens ~allFiles ~pos ~env ~scope + with + | {kind = Type {kind = Abstract (Some (p, _))}; env} :: _ -> + let pathRev = p |> Utils.expandPath in + pathRev |> List.rev |> digToTypeForCompletion ~env + | {kind = Type {kind = Record fields}; env} :: _ -> ( + 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 ~env + else + CompletionJsx.getJsxLabels ~componentPath:pathToComponent + ~findTypeOfValue ~package + |> List.find_opt (fun (label, _, _) -> label = propName) in match targetLabel with | None -> [] diff --git a/analysis/src/Utils.ml b/analysis/src/Utils.ml index c5dfed716..00468a339 100644 --- a/analysis/src/Utils.ml +++ b/analysis/src/Utils.ml @@ -206,3 +206,9 @@ let rangeOfLoc (loc : Location.t) = let start = loc |> Loc.start |> mkPosition in let end_ = loc |> Loc.end_ |> mkPosition in {Protocol.start; end_} + +let rec expandPath (path : Path.t) = + match path with + | Pident id -> [Ident.name id] + | Pdot (p, s, _) -> s :: expandPath p + | Papply _ -> [] diff --git a/analysis/tests/src/CompletionJsxProps.res b/analysis/tests/src/CompletionJsxProps.res index 41dcfe57f..bf2510ae6 100644 --- a/analysis/tests/src/CompletionJsxProps.res +++ b/analysis/tests/src/CompletionJsxProps.res @@ -13,3 +13,6 @@ // let _ = +// ^com + diff --git a/analysis/tests/src/expected/CompletionJsxProps.res.txt b/analysis/tests/src/expected/CompletionJsxProps.res.txt index 261fa6328..5e01cdab2 100644 --- a/analysis/tests/src/expected/CompletionJsxProps.res.txt +++ b/analysis/tests/src/expected/CompletionJsxProps.res.txt @@ -132,3 +132,21 @@ Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] polyAr "insertTextFormat": 2 }] +Complete src/CompletionJsxProps.res 15:22 +posCursor:[15:22] posNoWhite:[15:21] Found expr:[15:12->15:25] +JSX 15:15] muted[15:16->15:21]=...__ghost__[0:-1->0:-1]> _children:15:23 +Completable: Cexpression CJsxPropValue [div] muted +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "false", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + From 4c6403f70d8f833a973d94820988d03f2bb9868a Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Wed, 18 Jan 2023 21:39:37 +0100 Subject: [PATCH 2/3] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 812bacb45..7a9ea2a50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ - Let `_` trigger completion in patterns. https://github.com/rescript-lang/rescript-vscode/pull/692 - Support inline records in completion. https://github.com/rescript-lang/rescript-vscode/pull/695 - Add way to autocomplete an exhaustive switch statement for identifiers. Example: an identifier that's a variant can have a switch autoinserted matching all variant cases. https://github.com/rescript-lang/rescript-vscode/pull/699 +- Support typed expression completion for lowercase (builtin) JSX tags. https://github.com/rescript-lang/rescript-vscode/pull/702 #### :nail_care: Polish From 1c75be487b35f2e8fa90b18b11963be42cb9cd1b Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Fri, 20 Jan 2023 09:48:34 +0100 Subject: [PATCH 3/3] fixes + autowrap insertion text with braces when in jsx --- analysis/src/CompletionBackEnd.ml | 24 ++++++++++++-- analysis/tests/src/CompletionJsxProps.res | 3 ++ .../src/expected/CompletionJsxProps.res.txt | 33 ++++++++++++++----- 3 files changed, 48 insertions(+), 12 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 8d8a3776f..f503721cb 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -876,13 +876,13 @@ let rec getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env ~opens ~allFiles ~pos ~env ~scope |> completionsGetTypeEnv in - let isBuiltin = + let lowercaseComponent = match pathToComponent with | [elName] when Char.lowercase_ascii elName.[0] = elName.[0] -> true | _ -> false in let targetLabel = - if isBuiltin then + if lowercaseComponent then let rec digToTypeForCompletion path ~env = match path @@ -890,6 +890,9 @@ let rec getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env ~package ~opens ~allFiles ~pos ~env ~scope with | {kind = Type {kind = Abstract (Some (p, _))}; env} :: _ -> + (* 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 ~env | {kind = Type {kind = Record fields}; env} :: _ -> ( @@ -1149,7 +1152,7 @@ let rec completeTypedValue (t : SharedTypes.completionType) ~env ~full ~prefix (* Pretty print a few common patterns. *) match Path.head p |> Ident.name with | "unit" -> "()" - | "ReactEvent" -> "event" + | "ReactEvent" | "JsxEvent" -> "event" | _ -> "v" ^ indexText) in let mkFnArgs ~asSnippet = @@ -1333,10 +1336,25 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover with | None -> [] | Some (typ, env, completionContext) -> ( + let isJsx = + match contextPath with + | CJsxPropValue _ | CPPipe {inJsx = true} -> true + | _ -> false + in let items = typ |> completeTypedValue ~mode:Expression ~env ~full ~prefix ~completionContext + |> List.map (fun (c : Completion.t) -> + if isJsx then + { + c with + insertText = + (match c.insertText with + | None -> None + | Some text -> Some ("{" ^ text ^ "}")); + } + else c) in match (prefix, completionContext) with | "", _ -> items diff --git a/analysis/tests/src/CompletionJsxProps.res b/analysis/tests/src/CompletionJsxProps.res index bf2510ae6..d9d579f76 100644 --- a/analysis/tests/src/CompletionJsxProps.res +++ b/analysis/tests/src/CompletionJsxProps.res @@ -16,3 +16,6 @@ // let _ =
// ^com +// let _ =
+// ^com + diff --git a/analysis/tests/src/expected/CompletionJsxProps.res.txt b/analysis/tests/src/expected/CompletionJsxProps.res.txt index 5e01cdab2..31e39f0a8 100644 --- a/analysis/tests/src/expected/CompletionJsxProps.res.txt +++ b/analysis/tests/src/expected/CompletionJsxProps.res.txt @@ -39,7 +39,7 @@ Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] test=T "detail": "Two\n\ntype testVariant = One | Two | Three(int)", "documentation": null, "sortText": "A Two", - "insertText": "Two", + "insertText": "{Two}", "insertTextFormat": 2 }, { "label": "Three(_)", @@ -48,7 +48,7 @@ Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] test=T "detail": "Three(int)\n\ntype testVariant = One | Two | Three(int)", "documentation": null, "sortText": "A Three(_)", - "insertText": "Three(${1:_})", + "insertText": "{Three(${1:_})}", "insertTextFormat": 2 }, { "label": "TableclothMap", @@ -74,7 +74,7 @@ Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] polyAr "tags": [], "detail": "#one\n\n[#one | #three(int, bool) | #two | #two2]", "documentation": null, - "insertText": "#one", + "insertText": "{#one}", "insertTextFormat": 2 }, { "label": "#three(_, _)", @@ -82,7 +82,7 @@ Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] polyAr "tags": [], "detail": "#three(int, bool)\n\n[#one | #three(int, bool) | #two | #two2]", "documentation": null, - "insertText": "#three(${1:_}, ${2:_})", + "insertText": "{#three(${1:_}, ${2:_})}", "insertTextFormat": 2 }, { "label": "#two", @@ -90,7 +90,7 @@ Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] polyAr "tags": [], "detail": "#two\n\n[#one | #three(int, bool) | #two | #two2]", "documentation": null, - "insertText": "#two", + "insertText": "{#two}", "insertTextFormat": 2 }, { "label": "#two2", @@ -98,7 +98,7 @@ Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] polyAr "tags": [], "detail": "#two2\n\n[#one | #three(int, bool) | #two | #two2]", "documentation": null, - "insertText": "#two2", + "insertText": "{#two2}", "insertTextFormat": 2 }] @@ -112,7 +112,7 @@ Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] polyAr "tags": [], "detail": "#three(int, bool)\n\n[#one | #three(int, bool) | #two | #two2]", "documentation": null, - "insertText": "three(${1:_}, ${2:_})", + "insertText": "{three(${1:_}, ${2:_})}", "insertTextFormat": 2 }, { "label": "#two", @@ -120,7 +120,7 @@ Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] polyAr "tags": [], "detail": "#two\n\n[#one | #three(int, bool) | #two | #two2]", "documentation": null, - "insertText": "two", + "insertText": "{two}", "insertTextFormat": 2 }, { "label": "#two2", @@ -128,7 +128,7 @@ Completable: Cexpression CJsxPropValue [CompletionSupport, TestComponent] polyAr "tags": [], "detail": "#two2\n\n[#one | #three(int, bool) | #two | #two2]", "documentation": null, - "insertText": "two2", + "insertText": "{two2}", "insertTextFormat": 2 }] @@ -150,3 +150,18 @@ Completable: Cexpression CJsxPropValue [div] muted "documentation": null }] +Complete src/CompletionJsxProps.res 18:29 +posCursor:[18:29] posNoWhite:[18:28] Found expr:[18:12->18:32] +JSX 18:15] onMouseEnter[18:16->18:28]=...__ghost__[0:-1->0:-1]> _children:18:30 +Completable: Cexpression CJsxPropValue [div] onMouseEnter +[{ + "label": "event => {}", + "kind": 12, + "tags": [], + "detail": "JsxEvent.Mouse.t => unit", + "documentation": null, + "sortText": "A", + "insertText": "{${1:event} => {$0}}", + "insertTextFormat": 2 + }] +