Skip to content

Commit e49c8c8

Browse files
committed
complete JSX prop values
1 parent 2efec0b commit e49c8c8

File tree

8 files changed

+203
-89
lines changed

8 files changed

+203
-89
lines changed

analysis/src/CompletionBackEnd.ml

Lines changed: 93 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1572,6 +1572,81 @@ let completeTypedValue ~env ~envWhereCompletionStarted ~full ~prefix
15721572
in
15731573
completeTypedValueInner ~env ~full ~prefix ~expandOption
15741574

1575+
let getJsxLabels ~componentPath ~findTypeOfValue ~package =
1576+
match componentPath @ ["make"] |> findTypeOfValue with
1577+
| Some (typ, make_env) ->
1578+
let rec getFieldsV3 (texp : Types.type_expr) =
1579+
match texp.desc with
1580+
| Tfield (name, _, t1, t2) ->
1581+
let fields = t2 |> getFieldsV3 in
1582+
if name = "children" then fields else (name, t1, make_env) :: fields
1583+
| Tlink te | Tsubst te | Tpoly (te, []) -> te |> getFieldsV3
1584+
| Tvar None -> []
1585+
| _ -> []
1586+
in
1587+
let getFieldsV4 ~path ~typeArgs =
1588+
match References.digConstructor ~env:make_env ~package path with
1589+
| Some
1590+
( env,
1591+
{
1592+
item =
1593+
{
1594+
decl =
1595+
{
1596+
type_kind = Type_record (labelDecls, _repr);
1597+
type_params = typeParams;
1598+
};
1599+
};
1600+
} ) ->
1601+
labelDecls
1602+
|> List.map (fun (ld : Types.label_declaration) ->
1603+
let name = Ident.name ld.ld_id in
1604+
let t = ld.ld_type |> instantiateType ~typeParams ~typeArgs in
1605+
(name, t, env))
1606+
| _ -> []
1607+
in
1608+
let rec getLabels (t : Types.type_expr) =
1609+
match t.desc with
1610+
| Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> getLabels t1
1611+
| Tarrow
1612+
( Nolabel,
1613+
{
1614+
desc =
1615+
( Tconstr (* Js.t *) (_, [{desc = Tobject (tObj, _)}], _)
1616+
| Tobject (tObj, _) );
1617+
},
1618+
_,
1619+
_ ) ->
1620+
(* JSX V3 *)
1621+
getFieldsV3 tObj
1622+
| Tarrow (Nolabel, {desc = Tconstr (path, typeArgs, _)}, _, _)
1623+
when Path.last path = "props" ->
1624+
(* JSX V4 *)
1625+
getFieldsV4 ~path ~typeArgs
1626+
| Tconstr
1627+
( clPath,
1628+
[
1629+
{
1630+
desc =
1631+
( Tconstr (* Js.t *) (_, [{desc = Tobject (tObj, _)}], _)
1632+
| Tobject (tObj, _) );
1633+
};
1634+
_;
1635+
],
1636+
_ )
1637+
when Path.name clPath = "React.componentLike" ->
1638+
(* JSX V3 external or interface *)
1639+
getFieldsV3 tObj
1640+
| Tconstr (clPath, [{desc = Tconstr (path, typeArgs, _)}; _], _)
1641+
when Path.name clPath = "React.componentLike"
1642+
&& Path.last path = "props" ->
1643+
(* JSX V4 external or interface *)
1644+
getFieldsV4 ~path ~typeArgs
1645+
| _ -> []
1646+
in
1647+
typ |> getLabels
1648+
| None -> []
1649+
15751650
let processCompletable ~debug ~full ~scope ~env ~pos ~forHover
15761651
(completable : Completable.t) =
15771652
let package = full.package in
@@ -1591,6 +1666,7 @@ let processCompletable ~debug ~full ~scope ~env ~pos ~forHover
15911666
|> getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env
15921667
~exact:forHover ~scope
15931668
| Cjsx ([id], prefix, identsSeen) when String.uncapitalize_ascii id = id ->
1669+
(* Lowercase JSX tag means builtin *)
15941670
let mkLabel (name, typString) =
15951671
Completion.create ~name ~kind:(Label typString) ~env
15961672
in
@@ -1604,99 +1680,37 @@ let processCompletable ~debug ~full ~scope ~env ~pos ~forHover
16041680
|> List.map mkLabel)
16051681
@ keyLabels
16061682
| Cjsx (componentPath, prefix, identsSeen) ->
1607-
let labels =
1608-
match componentPath @ ["make"] |> findTypeOfValue with
1609-
| Some (typ, make_env) ->
1610-
let rec getFieldsV3 (texp : Types.type_expr) =
1611-
match texp.desc with
1612-
| Tfield (name, _, t1, t2) ->
1613-
let fields = t2 |> getFieldsV3 in
1614-
if name = "children" then fields else (name, t1) :: fields
1615-
| Tlink te | Tsubst te | Tpoly (te, []) -> te |> getFieldsV3
1616-
| Tvar None -> []
1617-
| _ -> []
1618-
in
1619-
let getFieldsV4 ~path ~typeArgs =
1620-
match References.digConstructor ~env:make_env ~package path with
1621-
| Some
1622-
( _env,
1623-
{
1624-
item =
1625-
{
1626-
decl =
1627-
{
1628-
type_kind = Type_record (labelDecls, _repr);
1629-
type_params = typeParams;
1630-
};
1631-
};
1632-
} ) ->
1633-
labelDecls
1634-
|> List.map (fun (ld : Types.label_declaration) ->
1635-
let name = Ident.name ld.ld_id in
1636-
let t =
1637-
ld.ld_type |> instantiateType ~typeParams ~typeArgs
1638-
in
1639-
(name, t))
1640-
| _ -> []
1641-
in
1642-
let rec getLabels (t : Types.type_expr) =
1643-
match t.desc with
1644-
| Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> getLabels t1
1645-
| Tarrow
1646-
( Nolabel,
1647-
{
1648-
desc =
1649-
( Tconstr (* Js.t *) (_, [{desc = Tobject (tObj, _)}], _)
1650-
| Tobject (tObj, _) );
1651-
},
1652-
_,
1653-
_ ) ->
1654-
(* JSX V3 *)
1655-
getFieldsV3 tObj
1656-
| Tarrow (Nolabel, {desc = Tconstr (path, typeArgs, _)}, _, _)
1657-
when Path.last path = "props" ->
1658-
(* JSX V4 *)
1659-
getFieldsV4 ~path ~typeArgs
1660-
| Tconstr
1661-
( clPath,
1662-
[
1663-
{
1664-
desc =
1665-
( Tconstr (* Js.t *) (_, [{desc = Tobject (tObj, _)}], _)
1666-
| Tobject (tObj, _) );
1667-
};
1668-
_;
1669-
],
1670-
_ )
1671-
when Path.name clPath = "React.componentLike" ->
1672-
(* JSX V3 external or interface *)
1673-
getFieldsV3 tObj
1674-
| Tconstr (clPath, [{desc = Tconstr (path, typeArgs, _)}; _], _)
1675-
when Path.name clPath = "React.componentLike"
1676-
&& Path.last path = "props" ->
1677-
(* JSX V4 external or interface *)
1678-
getFieldsV4 ~path ~typeArgs
1679-
| _ -> []
1680-
in
1681-
typ |> getLabels
1682-
| None -> []
1683-
in
1683+
let labels = getJsxLabels ~componentPath ~findTypeOfValue ~package in
16841684
let mkLabel_ name typString =
16851685
Completion.create ~name ~kind:(Label typString) ~env
16861686
in
1687-
let mkLabel (name, typ) = mkLabel_ name (typ |> Shared.typeToString) in
1687+
let mkLabel (name, typ, _env) =
1688+
mkLabel_ name (typ |> Shared.typeToString)
1689+
in
16881690
let keyLabels =
16891691
if Utils.startsWith "key" prefix then [mkLabel_ "key" "string"] else []
16901692
in
16911693
if labels = [] then []
16921694
else
16931695
(labels
1694-
|> List.filter (fun (name, _t) ->
1696+
|> List.filter (fun (name, _t, _env) ->
16951697
Utils.startsWith name prefix
16961698
&& name <> "key"
16971699
&& (forHover || not (List.mem name identsSeen)))
16981700
|> List.map mkLabel)
16991701
@ keyLabels
1702+
| CjsxPropValue {pathToComponent; prefix; propName} -> (
1703+
let targetLabel =
1704+
getJsxLabels ~componentPath:pathToComponent ~findTypeOfValue ~package
1705+
|> List.find_opt (fun (label, _, _) -> label = propName)
1706+
in
1707+
let envWhereCompletionStarted = env in
1708+
match targetLabel with
1709+
| None -> []
1710+
| Some (_, typ, env) ->
1711+
typ
1712+
|> completeTypedValue ~env ~envWhereCompletionStarted ~full ~prefix
1713+
~expandOption:true)
17001714
| Cdecorator prefix ->
17011715
let mkDecorator (name, docstring) =
17021716
{(Completion.create ~name ~kind:(Label "") ~env) with docstring}

analysis/src/CompletionFrontEnd.ml

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,17 @@ let rec skipWhite text i =
77
| ' ' | '\n' | '\r' | '\t' -> skipWhite text (i - 1)
88
| _ -> i
99

10+
let extractCompletableArgValueInfo exp =
11+
match exp.Parsetree.pexp_desc with
12+
| Pexp_ident {txt = Lident prefix} -> Some prefix
13+
| Pexp_construct ({txt = Lident prefix}, _) -> Some prefix
14+
| _ -> None
15+
16+
let isExprHole exp =
17+
match exp.Parsetree.pexp_desc with
18+
| Pexp_extension ({txt = "rescript.exprhole"}, _) -> true
19+
| _ -> false
20+
1021
type prop = {
1122
name: string;
1223
posStart: int * int;
@@ -44,11 +55,28 @@ let findJsxPropsCompletable ~jsxProps ~endPos ~posBeforeCursor ~posAfterCompName
4455
None
4556
else if prop.exp.pexp_loc |> Loc.hasPos ~pos:posBeforeCursor then
4657
(* Cursor on expr assigned *)
47-
None
58+
match extractCompletableArgValueInfo prop.exp with
59+
| Some prefix ->
60+
Some
61+
(CjsxPropValue
62+
{
63+
pathToComponent =
64+
Utils.flattenLongIdent ~jsx:true jsxProps.compName.txt;
65+
prefix;
66+
propName = prop.name;
67+
})
68+
| _ -> None
4869
else if prop.exp.pexp_loc |> Loc.end_ = (Location.none |> Loc.end_) then
49-
(* Expr assigned presumably is "rescript.exprhole" after parser recovery.
50-
To be on the safe side, don't do label completion. *)
51-
None
70+
if isExprHole prop.exp then
71+
Some
72+
(CjsxPropValue
73+
{
74+
pathToComponent =
75+
Utils.flattenLongIdent ~jsx:true jsxProps.compName.txt;
76+
prefix = "";
77+
propName = prop.name;
78+
})
79+
else None
5280
else loop rest
5381
| [] ->
5482
let beforeChildrenStart =

analysis/src/SharedTypes.ml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,11 @@ module Completable = struct
537537
prefix: string;
538538
}
539539
(** e.g. someFunction(~someBoolArg=<com>), complete for the value of `someBoolArg` (true or false). *)
540+
| CjsxPropValue of {
541+
pathToComponent: string list;
542+
propName: string;
543+
prefix: string;
544+
}
540545

541546
(** An extracted type from a type expr *)
542547
type extractedType =
@@ -597,6 +602,9 @@ module Completable = struct
597602
| Optional name -> "~" ^ name ^ "=?")
598603
^ (if prefix <> "" then "=" ^ prefix else "")
599604
^ ")"
605+
| CjsxPropValue {prefix; pathToComponent; propName} ->
606+
"CjsxPropValue " ^ (pathToComponent |> list) ^ " " ^ propName ^ "="
607+
^ prefix
600608
end
601609

602610
module CursorPosition = struct
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// let _ = <CompletionSupport.TestComponent on=
2+
// ^com
3+
4+
// let _ = <CompletionSupport.TestComponent on=t
5+
// ^com
6+
7+
// let _ = <CompletionSupport.TestComponent test=T
8+
// ^com
9+

analysis/tests/src/CompletionSupport.res

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,14 @@ module Test = {
44
let addSelf = (ax: t) => {name: ax.name + 1}
55
let make = (name: int): t => {name: name}
66
}
7+
8+
type testVariant = One | Two | Three(int)
9+
10+
module TestComponent = {
11+
@react.component
12+
let make = (~on: bool, ~test: testVariant) => {
13+
ignore(on)
14+
ignore(test)
15+
React.null
16+
}
17+
}

analysis/tests/src/expected/Completion.res.txt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -463,9 +463,7 @@ Completable: Cpath Value[Js, Dict, u]
463463
Complete src/Completion.res 59:30
464464
posCursor:[59:30] posNoWhite:[59:29] Found expr:[59:15->59:30]
465465
JSX <O.Comp:[59:15->59:21] second[59:22->59:28]=...[59:29->59:30]> _children:None
466-
posCursor:[59:30] posNoWhite:[59:29] Found expr:[59:29->59:30]
467-
Pexp_ident z:[59:29->59:30]
468-
Completable: Cpath Value[z]
466+
Completable: CjsxPropValue [O, Comp] second=z
469467
[{
470468
"label": "zzz",
471469
"kind": 12,
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
Complete src/CompletionJsxProps.res 0:47
2+
posCursor:[0:47] posNoWhite:[0:46] Found expr:[0:12->0:47]
3+
JSX <CompletionSupport.TestComponent:[0:12->0:43] on[0:44->0:46]=...__ghost__[0:-1->0:-1]> _children:None
4+
Completable: CjsxPropValue [CompletionSupport, TestComponent] on=
5+
[{
6+
"label": "true",
7+
"kind": 4,
8+
"tags": [],
9+
"detail": "bool",
10+
"documentation": null
11+
}, {
12+
"label": "false",
13+
"kind": 4,
14+
"tags": [],
15+
"detail": "bool",
16+
"documentation": null
17+
}]
18+
19+
Complete src/CompletionJsxProps.res 3:48
20+
posCursor:[3:48] posNoWhite:[3:47] Found expr:[3:12->3:48]
21+
JSX <CompletionSupport.TestComponent:[3:12->3:43] on[3:44->3:46]=...[3:47->3:48]> _children:None
22+
Completable: CjsxPropValue [CompletionSupport, TestComponent] on=t
23+
[{
24+
"label": "true",
25+
"kind": 4,
26+
"tags": [],
27+
"detail": "bool",
28+
"documentation": null
29+
}]
30+
31+
Complete src/CompletionJsxProps.res 6:50
32+
posCursor:[6:50] posNoWhite:[6:49] Found expr:[6:12->6:50]
33+
JSX <CompletionSupport.TestComponent:[6:12->6:43] test[6:44->6:48]=...[6:49->6:50]> _children:None
34+
Completable: CjsxPropValue [CompletionSupport, TestComponent] test=T
35+
[{
36+
"label": "Two",
37+
"kind": 4,
38+
"tags": [],
39+
"detail": "Two\n\ntype testVariant = One | Two | Three(int)",
40+
"documentation": null
41+
}, {
42+
"label": "Three(_)",
43+
"kind": 4,
44+
"tags": [],
45+
"detail": "Three(int)\n\ntype testVariant = One | Two | Three(int)",
46+
"documentation": null
47+
}]
48+

analysis/tests/src/expected/Jsx2.res.txt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ the type is not great but jump to definition works
66
Complete src/Jsx2.res 8:15
77
posCursor:[8:15] posNoWhite:[8:14] Found expr:[8:4->8:15]
88
JSX <M:[8:4->8:5] second[8:6->8:12]=...[8:13->8:15]> _children:None
9-
posCursor:[8:15] posNoWhite:[8:14] Found expr:[8:13->8:15]
10-
Pexp_ident fi:[8:13->8:15]
11-
Completable: Cpath Value[fi]
9+
Completable: CjsxPropValue [M] second=fi
1210
[]
1311

1412
Complete src/Jsx2.res 11:20

0 commit comments

Comments
 (0)