Skip to content

Commit 5b39b09

Browse files
authored
Generic JSX transform completion (#919)
* add test project for generic JSX transform * adapt completion to look up the correct types when using a generic JSX transform * changelog * adapt the primitive completions inside of jsx to the generic JSX moe * remove uneccessary ignore * update * update
1 parent a4d9c0a commit 5b39b09

24 files changed

+406
-37
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ examples/*/lib
55

66
analysis/tests/lib
77
analysis/tests/.bsb.lock
8-
analysis/tests/.merlin
8+
9+
analysis/tests-generic-jsx-transform/lib
10+
analysis/tests-generic-jsx-transform/.bsb.lock
911

1012
tools/node_modules
1113
tools/lib

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
- Relax filter for what local files that come up in from and regular string completion in `@module`. https://github.com/rescript-lang/rescript-vscode/pull/918
2020
- Make from completion trigger for expr hole so we get a nice experience when completing {from: <com>} in `@module`. https://github.com/rescript-lang/rescript-vscode/pull/918
2121
- Latest parser for newest syntax features. https://github.com/rescript-lang/rescript-vscode/pull/917
22+
- Handle completion for DOM/element attributes and attribute values properly when using a generic JSX transform. https://github.com/rescript-lang/rescript-vscode/pull/919
2223

2324
## 1.38.0
2425

analysis/Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,20 @@ SHELL = /bin/bash
33
build-tests:
44
make -C tests build
55

6+
build-tests-generic-jsx-transform:
7+
make -C tests-generic-jsx-transform build
8+
69
build-reanalyze:
710
make -C reanalyze build
811

9-
build: build-reanalyze build-tests
12+
build: build-reanalyze build-tests build-tests-generic-jsx-transform
1013

1114
dce: build-analysis-binary
1215
opam exec reanalyze.exe -- -dce-cmt _build -suppress vendor
1316

1417
test-analysis-binary:
1518
make -C tests test
19+
make -C tests-generic-jsx-transform test
1620

1721
test-reanalyze:
1822
make -C reanalyze test

analysis/src/CompletionBackEnd.ml

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,14 +1010,23 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact
10101010
| Some (builtinNameToComplete, typ)
10111011
when Utils.checkName builtinNameToComplete ~prefix:funNamePrefix
10121012
~exact:false ->
1013+
let name =
1014+
match package.genericJsxModule with
1015+
| None -> "React." ^ builtinNameToComplete
1016+
| Some g ->
1017+
g ^ "." ^ builtinNameToComplete
1018+
|> String.split_on_char '.'
1019+
|> TypeUtils.removeOpensFromCompletionPath ~rawOpens
1020+
~package:full.package
1021+
|> String.concat "."
1022+
in
10131023
[
1014-
Completion.createWithSnippet
1015-
~name:("React." ^ builtinNameToComplete)
1016-
~kind:(Value typ) ~env ~sortText:"A"
1024+
Completion.createWithSnippet ~name ~kind:(Value typ) ~env
1025+
~sortText:"A"
10171026
~docstring:
10181027
[
10191028
"Turns `" ^ builtinNameToComplete
1020-
^ "` into `React.element` so it can be used inside of JSX.";
1029+
^ "` into a JSX element so it can be used inside of JSX.";
10211030
]
10221031
();
10231032
]
@@ -1078,7 +1087,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact
10781087
| Some f -> Some (f.fname.txt, f.typ, env))
10791088
| _ -> None
10801089
in
1081-
["ReactDOM"; "domProps"] |> digToTypeForCompletion
1090+
TypeUtils.pathToElementProps package |> digToTypeForCompletion
10821091
else
10831092
CompletionJsx.getJsxLabels ~componentPath:pathToComponent
10841093
~findTypeOfValue ~package
@@ -1692,9 +1701,14 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover completable =
16921701
(* We always try to look up completion from the actual domProps type first.
16931702
This works in JSXv4. For JSXv3, we have a backup hardcoded list of dom
16941703
labels we can use for completion. *)
1695-
let fromDomProps =
1704+
let pathToElementProps = TypeUtils.pathToElementProps package in
1705+
if Debug.verbose () then
1706+
Printf.printf
1707+
"[completing-lowercase-jsx] Attempting to complete from type at %s\n"
1708+
(pathToElementProps |> String.concat ".");
1709+
let fromElementProps =
16961710
match
1697-
["ReactDOM"; "domProps"]
1711+
pathToElementProps
16981712
|> digToRecordFieldsForCompletion ~debug ~package ~opens ~full ~pos ~env
16991713
~scope
17001714
with
@@ -1713,11 +1727,13 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover completable =
17131727
else None)
17141728
|> List.map mkLabel)
17151729
in
1716-
match fromDomProps with
1717-
| Some domProps -> domProps
1730+
match fromElementProps with
1731+
| Some elementProps -> elementProps
17181732
| None ->
17191733
if debug then
1720-
Printf.printf "Could not find ReactDOM.domProps to complete from.\n";
1734+
Printf.printf
1735+
"[completing-lowercase-jsx] could not find element props to complete \
1736+
from.\n";
17211737
(CompletionJsx.domLabels
17221738
|> List.filter (fun (name, _t) ->
17231739
Utils.startsWith name prefix

analysis/src/CompletionDecorators.ml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,17 @@ Example `@raises(Exn)` or `@raises([E1, E2, E3])` for multiple exceptions.
164164

165165
You will need this decorator whenever you want to use a ReScript / React component in ReScript JSX expressions.
166166

167-
Note: The `@react.component` decorator requires the react-jsx config to be set in your `bsconfig.json` to enable the required React transformations.
167+
Note: The `@react.component` decorator requires the `jsx` config to be set in your `rescript.json`/`bsconfig.json` to enable the required React transformations.
168168

169169
[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#react-component-decorator).|};
170170
] );
171+
( "jsx.component",
172+
None,
173+
[
174+
{|The `@jsx.component` decorator is used to annotate functions that are JSX components used with ReScript's [generic JSX transform](https://rescript-lang.org/docs/manual/latest/jsx#generic-jsx-transform-jsx-beyond-react-experimental).
175+
176+
You will need this decorator whenever you want to use a JSX component in ReScript JSX expressions.|};
177+
] );
171178
( "return",
172179
Some "return(${1:nullable})",
173180
[

analysis/src/CompletionFrontEnd.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -659,7 +659,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
659659
let value_binding (iterator : Ast_iterator.iterator)
660660
(value_binding : Parsetree.value_binding) =
661661
let oldInJsxContext = !inJsxContext in
662-
if Utils.isReactComponent value_binding then inJsxContext := true;
662+
if Utils.isJsxComponent value_binding then inJsxContext := true;
663663
(match value_binding with
664664
| {pvb_pat = {ppat_desc = Ppat_constraint (_pat, coreType)}; pvb_expr}
665665
when locHasCursor pvb_expr.pexp_loc -> (

analysis/src/Packages.ml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,16 @@ let newBsPackage ~rootPath =
5959
| (major, _), None when major >= 11 -> Some true
6060
| _, ns -> Option.bind ns Json.bool
6161
in
62+
let genericJsxModule =
63+
let jsxConfig = config |> Json.get "jsx" in
64+
match jsxConfig with
65+
| Some jsxConfig -> (
66+
match jsxConfig |> Json.get "module" with
67+
| Some (String m) when String.lowercase_ascii m <> "react" ->
68+
Some m
69+
| _ -> None)
70+
| None -> None
71+
in
6272
let uncurried = uncurried = Some true in
6373
let sourceDirectories =
6474
FindFiles.getSourceDirectories ~includeDev:true ~baseDir:rootPath
@@ -121,6 +131,7 @@ let newBsPackage ~rootPath =
121131
("Opens from ReScript config file: "
122132
^ (opens |> List.map pathToString |> String.concat " "));
123133
{
134+
genericJsxModule;
124135
suffix;
125136
rescriptVersion;
126137
rootPath;

analysis/src/SharedTypes.ml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,7 @@ type builtInCompletionModules = {
497497
}
498498

499499
type package = {
500+
genericJsxModule: string option;
500501
suffix: string;
501502
rootPath: filePath;
502503
projectFiles: FileSet.t;
@@ -718,7 +719,8 @@ module Completable = struct
718719
| Cpath cp -> "Cpath " ^ contextPathToString cp
719720
| Cdecorator s -> "Cdecorator(" ^ str s ^ ")"
720721
| CdecoratorPayload (Module s) -> "CdecoratorPayload(module=" ^ s ^ ")"
721-
| CdecoratorPayload (ModuleWithImportAttributes _) -> "CdecoratorPayload(moduleWithImportAttributes)"
722+
| CdecoratorPayload (ModuleWithImportAttributes _) ->
723+
"CdecoratorPayload(moduleWithImportAttributes)"
722724
| CdecoratorPayload (JsxConfig _) -> "JsxConfig"
723725
| CnamedArg (cp, s, sl2) ->
724726
"CnamedArg("

analysis/src/TypeUtils.ml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,3 +1079,8 @@ let removeOpensFromCompletionPath ~rawOpens ~package completionPath =
10791079
|> removeRawOpens rawOpens
10801080
in
10811081
completionPathMinusOpens
1082+
1083+
let pathToElementProps package =
1084+
match package.genericJsxModule with
1085+
| None -> ["ReactDOM"; "domProps"]
1086+
| Some g -> (g |> String.split_on_char '.') @ ["Elements"; "props"]

analysis/src/Utils.ml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,10 @@ let rec unwrapIfOption (t : Types.type_expr) =
156156
| Tconstr (Path.Pident {name = "option"}, [unwrappedType], _) -> unwrappedType
157157
| _ -> t
158158

159-
let isReactComponent (vb : Parsetree.value_binding) =
159+
let isJsxComponent (vb : Parsetree.value_binding) =
160160
vb.pvb_attributes
161161
|> List.exists (function
162-
| {Location.txt = "react.component"}, _payload -> true
162+
| {Location.txt = "react.component" | "jsx.component"}, _payload -> true
163163
| _ -> false)
164164

165165
let checkName name ~prefix ~exact =

0 commit comments

Comments
 (0)