Skip to content

Commit 6bb7e0a

Browse files
nojafshulhi
andauthored
Jsx ast (#7286)
* Intial exploration of JSX ast Map child expressions Initial mapping of Pexp_jsx_fragment to 0 Correct location in mapping Update analysis for jsx_fragment Remove unused code Print something for ml print Commit invalid test results for reference Try improve printing Correct fragment range, try and print comments Indent jsx Process comments from children inside fragment Attach comments to fragment tags Fix comment Improve comment formatting Print single element on same line Update comment WIP: Debug More debugging Works Fix some jsx printing Fix the test Clean up Update tests with location changes * Initial mapping from0 * Format code * Fix jsx fragment mapping * Remove fragment * Update test output * Introducing Pexp_jsx_unary_element & Pexp_jsx_container_element * Refactor fragment transformation. * Initial transform of Pexp_jsx_unary_element in automatic mode * Make props for unary element in automatic mode. * Initial custom component unary tag * lowercase container element with children. * Uppercase container elements * Streamline automatic element calls * lowercase container element in classic mode * Deal with uppercase tags in classic mode. * Remove old code * Improve recovery of incomplete jsx tags. Port frontend completion. * Correct range of incomplete jsx elements * Update semantic tokens * Make the closing tag optional for jsx_container_element. * Add tighter pattern match for edge case in jsx props completion. * Update analysis tests for jsx elements ast. * print_jsx_unary_tag * Rough print_jsx_container_tag * Add ml printing * Wing the sexp thing * First step towards ast mapping * prop punning conversion * Map prop value * Map prop spreading * Initial container element mapping. * Try support children spreading * Only print space when there are props * Add space after children. * Restore braces in props and children * Inline is_jsx_expression and remove old code * Better indentation of children * Handle unary tag * Refactor * WIP: Fix unary tag comments handling * Fix comments inside prop expression * Formats * Fix closing tag indentation * Fix closing tag indentation for cases with break vs inline * Handle some edge cases * Refactor * WIP: Handle punning * Fix optional printing with braces * WIP: Handle comments attachment in a different way * Handle empty props * Handle props spread * Fix optional with punning * Indent props if they don't fit on one line. * Fix indentation of children when props are multiline. * Refactor to Pexp_jsx_element * Don't always append make when uppercase component * Use Doc.line for child spreading * Exotic prop name * Don't create record if only spreading prop, use that expression instead. * Key prop can be optional * Keep comment after opening greater than * Print prop value with comments * Update generic jsx completion tests * Revert isJsxComponent * Assign comment to the opening element tag name. * Attach comments to closing > and closing tag * Extract helpers to parsetree_viewer * setup to walk jsx props * Correct range for prop spreading * Complete walk_jsx_prop * Finish comment assignment for container elements * Revisit comment attachment for unary tags * Improve behaviour for comments in unary tag * Keep location as is for prop spreading * print leading comments of closing unary token * Attach comments between empty container tag * comments between opening and closing tag * Correct opening_greater_than_doc * Deal with container element comment edge cases. * Ensure comments inside braced property values are correct. * More accurate locations * Update syntax roundtrip test files * Update mapping test results * comment after prop name * For them older camls * More older caml stuff * Remove unreached code in typecore * Remove leftover comments * Preserve lines between jsx elements * Preserve newline between jsx children, streamline jsx fragment children printing * Update analysis expected * Another location change in expected * One more update? * Remove leftover comments * Remove obsolete comment * Ensure fragment starts after arrow * Add changelog entry * Add missing Pexp_await --------- Co-authored-by: Shulhi Sapli <[email protected]>
1 parent 712dec7 commit 6bb7e0a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+2470
-1735
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
1313
# 12.0.0-alpha.12 (Unreleased)
1414

15+
#### :house: Internal
16+
- Better representation of JSX in AST . https://github.com/rescript-lang/rescript/pull/7286
17+
1518
# 12.0.0-alpha.11
1619

1720
#### :bug: Bug fix

analysis/src/CompletionFrontEnd.ml

+37-9
Original file line numberDiff line numberDiff line change
@@ -1233,8 +1233,6 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
12331233
then ValueOrField
12341234
else Value);
12351235
}))
1236-
| Pexp_construct ({txt = Lident ("::" | "()")}, _) ->
1237-
(* Ignore list expressions, used in JSX, unit, and more *) ()
12381236
| Pexp_construct (lid, eOpt) -> (
12391237
let lidPath = flattenLidCheckDot lid in
12401238
if debug then
@@ -1325,10 +1323,29 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
13251323
inJsx = !inJsxContext;
13261324
}))
13271325
| None -> ())
1328-
| Pexp_apply {funct = {pexp_desc = Pexp_ident compName}; args}
1329-
when Res_parsetree_viewer.is_jsx_expression expr ->
1326+
| Pexp_jsx_element
1327+
( Jsx_unary_element
1328+
{
1329+
jsx_unary_element_tag_name = compName;
1330+
jsx_unary_element_props = props;
1331+
}
1332+
| Jsx_container_element
1333+
{
1334+
jsx_container_element_tag_name_start = compName;
1335+
jsx_container_element_props = props;
1336+
} ) ->
13301337
inJsxContext := true;
1331-
let jsxProps = CompletionJsx.extractJsxProps ~compName ~args in
1338+
let children =
1339+
match expr.pexp_desc with
1340+
| Pexp_jsx_element
1341+
(Jsx_container_element
1342+
{jsx_container_element_children = children}) ->
1343+
children
1344+
| _ -> JSXChildrenItems []
1345+
in
1346+
let jsxProps =
1347+
CompletionJsx.extractJsxProps ~compName ~props ~children
1348+
in
13321349
let compNamePath = flattenLidCheckDot ~jsx:true compName in
13331350
if debug then
13341351
Printf.printf "JSX <%s:%s %s> _children:%s\n"
@@ -1345,10 +1362,21 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
13451362
| None -> "None"
13461363
| Some childrenPosStart -> Pos.toString childrenPosStart);
13471364
let jsxCompletable =
1348-
CompletionJsx.findJsxPropsCompletable ~jsxProps
1349-
~endPos:(Loc.end_ expr.pexp_loc) ~posBeforeCursor
1350-
~posAfterCompName:(Loc.end_ compName.loc)
1351-
~firstCharBeforeCursorNoWhite ~charAtCursor
1365+
match expr.pexp_desc with
1366+
| Pexp_jsx_element
1367+
(Jsx_container_element
1368+
{
1369+
jsx_container_element_closing_tag = None;
1370+
jsx_container_element_children =
1371+
JSXChildrenSpreading _ | JSXChildrenItems (_ :: _);
1372+
}) ->
1373+
(* This is a weird edge case where there is no closing tag but there are children *)
1374+
None
1375+
| _ ->
1376+
CompletionJsx.findJsxPropsCompletable ~jsxProps
1377+
~endPos:(Loc.end_ expr.pexp_loc) ~posBeforeCursor
1378+
~posAfterCompName:(Loc.end_ compName.loc)
1379+
~firstCharBeforeCursorNoWhite ~charAtCursor
13521380
in
13531381
if jsxCompletable <> None then setResultOpt jsxCompletable
13541382
else if compName.loc |> Loc.hasPos ~pos:posBeforeCursor then

analysis/src/CompletionJsx.ml

+34-35
Original file line numberDiff line numberDiff line change
@@ -455,40 +455,39 @@ let findJsxPropsCompletable ~jsxProps ~endPos ~posBeforeCursor
455455
in
456456
loop jsxProps.props
457457

458-
let extractJsxProps ~(compName : Longident.t Location.loc) ~args =
459-
let thisCaseShouldNotHappen =
460-
{
461-
compName = Location.mknoloc (Longident.Lident "");
462-
props = [];
463-
childrenStart = None;
464-
}
458+
let extractJsxProps ~(compName : Longident.t Location.loc) ~props ~children =
459+
let open Parsetree in
460+
let childrenStart =
461+
match children with
462+
| JSXChildrenItems [] -> None
463+
| JSXChildrenSpreading child | JSXChildrenItems (child :: _) ->
464+
if child.pexp_loc.loc_ghost then None else Some (Loc.start child.pexp_loc)
465465
in
466-
let rec processProps ~acc args =
467-
match args with
468-
| (Asttypes.Labelled {txt = "children"}, {Parsetree.pexp_loc}) :: _ ->
469-
{
470-
compName;
471-
props = List.rev acc;
472-
childrenStart =
473-
(if pexp_loc.loc_ghost then None else Some (Loc.start pexp_loc));
474-
}
475-
| ( (Labelled {txt = s; loc} | Optional {txt = s; loc}),
476-
(eProp : Parsetree.expression) )
477-
:: rest -> (
478-
let namedArgLoc = if loc = Location.none then None else Some loc in
479-
match namedArgLoc with
480-
| Some loc ->
481-
processProps
482-
~acc:
483-
({
484-
name = s;
485-
posStart = Loc.start loc;
486-
posEnd = Loc.end_ loc;
487-
exp = eProp;
488-
}
489-
:: acc)
490-
rest
491-
| None -> processProps ~acc rest)
492-
| _ -> thisCaseShouldNotHappen
466+
let props =
467+
props
468+
|> List.map (function
469+
| JSXPropPunning (_, name) ->
470+
{
471+
name = name.txt;
472+
posStart = Loc.start name.loc;
473+
posEnd = Loc.end_ name.loc;
474+
exp =
475+
Ast_helper.Exp.ident ~loc:name.loc
476+
{txt = Longident.Lident name.txt; loc = name.loc};
477+
}
478+
| JSXPropValue (name, _, value) ->
479+
{
480+
name = name.txt;
481+
posStart = Loc.start name.loc;
482+
posEnd = Loc.end_ name.loc;
483+
exp = value;
484+
}
485+
| JSXPropSpreading (loc, expr) ->
486+
{
487+
name = "_spreadProps";
488+
posStart = Loc.start loc;
489+
posEnd = Loc.end_ loc;
490+
exp = expr;
491+
})
493492
in
494-
args |> processProps ~acc:[]
493+
{compName; props; childrenStart}

analysis/src/SemanticTokens.ml

+53-45
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,7 @@ let emitLongident ?(backwards = false) ?(jsx = false)
120120
let rec flatten acc lid =
121121
match lid with
122122
| Longident.Lident txt -> txt :: acc
123-
| Ldot (lid, txt) ->
124-
let acc = if jsx && txt = "createElement" then acc else txt :: acc in
125-
flatten acc lid
123+
| Ldot (lid, txt) -> flatten (txt :: acc) lid
126124
| _ -> acc
127125
in
128126
let rec loop pos segments =
@@ -247,8 +245,8 @@ let command ~debug ~emitter ~path =
247245
~posEnd:(Some (Loc.end_ loc))
248246
~lid ~debug;
249247
Ast_iterator.default_iterator.expr iterator e
250-
| Pexp_apply {funct = {pexp_desc = Pexp_ident lident; pexp_loc}; args}
251-
when Res_parsetree_viewer.is_jsx_expression e ->
248+
| Pexp_jsx_element (Jsx_unary_element {jsx_unary_element_tag_name = lident})
249+
->
252250
(*
253251
Angled brackets:
254252
- These are handled in the grammar: <> </> </ />
@@ -258,46 +256,56 @@ let command ~debug ~emitter ~path =
258256
- handled like other Longitent.t, except lowercase id is marked Token.JsxLowercase
259257
*)
260258
emitter (* --> <div... *)
261-
|> emitJsxTag ~debug ~name:"<"
262-
~pos:
263-
(let pos = Loc.start e.pexp_loc in
264-
(fst pos, snd pos - 1 (* the AST skips the loc of < somehow *)));
265-
emitter |> emitJsxOpen ~lid:lident.txt ~debug ~loc:pexp_loc;
266-
267-
let posOfGreatherthanAfterProps =
268-
let rec loop = function
269-
| (Asttypes.Labelled {txt = "children"}, {Parsetree.pexp_loc}) :: _ ->
270-
Loc.start pexp_loc
271-
| _ :: args -> loop args
272-
| [] -> (* should not happen *) (-1, -1)
273-
in
274-
275-
loop args
276-
in
277-
let posOfFinalGreatherthan =
278-
let pos = Loc.end_ e.pexp_loc in
279-
(fst pos, snd pos - 1)
280-
in
281-
let selfClosing =
282-
fst posOfGreatherthanAfterProps == fst posOfFinalGreatherthan
283-
&& snd posOfGreatherthanAfterProps + 1 == snd posOfFinalGreatherthan
284-
(* there's an off-by one somehow in the AST *)
285-
in
286-
(if not selfClosing then
287-
let lineStart, colStart = Loc.start pexp_loc in
288-
let lineEnd, colEnd = Loc.end_ pexp_loc in
289-
let length = if lineStart = lineEnd then colEnd - colStart else 0 in
290-
let lineEndWhole, colEndWhole = Loc.end_ e.pexp_loc in
291-
if length > 0 && colEndWhole > length then (
292-
emitter
293-
|> emitJsxClose ~debug ~lid:lident.txt
294-
~pos:(lineEndWhole, colEndWhole - 1);
295-
emitter (* <foo ...props > <-- *)
296-
|> emitJsxTag ~debug ~name:">" ~pos:posOfGreatherthanAfterProps;
297-
emitter (* <foo> ... </foo> <-- *)
298-
|> emitJsxTag ~debug ~name:">" ~pos:posOfFinalGreatherthan));
299-
300-
args |> List.iter (fun (_lbl, arg) -> iterator.expr iterator arg)
259+
|> emitJsxTag ~debug ~name:"<" ~pos:(Loc.start e.pexp_loc);
260+
emitter |> emitJsxOpen ~lid:lident.txt ~debug ~loc:lident.loc;
261+
let closing_line, closing_column = Loc.end_ e.pexp_loc in
262+
emitter (* <foo ...props /> <-- *)
263+
|> emitJsxTag ~debug ~name:"/>" ~pos:(closing_line, closing_column - 2)
264+
(* minus two for /> *)
265+
| Pexp_jsx_element
266+
(Jsx_container_element
267+
{
268+
jsx_container_element_tag_name_start = lident;
269+
jsx_container_element_opening_tag_end = posOfGreatherthanAfterProps;
270+
jsx_container_element_children = children;
271+
jsx_container_element_closing_tag = closing_tag_opt;
272+
}) ->
273+
(* opening tag *)
274+
emitter (* --> <div... *)
275+
|> emitJsxTag ~debug ~name:"<" ~pos:(Loc.start e.pexp_loc);
276+
emitter |> emitJsxOpen ~lid:lident.txt ~debug ~loc:lident.loc;
277+
emitter (* <foo ...props > <-- *)
278+
|> emitJsxTag ~debug ~name:">"
279+
~pos:(Pos.ofLexing posOfGreatherthanAfterProps);
280+
281+
(* children *)
282+
(match children with
283+
| Parsetree.JSXChildrenSpreading child -> iterator.expr iterator child
284+
| Parsetree.JSXChildrenItems children ->
285+
List.iter (iterator.expr iterator) children);
286+
287+
(* closing tag *)
288+
closing_tag_opt
289+
|> Option.iter
290+
(fun
291+
{
292+
(* </ *)
293+
Parsetree.jsx_closing_container_tag_start = closing_less_than;
294+
(* name *)
295+
jsx_closing_container_tag_name = tag_name_end;
296+
(* > *)
297+
jsx_closing_container_tag_end = final_greather_than;
298+
}
299+
->
300+
emitter
301+
|> emitJsxTag ~debug ~name:"</"
302+
~pos:(Pos.ofLexing closing_less_than);
303+
emitter
304+
|> emitJsxClose ~debug ~lid:lident.txt
305+
~pos:(Loc.start tag_name_end.loc);
306+
emitter (* <foo> ... </foo> <-- *)
307+
|> emitJsxTag ~debug ~name:">"
308+
~pos:(Pos.ofLexing final_greather_than))
301309
| Pexp_apply
302310
{
303311
funct =

analysis/src/Utils.ml

+1
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ let identifyPexp pexp =
112112
| Pexp_extension _ -> "Pexp_extension"
113113
| Pexp_open _ -> "Pexp_open"
114114
| Pexp_await _ -> "Pexp_await"
115+
| Pexp_jsx_element _ -> "Pexp_jsx_element"
115116

116117
let identifyPpat pat =
117118
match pat with

compiler/frontend/bs_ast_mapper.ml

+39
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,20 @@ module M = struct
292292
end
293293

294294
module E = struct
295+
let map_jsx_children sub = function
296+
| JSXChildrenSpreading e -> JSXChildrenSpreading (sub.expr sub e)
297+
| JSXChildrenItems xs -> JSXChildrenItems (List.map (sub.expr sub) xs)
298+
299+
let map_jsx_prop sub = function
300+
| JSXPropPunning (optional, name) ->
301+
JSXPropPunning (optional, map_loc sub name)
302+
| JSXPropValue (name, optional, value) ->
303+
JSXPropValue (map_loc sub name, optional, sub.expr sub value)
304+
| JSXPropSpreading (loc, e) ->
305+
JSXPropSpreading (sub.location sub loc, sub.expr sub e)
306+
307+
let map_jsx_props sub = List.map (map_jsx_prop sub)
308+
295309
(* Value expressions for the core language *)
296310

297311
let map sub {pexp_loc = loc; pexp_desc = desc; pexp_attributes = attrs} =
@@ -367,6 +381,31 @@ module E = struct
367381
open_ ~loc ~attrs ovf (map_loc sub lid) (sub.expr sub e)
368382
| Pexp_extension x -> extension ~loc ~attrs (sub.extension sub x)
369383
| Pexp_await e -> await ~loc ~attrs (sub.expr sub e)
384+
| Pexp_jsx_element
385+
(Jsx_fragment
386+
{
387+
jsx_fragment_opening = o;
388+
jsx_fragment_children = children;
389+
jsx_fragment_closing = c;
390+
}) ->
391+
jsx_fragment o (map_jsx_children sub children) c
392+
| Pexp_jsx_element
393+
(Jsx_unary_element
394+
{jsx_unary_element_tag_name = name; jsx_unary_element_props = props})
395+
->
396+
jsx_unary_element ~loc ~attrs name (map_jsx_props sub props)
397+
| Pexp_jsx_element
398+
(Jsx_container_element
399+
{
400+
jsx_container_element_tag_name_start = name;
401+
jsx_container_element_opening_tag_end = ote;
402+
jsx_container_element_props = props;
403+
jsx_container_element_children = children;
404+
jsx_container_element_closing_tag = closing_tag;
405+
}) ->
406+
jsx_container_element ~loc ~attrs name (map_jsx_props sub props) ote
407+
(map_jsx_children sub children)
408+
closing_tag
370409
end
371410

372411
module P = struct

compiler/ml/ast_helper.ml

+52
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,59 @@ module Exp = struct
181181
let open_ ?loc ?attrs a b c = mk ?loc ?attrs (Pexp_open (a, b, c))
182182
let extension ?loc ?attrs a = mk ?loc ?attrs (Pexp_extension a)
183183
let await ?loc ?attrs a = mk ?loc ?attrs (Pexp_await a)
184+
let jsx_fragment ?loc ?attrs a b c =
185+
mk ?loc ?attrs
186+
(Pexp_jsx_element
187+
(Jsx_fragment
188+
{
189+
jsx_fragment_opening = a;
190+
jsx_fragment_children = b;
191+
jsx_fragment_closing = c;
192+
}))
193+
let jsx_unary_element ?loc ?attrs a b =
194+
mk ?loc ?attrs
195+
(Pexp_jsx_element
196+
(Jsx_unary_element
197+
{jsx_unary_element_tag_name = a; jsx_unary_element_props = b}))
198+
199+
let jsx_container_element ?loc ?attrs a b c d e =
200+
mk ?loc ?attrs
201+
(Pexp_jsx_element
202+
(Jsx_container_element
203+
{
204+
jsx_container_element_tag_name_start = a;
205+
jsx_container_element_props = b;
206+
jsx_container_element_opening_tag_end = c;
207+
jsx_container_element_children = d;
208+
jsx_container_element_closing_tag = e;
209+
}))
210+
184211
let case lhs ?guard rhs = {pc_lhs = lhs; pc_guard = guard; pc_rhs = rhs}
212+
213+
let make_list_expression loc seq ext_opt =
214+
let rec handle_seq = function
215+
| [] -> (
216+
match ext_opt with
217+
| Some ext -> ext
218+
| None ->
219+
let loc = {loc with Location.loc_ghost = true} in
220+
let nil = Location.mkloc (Longident.Lident "[]") loc in
221+
construct ~loc nil None)
222+
| e1 :: el ->
223+
let exp_el = handle_seq el in
224+
let loc =
225+
Location.
226+
{
227+
loc_start = e1.Parsetree.pexp_loc.Location.loc_start;
228+
loc_end = exp_el.pexp_loc.loc_end;
229+
loc_ghost = false;
230+
}
231+
in
232+
let arg = tuple ~loc [e1; exp_el] in
233+
construct ~loc (Location.mkloc (Longident.Lident "::") loc) (Some arg)
234+
in
235+
let expr = handle_seq seq in
236+
{expr with pexp_loc = loc}
185237
end
186238

187239
module Mty = struct

0 commit comments

Comments
 (0)