Skip to content

Commit baf1d47

Browse files
authored
Fix ambigious case with JSX brace wraps and record body (#894)
* fix ambigious case where JSX brace wraps in JSX props are interpreted as a record body, but we dont know whether it actually is a record body or just wraps for the prop value itself * comment + changelog * move comment * update changelog desc
1 parent ddf14cf commit baf1d47

File tree

5 files changed

+164
-20
lines changed

5 files changed

+164
-20
lines changed

Diff for: CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
1313
## master
1414

15+
#### :bug: Bug Fix
16+
17+
- Fix issue with ambigious wraps in JSX prop values (`<SomeComp someProp={<com>}`) - need to figure out if we're completing for a record body or if `{}` are just wraps for the type of `someProp`. In the case of ambiguity, completions for both scenarios are provided. https://github.com/rescript-lang/rescript-vscode/pull/894
18+
1519
#### :nail_care: Polish
1620

1721
- More cases of not emitting `_` when completing in expressions. https://github.com/rescript-lang/rescript-vscode/pull/890

Diff for: analysis/src/CompletionBackEnd.ml

+56-4
Original file line numberDiff line numberDiff line change
@@ -697,41 +697,48 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact
697697
let package = full.package in
698698
match contextPath with
699699
| CPString ->
700+
if Debug.verbose () then print_endline "[ctx_path]--> CPString";
700701
[
701702
Completion.create "dummy" ~env
702703
~kind:
703704
(Completion.Value
704705
(Ctype.newconstr (Path.Pident (Ident.create "string")) []));
705706
]
706707
| CPBool ->
708+
if Debug.verbose () then print_endline "[ctx_path]--> CPBool";
707709
[
708710
Completion.create "dummy" ~env
709711
~kind:
710712
(Completion.Value
711713
(Ctype.newconstr (Path.Pident (Ident.create "bool")) []));
712714
]
713715
| CPInt ->
716+
if Debug.verbose () then print_endline "[ctx_path]--> CPInt";
714717
[
715718
Completion.create "dummy" ~env
716719
~kind:
717720
(Completion.Value
718721
(Ctype.newconstr (Path.Pident (Ident.create "int")) []));
719722
]
720723
| CPFloat ->
724+
if Debug.verbose () then print_endline "[ctx_path]--> CPFloat";
721725
[
722726
Completion.create "dummy" ~env
723727
~kind:
724728
(Completion.Value
725729
(Ctype.newconstr (Path.Pident (Ident.create "float")) []));
726730
]
727731
| CPArray None ->
732+
if Debug.verbose () then print_endline "[ctx_path]--> CPArray (no payload)";
728733
[
729734
Completion.create "array" ~env
730735
~kind:
731736
(Completion.Value
732737
(Ctype.newconstr (Path.Pident (Ident.create "array")) []));
733738
]
734739
| CPArray (Some cp) -> (
740+
if Debug.verbose () then
741+
print_endline "[ctx_path]--> CPArray (with payload)";
735742
match mode with
736743
| Regular -> (
737744
match
@@ -757,6 +764,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact
757764
(Ctype.newconstr (Path.Pident (Ident.create "array")) []));
758765
])
759766
| CPOption cp -> (
767+
if Debug.verbose () then print_endline "[ctx_path]--> CPOption";
760768
match
761769
cp
762770
|> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env
@@ -771,6 +779,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact
771779
(Completion.ExtractedType (Toption (env, ExtractedType typ), `Type));
772780
])
773781
| CPAwait cp -> (
782+
if Debug.verbose () then print_endline "[ctx_path]--> CPAwait";
774783
match
775784
cp
776785
|> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env
@@ -781,10 +790,12 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact
781790
[Completion.create "dummy" ~env ~kind:(Completion.Value typ)]
782791
| _ -> [])
783792
| CPId (path, completionContext) ->
793+
if Debug.verbose () then print_endline "[ctx_path]--> CPId";
784794
path
785795
|> getCompletionsForPath ~debug ~opens ~full ~pos ~exact ~completionContext
786796
~env ~scope
787797
| CPApply (cp, labels) -> (
798+
if Debug.verbose () then print_endline "[ctx_path]--> CPApply";
788799
match
789800
cp
790801
|> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env
@@ -826,11 +837,13 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact
826837
| _ -> [])
827838
| _ -> [])
828839
| CPField (CPId (path, Module), fieldName) ->
840+
if Debug.verbose () then print_endline "[ctx_path]--> CPField: M.field";
829841
(* M.field *)
830842
path @ [fieldName]
831843
|> getCompletionsForPath ~debug ~opens ~full ~pos ~exact
832844
~completionContext:Field ~env ~scope
833845
| CPField (cp, fieldName) -> (
846+
if Debug.verbose () then print_endline "[ctx_path]--> CPField";
834847
let completionsForCtxPath =
835848
cp
836849
|> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env
@@ -869,6 +882,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact
869882
else None))
870883
| CPObj (cp, label) -> (
871884
(* TODO: Also needs to support ExtractedType *)
885+
if Debug.verbose () then print_endline "[ctx_path]--> CPObj";
872886
match
873887
cp
874888
|> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env
@@ -896,6 +910,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact
896910
| None -> [])
897911
| None -> [])
898912
| CPPipe {contextPath = cp; id = funNamePrefix; lhsLoc; inJsx} -> (
913+
if Debug.verbose () then print_endline "[ctx_path]--> CPPipe";
899914
match
900915
cp
901916
|> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env
@@ -1008,6 +1023,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact
10081023
| _ -> completions)
10091024
| None -> []))
10101025
| CTuple ctxPaths ->
1026+
if Debug.verbose () then print_endline "[ctx_path]--> CTuple";
10111027
(* Turn a list of context paths into a list of type expressions. *)
10121028
let typeExrps =
10131029
ctxPaths
@@ -1027,6 +1043,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact
10271043
]
10281044
else []
10291045
| CJsxPropValue {pathToComponent; propName} -> (
1046+
if Debug.verbose () then print_endline "[ctx_path]--> CJsxPropValue";
10301047
let findTypeOfValue path =
10311048
path
10321049
|> getCompletionsForPath ~debug ~completionContext:Value ~exact:true
@@ -1038,6 +1055,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact
10381055
| [elName] when Char.lowercase_ascii elName.[0] = elName.[0] -> true
10391056
| _ -> false
10401057
in
1058+
(* TODO(env-stuff) Does this need to potentially be instantiated with type args too? *)
10411059
let targetLabel =
10421060
if lowercaseComponent then
10431061
let rec digToTypeForCompletion path =
@@ -1072,6 +1090,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact
10721090
~kind:(Completion.Value (Utils.unwrapIfOption typ));
10731091
])
10741092
| CArgument {functionContextPath; argumentLabel} -> (
1093+
if Debug.verbose () then print_endline "[ctx_path]--> CArgument";
10751094
if Debug.verbose () then
10761095
Printf.printf "--> function argument: %s\n"
10771096
(match argumentLabel with
@@ -1124,6 +1143,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact
11241143
(if expandOption then Utils.unwrapIfOption typ else typ));
11251144
])
11261145
| CPatternPath {rootCtxPath; nested} -> (
1146+
if Debug.verbose () then print_endline "[ctx_path]--> CPatternPath";
11271147
(* TODO(env-stuff) Get rid of innerType etc *)
11281148
match
11291149
rootCtxPath
@@ -1138,6 +1158,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact
11381158
| None -> [])
11391159
| None -> [])
11401160
| CTypeAtPos loc -> (
1161+
if Debug.verbose () then print_endline "[ctx_path]--> CTypeAtPos";
11411162
match
11421163
References.getLocItem ~full ~pos:(Pos.ofLexing loc.loc_start) ~debug
11431164
with
@@ -1872,6 +1893,22 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover completable =
18721893
fallbackOrEmpty ~items ())
18731894
| None -> fallbackOrEmpty ())
18741895
| Cexpression {contextPath; prefix; nested} -> (
1896+
let isAmbigiousRecordBodyOrJsxWrap =
1897+
match (contextPath, nested) with
1898+
| CJsxPropValue _, [NRecordBody _] -> true
1899+
| _ -> false
1900+
in
1901+
if Debug.verbose () then
1902+
(* This happens in this scenario: `<SomeComponent someProp={<com>}`
1903+
Here, we don't know whether `{}` is just wraps for the type of
1904+
`someProp`, or if it's a record body where we want to complete
1905+
for the fields in the record. We need to look up what the type is
1906+
first before deciding what completions to show. So we do that here.*)
1907+
if isAmbigiousRecordBodyOrJsxWrap then
1908+
print_endline
1909+
"[process_completable]--> Cexpression special case: JSX prop value \
1910+
that might be record body or JSX wrap"
1911+
else print_endline "[process_completable]--> Cexpression";
18751912
(* Completions for local things like variables in scope, modules in the
18761913
project, etc. We only add completions when there's a prefix of some sort
18771914
we can filter on, since we know we're in some sort of context, and
@@ -1890,17 +1927,32 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover completable =
18901927
with
18911928
| None ->
18921929
if Debug.verbose () then
1893-
print_endline "--> could not get completions for context path";
1930+
print_endline
1931+
"[process_completable]--> could not get completions for context path";
18941932
regularCompletions
18951933
| Some (typ, env) -> (
18961934
match typ |> TypeUtils.resolveNested ~env ~full ~nested with
18971935
| None ->
18981936
if Debug.verbose () then
1899-
print_endline "--> could not resolve nested expression path";
1900-
regularCompletions
1937+
print_endline
1938+
"[process_completable]--> could not resolve nested expression path";
1939+
if isAmbigiousRecordBodyOrJsxWrap then (
1940+
if Debug.verbose () then
1941+
print_endline
1942+
"[process_completable]--> case is ambigious Jsx prop vs record \
1943+
body case, complete also for the JSX prop value directly";
1944+
let itemsForRawJsxPropValue =
1945+
typ
1946+
|> completeTypedValue ~rawOpens ~mode:Expression ~full ~prefix
1947+
~completionContext:None
1948+
in
1949+
itemsForRawJsxPropValue @ regularCompletions)
1950+
else regularCompletions
19011951
| Some (typ, _env, completionContext, typeArgContext) -> (
19021952
if Debug.verbose () then
1903-
print_endline "--> found type in nested expression completion";
1953+
print_endline
1954+
"[process_completable]--> found type in nested expression \
1955+
completion";
19041956
(* Wrap the insert text in braces when we're completing the root of a
19051957
JSX prop value. *)
19061958
let wrapInsertTextInBraces =

Diff for: analysis/src/CompletionJsx.ml

+33-16
Original file line numberDiff line numberDiff line change
@@ -816,20 +816,27 @@ let findJsxPropsCompletable ~jsxProps ~endPos ~posBeforeCursor
816816
let rec loop props =
817817
match props with
818818
| prop :: rest ->
819-
if prop.posStart <= posBeforeCursor && posBeforeCursor < prop.posEnd then
820-
(* Cursor on the prop name *)
819+
if prop.posStart <= posBeforeCursor && posBeforeCursor < prop.posEnd then (
820+
if Debug.verbose () then
821+
print_endline "[jsx_props_completable]--> Cursor on the prop name";
822+
821823
Some
822824
(Completable.Cjsx
823825
( Utils.flattenLongIdent ~jsx:true jsxProps.compName.txt,
824826
prop.name,
825-
allLabels ))
827+
allLabels )))
826828
else if
827829
prop.posEnd <= posBeforeCursor
828830
&& posBeforeCursor < Loc.start prop.exp.pexp_loc
829-
then (* Cursor between the prop name and expr assigned *)
830-
None
831-
else if prop.exp.pexp_loc |> Loc.hasPos ~pos:posBeforeCursor then
832-
(* Cursor on expr assigned *)
831+
then (
832+
if Debug.verbose () then
833+
print_endline
834+
"[jsx_props_completable]--> Cursor between the prop name and expr \
835+
assigned";
836+
None)
837+
else if prop.exp.pexp_loc |> Loc.hasPos ~pos:posBeforeCursor then (
838+
if Debug.verbose () then
839+
print_endline "[jsx_props_completable]--> Cursor on expr assigned";
833840
match
834841
CompletionExpressions.traverseExpr prop.exp ~exprPath:[]
835842
~pos:posBeforeCursor ~firstCharBeforeCursorNoWhite
@@ -848,9 +855,13 @@ let findJsxPropsCompletable ~jsxProps ~endPos ~posBeforeCursor
848855
nested = List.rev nested;
849856
prefix;
850857
})
851-
| _ -> None
852-
else if prop.exp.pexp_loc |> Loc.end_ = (Location.none |> Loc.end_) then
853-
if CompletionExpressions.isExprHole prop.exp then
858+
| _ -> None)
859+
else if prop.exp.pexp_loc |> Loc.end_ = (Location.none |> Loc.end_) then (
860+
if Debug.verbose () then
861+
print_endline "[jsx_props_completable]--> Loc is broken";
862+
if CompletionExpressions.isExprHole prop.exp then (
863+
if Debug.verbose () then
864+
print_endline "[jsx_props_completable]--> Expr was expr hole";
854865
Some
855866
(Cexpression
856867
{
@@ -863,13 +874,17 @@ let findJsxPropsCompletable ~jsxProps ~endPos ~posBeforeCursor
863874
};
864875
prefix = "";
865876
nested = [];
866-
})
867-
else None
868-
else if rest = [] && beforeChildrenStart && charAtCursor = '>' then
877+
}))
878+
else None)
879+
else if rest = [] && beforeChildrenStart && charAtCursor = '>' then (
869880
(* This is a special case for: <SomeComponent someProp=> (completing directly after the '=').
870881
The completion comes at the end of the component, after the equals sign, but before any
871882
children starts, and '>' marks that it's at the end of the component JSX.
872883
This little heuristic makes sure we pick up this special case. *)
884+
if Debug.verbose () then
885+
print_endline
886+
"[jsx_props_completable]--> Special case: last prop, '>' after \
887+
cursor";
873888
Some
874889
(Cexpression
875890
{
@@ -882,16 +897,18 @@ let findJsxPropsCompletable ~jsxProps ~endPos ~posBeforeCursor
882897
};
883898
prefix = "";
884899
nested = [];
885-
})
900+
}))
886901
else loop rest
887902
| [] ->
888903
let afterCompName = posBeforeCursor >= posAfterCompName in
889-
if afterCompName && beforeChildrenStart then
904+
if afterCompName && beforeChildrenStart then (
905+
if Debug.verbose () then
906+
print_endline "[jsx_props_completable]--> Complete for JSX prop name";
890907
Some
891908
(Cjsx
892909
( Utils.flattenLongIdent ~jsx:true jsxProps.compName.txt,
893910
"",
894-
allLabels ))
911+
allLabels )))
895912
else None
896913
in
897914
loop jsxProps.props

Diff for: analysis/tests/src/CompletionJsxProps.res

+7
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,10 @@
2727
// let _ = <CompletionSupport.TestComponent testArr={[]}
2828
// ^com
2929

30+
let tsomeVar = #two
31+
32+
// let _ = <CompletionSupport.TestComponent polyArg={}
33+
// ^com
34+
35+
// let _ = <CompletionSupport.TestComponent on={t}
36+
// ^com

0 commit comments

Comments
 (0)