Skip to content

Extend genType to recognise JSX V4 components. #5619

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Aug 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 120 additions & 31 deletions jscomp/gentype/EmitJs.ml
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,9 @@ let emitExportFromTypeDeclarations ~config ~emitters ~env ~typeGetNormalized
~typeNameIsInterface)
(env, emitters)

let rec emitCodeItem ~config ~emitters ~moduleItemsEmitter ~env ~fileName
~outputFileRelative ~resolver ~typeGetConverter ~typeGetNormalized
~typeNameIsInterface ~variantTables codeItem =
let emitCodeItem ~config ~emitters ~moduleItemsEmitter ~env ~fileName
~outputFileRelative ~resolver ~typeGetConverter ~inlineOneLevel
~typeGetNormalized ~typeNameIsInterface ~variantTables codeItem =
if !Debug.codeItems then
Log_.item "Code Item: %s\n"
(codeItem |> codeItemToString ~config ~typeNameIsInterface);
Expand Down Expand Up @@ -162,6 +162,7 @@ let rec emitCodeItem ~config ~emitters ~moduleItemsEmitter ~env ~fileName
retType;
} as function_)
when retType |> EmitType.isTypeFunctionComponent ~fields ->
(* JSX V3 *)
let componentName =
match importFile with "." | ".." -> None | _ -> Some importFile
in
Expand All @@ -182,6 +183,41 @@ let rec emitCodeItem ~config ~emitters ~moduleItemsEmitter ~env ~fileName
}
in
Function { function_ with componentName }
| Function
({
argTypes = [ { aType = Ident { name } as propsType; aName } ];
retType;
} as function_)
when Filename.check_suffix name "props"
&& retType |> EmitType.isTypeFunctionComponent ~fields:[] -> (
match inlineOneLevel propsType with
| Object (closedFlags, fields) ->
(* JSX V3 *)
let componentName =
match importFile with
| "." | ".." -> None
| _ -> Some importFile
in
let fields =
Ext_list.filter_map fields (fun (field : field) ->
match field.nameJS with
| "children"
when field.type_ |> EmitType.isTypeReactElement ->
Some { field with type_ = EmitType.typeReactChild }
| "key" ->
(* Filter out key, which is added to the props type definition in V4 *)
None
| _ -> Some field)
in
let function_ =
{
function_ with
argTypes =
[ { aType = Object (closedFlags, fields); aName } ];
}
in
Function { function_ with componentName }
| _ -> type_)
| _ -> type_
in
let converter = type_ |> typeGetConverter in
Expand Down Expand Up @@ -249,6 +285,23 @@ let rec emitCodeItem ~config ~emitters ~moduleItemsEmitter ~env ~fileName
typeVars : string list;
}
end in
let getHookName () =
let chopSuffix suffix =
match resolvedNameStr = suffix with
| true -> ""
| false -> (
match Filename.check_suffix resolvedNameStr ("_" ^ suffix) with
| true -> Filename.chop_suffix resolvedNameStr ("_" ^ suffix)
| false -> resolvedNameStr)
in
let suffix =
if originalName = default then chopSuffix default
else if originalName = make then chopSuffix make
else resolvedNameStr
in
(fileName |> ModuleName.toString)
^ match suffix = "" with true -> suffix | false -> "_" ^ suffix
in
let type_, hookType =
match type_ with
| Function
Expand All @@ -258,6 +311,7 @@ let rec emitCodeItem ~config ~emitters ~moduleItemsEmitter ~env ~fileName
typeVars;
} as function_)
when retType |> EmitType.isTypeFunctionComponent ~fields ->
(* JSX V3 *)
let propsType =
let fields =
fields
Expand All @@ -277,25 +331,6 @@ let rec emitCodeItem ~config ~emitters ~moduleItemsEmitter ~env ~fileName
argTypes = [ { aName = ""; aType = propsType } ];
}
in
let chopSuffix suffix =
match resolvedNameStr = suffix with
| true -> ""
| false -> (
match
Filename.check_suffix resolvedNameStr ("_" ^ suffix)
with
| true -> Filename.chop_suffix resolvedNameStr ("_" ^ suffix)
| false -> resolvedNameStr)
in
let suffix =
if originalName = default then chopSuffix default
else if originalName = make then chopSuffix make
else resolvedNameStr
in
let hookName =
(fileName |> ModuleName.toString)
^ match suffix = "" with true -> suffix | false -> "_" ^ suffix
in
let resolvedTypeName =
if
(not config.emitTypePropDone)
Expand All @@ -305,8 +340,43 @@ let rec emitCodeItem ~config ~emitters ~moduleItemsEmitter ~env ~fileName
ResolvedName.fromString "Props")
else ResolvedName.fromString name |> ResolvedName.dot "Props"
in
( Function { function_ with componentName = Some hookName },
( Function { function_ with componentName = Some (getHookName ()) },
Some { HookType.propsType; resolvedTypeName; typeVars } )
| Function
({ argTypes = [ { aType = Ident { name } as propsType } ]; retType }
as function_)
when Filename.check_suffix name "props"
&& retType |> EmitType.isTypeFunctionComponent ~fields:[] ->
let compType =
match inlineOneLevel propsType with
| Object (closedFlags, fields) ->
(* JSX V4 *)
let propsType =
let fields =
Ext_list.filter_map fields (fun (field : field) ->
match field.nameJS with
| "children"
when field.type_ |> EmitType.isTypeReactElement ->
Some
{ field with type_ = EmitType.typeReactChild }
| "key" ->
(* Filter out key, which is added to the props type definition in V4 *)
None
| _ -> Some field)
in
Object (closedFlags, fields)
in
let function_ =
{
function_ with
argTypes = [ { aName = ""; aType = propsType } ];
}
in
Function
{ function_ with componentName = Some (getHookName ()) }
| _ -> type_
in
(compType, None)
| _ -> (type_, None)
in

Expand Down Expand Up @@ -349,15 +419,15 @@ let rec emitCodeItem ~config ~emitters ~moduleItemsEmitter ~env ~fileName
in
(envWithRequires, emitters)

and emitCodeItems ~config ~outputFileRelative ~emitters ~moduleItemsEmitter ~env
~fileName ~resolver ~typeNameIsInterface ~typeGetConverter
let emitCodeItems ~config ~outputFileRelative ~emitters ~moduleItemsEmitter ~env
~fileName ~resolver ~typeNameIsInterface ~typeGetConverter ~inlineOneLevel
~typeGetNormalized ~variantTables codeItems =
codeItems
|> List.fold_left
(fun (env, emitters) ->
emitCodeItem ~config ~emitters ~moduleItemsEmitter ~env ~fileName
~outputFileRelative ~resolver ~typeGetConverter ~typeGetNormalized
~typeNameIsInterface ~variantTables)
~outputFileRelative ~resolver ~typeGetConverter ~inlineOneLevel
~typeGetNormalized ~typeNameIsInterface ~variantTables)
(env, emitters)

