Skip to content

Commit 4bbea10

Browse files
committed
jsx
1 parent cc8efb4 commit 4bbea10

File tree

4 files changed

+201
-12
lines changed

4 files changed

+201
-12
lines changed

analysis/src/CompletionFrontEndNew.ml

+139-12
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@ type ctxPath =
101101
id: string;
102102
lhsLoc: Location.t; (** Location of the left hand side. *)
103103
} (** Piped call. `foo->someFn`. *)
104+
| CJsxPropValue of {
105+
pathToComponent: string list;
106+
(** The path to the component this property is from. *)
107+
propName: string; (** The prop name we're going through. *)
108+
} (** A JSX property. *)
104109

105110
let rec ctxPathToString (ctxPath : ctxPath) =
106111
match ctxPath with
@@ -109,6 +114,8 @@ let rec ctxPathToString (ctxPath : ctxPath) =
109114
| CFloat -> "float"
110115
| CInt -> "int"
111116
| CString -> "string"
117+
| CJsxPropValue {pathToComponent; propName} ->
118+
"CJsxPropValue " ^ (pathToComponent |> list) ^ " " ^ propName
112119
| CAwait ctxPath -> Printf.sprintf "await %s" (ctxPathToString ctxPath)
113120
| CApply (ctxPath, args) ->
114121
Printf.sprintf "%s(%s)" (ctxPathToString ctxPath)
@@ -245,6 +252,15 @@ module CompletionInstruction = struct
245252
(** All the already seen labels in the function call. *)
246253
prefix: string; (** The text the user has written so far.*)
247254
}
255+
| Cjsx of {
256+
pathToComponent: string list;
257+
(** The path to the component: `["M", "Comp"]`. *)
258+
prefix: string; (** What the user has already written. `"id"`. *)
259+
seenProps: string list;
260+
(** A list of all of the props that has already been entered.*)
261+
}
262+
| ChtmlElement of {prefix: string (** What the user has written so far. *)}
263+
(** Completing for a regular HTML element. *)
248264

249265
let ctxPath ctxPath = CtxPath ctxPath
250266

@@ -267,6 +283,11 @@ module CompletionInstruction = struct
267283
let namedArg ~prefix ~functionContextPath ~seenLabels =
268284
CnamedArg {prefix; ctxPath = functionContextPath; seenLabels}
269285

286+
let jsx ~prefix ~pathToComponent ~seenProps =
287+
Cjsx {prefix; pathToComponent; seenProps}
288+
289+
let htmlElement ~prefix = ChtmlElement {prefix}
290+
270291
let toString (c : t) =
271292
match c with
272293
| CtxPath ctxPath ->
@@ -290,34 +311,51 @@ module CompletionInstruction = struct
290311
"CnamedArg("
291312
^ (ctxPath |> ctxPathToString)
292313
^ ", " ^ str prefix ^ ", " ^ (seenLabels |> list) ^ ")"
314+
| Cjsx {prefix; pathToComponent; seenProps} ->
315+
"Cjsx(" ^ (pathToComponent |> ident) ^ ", " ^ str prefix ^ ", "
316+
^ (seenProps |> list) ^ ")"
317+
| ChtmlElement {prefix} -> "ChtmlElement <" ^ prefix ^ " />"
293318
end
294319

295320
module CompletionResult = struct
296321
type t = (CompletionInstruction.t * CompletionContext.t) option
297322

323+
let make (instruction : CompletionInstruction.t)
324+
(context : CompletionContext.t) =
325+
Some (instruction, context)
326+
298327
let ctxPath (ctxPath : ctxPath) (completionContext : CompletionContext.t) =
299328
let completionContext =
300329
completionContext |> CompletionContext.addCtxPathItem ctxPath
301330
in
302-
Some
303-
( CompletionInstruction.ctxPath completionContext.ctxPath,
304-
completionContext )
331+
make
332+
(CompletionInstruction.ctxPath completionContext.ctxPath)
333+
completionContext
305334

306335
let pattern ~(completionContext : CompletionContext.t) ~prefix =
307-
Some
308-
( CompletionInstruction.pattern ~completionContext ~prefix,
309-
completionContext )
336+
make
337+
(CompletionInstruction.pattern ~completionContext ~prefix)
338+
completionContext
310339

311340
let expression ~(completionContext : CompletionContext.t) ~prefix =
312-
Some
313-
( CompletionInstruction.expression ~completionContext ~prefix,
314-
completionContext )
341+
make
342+
(CompletionInstruction.expression ~completionContext ~prefix)
343+
completionContext
315344

