Skip to content

Commit 2019372

Browse files
authored
Add completion for React primitives (#1074)
* add completion for React primitives * changelog
1 parent 82bbdc5 commit 2019372

7 files changed

+243
-32
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
#### :rocket: New Feature
1616

17+
- Port [7292](https://github.com/rescript-lang/rescript/pull/7292): Fix dot completion issue with React primitives. https://github.com/rescript-lang/rescript-vscode/pull/1074
18+
1719
- Add support for "dot completion everywhere". In addition to record fields, dots will now complete for object fields, and pipe completions applicable to the type the dot is on. You can also configure where the editor draws extra pipe completions from via the `@editor.completeFrom` attribute. https://github.com/rescript-lang/rescript-vscode/pull/1054
1820

1921
#### :bug: Bug fix

analysis/src/CompletionBackEnd.ml

+2-2
Original file line numberDiff line numberDiff line change
@@ -990,7 +990,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact
990990
path @ [fieldName]
991991
|> getCompletionsForPath ~debug ~opens ~full ~pos ~exact
992992
~completionContext:Field ~env ~scope
993-
| CPField {contextPath = cp; fieldName; posOfDot; exprLoc} -> (
993+
| CPField {contextPath = cp; fieldName; posOfDot; exprLoc; inJsx} -> (
994994
if Debug.verbose () then print_endline "[dot_completion]--> Triggered";
995995
let completionsFromCtxPath =
996996
cp
@@ -1024,7 +1024,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact
10241024
CPApply (cp, [Asttypes.Nolabel])
10251025
| _ -> cp);
10261026
id = fieldName;
1027-
inJsx = false;
1027+
inJsx;
10281028
lhsLoc = exprLoc;
10291029
}
10301030
in

analysis/src/CompletionFrontEnd.ml

+44-29
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,8 @@ let findArgCompletables ~(args : arg list) ~endPos ~posBeforeCursor
206206
})
207207
| _ -> loop args
208208

209-
let rec exprToContextPathInner (e : Parsetree.expression) =
209+
let rec exprToContextPathInner ~(inJsxContext : bool) (e : Parsetree.expression)
210+
=
210211
match e.pexp_desc with
211212
| Pexp_constant (Pconst_string _) -> Some Completable.CPString
212213
| Pexp_constant (Pconst_integer _) -> Some CPInt
@@ -217,13 +218,13 @@ let rec exprToContextPathInner (e : Parsetree.expression) =
217218
(CPArray
218219
(match exprs with
219220
| [] -> None
220-
| exp :: _ -> exprToContextPath exp))
221+
| exp :: _ -> exprToContextPath ~inJsxContext exp))
221222
| Pexp_ident {txt = Lident ("|." | "|.u")} -> None
222223
| Pexp_ident {txt; loc} ->
223224
Some
224225
(CPId {path = Utils.flattenLongIdent txt; completionContext = Value; loc})
225226
| Pexp_field (e1, {txt = Lident name}) -> (
226-
match exprToContextPath e1 with
227+
match exprToContextPath ~inJsxContext e1 with
227228
| Some contextPath ->
228229
Some
229230
(CPField
@@ -232,6 +233,7 @@ let rec exprToContextPathInner (e : Parsetree.expression) =
232233
fieldName = name;
233234
posOfDot = None;
234235
exprLoc = e1.pexp_loc;
236+
inJsx = inJsxContext;
235237
})
236238
| _ -> None)
237239
| Pexp_field (e1, {loc; txt = Ldot (lid, name)}) ->
@@ -249,9 +251,10 @@ let rec exprToContextPathInner (e : Parsetree.expression) =
249251
fieldName = name;
250252
posOfDot = None;
251253
exprLoc = e1.pexp_loc;
254+
inJsx = inJsxContext;
252255
})
253256
| Pexp_send (e1, {txt}) -> (
254-
match exprToContextPath e1 with
257+
match exprToContextPath ~inJsxContext e1 with
255258
| None -> None
256259
| Some contexPath -> Some (CPObj (contexPath, txt)))
257260
| Pexp_apply
@@ -261,7 +264,7 @@ let rec exprToContextPathInner (e : Parsetree.expression) =
261264
(_, {pexp_desc = Pexp_apply (d, args); pexp_loc; pexp_attributes});
262265
] ) ->
263266
(* Transform away pipe with apply call *)
264-
exprToContextPath
267+
exprToContextPath ~inJsxContext
265268
{
266269
pexp_desc = Pexp_apply (d, (Nolabel, lhs) :: args);
267270
pexp_loc;
@@ -272,7 +275,7 @@ let rec exprToContextPathInner (e : Parsetree.expression) =
272275
[(_, lhs); (_, {pexp_desc = Pexp_ident id; pexp_loc; pexp_attributes})]
273276
) ->
274277
(* Transform away pipe with identifier *)
275-
exprToContextPath
278+
exprToContextPath ~inJsxContext
276279
{
277280
pexp_desc =
278281
Pexp_apply
@@ -282,26 +285,28 @@ let rec exprToContextPathInner (e : Parsetree.expression) =
282285
pexp_attributes;
283286
}
284287
| Pexp_apply (e1, args) -> (
285-
match exprToContextPath e1 with
288+
match exprToContextPath ~inJsxContext e1 with
286289
| None -> None
287290
| Some contexPath -> Some (CPApply (contexPath, args |> List.map fst)))
288291
| Pexp_tuple exprs ->
289-
let exprsAsContextPaths = exprs |> List.filter_map exprToContextPath in
292+
let exprsAsContextPaths =
293+
exprs |> List.filter_map (exprToContextPath ~inJsxContext)
294+
in
290295
if List.length exprs = List.length exprsAsContextPaths then
291296
Some (CTuple exprsAsContextPaths)
292297
else None
293298
| _ -> None
294299