let emitRequires ~importedValueOrComponent ~early ~config ~requires emitters =
Expand Down Expand Up @@ -627,12 +697,12 @@ let emitTranslationAsString ~config ~fileName ~inputCmtTranslateTypeDeclarations
try exportTypeMap |> StringMap.find s
with Not_found -> env.exportTypeMapFromOtherFiles |> StringMap.find s
in
let typeGetNormalized_ ~env type_ =
let typeGetNormalized__ ~inline ~env type_ =
type_
|> Converter.typeGetNormalized ~config ~inline:false
~lookupId:(lookupId_ ~env)
|> Converter.typeGetNormalized ~config ~inline ~lookupId:(lookupId_ ~env)
~typeNameIsInterface:(typeNameIsInterface ~env)
in
let typeGetNormalized_ = typeGetNormalized__ ~inline:false in
let typeGetConverter_ ~env type_ =
type_
|> Converter.typeGetConverter ~config ~lookupId:(lookupId_ ~env)
Expand All @@ -654,10 +724,29 @@ let emitTranslationAsString ~config ~fileName ~inputCmtTranslateTypeDeclarations
~typeGetNormalized:(typeGetNormalized_ ~env) ~env
~typeNameIsInterface:(typeNameIsInterface ~env)
in
let inlineOneLevel type_ =
match type_ with
| Ident { builtin = false; name; typeArgs } -> (
match name |> lookupId_ ~env with
| { type_; typeVars } ->
let pairs =
try List.combine typeVars typeArgs with Invalid_argument _ -> []
in
let f typeVar =
match
pairs |> List.find (fun (typeVar1, _) -> typeVar = typeVar1)
with
| _, typeArgument -> Some typeArgument
| exception Not_found -> None
in
type_ |> TypeVars.substitute ~f
| exception Not_found -> type_)
| _ -> type_
in
let env, emitters =
translation.codeItems
|> emitCodeItems ~config ~emitters ~moduleItemsEmitter ~env ~fileName
~outputFileRelative ~resolver
~outputFileRelative ~resolver ~inlineOneLevel
~typeGetNormalized:(typeGetNormalized_ ~env)
~typeGetConverter:(typeGetConverter_ ~env)
~typeNameIsInterface:(typeNameIsInterface ~env) ~variantTables
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions jscomp/gentype_tests/typescript-react-example/src/JSXV4.bs.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 59 additions & 0 deletions jscomp/gentype_tests/typescript-react-example/src/JSXV4.gen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/* TypeScript file generated from JSXV4.res by genType. */
/* eslint-disable import/first */


