Skip to content

Commit

Permalink
Add completion for React primitives (#7292)
Browse files Browse the repository at this point in the history
* During call

* Simplify example

* Add completion for integers inside braces.

* Add changelog entry

* Remove completion of integer in braces

* Fix typo
  • Loading branch information
nojaf authored Feb 13, 2025
1 parent 9da9b19 commit 5d9c682
Show file tree
Hide file tree
Showing 8 changed files with 247 additions and 34 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
- Fix completion for application with tagged template. https://github.com/rescript-lang/rescript/pull/7278
- Fix error message for arity in the presence of optional arguments. https://github.com/rescript-lang/rescript/pull/7284
- Fix issue in functors with more than one argument (which are curried): emit nested function always. https://github.com/rescript-lang/rescript/pull/7273
- Fix dot completion issue with React primitives. https://github.com/rescript-lang/rescript/pull/7292

#### :house: Internal

Expand Down
4 changes: 2 additions & 2 deletions analysis/src/CompletionBackEnd.ml
Original file line number Diff line number Diff line change
Expand Up @@ -979,7 +979,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact
path @ [fieldName]
|> getCompletionsForPath ~debug ~opens ~full ~pos ~exact
~completionContext:Field ~env ~scope
| CPField {contextPath = cp; fieldName; posOfDot; exprLoc} -> (
| CPField {contextPath = cp; fieldName; posOfDot; exprLoc; inJsx} -> (
if Debug.verbose () then print_endline "[dot_completion]--> Triggered";
let completionsFromCtxPath =
cp
Expand Down Expand Up @@ -1013,7 +1013,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact
CPApply (cp, [Asttypes.Noloc.Nolabel])
| _ -> cp);
id = fieldName;
inJsx = false;
inJsx;
lhsLoc = exprLoc;
}
in
Expand Down
79 changes: 48 additions & 31 deletions analysis/src/CompletionFrontEnd.ml
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,8 @@ let findArgCompletables ~(args : arg list) ~endPos ~posBeforeCursor
})
| _ -> loop args

let rec exprToContextPathInner (e : Parsetree.expression) =
let rec exprToContextPathInner ~(inJsxContext : bool) (e : Parsetree.expression)
=
match e.pexp_desc with
| Pexp_constant (Pconst_string _) -> Some Completable.CPString
| Pexp_constant (Pconst_integer _) -> Some CPInt
Expand All @@ -217,13 +218,13 @@ let rec exprToContextPathInner (e : Parsetree.expression) =
(CPArray
(match exprs with
| [] -> None
| exp :: _ -> exprToContextPath exp))
| exp :: _ -> exprToContextPath ~inJsxContext exp))
| Pexp_ident {txt = Lident "->"} -> None
| Pexp_ident {txt; loc} ->
Some
(CPId {path = Utils.flattenLongIdent txt; completionContext = Value; loc})
| Pexp_field (e1, {txt = Lident name}) -> (
match exprToContextPath e1 with
match exprToContextPath ~inJsxContext e1 with
| Some contextPath ->
Some
(CPField
Expand All @@ -232,6 +233,7 @@ let rec exprToContextPathInner (e : Parsetree.expression) =
fieldName = name;
posOfDot = None;
exprLoc = e1.pexp_loc;
inJsx = inJsxContext;
})
| _ -> None)
| Pexp_field (e1, {loc; txt = Ldot (lid, name)}) ->
Expand All @@ -249,9 +251,10 @@ let rec exprToContextPathInner (e : Parsetree.expression) =
fieldName = name;
posOfDot = None;
exprLoc = e1.pexp_loc;
inJsx = inJsxContext;
})
| Pexp_send (e1, {txt}) -> (
match exprToContextPath e1 with
match exprToContextPath ~inJsxContext e1 with
| None -> None
| Some contexPath -> Some (CPObj (contexPath, txt)))
| Pexp_apply
Expand All @@ -266,7 +269,7 @@ let rec exprToContextPathInner (e : Parsetree.expression) =
[(_, lhs); (_, {pexp_desc = Pexp_apply {funct = d; args; partial}})];
} ->
(* Transform away pipe with apply call *)
exprToContextPath
exprToContextPath ~inJsxContext
{
pexp_desc =
Pexp_apply {funct = d; args = (Nolabel, lhs) :: args; partial};
Expand All @@ -283,7 +286,7 @@ let rec exprToContextPathInner (e : Parsetree.expression) =
partial;
} ->
(* Transform away pipe with identifier *)
exprToContextPath
exprToContextPath ~inJsxContext
{
pexp_desc =
Pexp_apply
Expand All @@ -296,29 +299,31 @@ let rec exprToContextPathInner (e : Parsetree.expression) =
pexp_attributes;
}
| Pexp_apply {funct = e1; args} -> (
match exprToContextPath e1 with
match exprToContextPath ~inJsxContext e1 with
| None -> None
| Some contexPath ->
Some
(CPApply (contexPath, args |> List.map fst |> List.map Asttypes.to_noloc))
)
| Pexp_tuple exprs ->
let exprsAsContextPaths = exprs |> List.filter_map exprToContextPath in
let exprsAsContextPaths =
exprs |> List.filter_map (exprToContextPath ~inJsxContext)
in
if List.length exprs = List.length exprsAsContextPaths then
Some (CTuple exprsAsContextPaths)
else None
| _ -> None

