Skip to content

Commit 81a28cd

Browse files
committed
complete for functions creating React.element when in a JSX context
1 parent e595eae commit 81a28cd

File tree

7 files changed

+506
-20
lines changed

7 files changed

+506
-20
lines changed

analysis/src/CompletionBackEnd.ml

+46-10
Original file line numberDiff line numberDiff line change
@@ -1477,7 +1477,7 @@ let rec getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env
14771477
else None)
14781478
| None -> [])
14791479
| None -> [])
1480-
| CPPipe {contextPath = cp; id = funNamePrefix; lhsLoc} -> (
1480+
| CPPipe {contextPath = cp; id = funNamePrefix; lhsLoc; inJsx} -> (
14811481
match
14821482
cp
14831483
|> getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env
@@ -1584,7 +1584,7 @@ let rec getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env
15841584
| None -> None
15851585
in
15861586
match completionPath with
1587-
| Some completionPath ->
1587+
| Some completionPath -> (
15881588
let completionPathMinusOpens =
15891589
completionPath
15901590
|> removeRawOpens package.opens
@@ -1599,14 +1599,50 @@ let rec getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env
15991599
|> getCompletionsForPath ~completionContext:Value ~exact:false
16001600
~package ~opens ~allFiles ~pos ~env ~scope
16011601
in
1602-
completions
1603-
|> List.map (fun (completion : Completion.t) ->
1604-
{
1605-
completion with
1606-
name = completionName completion.name;
1607-
env
1608-
(* Restore original env for the completion after x->foo()... *);
1609-
})
1602+
let completions =
1603+
completions
1604+
|> List.map (fun (completion : Completion.t) ->
1605+
{
1606+
completion with
1607+
name = completionName completion.name;
1608+
env
1609+
(* Restore original env for the completion after x->foo()... *);
1610+
})
1611+
in
1612+
(* We add React element functions to the completion if we're in a JSX context *)
1613+
let forJsxCompletion =
1614+
match (inJsx, getTypePath typ) with
1615+
| true, Some (Path.Pident id) when Ident.name id = "int" -> Some "int"
1616+
| true, Some (Path.Pident id) when Ident.name id = "float" ->
1617+
Some "float"
1618+
| true, Some (Path.Pident id) when Ident.name id = "string" ->
1619+
Some "string"
1620+
| true, Some (Path.Pident id) when Ident.name id = "array" ->
1621+
(* Make sure the array contains React.element *)
1622+
let isReactElementArray =
1623+
match typ |> extractType ~env ~package with
1624+
| Some (Tarray (_env, payload)) -> (
1625+
match
1626+
payload |> getTypePath |> Option.map pathIdentToString
1627+
with
1628+
| Some "React.element" -> true
1629+
| _ -> false)
1630+
| _ -> false
1631+
in
1632+
if isReactElementArray then Some "array" else None
1633+
| _ -> None
1634+
in
1635+
match forJsxCompletion with
1636+
| Some builtinNameToComplete
1637+
when checkName builtinNameToComplete ~prefix:funNamePrefix
1638+
~exact:false ->
1639+
[
1640+
Completion.create
1641+
~name:("React." ^ builtinNameToComplete)
1642+
~kind:(Value typ) ~env;
1643+
]
1644+
@ completions
1645+
| _ -> completions)
16101646
| None -> [])
16111647
| None -> [])
16121648
| CTuple ctxPaths ->

analysis/src/CompletionFrontEnd.ml

+22-2
Original file line numberDiff line numberDiff line change
@@ -914,6 +914,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text =
914914
!scope |> Scope.addModule ~name:md.pmd_name.txt ~loc:md.pmd_name.loc
915915
in
916916
let setLookingForPat ctxPath = lookingForPat := Some ctxPath in
917+
let inJsxContext = ref false in
917918

918919
let unsetLookingForPat () = lookingForPat := None in
919920
(* Identifies expressions where we can do typed pattern or expr completion. *)
@@ -1035,6 +1036,14 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text =
10351036
if not !processed then
10361037
Ast_iterator.default_iterator.structure_item iterator item
10371038
in
1039+
let value_binding (iterator : Ast_iterator.iterator)
1040+
(value_binding : Parsetree.value_binding) =
1041+
if
1042+
locHasCursor value_binding.pvb_expr.pexp_loc
1043+
&& Utils.isReactComponent value_binding
1044+
then inJsxContext := true;
1045+
Ast_iterator.default_iterator.value_binding iterator value_binding
1046+
in
10381047
let signature (iterator : Ast_iterator.iterator)
10391048
(signature : Parsetree.signature) =
10401049
let oldScope = !scope in
@@ -1125,11 +1134,20 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text =
11251134
match exprToContextPath lhs with
11261135
| Some pipe ->
11271136
setResult
1128-
(Cpath (CPPipe {contextPath = pipe; id; lhsLoc = lhs.pexp_loc}));
1137+
(Cpath
1138+
(CPPipe
1139+
{
1140+
contextPath = pipe;
1141+
id;
1142+
lhsLoc = lhs.pexp_loc;
1143+
inJsx = !inJsxContext;
1144+
}));
11291145
true
11301146
| None -> false)
11311147
| Some (pipe, lhsLoc) ->
1132-
setResult (Cpath (CPPipe {contextPath = pipe; id; lhsLoc}));
1148+
setResult
1149+
(Cpath
1150+
(CPPipe {contextPath = pipe; id; lhsLoc; inJsx = !inJsxContext}));
11331151
true
11341152
in
11351153
typedCompletionExpr expr;
@@ -1201,6 +1219,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text =
12011219
| None -> ())
12021220
| Pexp_apply ({pexp_desc = Pexp_ident compName}, args)
12031221
when Res_parsetree_viewer.isJsxExpression expr ->
1222+
inJsxContext := true;
12041223
let jsxProps = extractJsxProps ~compName ~args in
12051224
let compNamePath = flattenLidCheckDot ~jsx:true compName in
12061225
if debug then
@@ -1468,6 +1487,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text =
14681487
structure_item;
14691488
typ;
14701489
type_kind;
1490+
value_binding;
14711491
}
14721492
in
14731493