295-
and exprToContextPath (e : Parsetree.expression) =
300+
and exprToContextPath ~(inJsxContext : bool) (e : Parsetree.expression) =
296301
match
297302
( Res_parsetree_viewer.has_await_attribute e.pexp_attributes,
298-
exprToContextPathInner e )
303+
exprToContextPathInner ~inJsxContext e )
299304
with
300305
| true, Some ctxPath -> Some (CPAwait ctxPath)
301306
| false, Some ctxPath -> Some ctxPath
302307
| _, None -> None
303308

304-
let completePipeChain (exp : Parsetree.expression) =
309+
let completePipeChain ~(inJsxContext : bool) (exp : Parsetree.expression) =
305310
(* Complete the end of pipe chains by reconstructing the pipe chain as a single pipe,
306311
so it can be completed.
307312
Example:
@@ -315,13 +320,15 @@ let completePipeChain (exp : Parsetree.expression) =
315320
| Pexp_apply
316321
( {pexp_desc = Pexp_ident {txt = Lident ("|." | "|.u")}},
317322
[_; (_, {pexp_desc = Pexp_apply (d, _)})] ) ->
318-
exprToContextPath exp |> Option.map (fun ctxPath -> (ctxPath, d.pexp_loc))
323+
exprToContextPath ~inJsxContext exp
324+
|> Option.map (fun ctxPath -> (ctxPath, d.pexp_loc))
319325
(* When the left side of the pipe we're completing is an identifier application.
320326
Example: someArray->filterAllTheGoodStuff-> *)
321327
| Pexp_apply
322328
( {pexp_desc = Pexp_ident {txt = Lident ("|." | "|.u")}},
323329
[_; (_, {pexp_desc = Pexp_ident _; pexp_loc})] ) ->
324-
exprToContextPath exp |> Option.map (fun ctxPath -> (ctxPath, pexp_loc))
330+
exprToContextPath ~inJsxContext exp
331+
|> Option.map (fun ctxPath -> (ctxPath, pexp_loc))
325332
| _ -> None
326333

327334
let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
@@ -408,6 +415,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
408415
(Completable.toString x);
409416
result := Some (x, !scope)
410417
in
418+
let inJsxContext = ref false in
411419
let setResult x = setResultOpt (Some x) in
412420
let scopeValueDescription (vd : Parsetree.value_description) =
413421
scope :=
@@ -542,9 +550,9 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
542550
(* Pipe chains get special treatment here, because when assigning values
543551
we want the return of the entire pipe chain as a function call, rather
544552
than as a pipe completion call. *)
545-
match completePipeChain vb.pvb_expr with
553+
match completePipeChain ~inJsxContext:!inJsxContext vb.pvb_expr with
546554
| Some (ctxPath, _) -> Some ctxPath
547-
| None -> exprToContextPath vb.pvb_expr
555+
| None -> exprToContextPath ~inJsxContext:!inJsxContext vb.pvb_expr
548556
in
549557
scopePattern ?contextPath vb.pvb_pat
550558
in
@@ -576,7 +584,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
576584
scope :=
577585
!scope |> Scope.addModule ~name:md.pmd_name.txt ~loc:md.pmd_name.loc
578586
in
579-
let inJsxContext = ref false in
587+
580588
(* Identifies expressions where we can do typed pattern or expr completion. *)
581589
let typedCompletionExpr (exp : Parsetree.expression) =
582590
let debugTypedCompletionExpr = false in
@@ -593,7 +601,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
593601
print_endline "[typedCompletionExpr] No cases - has cursor";
594602
(* We can do exhaustive switch completion if this is an ident we can
595603
complete from. *)
596-
match exprToContextPath expr with
604+
match exprToContextPath ~inJsxContext:!inJsxContext expr with
597605
| None -> ()
598606
| Some contextPath ->
599607
setResult (CexhaustiveSwitch {contextPath; exprLoc = exp.pexp_loc}))
@@ -623,7 +631,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
623631
};
624632
] ) -> (
625633
(* A single case that's a pattern hole typically means `switch x { | }`. Complete as the pattern itself with nothing nested. *)
626-
match exprToContextPath exp with
634+
match exprToContextPath ~inJsxContext:!inJsxContext exp with
627635
| None -> ()
628636
| Some ctxPath ->
629637
setResult
@@ -640,7 +648,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
640648
print_endline "[typedCompletionExpr] Has cases";
641649
(* If there's more than one case, or the case isn't a pattern hole, figure out if we're completing another
642650
broken parser case (`switch x { | true => () | <com> }` for example). *)
643-
match exp |> exprToContextPath with
651+
match exp |> exprToContextPath ~inJsxContext:!inJsxContext with
644652
| None ->
645653
if Debug.verbose () && debugTypedCompletionExpr then
646654
print_endline "[typedCompletionExpr] Has cases - no ctx path"
@@ -781,7 +789,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
781789
( pvb_pat
782790
|> CompletionPatterns.traversePattern ~patternPath:[] ~locHasCursor
783791
~firstCharBeforeCursorNoWhite ~posBeforeCursor,
784-
exprToContextPath pvb_expr )
792+
exprToContextPath ~inJsxContext:!inJsxContext pvb_expr )
785793
with
786794
| Some (prefix, nested), Some ctxPath ->
787795
setResult
@@ -1037,14 +1045,14 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
10371045
in
10381046
(match findThisExprLoc with
10391047
| Some loc when expr.pexp_loc = loc -> (
1040-
match exprToContextPath expr with
1048+
match exprToContextPath ~inJsxContext:!inJsxContext expr with
10411049
| None -> ()
10421050
| Some ctxPath -> setResult (Cpath ctxPath))
10431051
| _ -> ());
10441052
let setPipeResult ~(lhs : Parsetree.expression) ~id =
1045-
match completePipeChain lhs with
1053+
match completePipeChain ~inJsxContext:!inJsxContext lhs with
10461054
| None -> (
1047-
match exprToContextPath lhs with
1055+
match exprToContextPath ~inJsxContext:!inJsxContext lhs with
10481056
| Some pipe ->
10491057
setResult
10501058
(Cpath
@@ -1079,7 +1087,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
10791087
&& Option.is_none findThisExprLoc ->
10801088
if Debug.verbose () then
10811089
print_endline "[completionFrontend] Checking each case";
1082-
let ctxPath = exprToContextPath expr in
1090+
let ctxPath = exprToContextPath ~inJsxContext:!inJsxContext expr in
10831091
let oldCtxPath = !currentCtxPath in
10841092
cases
10851093
|> List.iter (fun (case : Parsetree.case) ->
@@ -1178,7 +1186,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
11781186
if fieldName.loc |> Loc.hasPos ~pos:posBeforeCursor then
11791187
match fieldName.txt with
11801188
| Lident name -> (
1181-
match exprToContextPath e with
1189+
match exprToContextPath ~inJsxContext:!inJsxContext e with
11821190
| Some contextPath ->
11831191
let contextPath =
11841192
Completable.CPField
@@ -1187,6 +1195,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
11871195
fieldName = name;
11881196
posOfDot;
11891197
exprLoc = e.pexp_loc;
1198+
inJsx = !inJsxContext;
11901199
}
11911200
in
11921201
setResult (Cpath contextPath)
@@ -1210,12 +1219,13 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
12101219
else name);
12111220
posOfDot;
12121221
exprLoc = e.pexp_loc;
1222+
inJsx = !inJsxContext;
12131223
}
12141224
in
12151225
setResult (Cpath contextPath)
12161226
| Lapply _ -> ()
12171227
else if Loc.end_ e.pexp_loc = posBeforeCursor then
1218-
match exprToContextPath e with
1228+
match exprToContextPath ~inJsxContext:!inJsxContext e with
12191229
| Some contextPath ->
12201230
setResult
12211231
(Cpath
@@ -1225,6 +1235,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
12251235
fieldName = "";
12261236
posOfDot;
12271237
exprLoc = e.pexp_loc;
1238+
inJsx = !inJsxContext;
12281239
}))
12291240
| None -> ())
12301241
| Pexp_apply ({pexp_desc = Pexp_ident compName}, args)
@@ -1295,7 +1306,9 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
12951306
if Debug.verbose () then
12961307
print_endline "[expr_iter] Complete fn arguments (piped)";
12971308
let args = extractExpApplyArgs ~args in
1298-
let funCtxPath = exprToContextPath funExpr in
1309+
let funCtxPath =
1310+
exprToContextPath ~inJsxContext:!inJsxContext funExpr
1311+
in
12991312
let argCompletable =
13001313
match funCtxPath with
13011314
| Some contextPath ->
@@ -1343,7 +1356,9 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
13431356
(Loc.toString exp.pexp_loc))
13441357
|> String.concat ", ");
13451358