and exprToContextPath (e : Parsetree.expression) =
and exprToContextPath ~(inJsxContext : bool) (e : Parsetree.expression) =
match
( Res_parsetree_viewer.has_await_attribute e.pexp_attributes,
exprToContextPathInner e )
exprToContextPathInner ~inJsxContext e )
with
| true, Some ctxPath -> Some (CPAwait ctxPath)
| false, Some ctxPath -> Some ctxPath
| _, None -> None

let completePipeChain (exp : Parsetree.expression) =
let completePipeChain ~(inJsxContext : bool) (exp : Parsetree.expression) =
(* Complete the end of pipe chains by reconstructing the pipe chain as a single pipe,
so it can be completed.
Example:
Expand All @@ -334,15 +339,17 @@ let completePipeChain (exp : Parsetree.expression) =
funct = {pexp_desc = Pexp_ident {txt = Lident "->"}};
args = [_; (_, {pexp_desc = Pexp_apply {funct = d}})];
} ->
exprToContextPath exp |> Option.map (fun ctxPath -> (ctxPath, d.pexp_loc))
exprToContextPath ~inJsxContext exp
|> Option.map (fun ctxPath -> (ctxPath, d.pexp_loc))
(* When the left side of the pipe we're completing is an identifier application.
Example: someArray->filterAllTheGoodStuff-> *)
| Pexp_apply
{
funct = {pexp_desc = Pexp_ident {txt = Lident "->"}};
args = [_; (_, {pexp_desc = Pexp_ident _; pexp_loc})];
} ->
exprToContextPath exp |> Option.map (fun ctxPath -> (ctxPath, pexp_loc))
exprToContextPath ~inJsxContext exp
|> Option.map (fun ctxPath -> (ctxPath, pexp_loc))
| _ -> None

let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
Expand Down Expand Up @@ -429,6 +436,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
(Completable.toString x);
result := Some (x, !scope)
in
let inJsxContext = ref false in
let setResult x = setResultOpt (Some x) in
let scopeValueDescription (vd : Parsetree.value_description) =
scope :=
Expand Down Expand Up @@ -563,9 +571,9 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
(* Pipe chains get special treatment here, because when assigning values
we want the return of the entire pipe chain as a function call, rather
than as a pipe completion call. *)
match completePipeChain vb.pvb_expr with
match completePipeChain ~inJsxContext:!inJsxContext vb.pvb_expr with
| Some (ctxPath, _) -> Some ctxPath
| None -> exprToContextPath vb.pvb_expr
| None -> exprToContextPath ~inJsxContext:!inJsxContext vb.pvb_expr
in
scopePattern ?contextPath vb.pvb_pat
in
Expand Down Expand Up @@ -597,7 +605,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
scope :=
!scope |> Scope.addModule ~name:md.pmd_name.txt ~loc:md.pmd_name.loc
in
let inJsxContext = ref false in