analysis/src/SharedTypes.ml

+5-1
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,7 @@ module Completable = struct
546546
| CPPipe of {
547547
contextPath: contextPath;
548548
id: string;
549+
inJsx: bool; (** Whether this pipe was found in a JSX context. *)
549550
lhsLoc: Location.t;
550551
(** The loc item for the left hand side of the pipe. *)
551552
}
@@ -648,7 +649,10 @@ module Completable = struct
648649
completionContextToString completionContext ^ list sl
649650
| CPField (cp, s) -> contextPathToString cp ^ "." ^ str s
650651
| CPObj (cp, s) -> contextPathToString cp ^ "[\"" ^ s ^ "\"]"
651-
| CPPipe {contextPath; id} -> contextPathToString contextPath ^ "->" ^ id
652+
| CPPipe {contextPath; id; inJsx} ->
653+
contextPathToString contextPath
654+
^ "->" ^ id
655+
^ if inJsx then " <<jsx>>" else ""
652656
| CTuple ctxPaths ->
653657
"CTuple("
654658
^ (ctxPaths |> List.map contextPathToString |> String.concat ", ")

analysis/src/Utils.ml

+5
Original file line numberDiff line numberDiff line change
@@ -168,3 +168,8 @@ let rec unwrapIfOption (t : Types.type_expr) =
168168
| Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> unwrapIfOption t1
169169
| Tconstr (Path.Pident {name = "option"}, [unwrappedType], _) -> unwrappedType
170170
| _ -> t
171+
let isReactComponent (vb : Parsetree.value_binding) =
172+
vb.pvb_attributes
173+
|> List.exists (function
174+
| {Location.txt = "react.component"}, _payload -> true
175+
| _ -> false)

analysis/src/Xform.ml

+2-7
Original file line numberDiff line numberDiff line change
@@ -212,13 +212,8 @@ module AddTypeAnnotation = struct
212212
match si.pstr_desc with
213213
| Pstr_value (_recFlag, bindings) ->
214214
let processBinding (vb : Parsetree.value_binding) =
215-
let isReactComponent =
216-
(* Can't add a type annotation to a react component, or the compiler crashes *)
217-
vb.pvb_attributes
218-
|> List.exists (function
219-
| {Location.txt = "react.component"}, _payload -> true
220-
| _ -> false)
221-
in
215+
(* Can't add a type annotation to a react component, or the compiler crashes *)
216+
let isReactComponent = Utils.isReactComponent vb in
222217
if not isReactComponent then processPattern vb.pvb_pat;
223218
processFunction vb.pvb_expr
224219
in

analysis/tests/src/CompletionJsx.res

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
let someString = "hello"
2+
ignore(someString)
3+
4+
// someString->st
5+
// ^com
6+
7+
module SomeComponent = {
8+
@react.component
9+
let make = (~someProp) => {
10+
let someInt = 12
11+
let someArr = [React.null]
12+
let someInvalidArr = [12]
13+
ignore(someInt)
14+
ignore(someArr)
15+
ignore(someInvalidArr)
16+
// someString->st
17+
// ^com
18+
<div>
19+
{React.string(someProp)}
20+
<div> {React.null} </div>
21+
// {someString->st}
22+
// ^com
23+
// {"Some string"->st}
24+
// ^com
25+
// {"Some string"->Js.String2.trim->st}
26+
// ^com
27+
// {someInt->}
28+
// ^com
29+
// {12->}
30+
// ^com
31+
// {someArr->a}
32+
// ^com
33+
// {someInvalidArr->a}
34+
// ^com
35+
</div>
36+
}
37+
}

0 commit comments

Comments
 (0)