316345
let namedArg ~(completionContext : CompletionContext.t) ~prefix ~seenLabels
317346
~functionContextPath =
318-
Some
319-
( CompletionInstruction.namedArg ~functionContextPath ~prefix ~seenLabels,
320-
completionContext )
347+
make
348+
(CompletionInstruction.namedArg ~functionContextPath ~prefix ~seenLabels)
349+
completionContext
350+
351+
let jsx ~(completionContext : CompletionContext.t) ~prefix ~pathToComponent
352+
~seenProps =
353+
make
354+
(CompletionInstruction.jsx ~prefix ~pathToComponent ~seenProps)
355+
completionContext
356+
357+
let htmlElement ~(completionContext : CompletionContext.t) ~prefix =
358+
make (CompletionInstruction.htmlElement ~prefix) completionContext
321359
end
322360

323361
let flattenLidCheckDot ?(jsx = true) ~(completionContext : CompletionContext.t)
@@ -938,6 +976,95 @@ and completeExpr ~completionContext (expr : Parsetree.expression) :
938976
if locHasPos lhs.pexp_loc then completeExpr ~completionContext lhs
939977
else if locHasPos rhs.pexp_loc then completeExpr ~completionContext rhs
940978
else None
979+
| Pexp_apply ({pexp_desc = Pexp_ident compName}, args)
980+
when Res_parsetree_viewer.isJsxExpression expr -> (
981+
(* == JSX == *)
982+
let jsxProps = CompletionJsx.extractJsxProps ~compName ~args in
983+
let compNamePath =
984+
flattenLidCheckDot ~completionContext ~jsx:true compName
985+
in
986+
let beforeCursor = completionContext.positionContext.beforeCursor in
987+
let endPos = Loc.end_ expr.pexp_loc in
988+
let posAfterCompName = Loc.end_ compName.loc in
989+
let allLabels =
990+
List.fold_right
991+
(fun (prop : CompletionJsx.prop) allLabels -> prop.name :: allLabels)
992+
jsxProps.props []
993+
in
994+
let rec loop (props : CompletionJsx.prop list) =
995+
match props with
996+
| prop :: rest ->
997+
if prop.posStart <= beforeCursor && beforeCursor < prop.posEnd then
998+
(* Cursor on the prop name *)
999+
CompletionResult.jsx ~completionContext ~prefix:prop.name
1000+
~pathToComponent:
1001+
(Utils.flattenLongIdent ~jsx:true jsxProps.compName.txt)
1002+
~seenProps:allLabels
1003+
else if
1004+
prop.posEnd <= beforeCursor
1005+
&& beforeCursor < Loc.start prop.exp.pexp_loc
1006+
then (* Cursor between the prop name and expr assigned *)
1007+
None
1008+
else if locHasPos prop.exp.pexp_loc then
1009+
(* Cursor in the expr assigned. Move into the expr and set that we're
1010+
expecting the return type of the prop. *)
1011+
let completionContext =
1012+
completionContext
1013+
|> CompletionContext.setCurrentlyExpecting
1014+
(Type
1015+
(CJsxPropValue
1016+
{
1017+
propName = prop.name;
1018+
pathToComponent =
1019+
Utils.flattenLongIdent ~jsx:true
1020+
jsxProps.compName.txt;
1021+
}))
1022+
in
1023+
completeExpr ~completionContext prop.exp
1024+
else if prop.exp.pexp_loc |> Loc.end_ = (Location.none |> Loc.end_) then
1025+
if CompletionExpressions.isExprHole prop.exp then
1026+
let completionContext =
1027+
completionContext
1028+
|> CompletionContext.addCtxPathItem
1029+
(CJsxPropValue
1030+
{
1031+
propName = prop.name;
1032+
pathToComponent =
1033+
Utils.flattenLongIdent ~jsx:true jsxProps.compName.txt;
1034+
})
1035+
in
1036+
CompletionResult.expression ~completionContext ~prefix:""
1037+
else None
1038+
else loop rest
1039+
| [] ->
1040+
let beforeChildrenStart =
1041+
match jsxProps.childrenStart with
1042+
| Some childrenPos -> beforeCursor < childrenPos
1043+
| None -> beforeCursor <= endPos
1044+
in
1045+
let afterCompName = beforeCursor >= posAfterCompName in
1046+
if afterCompName && beforeChildrenStart then
1047+
CompletionResult.jsx ~completionContext ~prefix:""
1048+
~pathToComponent:
1049+
(Utils.flattenLongIdent ~jsx:true jsxProps.compName.txt)
1050+
~seenProps:allLabels
1051+
else None
1052+
in
1053+
let jsxCompletable = loop jsxProps.props in
1054+
match jsxCompletable with
1055+
| Some jsxCompletable -> Some jsxCompletable
1056+
| None ->
1057+
if locHasPos compName.loc then
1058+
(* The component name has the cursor.
1059+
Check if this is a HTML element (lowercase initial char) or a component (uppercase initial char). *)
1060+
match compNamePath with
1061+
| [prefix] when Char.lowercase_ascii prefix.[0] = prefix.[0] ->
1062+
CompletionResult.htmlElement ~completionContext ~prefix
1063+
| _ ->
1064+
CompletionResult.ctxPath
1065+
(CId (compNamePath, Module))
1066+
(completionContext |> CompletionContext.resetCtx)
1067+
else None)
9411068
| Pexp_apply (fnExpr, _args) when locHasPos fnExpr.pexp_loc ->
9421069
(* Handle when the cursor is in the function expression itself. *)
9431070
fnExpr