(* Identifies expressions where we can do typed pattern or expr completion. *)
let typedCompletionExpr (exp : Parsetree.expression) =
let debugTypedCompletionExpr = false in
Expand All @@ -614,7 +622,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
print_endline "[typedCompletionExpr] No cases - has cursor";
(* We can do exhaustive switch completion if this is an ident we can
complete from. *)
match exprToContextPath expr with
match exprToContextPath ~inJsxContext:!inJsxContext expr with
| None -> ()
| Some contextPath ->
setResult (CexhaustiveSwitch {contextPath; exprLoc = exp.pexp_loc}))
Expand Down Expand Up @@ -644,7 +652,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
};
] ) -> (
(* A single case that's a pattern hole typically means `switch x { | }`. Complete as the pattern itself with nothing nested. *)
match exprToContextPath exp with
match exprToContextPath ~inJsxContext:!inJsxContext exp with
| None -> ()
| Some ctxPath ->
setResult
Expand All @@ -661,7 +669,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
print_endline "[typedCompletionExpr] Has cases";
(* If there's more than one case, or the case isn't a pattern hole, figure out if we're completing another
broken parser case (`switch x { | true => () | <com> }` for example). *)
match exp |> exprToContextPath with
match exp |> exprToContextPath ~inJsxContext:!inJsxContext with
| None ->
if Debug.verbose () && debugTypedCompletionExpr then
print_endline "[typedCompletionExpr] Has cases - no ctx path"
Expand Down Expand Up @@ -802,7 +810,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
( pvb_pat
|> CompletionPatterns.traversePattern ~patternPath:[] ~locHasCursor
~firstCharBeforeCursorNoWhite ~posBeforeCursor,
exprToContextPath pvb_expr )
exprToContextPath ~inJsxContext:!inJsxContext pvb_expr )
with
| Some (prefix, nested), Some ctxPath ->
setResult
Expand Down Expand Up @@ -1059,14 +1067,14 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
in
(match findThisExprLoc with
| Some loc when expr.pexp_loc = loc -> (
match exprToContextPath expr with
match exprToContextPath ~inJsxContext:!inJsxContext expr with
| None -> ()
| Some ctxPath -> setResult (Cpath ctxPath))
| _ -> ());
let setPipeResult ~(lhs : Parsetree.expression) ~id =
match completePipeChain lhs with
match completePipeChain ~inJsxContext:!inJsxContext lhs with
| None -> (
match exprToContextPath lhs with
match exprToContextPath ~inJsxContext:!inJsxContext lhs with
| Some pipe ->
setResult
(Cpath
Expand Down Expand Up @@ -1101,7 +1109,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
&& Option.is_none findThisExprLoc ->
if Debug.verbose () then
print_endline "[completionFrontend] Checking each case";
let ctxPath = exprToContextPath expr in
let ctxPath = exprToContextPath ~inJsxContext:!inJsxContext expr in
let oldCtxPath = !currentCtxPath in
cases
|> List.iter (fun (case : Parsetree.case) ->
Expand Down Expand Up @@ -1144,7 +1152,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
];
}
when Res_parsetree_viewer.is_tagged_template_literal innerExpr ->
exprToContextPath innerExpr
exprToContextPath ~inJsxContext:!inJsxContext innerExpr
|> Option.iter (fun cpath ->
setResult
(Cpath
Expand All @@ -1154,6 +1162,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
fieldName = "";
posOfDot;
exprLoc = expr.pexp_loc;
inJsx = !inJsxContext;
}));
setFound ())
(*
Expand All @@ -1174,7 +1183,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
}
when Res_parsetree_viewer.is_tagged_template_literal innerExpr
&& expr.pexp_loc |> Loc.hasPos ~pos:posBeforeCursor ->
exprToContextPath innerExpr
exprToContextPath ~inJsxContext:!inJsxContext innerExpr
|> Option.iter (fun cpath ->
setResult
(Cpath
Expand All @@ -1184,6 +1193,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
fieldName;
posOfDot;
exprLoc = expr.pexp_loc;
inJsx = !inJsxContext;
}));
setFound ())
| _ -> (
Expand Down Expand Up @@ -1262,7 +1272,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
if fieldName.loc |> Loc.hasPos ~pos:posBeforeCursor then
match fieldName.txt with
| Lident name -> (
match exprToContextPath e with
match exprToContextPath ~inJsxContext:!inJsxContext e with
| Some contextPath ->
let contextPath =
Completable.CPField
Expand All @@ -1271,6 +1281,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
fieldName = name;
posOfDot;
exprLoc = e.pexp_loc;
inJsx = !inJsxContext;
}
in
setResult (Cpath contextPath)
Expand All @@ -1294,12 +1305,13 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
else name);
posOfDot;
exprLoc = e.pexp_loc;
inJsx = !inJsxContext;
}
in
setResult (Cpath contextPath)
| Lapply _ -> ()
else if Loc.end_ e.pexp_loc = posBeforeCursor then
match exprToContextPath e with
match exprToContextPath ~inJsxContext:!inJsxContext e with
| Some contextPath ->
setResult
(Cpath
Expand All @@ -1309,6 +1321,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
fieldName = "";
posOfDot;
exprLoc = e.pexp_loc;
inJsx = !inJsxContext;
}))
| None -> ())
| Pexp_apply {funct = {pexp_desc = Pexp_ident compName}; args}
Expand Down Expand Up @@ -1386,7 +1399,9 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
if Debug.verbose () then
print_endline "[expr_iter] Complete fn arguments (piped)";
let args = extractExpApplyArgs ~args in
let funCtxPath = exprToContextPath funExpr in
let funCtxPath =
exprToContextPath ~inJsxContext:!inJsxContext funExpr
in
let argCompletable =
match funCtxPath with
| Some contextPath ->
Expand Down Expand Up @@ -1437,7 +1452,9 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
(Loc.toString exp.pexp_loc))
|> String.concat ", ");

