Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add completion for React primitives #7292

Merged
merged 6 commits into from
Feb 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading