From eaa5407616cfd7fc63bc40693daa90308f5a7e2a Mon Sep 17 00:00:00 2001 From: Pedro Castro Date: Mon, 3 Aug 2026 11:35:03 -0300 Subject: [PATCH 1/2] Fix jsx completion for abstract Jsx.component --- CHANGELOG.md | 1 + analysis/src/completion_jsx.ml | 54 ++++++------ .../tests/src/CompletionJsx.res | 11 +++ .../tests/src/expected/CompletionJsx.res.txt | 88 ++++++++++++------- 4 files changed, 92 insertions(+), 62 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74e9abbbe6c..ba357d9ef34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ #### :bug: Bug fix +- Fix JSX prop completions in editor analysis for components backed by abstract `Jsx.component` values. https://github.com/rescript-lang/rescript/pull/8390 - Preserve multibyte characters when wrapping long source lines in compiler code frames. https://github.com/rescript-lang/rescript/pull/8520 #### :memo: Documentation diff --git a/analysis/src/completion_jsx.ml b/analysis/src/completion_jsx.ml index bfaf3bb5e99..7de2b6c1be0 100644 --- a/analysis/src/completion_jsx.ml +++ b/analysis/src/completion_jsx.ml @@ -231,42 +231,40 @@ let get_jsx_labels ~component_path ~find_type_of_value ~package ~state = (name, t, env)) | _ -> [] in + let is_component_path path = + match Path.last path with + | "component" -> true + | _ -> false + in + let is_component_like_path path = + match Path.last path with + | "componentLike" -> true + | _ -> false + in + let rec get_props_type (t : Types.type_expr) = + match t.desc with + | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> get_props_type t1 + | Tconstr (path, type_args, _) -> Some (path, type_args) + | _ -> None + in let rec get_labels (t : Types.type_expr) = match t.desc with | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> get_labels t1 - | Tconstr (p, [props_type], _) when Path.name p = "React.component" -> ( - let rec get_props_type (t : Types.type_expr) = - match t.desc with - | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> get_props_type t1 - | Tconstr (path, type_args, _) when Path.last path = "props" -> - Some (path, type_args) - | _ -> None - in + | Tconstr (p, [props_type], _) when is_component_path p -> ( match props_type |> get_props_type with | Some (path, type_args) -> get_fields ~path ~type_args | None -> []) | Tarrow - ({lbl = Nolabel; typ = {desc = Tconstr (path, type_args, _)}}, _, _, _) - when Path.last path = "props" -> - get_fields ~path ~type_args - | Tconstr (cl_path, [{desc = Tconstr (path, type_args, _)}; _], _) - when Path.name cl_path = "React.componentLike" - && Path.last path = "props" -> + ({lbl = Nolabel; typ}, _, _, _) -> ( + match typ |> get_props_type with + | Some (path, type_args) -> get_fields ~path ~type_args + | None -> []) + | Tconstr (cl_path, [props_type; _], _) + when is_component_like_path cl_path -> ( (* JSX V4 external or interface *) - get_fields ~path ~type_args - | Tarrow ({lbl = Nolabel; typ}, _, _, _) -> ( - (* Component without the JSX PPX, like a make fn taking a hand-written - type props. *) - let rec dig_to_constr typ = - match typ.Types.desc with - | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> dig_to_constr t1 - | Tconstr (path, type_args, _) when Path.last path = "props" -> - Some (path, type_args) - | _ -> None - in - match dig_to_constr typ with - | None -> [] - | Some (path, type_args) -> get_fields ~path ~type_args) + match props_type |> get_props_type with + | Some (path, type_args) -> get_fields ~path ~type_args + | None -> []) | _ -> [] in typ |> get_labels diff --git a/tests/analysis_tests/tests/src/CompletionJsx.res b/tests/analysis_tests/tests/src/CompletionJsx.res index 655d61298d3..fd072bc580d 100644 --- a/tests/analysis_tests/tests/src/CompletionJsx.res +++ b/tests/analysis_tests/tests/src/CompletionJsx.res @@ -87,9 +87,20 @@ module Info = { } } +module DomPropsComponent = { + @react.componentWithProps + let make = (props: JsxDOM.domProps) => { + ignore(props) + React.null + } +} + // // ^com +// +// ^com + // let _ =

{"".s}

// ^com diff --git a/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt b/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt index 4a660c15557..2eaa73325f8 100644 --- a/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt +++ b/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt @@ -1035,20 +1035,40 @@ Path MultiPropComp.make } ] -Complete src/CompletionJsx.res 89:26 -posCursor:[89:26] posNoWhite:[89:24] Found expr:[89:3->89:27] -JSX 89:8] _type[89:9->89:14]=...[89:16->89:24]> _children:None +Complete src/CompletionJsx.res 97:26 +posCursor:[97:26] posNoWhite:[97:24] Found expr:[97:3->97:27] +JSX 97:8] _type[97:9->97:14]=...[97:16->97:24]> _children:None Completable: Cjsx([Info], "", [_type]) Package opens Stdlib.place holder Pervasives.JsxModules.place holder Resolved opens 1 Stdlib Path Info.make [ { "detail": "string", "kind": 4, "label": "key", "tags": [] } ] -Complete src/CompletionJsx.res 93:19 -posCursor:[93:19] posNoWhite:[93:18] Found expr:[93:11->93:24] -JSX 93:13] > _children:93:15 -posCursor:[93:19] posNoWhite:[93:18] Found expr:[93:15->93:19] -Pexp_field [93:15->93:17] s:[93:18->93:19] +Complete src/CompletionJsx.res 100:30 +posCursor:[100:30] posNoWhite:[100:29] Found expr:[100:3->100:31] +JSX 100:21] onClick[100:22->100:29]=...[100:22->100:29]> _children:None +Completable: Cexpression CJsxPropValue [DomPropsComponent] onClick +Package opens Stdlib.place holder Pervasives.JsxModules.place holder +Resolved opens 1 Stdlib +ContextPath CJsxPropValue [DomPropsComponent] onClick +Path DomPropsComponent.make +[ + { + "detail": "JsxEvent.Mouse.t => unit", + "insertText": "{${1:event} => ${0:event}}", + "insertTextFormat": 2, + "kind": 12, + "label": "event => event", + "sortText": "A", + "tags": [] + } +] + +Complete src/CompletionJsx.res 104:19 +posCursor:[104:19] posNoWhite:[104:18] Found expr:[104:11->104:24] +JSX 104:13] > _children:104:15 +posCursor:[104:19] posNoWhite:[104:18] Found expr:[104:15->104:19] +Pexp_field [104:15->104:17] s:[104:18->104:19] Completable: Cpath string.s Package opens Stdlib.place holder Pervasives.JsxModules.place holder Resolved opens 1 Stdlib @@ -1064,8 +1084,8 @@ Path s { "newText": "", "range": { - "end": { "character": 18, "line": 93 }, - "start": { "character": 17, "line": 93 } + "end": { "character": 18, "line": 104 }, + "start": { "character": 17, "line": 104 } } } ], @@ -1086,8 +1106,8 @@ Path s { "newText": "", "range": { - "end": { "character": 18, "line": 93 }, - "start": { "character": 17, "line": 93 } + "end": { "character": 18, "line": 104 }, + "start": { "character": 17, "line": 104 } } } ], @@ -1107,8 +1127,8 @@ Path s { "newText": "", "range": { - "end": { "character": 18, "line": 93 }, - "start": { "character": 17, "line": 93 } + "end": { "character": 18, "line": 104 }, + "start": { "character": 17, "line": 104 } } } ], @@ -1128,8 +1148,8 @@ Path s { "newText": "", "range": { - "end": { "character": 18, "line": 93 }, - "start": { "character": 17, "line": 93 } + "end": { "character": 18, "line": 104 }, + "start": { "character": 17, "line": 104 } } } ], @@ -1149,8 +1169,8 @@ Path s { "newText": "", "range": { - "end": { "character": 18, "line": 93 }, - "start": { "character": 17, "line": 93 } + "end": { "character": 18, "line": 104 }, + "start": { "character": 17, "line": 104 } } } ], @@ -1171,8 +1191,8 @@ Path s { "newText": "", "range": { - "end": { "character": 18, "line": 93 }, - "start": { "character": 17, "line": 93 } + "end": { "character": 18, "line": 104 }, + "start": { "character": 17, "line": 104 } } } ], @@ -1192,8 +1212,8 @@ Path s { "newText": "", "range": { - "end": { "character": 18, "line": 93 }, - "start": { "character": 17, "line": 93 } + "end": { "character": 18, "line": 104 }, + "start": { "character": 17, "line": 104 } } } ], @@ -1213,8 +1233,8 @@ Path s { "newText": "", "range": { - "end": { "character": 18, "line": 93 }, - "start": { "character": 17, "line": 93 } + "end": { "character": 18, "line": 104 }, + "start": { "character": 17, "line": 104 } } } ], @@ -1235,8 +1255,8 @@ Path s { "newText": "", "range": { - "end": { "character": 18, "line": 93 }, - "start": { "character": 17, "line": 93 } + "end": { "character": 18, "line": 104 }, + "start": { "character": 17, "line": 104 } } } ], @@ -1256,8 +1276,8 @@ Path s { "newText": "", "range": { - "end": { "character": 18, "line": 93 }, - "start": { "character": 17, "line": 93 } + "end": { "character": 18, "line": 104 }, + "start": { "character": 17, "line": 104 } } } ], @@ -1277,8 +1297,8 @@ Path s { "newText": "", "range": { - "end": { "character": 18, "line": 93 }, - "start": { "character": 17, "line": 93 } + "end": { "character": 18, "line": 104 }, + "start": { "character": 17, "line": 104 } } } ], @@ -1298,8 +1318,8 @@ Path s { "newText": "", "range": { - "end": { "character": 18, "line": 93 }, - "start": { "character": 17, "line": 93 } + "end": { "character": 18, "line": 104 }, + "start": { "character": 17, "line": 104 } } } ], @@ -1319,8 +1339,8 @@ Path s { "newText": "", "range": { - "end": { "character": 18, "line": 93 }, - "start": { "character": 17, "line": 93 } + "end": { "character": 18, "line": 104 }, + "start": { "character": 17, "line": 104 } } } ], From 0a140c35e5af0a889592faf91ae91aea0aac35b4 Mon Sep 17 00:00:00 2001 From: Pedro Castro Date: Mon, 3 Aug 2026 11:36:44 -0300 Subject: [PATCH 2/2] format --- analysis/src/completion_jsx.ml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/analysis/src/completion_jsx.ml b/analysis/src/completion_jsx.ml index 7de2b6c1be0..870ec3660c1 100644 --- a/analysis/src/completion_jsx.ml +++ b/analysis/src/completion_jsx.ml @@ -254,8 +254,7 @@ let get_jsx_labels ~component_path ~find_type_of_value ~package ~state = match props_type |> get_props_type with | Some (path, type_args) -> get_fields ~path ~type_args | None -> []) - | Tarrow - ({lbl = Nolabel; typ}, _, _, _) -> ( + | Tarrow ({lbl = Nolabel; typ}, _, _, _) -> ( match typ |> get_props_type with | Some (path, type_args) -> get_fields ~path ~type_args | None -> [])