analysis/src/new-completions-todo.md

+5
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,8 @@
33
- Figure out location for completeExpr - when do we need to check locs, and when not?
44
- Pipe PPX transform, should we run it ourselves in the editor tooling?
55
- Is there a practical difference btween Cexpression and Cpath?
6+
7+
### Ideas
8+
9+
- Scope JSX completions to things we know are components?
10+
- Extra context: Now in a React component function, etc

analysis/tests/src/CompletionNew.res

+22
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,25 @@ let someFun = (~firstLabel, ~secondLabel=?, r: someRecord) => {
139139

140140
// let ff = someFun(~secondLabel, ~f)
141141
// ^co2
142+
143+
// == JSX ==
144+
// let jsx = <SomeCom
145+
// ^co2
146+
147+
// let jsx = <SomeModule.S
148+
// ^co2
149+
150+
// let jsx = <Component />
151+
// ^co2
152+
153+
// let jsx = <Component a />
154+
// ^co2
155+
156+
// let jsx = <Component aProp= />
157+
// ^co2
158+
159+
// let jsx = <Component aProp={if true {}} />
160+
// ^co2
161+
162+
// let jsx = <Component aProp={Stuff} />
163+
// ^co2

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

+35
Original file line numberDiff line numberDiff line change
@@ -183,3 +183,38 @@ Result: Cexpression: ctxPath: , rootType: Type<CFunctionArgument CId(Value)=some
183183
Scope: 2 items
184184
Looking for type: Type<CFunctionArgument CId(Value)=someFun($0)>
185185

186+
Complete2 src/CompletionNew.res 143:21
187+
Result: CtxPath: CId(Module)=SomeCom
188+
Scope: 2 items
189+
Looking for type: Unit
190+
191+
Complete2 src/CompletionNew.res 146:26
192+
Result: CtxPath: CId(Module)=SomeModule.S
193+
Scope: 2 items
194+
Looking for type: Unit
195+
196+
Complete2 src/CompletionNew.res 149:24
197+
Result: Cjsx(Component, "", [])
198+
Scope: 2 items
199+
Looking for type: TypeAtLoc: [149:7->149:10]
200+
201+
Complete2 src/CompletionNew.res 152:25
202+
Result: Cjsx(Component, a, [a])
203+
Scope: 2 items
204+
Looking for type: TypeAtLoc: [152:7->152:10]
205+
206+
Complete2 src/CompletionNew.res 155:30
207+
Result: Cexpression: ctxPath: CJsxPropValue [Component] aProp, rootType: TypeAtLoc: [155:7->155:10]
208+
Scope: 2 items
209+
Looking for type: TypeAtLoc: [155:7->155:10]
210+
211+
Complete2 src/CompletionNew.res 158:40
212+
Result: Cexpression: ctxPath: CId(Value)=, rootType: Type<CJsxPropValue [Component] aProp>
213+
Scope: 2 items
214+
Looking for type: Type<CJsxPropValue [Component] aProp>
215+
216+
Complete2 src/CompletionNew.res 161:35
217+
Result: Cexpression: ctxPath: CId(Module)=Stuff, rootType: Type<CJsxPropValue [Component] aProp>, prefix: "Stuff"
218+
Scope: 2 items
219+
Looking for type: Type<CJsxPropValue [Component] aProp>
220+

0 commit comments

Comments
 (0)