let funCtxPath = exprToContextPath funExpr in
let funCtxPath =
exprToContextPath ~inJsxContext:!inJsxContext funExpr
in
let argCompletable =
match funCtxPath with
| Some contextPath ->
Expand Down Expand Up @@ -1481,7 +1498,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
labelRange |> Range.hasPos ~pos:posBeforeCursor
|| (label = "" && posCursor = fst labelRange)
then
match exprToContextPath lhs with
match exprToContextPath ~inJsxContext:!inJsxContext lhs with
| Some contextPath -> setResult (Cpath (CPObj (contextPath, label)))
| None -> ())
| Pexp_fun
Expand Down
1 change: 1 addition & 0 deletions analysis/src/Packages.ml
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ let newBsPackage ~rootPath =
| ["RescriptCore"] -> true
| _ -> false)
|> Option.is_some
|| fst rescriptVersion >= 12
then
{
arrayModulePath = ["Array"];
Expand Down
2 changes: 2 additions & 0 deletions analysis/src/SharedTypes.ml
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,8 @@ module Completable = struct
fieldName: string;
posOfDot: (int * int) option;
exprLoc: Location.t;
inJsx: bool;
(** Whether this field access was found in a JSX context. *)
}
| CPObj of contextPath * string
| CPAwait of contextPath
Expand Down
5 changes: 4 additions & 1 deletion analysis/src/TypeUtils.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1152,7 +1152,10 @@ let transformCompletionToPipeCompletion ?(synthetic = false) ~env ?posOfDot
{
completion with
name = nameWithPipe;
sortText = Some (name |> String.split_on_char '.' |> List.rev |> List.hd);
sortText =
(match completion.sortText with
| Some _ -> completion.sortText
| None -> Some (name |> String.split_on_char '.' |> List.rev |> List.hd));
insertText = Some nameWithPipe;
env;
synthetic;
Expand Down
4 changes: 4 additions & 0 deletions tests/analysis_tests/tests/src/CompletionJsx.res
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,7 @@ module Info = {

// <Info _type={#warning} >
// ^com


// let _ = <p>{"".s}</p>
// ^com
Loading

0 comments on commit 5d9c682

Please sign in to comment.