import {make as makeNotChecked} from './hookExample';

import * as React from 'react';

// In case of type error, check the type of 'make' in 'JSXV4.re' and './hookExample'.
export const makeTypeChecked: React.ComponentType<{
readonly actions?: JSX.Element;
readonly person: person;
readonly children: React.ReactNode;
readonly renderMe: renderMe<any>
}> = makeNotChecked;

// Export 'make' early to allow circular import from the '.bs.js' file.
export const make: unknown = makeTypeChecked as React.ComponentType<{
readonly actions?: JSX.Element;
readonly person: person;
readonly children: React.ReactNode;
readonly renderMe: renderMe<any>
}>;

// tslint:disable-next-line:no-var-requires
const JSXV4BS = require('./JSXV4.bs');

// tslint:disable-next-line:interface-over-type-literal
export type CompV4_props<x,y> = {
readonly key?: string;
readonly x: x;
readonly y: y
};

// tslint:disable-next-line:interface-over-type-literal
export type person = { readonly name: string; readonly age: number };

// tslint:disable-next-line:interface-over-type-literal
export type renderMe<a> = React.ComponentType<{ readonly randomString: string; readonly poly: a }>;

// tslint:disable-next-line:interface-over-type-literal
export type props<actions,person,children,renderMe> = {
readonly key?: string;
readonly actions?: actions;
readonly person: person;
readonly children: children;
readonly renderMe: renderMe
};

export const CompV4_make: React.ComponentType<{ readonly x: string; readonly y: string }> = JSXV4BS.CompV4.make;

// tslint:disable-next-line:interface-over-type-literal
export type Props = { readonly x: string; readonly y: string };

export const CompV3_make: React.ComponentType<{ readonly x: string; readonly y: string }> = JSXV4BS.CompV3.make;

export const CompV3: { make: React.ComponentType<{ readonly x: string; readonly y: string }> } = JSXV4BS.CompV3

export const CompV4: { make: React.ComponentType<{ readonly x: string; readonly y: string }> } = JSXV4BS.CompV4
31 changes: 31 additions & 0 deletions jscomp/gentype_tests/typescript-react-example/src/JSXV4.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
@@jsxConfig({version: 4})

module CompV4 = {
@genType @react.component
let make = (~x, ~y) => React.string(x ++ y)
}

@@jsxConfig({version: 3})

module CompV3 = {
@genType @react.component
let make = (~x, ~y) => React.string(x ++ y)
}

@genType
type person = {
name: string,
age: int,
}

@genType type renderMe<'a> = React.component<{"randomString": string, "poly": 'a}>

@@jsxConfig({version: 4})

@genType.import("./hookExample") @react.component
external make: (
~actions: React.element=?,
~person: person,
~children: React.element,
~renderMe: renderMe<'a>,
) => React.element = "make"
Loading