1346-
let funCtxPath = exprToContextPath funExpr in
1359+
let funCtxPath =
1360+
exprToContextPath ~inJsxContext:!inJsxContext funExpr
1361+
in
13471362
let argCompletable =
13481363
match funCtxPath with
13491364
| Some contextPath ->
@@ -1387,7 +1402,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
13871402
labelRange |> Range.hasPos ~pos:posBeforeCursor
13881403
|| (label = "" && posCursor = fst labelRange)
13891404
then
1390-
match exprToContextPath lhs with
1405+
match exprToContextPath ~inJsxContext:!inJsxContext lhs with
13911406
| Some contextPath -> setResult (Cpath (CPObj (contextPath, label)))
13921407
| None -> ())
13931408
| Pexp_fun (lbl, defaultExpOpt, pat, e) ->

analysis/src/SharedTypes.ml

+2
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,8 @@ module Completable = struct
616616
fieldName: string;
617617
posOfDot: (int * int) option;
618618
exprLoc: Location.t;
619+
inJsx: bool;
620+
(** Whether this field access was found in a JSX context. *)
619621
}
620622
| CPObj of contextPath * string
621623
| CPAwait of contextPath

analysis/src/TypeUtils.ml

+4-1
Original file line numberDiff line numberDiff line change
@@ -1169,7 +1169,10 @@ let transformCompletionToPipeCompletion ?(synthetic = false) ~env ?posOfDot
11691169
{
11701170
completion with
11711171
name = nameWithPipe;
1172-
sortText = Some (name |> String.split_on_char '.' |> List.rev |> List.hd);
1172+
sortText =
1173+
(match completion.sortText with
1174+
| Some _ -> completion.sortText
1175+
| None -> Some (name |> String.split_on_char '.' |> List.rev |> List.hd));
11731176
insertText = Some nameWithPipe;
11741177
env;
11751178
synthetic;

analysis/tests/src/CompletionJsx.res

+3
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,6 @@ module Info = {
8989

9090
// <Info _type={#warning} >
9191
// ^com
92+
93+
// let _ = <p>{"".s}</p>
94+
// ^com

0 commit comments

Comments
 (0)