diff --git a/changes/hoist-nested-jsx-components.feature.md b/changes/hoist-nested-jsx-components.feature.md new file mode 100644 index 00000000000..362e6105601 --- /dev/null +++ b/changes/hoist-nested-jsx-components.feature.md @@ -0,0 +1 @@ +- Expose nested JSX component bindings through flat JavaScript exports while preserving normal module access. https://github.com/rescript-lang/rescript/pull/8886 diff --git a/compiler/core/lam_compile_main.ml b/compiler/core/lam_compile_main.ml index cada1a101d8..16e5b39e86c 100644 --- a/compiler/core/lam_compile_main.ml +++ b/compiler/core/lam_compile_main.ml @@ -187,10 +187,11 @@ let js_hoisted_aliases (export_ids : Ident.t list) fst (Ext_list.fold_left hoisted ([], occupied_names) (fun ((aliases, occupied_names) as state) hoisted -> - let {Lambda.binding; path; loc} = hoisted in + let {Lambda.binding; path; loc; explicit} = hoisted in let missing_path () = - Location.prerr_warning loc - (Warnings.Misplaced_attribute "res.hoistedFunction"); + if explicit then + Location.prerr_warning loc + (Warnings.Misplaced_attribute "res.hoistedFunction"); state in match path with @@ -208,7 +209,9 @@ let js_hoisted_aliases (export_ids : Ident.t list) |> String.concat "$" in let js_name = Ext_ident.convert name in - if Set_string.mem js_name occupied_names then + if Set_string.mem js_name occupied_names && not explicit then + state + else if Set_string.mem js_name occupied_names then let error_loc = match target with | Lambda.Lfunction {loc} -> loc diff --git a/compiler/frontend/ast_external.ml b/compiler/frontend/ast_external.ml index e2c121866ad..2971bf1d732 100644 --- a/compiler/frontend/ast_external.ml +++ b/compiler/frontend/ast_external.ml @@ -22,6 +22,10 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) +let is_hoisted_component attrs = + Option.is_some + (Translattribute.get_empty_attribute "res.hoistedComponent" attrs) + let handle_external_in_sig (self : Ast_mapper.mapper) (prim : Parsetree.value_description) (sigi : Parsetree.signature_item) : Parsetree.signature_item = @@ -67,15 +71,44 @@ let handle_external_in_stru (self : Ast_mapper.mapper) pval_attributes v with | {pval_type; pval_prim; pval_attributes; no_inline_cross_module} -> + let hoisted_component = + no_inline_cross_module && is_hoisted_component pval_attributes + in let external_result = { str with pstr_desc = Pstr_primitive - {prim with pval_type; pval_prim = Some pval_prim; pval_attributes}; + { + prim with + pval_type; + pval_prim = Some pval_prim; + pval_attributes = + (if hoisted_component then [] else pval_attributes); + }; } in - if not no_inline_cross_module then external_result + if hoisted_component then + (* Relative FFI already exposes an ordinary value across module + boundaries. Give that value a binding the hoist registry can track. + Other FFI must keep its primitive type for existing interfaces. *) + let open Ast_helper in + let module_name = Location.mkloc "JsxExternal" loc in + Str.value ~loc Nonrecursive + [ + Vb.mk ~loc ~attrs:pval_attributes + (Pat.var ~loc prim.pval_name) + (Exp.letmodule ~loc + ~attrs:[(Location.mkloc "res.hoistedComponent" loc, PStr [])] + module_name + (Mod.structure ~loc [external_result]) + (Exp.ident ~loc + { + txt = Ldot (Lident module_name.txt, prim.pval_name.txt); + loc; + })); + ] + else if not no_inline_cross_module then external_result else let open Ast_helper in Str.include_ ~loc diff --git a/compiler/gentype/translate_structure.ml b/compiler/gentype/translate_structure.ml index 97470feacbd..4a61f47aec1 100644 --- a/compiler/gentype/translate_structure.ml +++ b/compiler/gentype/translate_structure.ml @@ -79,8 +79,28 @@ let remove_value_binding_duplicates structure_items = let translate_value_binding ~config ~output_file_relative ~resolver ~type_env {Typedtree.vb_attributes; vb_expr; vb_pat} : Translation.t = - match vb_pat.pat_desc with - | Tpat_var (id, _) | Tpat_alias ({pat_desc = Tpat_any}, id, _) -> + match (vb_pat.pat_desc, vb_expr.exp_desc) with + | ( _, + Texp_letmodule + ( _, + _, + { + mod_desc = + Tmod_structure + {str_items = [{str_desc = Tstr_primitive primitive}]}; + }, + _ ) ) + when Annotation.has_attribute + (String.equal "res.hoistedComponent") + vb_attributes + && Annotation.has_attribute Annotation.tag_is_gentype_import + vb_attributes -> + (* FFI processing materializes nested external components as values. + Keep genType imports attached to the original external declaration. *) + {primitive with val_attributes = vb_attributes} + |> Translation.translate_primitive ~config ~output_file_relative ~resolver + ~type_env + | (Tpat_var (id, _) | Tpat_alias ({pat_desc = Tpat_any}, id, _)), _ -> let name = id |> Ident.name |> Ext_ident.unwrap_uppercase_exotic in if !(Debug.translation ()) then Log_.item "Translate Value Binding %s\n" name; diff --git a/compiler/ml/lambda.ml b/compiler/ml/lambda.ml index 57760bdf61f..38a26c89c9e 100644 --- a/compiler/ml/lambda.ml +++ b/compiler/ml/lambda.ml @@ -13,7 +13,12 @@ (* *) (**************************************************************************) -type hoisted_function = {binding: Ident.t; path: string list; loc: Location.t} +type hoisted_function = { + binding: Ident.t; + path: string list; + loc: Location.t; + explicit: bool; +} type tag_info = | Blk_constructor of { diff --git a/compiler/ml/lambda.mli b/compiler/ml/lambda.mli index 571568407e8..e90ff99de76 100644 --- a/compiler/ml/lambda.mli +++ b/compiler/ml/lambda.mli @@ -17,7 +17,14 @@ open Asttypes -type hoisted_function = {binding: Ident.t; path: string list; loc: Location.t} +type hoisted_function = { + binding: Ident.t; + path: string list; + loc: Location.t; + explicit: bool; + (** User annotations must be diagnosed when no export can be generated. + Automatic component hoists may fall back to normal module access. *) +} type tag_info = | Blk_constructor of { diff --git a/compiler/ml/translcore.ml b/compiler/ml/translcore.ml index 80618a82a77..a1d1e4cae26 100644 --- a/compiler/ml/translcore.ml +++ b/compiler/ml/translcore.ml @@ -958,24 +958,42 @@ let find_js_hoisted_attr attrs = emission. Record the binding and its source path here so later compiler stages can add the flat JS export and matching .cmj metadata. *) let mark_js_hoisted_pattern ~js_hoist attrs pat lam = + let register ~explicit loc = + let misplaced () = + if explicit then + Location.prerr_warning loc + (Warnings.Misplaced_attribute hoisted_function_attr_name) + in + match pat.pat_desc with + | Tpat_var (id, _) | Tpat_alias ({pat_desc = Tpat_any}, id, _) -> ( + match js_hoist with + | Some register -> + (* JSX names its function in a local block. Its outer [make] alias + is optimized away, so retain the returned binding's identity. *) + let rec returned_binding fallback = function + | Lvar id -> id + | Llet (_, id, value, body) -> + let result = returned_binding fallback body in + if Ident.same result id then returned_binding id value else result + | Lsequence (_, body) -> returned_binding fallback body + | _ -> fallback + in + let binding = if explicit then id else returned_binding id lam in + register ~explicit ~name:id.Ident.name binding loc + | None -> misplaced ()) + | _ -> misplaced () + in match find_js_hoisted_attr attrs with - | None -> () | Some loc -> ( match lam with - | Lfunction _ -> ( - match pat.pat_desc with - | Tpat_var (id, _) | Tpat_alias ({pat_desc = Tpat_any}, id, _) -> ( - match js_hoist with - | Some register -> register id loc - | None -> - Location.prerr_warning loc - (Warnings.Misplaced_attribute hoisted_function_attr_name)) - | _ -> - Location.prerr_warning loc - (Warnings.Misplaced_attribute hoisted_function_attr_name)) + | Lfunction _ -> register ~explicit:true loc | _ -> Location.prerr_warning loc (Warnings.Misplaced_attribute hoisted_function_attr_name)) + | None -> ( + match Translattribute.get_empty_attribute "res.hoistedComponent" attrs with + | Some loc -> register ~explicit:false loc + | None -> ()) (* JSX preserve mode: a call of a JSX external as the JSX PPX produces it, which [transl_jsx_element] turns into [Pjsx]. The JSX module's names @@ -1634,14 +1652,23 @@ and transl_let ~js_hoist rec_flag pat_expr_list body = | Nonrecursive -> let rec transl = function | [] -> body - | {vb_pat = pat; vb_expr = expr; vb_attributes = attr; vb_loc} :: rem -> + | {vb_pat = pat; vb_expr = expr; vb_attributes = attr; vb_loc} :: rem -> ( let lam = Builtin_attributes.warning_scope ~ppwarning:false attr (fun () -> transl_exp expr) in let lam = Translattribute.add_inline_attribute lam vb_loc attr in mark_js_hoisted_pattern ~js_hoist attr pat lam; - Matching.for_let pat.pat_loc lam pat (transl rem) + let body = transl rem in + match pat.pat_desc with + | Tpat_var (id, _) + when Option.is_some + (Translattribute.get_empty_attribute "res.hoistedComponent" + expr.exp_attributes) -> + (* Materialized externals must not introduce an import when unused, + just as their original declarations did not. *) + Lambda.let_ StrictOpt id lam body + | _ -> Matching.for_let pat.pat_loc lam pat body) in transl pat_expr_list | Recursive -> diff --git a/compiler/ml/translcore.mli b/compiler/ml/translcore.mli index 000953852fb..bd3652df952 100644 --- a/compiler/ml/translcore.mli +++ b/compiler/ml/translcore.mli @@ -19,7 +19,8 @@ val transl_exp : Typedtree.expression -> Lambda.t val transl_let : - js_hoist:(Ident.t -> Location.t -> unit) option -> + js_hoist: + (explicit:bool -> name:string -> Ident.t -> Location.t -> unit) option -> Asttypes.rec_flag -> Typedtree.value_binding list -> Lambda.t -> diff --git a/compiler/ml/translmod.ml b/compiler/ml/translmod.ml index 9bf1b0e0fa7..e9a3f55def9 100644 --- a/compiler/ml/translmod.ml +++ b/compiler/ml/translmod.ml @@ -241,9 +241,9 @@ let js_hoist_handler rootpath = | None -> None | Some path -> Some - (fun id loc -> + (fun ~explicit ~name id loc -> js_hoisted () := - {Lambda.binding = id; path = path @ [id.Ident.name]; loc} + {Lambda.binding = id; path = path @ [name]; loc; explicit} :: !(js_hoisted ())) let rec compile_functor mexp coercion root_path loc = diff --git a/compiler/syntax/JSX.md b/compiler/syntax/JSX.md index e929282d7a0..d94e15be432 100644 --- a/compiler/syntax/JSX.md +++ b/compiler/syntax/JSX.md @@ -32,6 +32,24 @@ interface expose compatible component types. External components and the legacy `componentWithProps` form have separate validation paths; tests for these forms live under `tests/build_tests/react_ppx/`. +Nested component value bindings are marked for automatic JS hoisting after +their wrappers are applied. Translation registers the final value with the +same backend machinery as `@res.hoistedFunction`. The backend emits a flat +alias such as `Provider$make` only when the source path still identifies that +binding in the exported module structure. Hidden paths and naming collisions +fall back to normal module access without a misplaced-attribute warning. +The module object and its `make` member remain available. Consumers use the +existing hoisted-export lookup in `.cmj` metadata; no JSX call-site marker or +additional ReScript signature declaration is needed. + +Nested external components are marked in the same way. Relative FFI already +exposes an ordinary value across module boundaries; FFI processing gives that +value a binding the hoist registry can track. Both component-annotated and +manually written external interfaces retain their existing types. Package +and global FFI remain primitives and keep their existing direct access to +the foreign component. The marker on the generated expression selects an +optional Lambda binding, so unused declarations introduce no import. + ## JSX expressions The parser represents JSX explicitly in the parsetree. The transformation diff --git a/compiler/syntax/src/jsx_v4.ml b/compiler/syntax/src/jsx_v4.ml index c36d55c754e..83111730562 100644 --- a/compiler/syntax/src/jsx_v4.ml +++ b/compiler/syntax/src/jsx_v4.ml @@ -55,6 +55,19 @@ let jsx_component_expr config ~loc expr = (Exp.ident ~loc {loc; txt = module_access_name config "component"}) [(Nolabel, expr)] +(* The backend adds the JS export after checking the actual exported module + path. Mark the final component value, including memo/forwardRef wrappers. *) +let hoisted_component_attributes ~(config : Jsx_common.jsx_config) attrs = + match config.nested_modules with + | [] -> attrs + | _ :: _ -> (Location.mknoloc "res.hoistedComponent", PStr []) :: attrs + +let mark_hoisted_component ~config binding = + { + binding with + pvb_attributes = hoisted_component_attributes ~config binding.pvb_attributes; + } + (* Finds the name of the variable the binding is assigned to, otherwise raises Invalid_argument *) let rec get_fn_name binding = match binding with @@ -808,7 +821,9 @@ let map_binding ~config ~empty_loc ~pstr_loc ~file_name binding = pvb_pat = Pat.var {txt = fn_name; loc = Location.none}; } in - let new_binding = Some (binding_wrapper full_expression) in + let new_binding = + Some (mark_hoisted_component ~config (binding_wrapper full_expression)) + in (Some props_record_type, binding, new_binding) | Some {pjc_kind = Pjc_with_props} -> let modified_binding = {binding with pvb_jsx_component = None} in @@ -898,7 +913,9 @@ let map_binding ~config ~empty_loc ~pstr_loc ~file_name binding = in let new_binding = - Some (make_new_binding ~loc:empty_loc ~full_module_name modified_binding) + Some + (make_new_binding ~loc:empty_loc ~full_module_name modified_binding + |> mark_hoisted_component ~config) in let binding_expr = { @@ -1001,6 +1018,8 @@ let transform_structure_item ~config item = value_description with pval_type = {pval_type with ptyp_desc = new_external_type}; pval_jsx_component = None; + pval_attributes = + hoisted_component_attributes ~config pval_attributes; }; } in diff --git a/tests/analysis_tests/tests/src/JsxV4.res b/tests/analysis_tests/tests/src/JsxV4.res index a48c69f7de2..eed538f6a55 100644 --- a/tests/analysis_tests/tests/src/JsxV4.res +++ b/tests/analysis_tests/tests/src/JsxV4.res @@ -25,4 +25,14 @@ module Other = { let make = (~name) => React.string(name) } +module External = { + @react.component @module("./client.js") + external make: (~children: React.element) => React.element = "default" +} + +ignore({React.string("child")}) +// ^hov +ignore({React.string("child")}) +// ^def + // ^int diff --git a/tests/analysis_tests/tests/src/expected/JsxV4.res.txt b/tests/analysis_tests/tests/src/expected/JsxV4.res.txt index 4ca5ee15219..e3472c33cbf 100644 --- a/tests/analysis_tests/tests/src/expected/JsxV4.res.txt +++ b/tests/analysis_tests/tests/src/expected/JsxV4.res.txt @@ -24,6 +24,23 @@ Hover src/JsxV4.res 14:9 } } +Hover src/JsxV4.res 32:8 +{ + "contents": { + "kind": "markdown", + "value": "```rescript\nReact.component>\n```\n\n---\n\n```\n \n```\n```rescript\ntype React.component<'props> = Jsx.component<'props>\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22React.res%22%2C13%2C0%5D)\n\n\n---\n\n```\n \n```\n```rescript\ntype External.props<'children> = {children: 'children}\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22JsxV4.res%22%2C28%2C2%5D)\n\n\n---\n\n```\n \n```\n```rescript\ntype React.element = Jsx.element\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22React.res%22%2C0%2C0%5D)\n" + } +} + +Definition src/JsxV4.res 34:8 +{ + "range": { + "end": { "character": 15, "line": 29 }, + "start": { "character": 11, "line": 29 } + }, + "uri": "file:///JsxV4.res" +} + Create Interface src/JsxV4.res module M4: { @react.component @@ -37,4 +54,8 @@ module Other: { @react.component let make: (~name: string) => React.element } +module External: { + @react.component + let make: (~children: React.element) => React.element +} diff --git a/tests/build_tests/jsx_hoisted_components/input.js b/tests/build_tests/jsx_hoisted_components/input.js new file mode 100644 index 00000000000..24e453d5967 --- /dev/null +++ b/tests/build_tests/jsx_hoisted_components/input.js @@ -0,0 +1,227 @@ +// @ts-check + +import assert from "node:assert/strict"; +import { readFile, writeFile } from "node:fs/promises"; +import { inspect } from "node:util"; +import ts from "typescript"; +import { setup } from "#dev/process"; +import * as jsxRuntime from "./src/jsx-runtime.js"; + +const { execBuildOrThrow, execClean } = setup(import.meta.dirname); +const configUrl = new URL("rescript.json", import.meta.url); +const originalConfig = await readFile(configUrl, "utf8"); +/** @param {string} name */ +const source = name => new URL(`src/${name}.mjs`, import.meta.url); + +/** + * Model the RSC boundary: each actual client export becomes an opaque reference. + * The server can pass it to JSX, but cannot inspect it or call its implementation. + * @param {string} client + */ +function clientReferences(client) { + const ast = ts.createSourceFile( + "Sidebar.mjs", + client, + ts.ScriptTarget.ES2022, + ); + const first = ast.statements[0]; + assert(ts.isExpressionStatement(first)); + assert(ts.isStringLiteral(first.expression)); + assert.equal(first.expression.text, "use client"); + + return Object.fromEntries( + ast.statements.flatMap(statement => + ts.isExportDeclaration(statement) && + statement.exportClause && + ts.isNamedExports(statement.exportClause) + ? statement.exportClause.elements.map(({ name }) => [ + name.text, + new Proxy( + () => { + throw new Error(`Cannot call client export ${name.text}`); + }, + { + get(_target, property) { + throw new Error( + `Cannot inspect client export ${name.text}.${String(property)}`, + ); + }, + }, + ), + ]) + : [], + ), + ); +} + +/** + * @param {string} client + * @param {string} externalClient + * @param {string} server + */ +function checkClientBoundary(client, externalClient, server) { + const references = clientReferences(client); + const externalReferences = clientReferences(externalClient); + + // Only convert the module syntax so the real compiled server code can run + // against the boundary references, following jsx_preserve_semantics's harness. + const code = ts.transpileModule(server, { + compilerOptions: { + module: ts.ModuleKind.CommonJS, + target: ts.ScriptTarget.ES2022, + esModuleInterop: false, + }, + }).outputText; + /** @param {string} id */ + const require = id => { + if (id === "./Sidebar.mjs") return references; + if (id === "./External.mjs") return externalReferences; + if (id === "./React.mjs" || id === "./jsx-runtime.js") return jsxRuntime; + assert.fail(`Unexpected server import: ${id}`); + }; + const page = new Function("require", "exports", `${code}\nreturn exports;`)( + require, + {}, + ); + let element = page.make({}); + for (const name of [ + "Provider$make", + "Group$Provider$make", + "Provider$make", + "WithProps$make", + "Memo$make", + "ForwardRef$make", + "WithPrivate$make", + "Sidebar$make", + "External$make", + "ExternalManual$make", + ]) { + assert(name in references, `Missing client export: ${name}`); + assert.equal(element.type, references[name]); + element = element.props.children; + } + assert("Debug$make" in externalReferences); + assert.equal(element.type, externalReferences.Debug$make); + assert.equal(element.props.children, "child"); + assert.equal(jsxRuntime.effects, 0); +} + +try { + for (const namespace of [false, true]) { + for (const optimize of [false, true]) { + const config = JSON.parse(originalConfig); + config.namespace = namespace; + config["compiler-flags"].push( + optimize ? "-bs-cross-module-opt" : "-bs-no-cross-module-opt", + ); + await writeFile(configUrl, JSON.stringify(config, null, 2) + "\n"); + await execClean(); + const result = await execBuildOrThrow(); + assert.doesNotMatch(result.stderr, /hoistedFunction|hoistedComponent/); + + const producer = await readFile(source("Sidebar"), "utf8"); + const consumer = await readFile(source("Consumer"), "utf8"); + for (const name of [ + "Provider$make", + "Group$Provider$make", + "WithProps$make", + "Memo$make", + "ForwardRef$make", + "WithPrivate$make", + "Sidebar$make", + "External$make", + "ExternalManual$make", + ]) { + assert(producer.includes(` ${name},`), `Missing flat export: ${name}`); + } + assert.match( + consumer, + /\.jsx\(Sidebar(?:\$JsxHoistedComponents)?\.Provider\$make,/, + ); + assert.match( + consumer, + /\.jsx\(Sidebar(?:\$JsxHoistedComponents)?\.Group\$Provider\$make,/, + ); + assert.match( + consumer, + /\.jsx\(Sidebar(?:\$JsxHoistedComponents)?\.WithProps\$make,/, + ); + assert.doesNotMatch(consumer, /\.Provider\.make/); + assert.doesNotMatch(producer, /Hidden\$make/); + assert.doesNotMatch(producer, /HiddenExternal|unused-private-component/); + assert.doesNotMatch(producer, /Sidebar\$Provider\$make/); + assert.doesNotMatch(producer, /Sidebar\$Sidebar\$make/); + assert.doesNotMatch(producer, /External(?:Debug|Annotated)\$make/); + assert.match(consumer, /from "node:util"/); + assert.doesNotMatch(consumer, /\.External(?:Debug|Annotated)/); + const dynamic = await readFile(source("Dynamic"), "utf8"); + assert.match(dynamic, /await import\("\.\/Sidebar\.mjs"\)/); + assert.match( + dynamic, + /\.jsx\(Sidebar(?:\$JsxHoistedComponents)?\.Provider\$make,/, + ); + checkClientBoundary( + producer, + await readFile(source("External"), "utf8"), + await readFile(source("Page"), "utf8"), + ); + } + } + + const sidebar = await import(source("Sidebar").href); + const consumer = await import(source("Consumer").href); + const dynamic = await import(source("Dynamic").href); + const external = await import(source("External").href); + assert.equal(jsxRuntime.effects, 1); + assert.equal(sidebar.Provider.make, sidebar.Provider$make); + assert.equal(sidebar.Group.Provider.make, sidebar.Group$Provider$make); + assert.equal(sidebar.WithProps.make, sidebar.WithProps$make); + assert.equal(sidebar.Memo.make, sidebar.Memo$make); + assert.equal(sidebar.ForwardRef.make, sidebar.ForwardRef$make); + assert.equal(sidebar.WithPrivate.make, sidebar.WithPrivate$make); + assert.equal(sidebar.Sidebar.make, sidebar.Sidebar$make); + assert.equal(sidebar.External.make, sidebar.External$make); + assert.equal(sidebar.ExternalManual.make, sidebar.ExternalManual$make); + assert.equal(external.Debug.make, external.Debug$make); + assert.equal(external.Debug$make({ children: "debug" }), "debug"); + assert.equal(consumer.debugElement.type, inspect); + assert.equal(consumer.annotatedElement.type, inspect); + assert.equal(sidebar.External$make({ children: "external" }), "external"); + const privateElement = sidebar.WithPrivate$make({ children: "hidden" }); + assert.equal(privateElement.type(privateElement.props), "hidden"); + assert.equal(dynamic.provider, sidebar.Provider.make); + assert.equal(dynamic.element.type, sidebar.Provider.make); + assert.equal(consumer.provider, sidebar.Provider.make); + assert.equal(consumer.element.type, sidebar.Provider.make); + assert.equal(consumer.deepElement.type, sidebar.Group.Provider.make); + assert.equal(consumer.aliasElement.type, sidebar.Provider.make); + assert.equal(consumer.propsElement.type, sidebar.WithProps.make); + assert.equal(consumer.memoElement.type, sidebar.Memo.make); + assert.equal(consumer.refElement.type, sidebar.ForwardRef.make); + assert.equal(consumer.element.type(consumer.element.props), "child"); + assert.equal(consumer.deepElement.type(consumer.deepElement.props), "deep"); + assert.equal( + consumer.aliasElement.type(consumer.aliasElement.props), + "alias", + ); + assert.equal( + consumer.propsElement.type(consumer.propsElement.props), + "props", + ); + assert.equal( + sidebar.privateElement.type(sidebar.privateElement.props), + "private", + ); + assert.equal(consumer.helper, "helper"); + assert.equal( + consumer.memoElement.type.type(consumer.memoElement.props), + "memo", + ); + assert.equal( + consumer.refElement.type.render(consumer.refElement.props, null), + "ref", + ); +} finally { + await writeFile(configUrl, originalConfig); + await execClean(); +} diff --git a/tests/build_tests/jsx_hoisted_components/rescript.json b/tests/build_tests/jsx_hoisted_components/rescript.json new file mode 100644 index 00000000000..248c9383a39 --- /dev/null +++ b/tests/build_tests/jsx_hoisted_components/rescript.json @@ -0,0 +1,17 @@ +{ + "name": "jsx-hoisted-components", + "sources": ["src"], + "namespace": true, + "jsx": { + "version": 4 + }, + "package-specs": { + "module": "esmodule", + "in-source": true, + "suffix": ".mjs" + }, + "compiler-flags": ["-check-lam"], + "warnings": { + "error": "+32+101" + } +} diff --git a/tests/build_tests/jsx_hoisted_components/src/Consumer.res b/tests/build_tests/jsx_hoisted_components/src/Consumer.res new file mode 100644 index 00000000000..57ac9134f21 --- /dev/null +++ b/tests/build_tests/jsx_hoisted_components/src/Consumer.res @@ -0,0 +1,14 @@ +module SidebarAlias = Sidebar +module ProviderAlias = SidebarAlias.Provider + +let provider = Sidebar.Provider.make +let element = {React.string("child")} +let deepElement = {React.string("deep")} +let aliasElement = {React.string("alias")} +let propsElement = {React.string("props")} +let helper = Sidebar.Provider.helper() +let memoElement = {React.string("memo")} +let refElement = {React.string("ref")} +let debugElement = {React.string("debug")} +let annotatedElement = + {React.string("annotated")} diff --git a/tests/build_tests/jsx_hoisted_components/src/Dynamic.res b/tests/build_tests/jsx_hoisted_components/src/Dynamic.res new file mode 100644 index 00000000000..8332b6d72f9 --- /dev/null +++ b/tests/build_tests/jsx_hoisted_components/src/Dynamic.res @@ -0,0 +1,4 @@ +module DynamicSidebar = await Sidebar + +let provider = DynamicSidebar.Provider.make +let element = {React.string("dynamic")} diff --git a/tests/build_tests/jsx_hoisted_components/src/External.res b/tests/build_tests/jsx_hoisted_components/src/External.res new file mode 100644 index 00000000000..33b96d4b197 --- /dev/null +++ b/tests/build_tests/jsx_hoisted_components/src/External.res @@ -0,0 +1,7 @@ +@@directive("'use client';") + +module Debug = { + // Relative FFI is also hoisted when there is no .resi. + @react.component @module("./external-component.js") + external make: (~children: React.element) => React.element = "default" +} diff --git a/tests/build_tests/jsx_hoisted_components/src/Page.res b/tests/build_tests/jsx_hoisted_components/src/Page.res new file mode 100644 index 00000000000..d4445b29281 --- /dev/null +++ b/tests/build_tests/jsx_hoisted_components/src/Page.res @@ -0,0 +1,27 @@ +module SidebarAlias = Sidebar +module ProviderAlias = SidebarAlias.Provider + +@react.component +let make = () => { + + + + + + + + + + + {React.string("child")} + + + + + + + + + + +} diff --git a/tests/build_tests/jsx_hoisted_components/src/React.res b/tests/build_tests/jsx_hoisted_components/src/React.res new file mode 100644 index 00000000000..bf4bca71d72 --- /dev/null +++ b/tests/build_tests/jsx_hoisted_components/src/React.res @@ -0,0 +1,15 @@ +type element = Jsx.element +type component<'props> = Jsx.component<'props> +type componentLike<'props, 'return> = 'props => 'return + +external component: componentLike<'props, element> => component<'props> = "%component_identity" +external string: string => element = "%identity" + +@module("./jsx-runtime.js") +external jsx: (component<'props>, 'props) => element = "jsx" + +@module("./jsx-runtime.js") +external memo: component<'props> => component<'props> = "memo" + +@module("./jsx-runtime.js") +external forwardRef: (('props, nullable<'ref>) => element) => component<'props> = "forwardRef" diff --git a/tests/build_tests/jsx_hoisted_components/src/Sidebar.res b/tests/build_tests/jsx_hoisted_components/src/Sidebar.res new file mode 100644 index 00000000000..feecaf54c35 --- /dev/null +++ b/tests/build_tests/jsx_hoisted_components/src/Sidebar.res @@ -0,0 +1,77 @@ +@@directive("'use client';") + +module Provider = { + @react.component + let make = (~children) => children + + let helper = () => "helper" +} + +module Group = { + module Provider = { + @react.component + let make = (~children) => children + } +} + +module WithProps = { + type props = {children: React.element} + + @react.componentWithProps + let make = props => props.children +} + +module Hidden = { + @react.component + let make = (~children=React.string("private")) => children +} + +module WithPrivate = { + @react.component + let make = (~children) => {children} +} + +module Sidebar = { + @react.component + let make = (~children) => children +} + +module External = { + @react.component @module("./external-component.js") + external make: (~children: React.element) => React.element = "default" +} + +module ExternalDebug = { + @react.component @module("node:util") + external make: (~children: React.element) => React.element = "inspect" +} + +module ExternalManual = { + @react.component @module("./external-component.js") + external make: (~children: React.element) => React.element = "default" +} + +module ExternalAnnotated = { + @react.component @module("node:util") + external make: (~children: React.element) => React.element = "inspect" +} + +module HiddenExternal = { + @react.component @module("./unused-private-component.js") + external make: (~children: React.element) => React.element = "default" + + @module("./jsx-runtime.js") external effect: unit => unit = "effect" + effect() +} + +module Memo = { + @react.component + let make = React.memo((~children) => children) +} + +module ForwardRef = { + @react.component + let make = React.forwardRef((~children, _ref) => children) +} + +let privateElement = diff --git a/tests/build_tests/jsx_hoisted_components/src/Sidebar.resi b/tests/build_tests/jsx_hoisted_components/src/Sidebar.resi new file mode 100644 index 00000000000..41b859beba3 --- /dev/null +++ b/tests/build_tests/jsx_hoisted_components/src/Sidebar.resi @@ -0,0 +1,62 @@ +module Provider: { + @react.component + let make: (~children: React.element) => React.element + let helper: unit => string +} + +module Group: { + module Provider: { + @react.component + let make: (~children: React.element) => React.element + } +} + +module WithProps: { + type props = {children: React.element} + let make: React.component +} + +let privateElement: React.element + +module Memo: { + @react.component + let make: (~children: React.element) => React.element +} + +module ForwardRef: { + @react.component + let make: (~children: React.element) => React.element +} + +module WithPrivate: { + @react.component + let make: (~children: React.element) => React.element +} + +module Sidebar: { + @react.component + let make: (~children: React.element) => React.element +} + +module External: { + @react.component + let make: (~children: React.element) => React.element +} + +module ExternalDebug: { + // An existing, manually written primitive interface must still match. + type props<'children> = {children: 'children} + @module("node:util") + external make: React.component> = "inspect" +} + +module ExternalManual: { + type props<'children> = {children: 'children} + @module("./external-component.js") + external make: React.component> = "default" +} + +module ExternalAnnotated: { + @react.component @module("node:util") + external make: (~children: React.element) => React.element = "inspect" +} diff --git a/tests/build_tests/jsx_hoisted_components/src/external-component.js b/tests/build_tests/jsx_hoisted_components/src/external-component.js new file mode 100644 index 00000000000..ed0849ca549 --- /dev/null +++ b/tests/build_tests/jsx_hoisted_components/src/external-component.js @@ -0,0 +1,3 @@ +export default function externalComponent(props) { + return props.children; +} diff --git a/tests/build_tests/jsx_hoisted_components/src/jsx-runtime.js b/tests/build_tests/jsx_hoisted_components/src/jsx-runtime.js new file mode 100644 index 00000000000..359ba710151 --- /dev/null +++ b/tests/build_tests/jsx_hoisted_components/src/jsx-runtime.js @@ -0,0 +1,17 @@ +export function jsx(type, props) { + return { type, props }; +} + +export function memo(type) { + return { type }; +} + +export function forwardRef(render) { + return { render }; +} + +export let effects = 0; + +export function effect() { + effects++; +} diff --git a/tests/build_tests/react_ppx/src/abstract_component_test.res.js b/tests/build_tests/react_ppx/src/abstract_component_test.res.js index 8863c77600f..89ea09a9edb 100644 --- a/tests/build_tests/react_ppx/src/abstract_component_test.res.js +++ b/tests/build_tests/react_ppx/src/abstract_component_test.res.js @@ -24,8 +24,14 @@ let GenericRenderProp = { make: Abstract_component_test$GenericRenderProp }; +let GenericRenderProp$make = GenericRenderProp.make; + +let PolyVariantLowerBound$make = PolyVariantLowerBound.make; + export { PolyVariantLowerBound, GenericRenderProp, + GenericRenderProp$make, + PolyVariantLowerBound$make, } /* No side effect */ diff --git a/tests/build_tests/react_ppx/src/gpr_3987_test.res.js b/tests/build_tests/react_ppx/src/gpr_3987_test.res.js index 30bedfb06b7..4c8a6382af7 100644 --- a/tests/build_tests/react_ppx/src/gpr_3987_test.res.js +++ b/tests/build_tests/react_ppx/src/gpr_3987_test.res.js @@ -55,10 +55,19 @@ JsxRuntime.jsx(Gpr_3987_test$Gpr3987ReproError, { onChange: (param, param$1) => {} }); +let Gpr3987ReproError$make = Gpr3987ReproError.make; + +let Gpr3987ReproOk2$make = Gpr3987ReproOk2.make; + +let Gpr3987ReproOk$make = Gpr3987ReproOk.make; + export { makeContainer, Gpr3987ReproOk, Gpr3987ReproOk2, Gpr3987ReproError, + Gpr3987ReproError$make, + Gpr3987ReproOk2$make, + Gpr3987ReproOk$make, } /* Not a pure module */ diff --git a/tests/build_tests/react_ppx/src/issue_7917_test.res.js b/tests/build_tests/react_ppx/src/issue_7917_test.res.js index 9df85100b95..89a3f77a531 100644 --- a/tests/build_tests/react_ppx/src/issue_7917_test.res.js +++ b/tests/build_tests/react_ppx/src/issue_7917_test.res.js @@ -18,8 +18,11 @@ function Issue_7917_test(props) { let make = Issue_7917_test; +let M$make = M.make; + export { M, make, + M$make, } /* react/jsx-runtime Not a pure module */ diff --git a/tests/build_tests/react_ppx/src/recursive_component_test.res.js b/tests/build_tests/react_ppx/src/recursive_component_test.res.js index 8c8e26e1254..bf10018c90c 100644 --- a/tests/build_tests/react_ppx/src/recursive_component_test.res.js +++ b/tests/build_tests/react_ppx/src/recursive_component_test.res.js @@ -20,7 +20,10 @@ let Rec = { make: Recursive_component_test$Rec }; +let Rec$make = Recursive_component_test$Rec; + export { Rec, + Rec$make, } /* No side effect */ diff --git a/tests/build_tests/react_ppx/src/recursive_explicit_component_test.res.js b/tests/build_tests/react_ppx/src/recursive_explicit_component_test.res.js index 9577ffd28ab..1a8cd30c368 100644 --- a/tests/build_tests/react_ppx/src/recursive_explicit_component_test.res.js +++ b/tests/build_tests/react_ppx/src/recursive_explicit_component_test.res.js @@ -49,10 +49,19 @@ let componentWithPropsElement = React.createElement(Recursive_explicit_component foo: 1 }); +let ComponentWithProps$make = ComponentWithProps.make; + +let RawSiblingCreateElement$make = Recursive_explicit_component_test$RawSiblingCreateElement; + +let SelfCreateElement$make = SelfCreateElement.make; + export { SelfCreateElement, RawSiblingCreateElement, ComponentWithProps, componentWithPropsElement, + ComponentWithProps$make, + RawSiblingCreateElement$make, + SelfCreateElement$make, } /* componentWithPropsElement Not a pure module */ diff --git a/tests/gentype_tests/typescript-react-example/src/Hooks.res.js b/tests/gentype_tests/typescript-react-example/src/Hooks.res.js index 8669c8eafed..f1eac61e96e 100644 --- a/tests/gentype_tests/typescript-react-example/src/Hooks.res.js +++ b/tests/gentype_tests/typescript-react-example/src/Hooks.res.js @@ -204,6 +204,32 @@ let make$1 = Hooks; let $$default = Hooks; +let DD$make = DD.make; + +let WithChildren$aComponentWithChildren = WithChildren.aComponentWithChildren; + +let RenderPropRequiresConversion$make = RenderPropRequiresConversion.make; + +let Fun$functionReturningReactElement = Fun.functionReturningReactElement; + +let Poly$polymorphicComponent = Poly.polymorphicComponent; + +let WithRef$make = WithRef.make; + +let WithRename$componentWithRenamedArgs = WithRename.componentWithRenamedArgs; + +let NoProps$make = NoProps.make; + +let Inner$Inner2$Another$anotherComponent = Inner2.Another.anotherComponent; + +let Inner$Inner2$make = Inner2.make; + +let Inner$Another$anotherComponent = Another$1.anotherComponent; + +let Inner$make = Hooks$Inner; + +let Another$anotherComponent = Another.anotherComponent; + export { make$1 as make, $$default as default, @@ -219,5 +245,18 @@ export { RenderPropRequiresConversion, WithChildren, DD, + DD$make, + WithChildren$aComponentWithChildren, + RenderPropRequiresConversion$make, + Fun$functionReturningReactElement, + Poly$polymorphicComponent, + WithRef$make, + WithRename$componentWithRenamedArgs, + NoProps$make, + Inner$Inner2$Another$anotherComponent, + Inner$Inner2$make, + Inner$Another$anotherComponent, + Inner$make, + Another$anotherComponent, } /* make Not a pure module */ diff --git a/tests/gentype_tests/typescript-react-example/src/ImportHookDefault.res.js b/tests/gentype_tests/typescript-react-example/src/ImportHookDefault.res.js index d3b91414d77..359f5a5b78a 100644 --- a/tests/gentype_tests/typescript-react-example/src/ImportHookDefault.res.js +++ b/tests/gentype_tests/typescript-react-example/src/ImportHookDefault.res.js @@ -11,8 +11,11 @@ let MM = { make2: make2 }; +let MM$make2 = MM.make2; + export { make, MM, + MM$make2, } /* make Not a pure module */ diff --git a/tests/gentype_tests/typescript-react-example/src/JSXV4.res.js b/tests/gentype_tests/typescript-react-example/src/JSXV4.res.js index 016dda6790c..a28942b199e 100644 --- a/tests/gentype_tests/typescript-react-example/src/JSXV4.res.js +++ b/tests/gentype_tests/typescript-react-example/src/JSXV4.res.js @@ -12,8 +12,11 @@ let CompV4 = { let make = JSXV4Gen.make; +let CompV4$make = CompV4.make; + export { CompV4, make, + CompV4$make, } /* make Not a pure module */ diff --git a/tests/ounit_tests/ounit_jsx_hoisting_tests.ml b/tests/ounit_tests/ounit_jsx_hoisting_tests.ml new file mode 100644 index 00000000000..9e65cfcb7b9 --- /dev/null +++ b/tests/ounit_tests/ounit_jsx_hoisting_tests.ml @@ -0,0 +1,66 @@ +open OUnit + +let loc = Location.none + +let compile ~path ~shadowed ~collision = + Request_local.with_fresh (fun () -> + (Clflags.current ()).dont_write_files := true; + let component = Ident.create "Component" in + let replacement = Ident.create "Replacement" in + let provider = Ident.create "Provider" in + let colliding = Ident.create "Provider$make" in + let exports = if collision then [provider; colliding] else [provider] in + let component_value value = + Lambda.function_ ~loc ~attr:Lambda.default_function_attribute + ~params:[Ident.create "props"] + ~body:(Lambda.const (Lambda.const_string value)) + in + let module_value = + Lambda.prim ~primitive:(Pmakeblock (Blk_module ["make"])) + ~args:[Lambda.var (if shadowed then replacement else component)] + loc + in + let body = + Lambda.let_ Lambda.Strict component + (component_value "component") + (Lambda.let_ Strict replacement + (component_value "replacement") + (Lambda.let_ Strict provider module_value + (Lambda.let_ Strict colliding Lambda.lambda_unit + (Lambda.prim + ~primitive:(Pmakeblock (Blk_module_export exports)) + ~args:(List.map Lambda.var exports) + loc)))) + in + let result = + Lam_compile_main.compile "" exports + [{Lambda.binding = component; path; loc; explicit = false}] + body + in + List.map Ident.name result.program.exports) + +let suites = + __FILE__ + >::: [ + ( "export a component through the existing hoisting backend" + >:: fun _ -> + assert_equal + ["Provider"; "Provider$make"] + (compile ~path:["Provider"; "make"] ~shadowed:false + ~collision:false) ); + ( "skip automatic hoists hidden by a signature" >:: fun _ -> + assert_equal ["Provider"] + (compile ~path:["Hidden"; "make"] ~shadowed:false ~collision:false) + ); + ( "do not hoist a replacement binding at the same source path" + >:: fun _ -> + assert_equal ["Provider"] + (compile ~path:["Provider"; "make"] ~shadowed:true ~collision:false) + ); + ( "preserve existing code when an automatic export name collides" + >:: fun _ -> + assert_equal + ["Provider"; "Provider$make"] + (compile ~path:["Provider"; "make"] ~shadowed:false ~collision:true) + ); + ] diff --git a/tests/ounit_tests/ounit_tests_main.ml b/tests/ounit_tests/ounit_tests_main.ml index 9db0f4674aa..cc666381e48 100644 --- a/tests/ounit_tests/ounit_tests_main.ml +++ b/tests/ounit_tests/ounit_tests_main.ml @@ -27,6 +27,7 @@ let suites = Ounit_external_shadow_tests.suites; Ounit_flow_parser_tests.suites; Ounit_jsx_loc_tests.suites; + Ounit_jsx_hoisting_tests.suites; Ounit_parser_cursor_tests.suites; Ounit_parser_recovery_tests.suites; Ounit_analysis_config_tests.suites; diff --git a/tests/syntax_tests/data/ast-mapping/expected/JsxComponents.res.txt b/tests/syntax_tests/data/ast-mapping/expected/JsxComponents.res.txt index b463e41a075..aa5df87a620 100644 --- a/tests/syntax_tests/data/ast-mapping/expected/JsxComponents.res.txt +++ b/tests/syntax_tests/data/ast-mapping/expected/JsxComponents.res.txt @@ -4,6 +4,7 @@ module A = { x: 'x, } let make = ({x, _}: props<_>): React.element => React.string(x) + @res.hoistedComponent let make = React.component({ let \"JsxComponents$A" = (props: props<_>) => make(props) \"JsxComponents$A" @@ -16,7 +17,7 @@ module B = { /** Doc */ @deprecated @genType let make = ({x, _}: props): React.element => React.string(x) /** Doc */ - @deprecated @genType + @res.hoistedComponent @deprecated @genType let make = React.component({ let \"JsxComponents$B" = props => make(props) @@ -26,6 +27,7 @@ module B = { module C = { let make = (props: props): React.element => React.string(props.x) + @res.hoistedComponent let make = React.component({ let \"JsxComponents$C" = (props: props): React.element => make(props) \"JsxComponents$C" @@ -34,6 +36,7 @@ module C = { module D = { let make = (props: props): React.element => React.string(props.x) + @res.hoistedComponent let make = React.component({ let \"JsxComponents$D" = (props: props): React.element => make(props) \"JsxComponents$D" @@ -46,7 +49,7 @@ module E = { x: 'x, } - @module("x") + @res.hoistedComponent @module("x") external make: React.component> = "default" } @@ -66,6 +69,7 @@ module F: { } let make = ({x, _}: props<_>): React.element => React.string(x) + @res.hoistedComponent let make = React.component({ let \"JsxComponents$F" = (props: props<_>) => make(props) diff --git a/tests/syntax_tests/data/ppx/react/expected/aliasProps.res.txt b/tests/syntax_tests/data/ppx/react/expected/aliasProps.res.txt index cf1e0bc6ad1..82d7bbddea3 100644 --- a/tests/syntax_tests/data/ppx/react/expected/aliasProps.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/aliasProps.res.txt @@ -15,6 +15,7 @@ module C0 = { let text = __text_value (React.string(text): React.element) } + @res.hoistedComponent let make = React.component({ let \"AliasProps$C0" = (props: props<_>) => make(props) @@ -37,6 +38,7 @@ module C1 = { let text = __text_value (React.string(p ++ text): React.element) } + @res.hoistedComponent let make = React.component({ let \"AliasProps$C1" = (props: props<_>) => make(props) @@ -58,6 +60,7 @@ module C2 = { let bar = __foo_value (React.string(bar): React.element) } + @res.hoistedComponent let make = React.component({ let \"AliasProps$C2" = (props: props<_>) => make(props) @@ -90,6 +93,7 @@ module C3 = { }: React.element ) } + @res.hoistedComponent let make = React.component({ let \"AliasProps$C3" = (props: props<_>) => make(props) @@ -112,6 +116,7 @@ module C4 = { let x = __x_value (ReactDOM.jsx("div", {children: ?ReactDOM.someElement(b)}): React.element) } + @res.hoistedComponent let make = React.component({ let \"AliasProps$C4" = (props: props<_>) => make(props) @@ -134,6 +139,7 @@ module C5 = { let z = __z_value (x + y + z: React.element) } + @res.hoistedComponent let make = React.component({ let \"AliasProps$C5" = (props: props<_>) => make(props) @@ -156,6 +162,7 @@ module C6 = { let make = ({comp: module(Comp: Comp), x: (a, b), _}: props<_, _>): React.element => React.jsx(Comp.make, {}) + @res.hoistedComponent let make = React.component({ let \"AliasProps$C6" = (props: props<_>) => make(props) diff --git a/tests/syntax_tests/data/ppx/react/expected/asyncAwait.res.txt b/tests/syntax_tests/data/ppx/react/expected/asyncAwait.res.txt index f0dce005e25..6f7839c0210 100644 --- a/tests/syntax_tests/data/ppx/react/expected/asyncAwait.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/asyncAwait.res.txt @@ -10,6 +10,7 @@ module C0 = { let a = await f(a) ReactDOM.jsx("div", {children: ?ReactDOM.someElement({React.int(a)})}) } + @res.hoistedComponent let make = React.component({ let \"AsyncAwait$C0" = (props: props<_>) => Jsx.promise(make(props)) @@ -29,6 +30,7 @@ module C1 = { | #off => React.string("off") } } + @res.hoistedComponent let make = React.component({ let \"AsyncAwait$C1" = (props: props<_>) => Jsx.promise(make(props)) @@ -46,6 +48,7 @@ module Braced = { let a = await f(a) ReactDOM.jsx("div", {children: ?ReactDOM.someElement({React.int(a)})}) } + @res.hoistedComponent let make = React.component({ let \"AsyncAwait$Braced" = (props: props<_>) => Jsx.promise(make(props)) diff --git a/tests/syntax_tests/data/ppx/react/expected/defaultPatternProp.res.txt b/tests/syntax_tests/data/ppx/react/expected/defaultPatternProp.res.txt index b1ea9117691..3e39abc39e8 100644 --- a/tests/syntax_tests/data/ppx/react/expected/defaultPatternProp.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/defaultPatternProp.res.txt @@ -6,6 +6,7 @@ module C0 = { type props = {} let make = (_: props): React.element => React.null + @res.hoistedComponent let make = React.component({ let \"DefaultPatternProp$C0$M" = props => make(props) @@ -27,6 +28,7 @@ module C0 = { let module(C: S) = __component_value (React.jsx(C.make, {}): React.element) } + @res.hoistedComponent let make = React.component({ let \"DefaultPatternProp$C0" = (props: props<_>) => make(props) diff --git a/tests/syntax_tests/data/ppx/react/expected/defaultValueProp.res.txt b/tests/syntax_tests/data/ppx/react/expected/defaultValueProp.res.txt index 5406c7724d4..af8b95401f2 100644 --- a/tests/syntax_tests/data/ppx/react/expected/defaultValueProp.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/defaultValueProp.res.txt @@ -17,6 +17,7 @@ module C0 = { let b = __b_value (React.int(a + b): React.element) } + @res.hoistedComponent let make = React.component({ let \"DefaultValueProp$C0" = (props: props<_>) => make(props) \"DefaultValueProp$C0" @@ -38,6 +39,7 @@ module C1 = { let a = __a_value (React.int(a + b): React.element) } + @res.hoistedComponent let make = React.component({ let \"DefaultValueProp$C1" = (props: props<_>) => make(props) @@ -60,6 +62,7 @@ module C2 = { let a = __a_value (React.string(a): React.element) } + @res.hoistedComponent let make = React.component({ let \"DefaultValueProp$C2" = (props: props<_>) => make(props) @@ -85,6 +88,7 @@ module C3 = { }: React.element ) } + @res.hoistedComponent let make = React.component({ let \"DefaultValueProp$C3" = (props: props<_>) => make(props) diff --git a/tests/syntax_tests/data/ppx/react/expected/externalWithCustomName.res.txt b/tests/syntax_tests/data/ppx/react/expected/externalWithCustomName.res.txt index dd98fc72ff3..fe5198d339c 100644 --- a/tests/syntax_tests/data/ppx/react/expected/externalWithCustomName.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/externalWithCustomName.res.txt @@ -7,7 +7,7 @@ module Foo = { b: 'b, } - @module("Foo") + @res.hoistedComponent @module("Foo") external component: React.component> = "component" } diff --git a/tests/syntax_tests/data/ppx/react/expected/externalWithInterface.resi.txt b/tests/syntax_tests/data/ppx/react/expected/externalWithInterface.resi.txt new file mode 100644 index 00000000000..a4675925737 --- /dev/null +++ b/tests/syntax_tests/data/ppx/react/expected/externalWithInterface.resi.txt @@ -0,0 +1,21 @@ +@@jsxConfig({version: 4}) + +module Client: { + @res.jsxComponentProps @live + type props<'children> = { + children: 'children, + } + + @module("client") + external make: React.component> = "Client" +} + +module type Client = { + @res.jsxComponentProps @live + type props<'children> = { + children: 'children, + } + + @module("client") + external make: React.component> = "Client" +} diff --git a/tests/syntax_tests/data/ppx/react/expected/externalWithRef.res.txt b/tests/syntax_tests/data/ppx/react/expected/externalWithRef.res.txt index 44a3420fd87..cef08d9277f 100644 --- a/tests/syntax_tests/data/ppx/react/expected/externalWithRef.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/externalWithRef.res.txt @@ -7,6 +7,6 @@ module V4C = { ref?: 'ref, } - @module("componentForwardRef") + @res.hoistedComponent @module("componentForwardRef") external make: React.component> = "component" } diff --git a/tests/syntax_tests/data/ppx/react/expected/externalWithTypeVariables.res.txt b/tests/syntax_tests/data/ppx/react/expected/externalWithTypeVariables.res.txt index cbdee832996..ef9e6c7c146 100644 --- a/tests/syntax_tests/data/ppx/react/expected/externalWithTypeVariables.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/externalWithTypeVariables.res.txt @@ -7,6 +7,6 @@ module V4C = { children: 'children, } - @module("c") + @res.hoistedComponent @module("c") external make: React.component, React.element>> = "component" } diff --git a/tests/syntax_tests/data/ppx/react/expected/fileLevelConfig.res.txt b/tests/syntax_tests/data/ppx/react/expected/fileLevelConfig.res.txt index 2cd52b3ac31..b36c9031fc3 100644 --- a/tests/syntax_tests/data/ppx/react/expected/fileLevelConfig.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/fileLevelConfig.res.txt @@ -9,6 +9,7 @@ module V4A = { let make = ({msg, _}: props<_>): Preact.element => { Preact.Elements.jsx("div", {children: ?Preact.Elements.someElement({msg->React.string})}) } + @res.hoistedComponent let make = Preact.component({ let \"FileLevelConfig$V4A" = (props: props<_>) => make(props) diff --git a/tests/syntax_tests/data/ppx/react/expected/forwardRef.res.txt b/tests/syntax_tests/data/ppx/react/expected/forwardRef.res.txt index f23f859040f..516f28e01d3 100644 --- a/tests/syntax_tests/data/ppx/react/expected/forwardRef.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/forwardRef.res.txt @@ -29,6 +29,7 @@ module V4A = { ]), }, ) + @res.hoistedComponent let make = React.forwardRef({ let \"ForwardRef$V4A$FancyInput" = (props: props<_>, ref) => make(props, ref) @@ -50,6 +51,7 @@ module V4A = { }, ) } + @res.hoistedComponent let make = React.component({ let \"ForwardRef$V4A" = props => make(props) @@ -86,6 +88,7 @@ module V4AUncurried = { ]), }, ) + @res.hoistedComponent let make = React.forwardRef({ let \"ForwardRef$V4AUncurried$FancyInput" = (props: props<_>, ref) => make(props, ref) @@ -107,6 +110,7 @@ module V4AUncurried = { }, ) } + @res.hoistedComponent let make = React.component({ let \"ForwardRef$V4AUncurried" = props => make(props) diff --git a/tests/syntax_tests/data/ppx/react/expected/interface.res.txt b/tests/syntax_tests/data/ppx/react/expected/interface.res.txt index 77b6e77aec2..e786a76f3d2 100644 --- a/tests/syntax_tests/data/ppx/react/expected/interface.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/interface.res.txt @@ -4,6 +4,7 @@ module A = { x: 'x, } let make = ({x, _}: props<_>): React.element => React.string(x) + @res.hoistedComponent let make = React.component({ let \"Interface$A" = (props: props<_>) => make(props) \"Interface$A" @@ -15,6 +16,7 @@ module NoProps = { type props = {} let make = (_: props): React.element => ReactDOM.jsx("div", {}) + @res.hoistedComponent let make = React.component({ let \"Interface$NoProps" = props => make(props) diff --git a/tests/syntax_tests/data/ppx/react/expected/mangleKeyword.res.txt b/tests/syntax_tests/data/ppx/react/expected/mangleKeyword.res.txt index fd905c590ba..404263e5d77 100644 --- a/tests/syntax_tests/data/ppx/react/expected/mangleKeyword.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/mangleKeyword.res.txt @@ -9,6 +9,7 @@ module C4A0 = { let make = ({@as("open") _open, @as("type") _type, _}: props<_, string>): React.element => React.string(_open) + @res.hoistedComponent let make = React.component({ let \"MangleKeyword$C4A0" = (props: props<_>) => make(props) @@ -22,6 +23,7 @@ module C4A1 = { @as("type") _type: 'T_type, } + @res.hoistedComponent external make: React.component> = "default" } diff --git a/tests/syntax_tests/data/ppx/react/expected/nested.res.txt b/tests/syntax_tests/data/ppx/react/expected/nested.res.txt index 4931436a500..fee5156d4ff 100644 --- a/tests/syntax_tests/data/ppx/react/expected/nested.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/nested.res.txt @@ -7,6 +7,7 @@ module Outer = { type props = {} let make = (_: props): React.element => ReactDOM.jsx("div", {}) + @res.hoistedComponent let make = React.component({ let \"Nested$Outer" = props => make(props) @@ -16,6 +17,7 @@ module Outer = { React.jsx(Inner.make, {}) } + @res.hoistedComponent let make = React.component({ let \"Nested$Outer" = props => make(props) \"Nested$Outer" diff --git a/tests/syntax_tests/data/ppx/react/expected/newtype.res.txt b/tests/syntax_tests/data/ppx/react/expected/newtype.res.txt index 5873c71b31e..e5a9ecac70a 100644 --- a/tests/syntax_tests/data/ppx/react/expected/newtype.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/newtype.res.txt @@ -10,6 +10,7 @@ module V4A = { let make = (type a, {a, b, c, _}: props>, 'a>): React.element => ReactDOM.jsx("div", {}) + @res.hoistedComponent let make = React.component({ let \"Newtype$V4A" = (props: props<_>) => make(props) @@ -27,6 +28,7 @@ module V4A1 = { let make = (type x y, {a, b, c, _}: props, 'a>): React.element => ReactDOM.jsx("div", {}) + @res.hoistedComponent let make = React.component({ let \"Newtype$V4A1" = (props: props<_>) => make(props) @@ -48,6 +50,7 @@ module V4A2 = { module T = unpack(foo) ReactDOM.jsx("div", {}) } + @res.hoistedComponent let make = React.component({ let \"Newtype$V4A2" = (props: props<_>) => make(props) @@ -65,6 +68,7 @@ module V4A3 = { module T = unpack(foo: T with type t = a) foo } + @res.hoistedComponent let make = React.component({ let \"Newtype$V4A3" = (props: props<_>) => make(props) @@ -93,6 +97,7 @@ module Uncurried = { } let make = (type a, {?foo, _}: props<_>): React.element => React.null + @res.hoistedComponent let make = React.component({ let \"Newtype$Uncurried" = (props: props<_>) => make(props) diff --git a/tests/syntax_tests/data/ppx/react/expected/noPropsWithKey.res.txt b/tests/syntax_tests/data/ppx/react/expected/noPropsWithKey.res.txt index 443bb57c62e..52f0843ed92 100644 --- a/tests/syntax_tests/data/ppx/react/expected/noPropsWithKey.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/noPropsWithKey.res.txt @@ -5,6 +5,7 @@ module V4CA = { type props = {} let make = (_: props): React.element => ReactDOM.jsx("div", {}) + @res.hoistedComponent let make = React.component({ let \"NoPropsWithKey$V4CA" = props => make(props) @@ -16,7 +17,7 @@ module V4CB = { @res.jsxComponentProps @live type props = {} - @module("c") + @res.hoistedComponent @module("c") external make: React.component = "component" } @@ -34,6 +35,7 @@ module V4C = { ]), }, ) + @res.hoistedComponent let make = React.component({ let \"NoPropsWithKey$V4C" = props => make(props) diff --git a/tests/syntax_tests/data/ppx/react/expected/optimizeAutomaticMode.res.txt b/tests/syntax_tests/data/ppx/react/expected/optimizeAutomaticMode.res.txt index 7ccf95dfb35..970377fb89d 100644 --- a/tests/syntax_tests/data/ppx/react/expected/optimizeAutomaticMode.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/optimizeAutomaticMode.res.txt @@ -12,6 +12,7 @@ module User = { let make = ({doctor, _}: props<_>): React.element => { ReactDOM.jsx("h1", {id: "h1", children: ?ReactDOM.someElement({React.string(format(doctor))})}) } + @res.hoistedComponent let make = React.component({ let \"OptimizeAutomaticMode$User" = (props: props<_>) => make(props) diff --git a/tests/syntax_tests/data/ppx/react/expected/returnConstraint.res.txt b/tests/syntax_tests/data/ppx/react/expected/returnConstraint.res.txt index 43e3ca14aef..44289b2de8f 100644 --- a/tests/syntax_tests/data/ppx/react/expected/returnConstraint.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/returnConstraint.res.txt @@ -5,6 +5,7 @@ module Standard = { type props = {} let make = (_: props): React.element => React.string("ok") + @res.hoistedComponent let make = React.component({ let \"ReturnConstraint$Standard" = props => make(props) @@ -17,6 +18,7 @@ module ForwardRef = { type props = {} let make = (_: props, _ref): React.element => ReactDOM.jsx("div", {}) + @res.hoistedComponent let make = React.forwardRef({ let \"ReturnConstraint$ForwardRef" = (props, ref) => make(props, ref) @@ -29,6 +31,7 @@ module WithProps = { let make = (props: props): React.element => ReactDOM.jsx("span", {children: ?ReactDOM.someElement({React.int(props.value)})}) + @res.hoistedComponent let make = React.component({ let \"ReturnConstraint$WithProps" = (props: props): React.element => make(props) \"ReturnConstraint$WithProps" @@ -41,6 +44,7 @@ module Async = { let make = async (_: props): React.element => ReactDOM.jsx("div", {children: ?ReactDOM.someElement({React.string("async")})}) + @res.hoistedComponent let make = React.component({ let \"ReturnConstraint$Async" = props => Jsx.promise(make(props)) diff --git a/tests/syntax_tests/data/ppx/react/expected/sharedProps.res.txt b/tests/syntax_tests/data/ppx/react/expected/sharedProps.res.txt index 34d29a757e4..13d7e847b88 100644 --- a/tests/syntax_tests/data/ppx/react/expected/sharedProps.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/sharedProps.res.txt @@ -4,6 +4,7 @@ module V4A1 = { type props = sharedProps let make = ({x, y, _}: props): React.element => React.string(x ++ y) + @res.hoistedComponent let make = React.component({ let \"SharedProps$V4A1" = props => make(props) @@ -15,6 +16,7 @@ module V4A2 = { type props<'a> = sharedProps<'a> let make = ({x, y, _}: props<_>): React.element => React.string(x ++ y) + @res.hoistedComponent let make = React.component({ let \"SharedProps$V4A2" = (props: props<_>) => make(props) @@ -26,6 +28,7 @@ module V4A3 = { type props<'a> = sharedProps let make = ({x, y, _}: props<_>): React.element => React.string(x ++ y) + @res.hoistedComponent let make = React.component({ let \"SharedProps$V4A3" = (props: props<_>) => make(props) @@ -37,6 +40,7 @@ module V4A4 = { type props = sharedProps let make = ({x, y, _}: props): React.element => React.string(x ++ y) + @res.hoistedComponent let make = React.component({ let \"SharedProps$V4A4" = props => make(props) @@ -47,23 +51,27 @@ module V4A4 = { module V4A5 = { type props = sharedProps + @res.hoistedComponent external make: React.component = "default" } module V4A6 = { type props<'a> = sharedProps<'a> + @res.hoistedComponent external make: React.component> = "default" } module V4A7 = { type props<'a> = sharedProps + @res.hoistedComponent external make: React.component> = "default" } module V4A8 = { type props = sharedProps + @res.hoistedComponent external make: React.component = "default" } diff --git a/tests/syntax_tests/data/ppx/react/expected/sharedPropsWithProps.res.txt b/tests/syntax_tests/data/ppx/react/expected/sharedPropsWithProps.res.txt index 1818ef31f27..bcabe4421ae 100644 --- a/tests/syntax_tests/data/ppx/react/expected/sharedPropsWithProps.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/sharedPropsWithProps.res.txt @@ -5,6 +5,7 @@ let f = a => Promise.resolve(a + a) module V4A1 = { type props = sharedProps let make = (props): React.element => React.string(props.x ++ props.y) + @res.hoistedComponent let make = React.component({ let \"SharedPropsWithProps$V4A1" = (props): React.element => make(props) \"SharedPropsWithProps$V4A1" @@ -14,6 +15,7 @@ module V4A1 = { module V4A2 = { type props = sharedProps let make = (props: props): React.element => React.string(props.x ++ props.y) + @res.hoistedComponent let make = React.component({ let \"SharedPropsWithProps$V4A2" = (props: props): React.element => make(props) \"SharedPropsWithProps$V4A2" @@ -23,6 +25,7 @@ module V4A2 = { module V4A3 = { type props<'a> = sharedProps<'a> let make = ({x, y}: props<_>): React.element => React.string(x ++ y) + @res.hoistedComponent let make = React.component({ let \"SharedPropsWithProps$V4A3" = (props: props<_>): React.element => make(props) \"SharedPropsWithProps$V4A3" @@ -32,6 +35,7 @@ module V4A3 = { module V4A4 = { type props<'a> = sharedProps let make = ({x, y}: props<_>): React.element => React.string(x ++ y) + @res.hoistedComponent let make = React.component({ let \"SharedPropsWithProps$V4A4" = (props: props<_>): React.element => make(props) \"SharedPropsWithProps$V4A4" @@ -44,6 +48,7 @@ module V4A5 = { let a = await f(a) ReactDOM.jsx("div", {children: ?ReactDOM.someElement({React.int(a)})}) } + @res.hoistedComponent let make = React.component({ let \"SharedPropsWithProps$V4A5" = (props: props<_>): React.element => Jsx.promise(make(props)) \"SharedPropsWithProps$V4A5" @@ -58,6 +63,7 @@ module V4A6 = { | #off => React.string("off") } } + @res.hoistedComponent let make = React.component({ let \"SharedPropsWithProps$V4A6" = (props: props<_>): React.element => Jsx.promise(make(props)) \"SharedPropsWithProps$V4A6" @@ -70,6 +76,7 @@ module V4A7 = { let make = (props): React.element => { React.int(props.count) } + @res.hoistedComponent let make = React.component({ let \"SharedPropsWithProps$V4A7" = @directive("'use memo'") (props): React.element => make(props) diff --git a/tests/syntax_tests/data/ppx/react/expected/topLevel.res.txt b/tests/syntax_tests/data/ppx/react/expected/topLevel.res.txt index bc712ece06e..c9f2d20e202 100644 --- a/tests/syntax_tests/data/ppx/react/expected/topLevel.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/topLevel.res.txt @@ -11,6 +11,7 @@ module V4A = { Console.log("This function should be named 'TopLevel.react'") ReactDOM.jsx("div", {}) } + @res.hoistedComponent let make = React.component({ let \"TopLevel$V4A" = (props: props<_>) => make(props) diff --git a/tests/syntax_tests/data/ppx/react/expected/typeConstraint.res.txt b/tests/syntax_tests/data/ppx/react/expected/typeConstraint.res.txt index 491f5c7d191..c08e84dff66 100644 --- a/tests/syntax_tests/data/ppx/react/expected/typeConstraint.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/typeConstraint.res.txt @@ -8,6 +8,7 @@ module V4A = { } let make = (type a, {a, b, _}: props<_, _>): React.element => ReactDOM.jsx("div", {}) + @res.hoistedComponent let make = React.component({ let \"TypeConstraint$V4A" = (props: props<_>) => make(props) diff --git a/tests/syntax_tests/data/ppx/react/expected/uncurriedProps.res.txt b/tests/syntax_tests/data/ppx/react/expected/uncurriedProps.res.txt index 9e635bafdd6..b53bdf158f1 100644 --- a/tests/syntax_tests/data/ppx/react/expected/uncurriedProps.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/uncurriedProps.res.txt @@ -46,6 +46,7 @@ module Foo = { }: React.element ) } + @res.hoistedComponent let make = React.component({ let \"UncurriedProps$Foo" = (props: props<_>) => make(props) @@ -58,6 +59,7 @@ module Bar = { type props = {} let make = (_: props): React.element => React.jsx(Foo.make, {callback: {(_, _, _) => ()}}) + @res.hoistedComponent let make = React.component({ let \"UncurriedProps$Bar" = props => make(props) diff --git a/tests/syntax_tests/data/ppx/react/expected/v4.res.txt b/tests/syntax_tests/data/ppx/react/expected/v4.res.txt index a23517ad7dd..08394c72d94 100644 --- a/tests/syntax_tests/data/ppx/react/expected/v4.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/v4.res.txt @@ -16,6 +16,7 @@ module Braced = { } let make = ({name, _}: props<_>): React.element => React.string(name) + @res.hoistedComponent let make = React.component({ let \"V4$Braced" = (props: props<_>) => make(props) @@ -31,6 +32,7 @@ module AnotherName = { } let anotherName = ({x, _}: props<_>): React.element => React.string(x) + @res.hoistedComponent let anotherName = React.component({ let \"V4$AnotherName$anotherName" = (props: props<_>) => anotherName(props) @@ -45,6 +47,7 @@ module Uncurried = { } let make = ({x, _}: props<_>): React.element => React.string(x) + @res.hoistedComponent let make = React.component({ let \"V4$Uncurried" = (props: props<_>) => make(props) @@ -67,6 +70,7 @@ module E = { x: 'x, } + @res.hoistedComponent external make: React.component> = "default" } @@ -76,6 +80,7 @@ module EUncurried = { x: 'x, } + @res.hoistedComponent external make: React.component> = "default" } @@ -86,6 +91,7 @@ module Rec = { let rec make = (_: props): React.element => { make(({}: props)) } + @res.hoistedComponent let make = React.component({ let \"V4$Rec" = props => make(props) @@ -100,6 +106,7 @@ module Rec1 = { let rec make = (_: props): React.element => { React.null } + @res.hoistedComponent let make = React.component({ let \"V4$Rec1" = props => make(props) @@ -116,6 +123,7 @@ module Rec2 = { } and mm = x => make(x) + @res.hoistedComponent let make = React.component({ let \"V4$Rec2" = props => make(props) diff --git a/tests/syntax_tests/data/ppx/react/externalWithInterface.resi b/tests/syntax_tests/data/ppx/react/externalWithInterface.resi new file mode 100644 index 00000000000..464e5160f46 --- /dev/null +++ b/tests/syntax_tests/data/ppx/react/externalWithInterface.resi @@ -0,0 +1,11 @@ +@@jsxConfig({version: 4}) + +module Client: { + @react.component @module("client") + external make: (~children: React.element) => React.element = "Client" +} + +module type Client = { + @react.component @module("client") + external make: (~children: React.element) => React.element = "Client" +} diff --git a/tests/tests/src/alias_default_value_test.mjs b/tests/tests/src/alias_default_value_test.mjs index 1541a25702d..95e3e69a36f 100644 --- a/tests/tests/src/alias_default_value_test.mjs +++ b/tests/tests/src/alias_default_value_test.mjs @@ -91,6 +91,22 @@ let C8 = { make: Alias_default_value_test$C8 }; +let C8$make = C8.make; + +let C7$make = C7.make; + +let C6$make = C6.make; + +let C4$make = C4.make; + +let C3$make = C3.make; + +let C2$make = C2.make; + +let C1$make = C1.make; + +let C0$make = C0.make; + export { C0, C1, @@ -100,5 +116,13 @@ export { C6, C7, C8, + C8$make, + C7$make, + C6$make, + C4$make, + C3$make, + C2$make, + C1$make, + C0$make, } /* No side effect */ diff --git a/tests/tests/src/async_jsx.mjs b/tests/tests/src/async_jsx.mjs index f02ec3cf205..76b21662b72 100644 --- a/tests/tests/src/async_jsx.mjs +++ b/tests/tests/src/async_jsx.mjs @@ -33,9 +33,15 @@ let Bar = { make: Async_jsx$Bar }; +let Bar$make = Bar.make; + +let Foo$make = Foo.make; + export { getNow, Foo, Bar, + Bar$make, + Foo$make, } /* react/jsx-runtime Not a pure module */ diff --git a/tests/tests/src/jsx_optional_props_test.mjs b/tests/tests/src/jsx_optional_props_test.mjs index 8fab49e1737..f0ee52b4bc8 100644 --- a/tests/tests/src/jsx_optional_props_test.mjs +++ b/tests/tests/src/jsx_optional_props_test.mjs @@ -31,6 +31,8 @@ let _comment_multiple_children = JsxRuntime.jsxs("div", { ] }); +let ComponentWithOptionalProps$make = ComponentWithOptionalProps.make; + export { ComponentWithOptionalProps, _element, @@ -38,5 +40,6 @@ export { _comment_only_fragment, _comment_single_child, _comment_multiple_children, + ComponentWithOptionalProps$make, } /* _element Not a pure module */ diff --git a/tests/tests/src/jsx_preserve_cases.mjs b/tests/tests/src/jsx_preserve_cases.mjs index 8d413e86aac..91b18175b3f 100644 --- a/tests/tests/src/jsx_preserve_cases.mjs +++ b/tests/tests/src/jsx_preserve_cases.mjs @@ -109,6 +109,10 @@ let undefinedKey =
; +let Req$make = Req.make; + +let AsProp$make = AsProp.make; + export { AsProp, asProp, @@ -131,5 +135,7 @@ export { copySpread, localComp, undefinedKey, + Req$make, + AsProp$make, } /* Not a pure module */ diff --git a/tests/tests/src/jsx_preserve_custom_runtime.mjs b/tests/tests/src/jsx_preserve_custom_runtime.mjs index 6fbd7c56768..1fc65206718 100644 --- a/tests/tests/src/jsx_preserve_custom_runtime.mjs +++ b/tests/tests/src/jsx_preserve_custom_runtime.mjs @@ -40,6 +40,8 @@ let keyed =
; +let Comp$make = Comp.make; + export { MyJsx, Comp, @@ -48,5 +50,6 @@ export { topMemo, fragment, keyed, + Comp$make, } /* memoComp Not a pure module */ diff --git a/tests/tests/src/jsx_preserve_test.mjs b/tests/tests/src/jsx_preserve_test.mjs index bc66ee92593..5dba69acb9f 100644 --- a/tests/tests/src/jsx_preserve_test.mjs +++ b/tests/tests/src/jsx_preserve_test.mjs @@ -308,6 +308,16 @@ let _comment_multiple_children =
let make$2 = Jsx_preserve_test; +let X$make = X.make; + +let ComponentWithOptionalProps$make = ComponentWithOptionalProps.make; + +let MyWeirdComponent$make = MyWeirdComponent.make; + +let B$make = B.make; + +let Icon$make = Icon.make; + export { Icon, _single_element_child, @@ -352,5 +362,10 @@ export { _comment_only_fragment, _comment_single_child, _comment_multiple_children, + X$make, + ComponentWithOptionalProps$make, + MyWeirdComponent$make, + B$make, + Icon$make, } /* _single_element_child Not a pure module */ diff --git a/tests/tests/src/jsxv4_newtype.mjs b/tests/tests/src/jsxv4_newtype.mjs index a6e2b5a2dc7..0bae95be788 100644 --- a/tests/tests/src/jsxv4_newtype.mjs +++ b/tests/tests/src/jsxv4_newtype.mjs @@ -33,10 +33,22 @@ let V4A3 = { make: Jsxv4_newtype$V4A3 }; +let V4A3$make = V4A3.make; + +let V4A2$make = V4A2.make; + +let V4A1$make = V4A1.make; + +let V4A$make = V4A.make; + export { V4A, V4A1, V4A2, V4A3, + V4A3$make, + V4A2$make, + V4A1$make, + V4A$make, } /* No side effect */ diff --git a/tests/tests/src/recursive_react_component.mjs b/tests/tests/src/recursive_react_component.mjs index d7c3005004b..c02d3abf7f5 100644 --- a/tests/tests/src/recursive_react_component.mjs +++ b/tests/tests/src/recursive_react_component.mjs @@ -50,11 +50,23 @@ let ShadowedByNestedParameter = { let make$1 = Recursive_react_component; +let ShadowedByNestedParameter$make = ShadowedByNestedParameter.make; + +let ShadowedByLocalLet$make = ShadowedByLocalLet.make; + +let Leaf$make = Leaf.make; + +let ShadowedSelfReference$make = ShadowedSelfReference.make; + export { make$1 as make, ShadowedSelfReference, Leaf, ShadowedByLocalLet, ShadowedByNestedParameter, + ShadowedByNestedParameter$make, + ShadowedByLocalLet$make, + Leaf$make, + ShadowedSelfReference$make, } /* react Not a pure module */