feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,368 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
export type PlatformType =
| 'iOS'
| 'android';
export interface SchemaType {
readonly modules: {
[hasteModuleName: string]: ComponentSchema | NativeModuleSchema;
};
}
export interface DoubleTypeAnnotation {
readonly type: 'DoubleTypeAnnotation';
}
export interface FloatTypeAnnotation {
readonly type: 'FloatTypeAnnotation';
}
export interface BooleanTypeAnnotation {
readonly type: 'BooleanTypeAnnotation';
}
export interface Int32TypeAnnotation {
readonly type: 'Int32TypeAnnotation';
}
export interface StringTypeAnnotation {
readonly type: 'StringTypeAnnotation';
}
export interface StringEnumTypeAnnotation {
readonly type: 'StringEnumTypeAnnotation';
readonly options: readonly string[];
}
export interface VoidTypeAnnotation {
readonly type: 'VoidTypeAnnotation';
}
export interface ObjectTypeAnnotation<T> {
readonly type: 'ObjectTypeAnnotation';
readonly properties: readonly NamedShape<T>[];
readonly baseTypes?: readonly string[] | undefined;
}
export interface MixedTypeAnnotation {
readonly type: 'MixedTypeAnnotation';
}
export interface FunctionTypeAnnotation<P, R> {
readonly type: 'FunctionTypeAnnotation';
readonly params: readonly NamedShape<P>[];
readonly returnTypeAnnotation: R;
}
export interface NamedShape<T> {
readonly name: string;
readonly optional: boolean;
readonly typeAnnotation: T;
}
export interface ComponentSchema {
readonly type: 'Component';
readonly components: {
[componentName: string]: ComponentShape;
};
}
export interface ComponentShape extends OptionsShape {
readonly extendsProps: readonly ExtendsPropsShape[];
readonly events: readonly EventTypeShape[];
readonly props: readonly NamedShape<PropTypeAnnotation>[];
readonly commands: readonly NamedShape<CommandTypeAnnotation>[];
readonly deprecatedViewConfigName?: string | undefined;
}
export interface OptionsShape {
readonly interfaceOnly?: boolean | undefined;
readonly paperComponentName?: string | undefined;
readonly excludedPlatforms?: readonly PlatformType[] | undefined;
readonly paperComponentNameDeprecated?: string | undefined;
}
export interface ExtendsPropsShape {
readonly type: 'ReactNativeBuiltInType';
readonly knownTypeName: 'ReactNativeCoreViewProps';
}
export interface EventTypeShape {
readonly name: string;
readonly bubblingType:
| 'direct'
| 'bubble';
readonly optional: boolean;
readonly paperTopLevelNameDeprecated?: string | undefined;
readonly typeAnnotation: {
readonly type: 'EventTypeAnnotation';
readonly argument?: ObjectTypeAnnotation<EventTypeAnnotation> | undefined;
};
}
export type EventTypeAnnotation =
| BooleanTypeAnnotation
| StringTypeAnnotation
| DoubleTypeAnnotation
| FloatTypeAnnotation
| Int32TypeAnnotation
| StringEnumTypeAnnotation
| ObjectTypeAnnotation<EventTypeAnnotation>
| {
readonly type: 'ArrayTypeAnnotation';
readonly elementType: EventTypeAnnotation
};
export type PropTypeAnnotation =
| {
readonly type: 'BooleanTypeAnnotation';
readonly default:
| boolean
| null;
}
| {
readonly type: 'StringTypeAnnotation';
readonly default:
| string
| null;
}
| {
readonly type: 'DoubleTypeAnnotation';
readonly default: number;
}
| {
readonly type: 'FloatTypeAnnotation';
readonly default:
| number
| null;
}
| {
readonly type: 'Int32TypeAnnotation';
readonly default: number;
}
| {
readonly type: 'StringEnumTypeAnnotation';
readonly default: string;
readonly options: readonly string[];
}
| {
readonly type: 'Int32EnumTypeAnnotation';
readonly default: number;
readonly options: readonly number[];
}
| ReservedPropTypeAnnotation
| ObjectTypeAnnotation<PropTypeAnnotation>
| {
readonly type: 'ArrayTypeAnnotation';
readonly elementType:
| BooleanTypeAnnotation
| StringTypeAnnotation
| DoubleTypeAnnotation
| FloatTypeAnnotation
| Int32TypeAnnotation
| {
readonly type: 'StringEnumTypeAnnotation';
readonly default: string;
readonly options: readonly string[];
}
| ObjectTypeAnnotation<PropTypeAnnotation>
| ReservedPropTypeAnnotation
| {
readonly type: 'ArrayTypeAnnotation';
readonly elementType: ObjectTypeAnnotation<PropTypeAnnotation>;
};
}
| MixedTypeAnnotation;
export interface ReservedPropTypeAnnotation {
readonly type: 'ReservedPropTypeAnnotation';
readonly name:
| 'ColorPrimitive'
| 'ImageSourcePrimitive'
| 'PointPrimitive'
| 'EdgeInsetsPrimitive'
| 'ImageRequestPrimitive'
| 'DimensionPrimitive';
}
export type CommandTypeAnnotation = FunctionTypeAnnotation<CommandParamTypeAnnotation, VoidTypeAnnotation>;
export type CommandParamTypeAnnotation =
| ReservedTypeAnnotation
| BooleanTypeAnnotation
| Int32TypeAnnotation
| DoubleTypeAnnotation
| FloatTypeAnnotation
| StringTypeAnnotation
| {
readonly type: 'ArrayTypeAnnotation';
readonly elementType:
| Int32TypeAnnotation
| DoubleTypeAnnotation
| FloatTypeAnnotation
| BooleanTypeAnnotation
| StringTypeAnnotation
};
export interface ReservedTypeAnnotation {
readonly type: 'ReservedTypeAnnotation';
readonly name: 'RootTag';
}
export type Nullable<T extends NativeModuleTypeAnnotation> =
| NullableTypeAnnotation<T>
| T;
export interface NullableTypeAnnotation<T extends NativeModuleTypeAnnotation> {
readonly type: 'NullableTypeAnnotation';
readonly typeAnnotation: T;
}
export interface NativeModuleSchema {
readonly type: 'NativeModule';
readonly aliasMap: NativeModuleAliasMap;
readonly enumMap: NativeModuleEnumMap;
readonly spec: NativeModuleSpec;
readonly moduleName: string;
readonly excludedPlatforms?: readonly PlatformType[] | undefined;
}
export interface NativeModuleSpec {
readonly properties: readonly NativeModulePropertyShape[];
}
export type NativeModulePropertyShape = NamedShape<Nullable<NativeModuleFunctionTypeAnnotation>>;
export interface NativeModuleEnumMap {
readonly [enumName: string]: NativeModuleEnumDeclarationWithMembers;
}
export interface NativeModuleAliasMap {
readonly [aliasName: string]: NativeModuleObjectTypeAnnotation;
}
export type NativeModuleFunctionTypeAnnotation = FunctionTypeAnnotation<Nullable<NativeModuleParamTypeAnnotation>, Nullable<NativeModuleReturnTypeAnnotation>>;
export type NativeModuleObjectTypeAnnotation = ObjectTypeAnnotation<Nullable<NativeModuleBaseTypeAnnotation>>;
export interface NativeModuleArrayTypeAnnotation<T extends Nullable<NativeModuleBaseTypeAnnotation>> {
readonly type: 'ArrayTypeAnnotation';
readonly elementType?: T | undefined;
}
export interface NativeModuleStringTypeAnnotation {
readonly type: 'StringTypeAnnotation';
}
export interface NativeModuleNumberTypeAnnotation {
readonly type: 'NumberTypeAnnotation';
}
export interface NativeModuleInt32TypeAnnotation {
readonly type: 'Int32TypeAnnotation';
}
export interface NativeModuleDoubleTypeAnnotation {
readonly type: 'DoubleTypeAnnotation';
}
export interface NativeModuleFloatTypeAnnotation {
readonly type: 'FloatTypeAnnotation';
}
export interface NativeModuleBooleanTypeAnnotation {
readonly type: 'BooleanTypeAnnotation';
}
export type NativeModuleEnumMembers = readonly {
readonly name: string;
readonly value: string | number;
}[];
export type NativeModuleEnumMemberType =
| 'NumberTypeAnnotation'
| 'StringTypeAnnotation';
export interface NativeModuleEnumDeclaration {
readonly name: string;
readonly type: 'EnumDeclaration';
readonly memberType: NativeModuleEnumMemberType;
}
export interface NativeModuleEnumDeclarationWithMembers {
name: string;
type: 'EnumDeclarationWithMembers';
memberType: NativeModuleEnumMemberType;
members: NativeModuleEnumMembers;
}
export interface NativeModuleGenericObjectTypeAnnotation {
readonly type: 'GenericObjectTypeAnnotation';
readonly dictionaryValueType?: Nullable<NativeModuleTypeAnnotation> | undefined;
}
export interface NativeModuleTypeAliasTypeAnnotation {
readonly type: 'TypeAliasTypeAnnotation';
readonly name: string;
}
export interface NativeModulePromiseTypeAnnotation {
readonly type: 'PromiseTypeAnnotation';
readonly elementType?: Nullable<NativeModuleBaseTypeAnnotation> | undefined;
}
export type UnionTypeAnnotationMemberType =
| 'NumberTypeAnnotation'
| 'ObjectTypeAnnotation'
| 'StringTypeAnnotation';
export interface NativeModuleUnionTypeAnnotation {
readonly type: 'UnionTypeAnnotation';
readonly memberType: UnionTypeAnnotationMemberType;
}
export interface NativeModuleMixedTypeAnnotation {
readonly type: 'MixedTypeAnnotation';
}
export type NativeModuleBaseTypeAnnotation =
| NativeModuleStringTypeAnnotation
| NativeModuleNumberTypeAnnotation
| NativeModuleInt32TypeAnnotation
| NativeModuleDoubleTypeAnnotation
| NativeModuleFloatTypeAnnotation
| NativeModuleBooleanTypeAnnotation
| NativeModuleEnumDeclaration
| NativeModuleGenericObjectTypeAnnotation
| ReservedTypeAnnotation
| NativeModuleTypeAliasTypeAnnotation
| NativeModuleArrayTypeAnnotation<Nullable<NativeModuleBaseTypeAnnotation>>
| NativeModuleObjectTypeAnnotation
| NativeModuleUnionTypeAnnotation
| NativeModuleMixedTypeAnnotation;
export type NativeModuleParamTypeAnnotation =
| NativeModuleBaseTypeAnnotation
| NativeModuleParamOnlyTypeAnnotation;
export type NativeModuleReturnTypeAnnotation =
| NativeModuleBaseTypeAnnotation
| NativeModuleReturnOnlyTypeAnnotation;
export type NativeModuleTypeAnnotation =
| NativeModuleBaseTypeAnnotation
| NativeModuleParamOnlyTypeAnnotation
| NativeModuleReturnOnlyTypeAnnotation;
export type NativeModuleParamOnlyTypeAnnotation = NativeModuleFunctionTypeAnnotation;
export type NativeModuleReturnOnlyTypeAnnotation =
| NativeModuleFunctionTypeAnnotation
| NativeModulePromiseTypeAnnotation
| VoidTypeAnnotation;

View File

@@ -0,0 +1,11 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';

View File

@@ -0,0 +1,397 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
export type PlatformType = 'iOS' | 'android';
export type SchemaType = $ReadOnly<{
modules: $ReadOnly<{
[hasteModuleName: string]: ComponentSchema | NativeModuleSchema,
}>,
}>;
/**
* Component Type Annotations
*/
export type DoubleTypeAnnotation = $ReadOnly<{
type: 'DoubleTypeAnnotation',
}>;
export type FloatTypeAnnotation = $ReadOnly<{
type: 'FloatTypeAnnotation',
}>;
export type BooleanTypeAnnotation = $ReadOnly<{
type: 'BooleanTypeAnnotation',
}>;
export type Int32TypeAnnotation = $ReadOnly<{
type: 'Int32TypeAnnotation',
}>;
export type StringTypeAnnotation = $ReadOnly<{
type: 'StringTypeAnnotation',
}>;
export type StringEnumTypeAnnotation = $ReadOnly<{
type: 'StringEnumTypeAnnotation',
options: $ReadOnlyArray<string>,
}>;
export type VoidTypeAnnotation = $ReadOnly<{
type: 'VoidTypeAnnotation',
}>;
export type ObjectTypeAnnotation<+T> = $ReadOnly<{
type: 'ObjectTypeAnnotation',
properties: $ReadOnlyArray<NamedShape<T>>,
// metadata for objects that generated from interfaces
baseTypes?: $ReadOnlyArray<string>,
}>;
export type MixedTypeAnnotation = $ReadOnly<{
type: 'MixedTypeAnnotation',
}>;
type FunctionTypeAnnotation<+P, +R> = $ReadOnly<{
type: 'FunctionTypeAnnotation',
params: $ReadOnlyArray<NamedShape<P>>,
returnTypeAnnotation: R,
}>;
export type NamedShape<+T> = $ReadOnly<{
name: string,
optional: boolean,
typeAnnotation: T,
}>;
export type ComponentSchema = $ReadOnly<{
type: 'Component',
components: $ReadOnly<{
[componentName: string]: ComponentShape,
}>,
}>;
export type ComponentShape = $ReadOnly<{
...OptionsShape,
extendsProps: $ReadOnlyArray<ExtendsPropsShape>,
events: $ReadOnlyArray<EventTypeShape>,
props: $ReadOnlyArray<NamedShape<PropTypeAnnotation>>,
commands: $ReadOnlyArray<NamedShape<CommandTypeAnnotation>>,
}>;
export type OptionsShape = $ReadOnly<{
interfaceOnly?: boolean,
// Use for components with no current paper rename in progress
// Does not check for new name
paperComponentName?: string,
// Use for components that are not used on other platforms.
excludedPlatforms?: $ReadOnlyArray<PlatformType>,
// Use for components currently being renamed in paper
// Will use new name if it is available and fallback to this name
paperComponentNameDeprecated?: string,
}>;
export type ExtendsPropsShape = $ReadOnly<{
type: 'ReactNativeBuiltInType',
knownTypeName: 'ReactNativeCoreViewProps',
}>;
export type EventTypeShape = $ReadOnly<{
name: string,
bubblingType: 'direct' | 'bubble',
optional: boolean,
paperTopLevelNameDeprecated?: string,
typeAnnotation: $ReadOnly<{
type: 'EventTypeAnnotation',
argument?: ObjectTypeAnnotation<EventTypeAnnotation>,
}>,
}>;
export type EventTypeAnnotation =
| BooleanTypeAnnotation
| StringTypeAnnotation
| DoubleTypeAnnotation
| FloatTypeAnnotation
| Int32TypeAnnotation
| MixedTypeAnnotation
| StringEnumTypeAnnotation
| ObjectTypeAnnotation<EventTypeAnnotation>
| $ReadOnly<{
type: 'ArrayTypeAnnotation',
elementType: EventTypeAnnotation,
}>;
export type ArrayTypeAnnotation = $ReadOnly<{
type: 'ArrayTypeAnnotation',
elementType:
| BooleanTypeAnnotation
| StringTypeAnnotation
| DoubleTypeAnnotation
| FloatTypeAnnotation
| Int32TypeAnnotation
| $ReadOnly<{
type: 'StringEnumTypeAnnotation',
default: string,
options: $ReadOnlyArray<string>,
}>
| ObjectTypeAnnotation<PropTypeAnnotation>
| ReservedPropTypeAnnotation
| $ReadOnly<{
type: 'ArrayTypeAnnotation',
elementType: ObjectTypeAnnotation<PropTypeAnnotation>,
}>,
}>;
export type PropTypeAnnotation =
| $ReadOnly<{
type: 'BooleanTypeAnnotation',
default: boolean | null,
}>
| $ReadOnly<{
type: 'StringTypeAnnotation',
default: string | null,
}>
| $ReadOnly<{
type: 'DoubleTypeAnnotation',
default: number,
}>
| $ReadOnly<{
type: 'FloatTypeAnnotation',
default: number | null,
}>
| $ReadOnly<{
type: 'Int32TypeAnnotation',
default: number,
}>
| $ReadOnly<{
type: 'StringEnumTypeAnnotation',
default: string,
options: $ReadOnlyArray<string>,
}>
| $ReadOnly<{
type: 'Int32EnumTypeAnnotation',
default: number,
options: $ReadOnlyArray<number>,
}>
| ReservedPropTypeAnnotation
| ObjectTypeAnnotation<PropTypeAnnotation>
| ArrayTypeAnnotation
| MixedTypeAnnotation;
export type ReservedPropTypeAnnotation = $ReadOnly<{
type: 'ReservedPropTypeAnnotation',
name:
| 'ColorPrimitive'
| 'ImageSourcePrimitive'
| 'PointPrimitive'
| 'EdgeInsetsPrimitive'
| 'ImageRequestPrimitive'
| 'DimensionPrimitive',
}>;
export type CommandTypeAnnotation = FunctionTypeAnnotation<
CommandParamTypeAnnotation,
VoidTypeAnnotation,
>;
export type CommandParamTypeAnnotation =
| ReservedTypeAnnotation
| BooleanTypeAnnotation
| Int32TypeAnnotation
| DoubleTypeAnnotation
| FloatTypeAnnotation
| StringTypeAnnotation
| ArrayTypeAnnotation;
export type ReservedTypeAnnotation = $ReadOnly<{
type: 'ReservedTypeAnnotation',
name: 'RootTag', // Union with more custom types.
}>;
/**
* NativeModule Types
*/
export type Nullable<+T: NativeModuleTypeAnnotation> =
| NullableTypeAnnotation<T>
| T;
export type NullableTypeAnnotation<+T: NativeModuleTypeAnnotation> = $ReadOnly<{
type: 'NullableTypeAnnotation',
typeAnnotation: T,
}>;
export type NativeModuleSchema = $ReadOnly<{
type: 'NativeModule',
aliasMap: NativeModuleAliasMap,
enumMap: NativeModuleEnumMap,
spec: NativeModuleSpec,
moduleName: string,
// Use for modules that are not used on other platforms.
// TODO: It's clearer to define `restrictedToPlatforms` instead, but
// `excludedPlatforms` is used here to be consistent with ComponentSchema.
excludedPlatforms?: $ReadOnlyArray<PlatformType>,
}>;
type NativeModuleSpec = $ReadOnly<{
properties: $ReadOnlyArray<NativeModulePropertyShape>,
}>;
export type NativeModulePropertyShape = NamedShape<
Nullable<NativeModuleFunctionTypeAnnotation>,
>;
export type NativeModuleEnumMap = $ReadOnly<{
[enumName: string]: NativeModuleEnumDeclarationWithMembers,
}>;
export type NativeModuleAliasMap = $ReadOnly<{
[aliasName: string]: NativeModuleObjectTypeAnnotation,
}>;
export type NativeModuleFunctionTypeAnnotation = FunctionTypeAnnotation<
Nullable<NativeModuleParamTypeAnnotation>,
Nullable<NativeModuleReturnTypeAnnotation>,
>;
export type NativeModuleObjectTypeAnnotation = ObjectTypeAnnotation<
Nullable<NativeModuleBaseTypeAnnotation>,
>;
export type NativeModuleArrayTypeAnnotation<
+T: Nullable<NativeModuleBaseTypeAnnotation>,
> = $ReadOnly<{
type: 'ArrayTypeAnnotation',
/**
* TODO(T72031674): Migrate all our NativeModule specs to not use
* invalid Array ElementTypes. Then, make the elementType required.
*/
elementType?: T,
}>;
export type NativeModuleStringTypeAnnotation = $ReadOnly<{
type: 'StringTypeAnnotation',
}>;
export type NativeModuleNumberTypeAnnotation = $ReadOnly<{
type: 'NumberTypeAnnotation',
}>;
export type NativeModuleInt32TypeAnnotation = $ReadOnly<{
type: 'Int32TypeAnnotation',
}>;
export type NativeModuleDoubleTypeAnnotation = $ReadOnly<{
type: 'DoubleTypeAnnotation',
}>;
export type NativeModuleFloatTypeAnnotation = $ReadOnly<{
type: 'FloatTypeAnnotation',
}>;
export type NativeModuleBooleanTypeAnnotation = $ReadOnly<{
type: 'BooleanTypeAnnotation',
}>;
export type NativeModuleEnumMembers = $ReadOnlyArray<
$ReadOnly<{
name: string,
value: string,
}>,
>;
export type NativeModuleEnumMemberType =
| 'NumberTypeAnnotation'
| 'StringTypeAnnotation';
export type NativeModuleEnumDeclaration = $ReadOnly<{
name: string,
type: 'EnumDeclaration',
memberType: NativeModuleEnumMemberType,
}>;
export type NativeModuleEnumDeclarationWithMembers = {
name: string,
type: 'EnumDeclarationWithMembers',
memberType: NativeModuleEnumMemberType,
members: NativeModuleEnumMembers,
};
export type NativeModuleGenericObjectTypeAnnotation = $ReadOnly<{
type: 'GenericObjectTypeAnnotation',
// a dictionary type is codegen as "Object"
// but we know all its members are in the same type
// when it happens, the following field is non-null
dictionaryValueType?: Nullable<NativeModuleTypeAnnotation>,
}>;
export type NativeModuleTypeAliasTypeAnnotation = $ReadOnly<{
type: 'TypeAliasTypeAnnotation',
name: string,
}>;
export type NativeModulePromiseTypeAnnotation = $ReadOnly<{
type: 'PromiseTypeAnnotation',
elementType?: Nullable<NativeModuleBaseTypeAnnotation>,
}>;
export type UnionTypeAnnotationMemberType =
| 'NumberTypeAnnotation'
| 'ObjectTypeAnnotation'
| 'StringTypeAnnotation';
export type NativeModuleUnionTypeAnnotation = $ReadOnly<{
type: 'UnionTypeAnnotation',
memberType: UnionTypeAnnotationMemberType,
}>;
export type NativeModuleMixedTypeAnnotation = $ReadOnly<{
type: 'MixedTypeAnnotation',
}>;
export type NativeModuleBaseTypeAnnotation =
| NativeModuleStringTypeAnnotation
| NativeModuleNumberTypeAnnotation
| NativeModuleInt32TypeAnnotation
| NativeModuleDoubleTypeAnnotation
| NativeModuleFloatTypeAnnotation
| NativeModuleBooleanTypeAnnotation
| NativeModuleEnumDeclaration
| NativeModuleGenericObjectTypeAnnotation
| ReservedTypeAnnotation
| NativeModuleTypeAliasTypeAnnotation
| NativeModuleArrayTypeAnnotation<Nullable<NativeModuleBaseTypeAnnotation>>
| NativeModuleObjectTypeAnnotation
| NativeModuleUnionTypeAnnotation
| NativeModuleMixedTypeAnnotation;
export type NativeModuleParamTypeAnnotation =
| NativeModuleBaseTypeAnnotation
| NativeModuleParamOnlyTypeAnnotation;
export type NativeModuleReturnTypeAnnotation =
| NativeModuleBaseTypeAnnotation
| NativeModuleReturnOnlyTypeAnnotation;
export type NativeModuleTypeAnnotation =
| NativeModuleBaseTypeAnnotation
| NativeModuleParamOnlyTypeAnnotation
| NativeModuleReturnOnlyTypeAnnotation;
type NativeModuleParamOnlyTypeAnnotation = NativeModuleFunctionTypeAnnotation;
type NativeModuleReturnOnlyTypeAnnotation =
| NativeModulePromiseTypeAnnotation
| VoidTypeAnnotation;

View File

@@ -0,0 +1,11 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import type { SchemaType } from './CodegenSchema';
export declare function getErrors(schema: SchemaType): readonly string[];
export declare function validate(schema: SchemaType): void;

View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const nullthrows = require('nullthrows');
function getErrors(schema) {
const errors = new Set();
// Map of component name -> Array of module names
const componentModules = new Map();
Object.keys(schema.modules).forEach(moduleName => {
const module = schema.modules[moduleName];
if (module.components == null) {
return;
}
Object.keys(module.components).forEach(componentName => {
if (module.components == null) {
return;
}
if (!componentModules.has(componentName)) {
componentModules.set(componentName, []);
}
nullthrows(componentModules.get(componentName)).push(moduleName);
});
});
componentModules.forEach((modules, componentName) => {
if (modules.length > 1) {
errors.add(
`Duplicate components found with name ${componentName}. Found in modules ${modules.join(
', ',
)}`,
);
}
});
return Array.from(errors).sort();
}
function validate(schema) {
const errors = getErrors(schema);
if (errors.length !== 0) {
throw new Error('Errors found validating schema:\n' + errors.join('\n'));
}
}
module.exports = {
getErrors,
validate,
};

View File

@@ -0,0 +1,67 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
import type {SchemaType} from './CodegenSchema';
const nullthrows = require('nullthrows');
function getErrors(schema: SchemaType): $ReadOnlyArray<string> {
const errors = new Set<string>();
// Map of component name -> Array of module names
const componentModules: Map<string, Array<string>> = new Map();
Object.keys(schema.modules).forEach(moduleName => {
const module = schema.modules[moduleName];
if (module.components == null) {
return;
}
Object.keys(module.components).forEach(componentName => {
if (module.components == null) {
return;
}
if (!componentModules.has(componentName)) {
componentModules.set(componentName, []);
}
nullthrows(componentModules.get(componentName)).push(moduleName);
});
});
componentModules.forEach((modules, componentName) => {
if (modules.length > 1) {
errors.add(
`Duplicate components found with name ${componentName}. Found in modules ${modules.join(
', ',
)}`,
);
}
});
return Array.from(errors).sort();
}
function validate(schema: SchemaType) {
const errors = getErrors(schema);
if (errors.length !== 0) {
throw new Error('Errors found validating schema:\n' + errors.join('\n'));
}
}
module.exports = {
getErrors,
validate,
};

View File

@@ -0,0 +1,75 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
* @oncall react_native
*/
'use strict';
function _toArray(arr) {
return (
_arrayWithHoles(arr) ||
_iterableToArray(arr) ||
_unsupportedIterableToArray(arr) ||
_nonIterableRest()
);
}
function _nonIterableRest() {
throw new TypeError(
'Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.',
);
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === 'string') return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === 'Object' && o.constructor) n = o.constructor.name;
if (n === 'Map' || n === 'Set') return Array.from(o);
if (n === 'Arguments' || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))
return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _iterableToArray(iter) {
if (
(typeof Symbol !== 'undefined' && iter[Symbol.iterator] != null) ||
iter['@@iterator'] != null
)
return Array.from(iter);
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
const _require = require('./combine-js-to-schema'),
combineSchemasInFileListAndWriteToFile =
_require.combineSchemasInFileListAndWriteToFile;
const yargs = require('yargs');
const argv = yargs
.option('p', {
alias: 'platform',
})
.option('e', {
alias: 'exclude',
})
.parseSync();
const _argv$_ = _toArray(argv._),
outfile = _argv$_[0],
fileList = _argv$_.slice(1);
const platform = argv.platform;
const exclude = argv.exclude;
const excludeRegExp =
exclude != null && exclude !== '' ? new RegExp(exclude) : null;
combineSchemasInFileListAndWriteToFile(
fileList,
platform != null ? platform.toLowerCase() : platform,
outfile,
excludeRegExp,
);

View File

@@ -0,0 +1,39 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
'use strict';
const {
combineSchemasInFileListAndWriteToFile,
} = require('./combine-js-to-schema');
const yargs = require('yargs');
const argv = yargs
.option('p', {
alias: 'platform',
})
.option('e', {
alias: 'exclude',
})
.parseSync();
const [outfile, ...fileList] = argv._;
const platform: ?string = argv.platform;
const exclude: string = argv.exclude;
const excludeRegExp: ?RegExp =
exclude != null && exclude !== '' ? new RegExp(exclude) : null;
combineSchemasInFileListAndWriteToFile(
fileList,
platform != null ? platform.toLowerCase() : platform,
outfile,
excludeRegExp,
);

View File

@@ -0,0 +1,158 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
enumerableOnly &&
(symbols = symbols.filter(function (sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
})),
keys.push.apply(keys, symbols);
}
return keys;
}
function _objectSpread(target) {
for (var i = 1; i < arguments.length; i++) {
var source = null != arguments[i] ? arguments[i] : {};
i % 2
? ownKeys(Object(source), !0).forEach(function (key) {
_defineProperty(target, key, source[key]);
})
: Object.getOwnPropertyDescriptors
? Object.defineProperties(
target,
Object.getOwnPropertyDescriptors(source),
)
: ownKeys(Object(source)).forEach(function (key) {
Object.defineProperty(
target,
key,
Object.getOwnPropertyDescriptor(source, key),
);
});
}
return target;
}
function _defineProperty(obj, key, value) {
key = _toPropertyKey(key);
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true,
});
} else {
obj[key] = value;
}
return obj;
}
function _toPropertyKey(arg) {
var key = _toPrimitive(arg, 'string');
return typeof key === 'symbol' ? key : String(key);
}
function _toPrimitive(input, hint) {
if (typeof input !== 'object' || input === null) return input;
var prim = input[Symbol.toPrimitive];
if (prim !== undefined) {
var res = prim.call(input, hint || 'default');
if (typeof res !== 'object') return res;
throw new TypeError('@@toPrimitive must return a primitive value.');
}
return (hint === 'string' ? String : Number)(input);
}
const _require = require('../../parsers/flow/parser'),
FlowParser = _require.FlowParser;
const _require2 = require('../../parsers/typescript/parser'),
TypeScriptParser = _require2.TypeScriptParser;
const _require3 = require('./combine-utils'),
filterJSFile = _require3.filterJSFile;
const fs = require('fs');
const glob = require('glob');
const path = require('path');
const flowParser = new FlowParser();
const typescriptParser = new TypeScriptParser();
function combineSchemas(files) {
return files.reduce(
(merged, filename) => {
const contents = fs.readFileSync(filename, 'utf8');
if (
contents &&
(/export\s+default\s+\(?codegenNativeComponent</.test(contents) ||
/extends TurboModule/.test(contents))
) {
const isTypeScript =
path.extname(filename) === '.ts' || path.extname(filename) === '.tsx';
const parser = isTypeScript ? typescriptParser : flowParser;
const schema = parser.parseFile(filename);
if (schema && schema.modules) {
merged.modules = _objectSpread(
_objectSpread({}, merged.modules),
schema.modules,
);
}
}
return merged;
},
{
modules: {},
},
);
}
function expandDirectoriesIntoFiles(fileList, platform, exclude) {
return fileList
.flatMap(file => {
if (!fs.lstatSync(file).isDirectory()) {
return [file];
}
const filePattern = path.sep === '\\' ? file.replace(/\\/g, '/') : file;
return glob.sync(`${filePattern}/**/*.{js,ts,tsx}`, {
nodir: true,
// TODO: This will remove the need of slash substitution above for Windows,
// but it requires glob@v9+; with the package currenlty relying on
// glob@7.1.1; and flow-typed repo not having definitions for glob@9+.
// windowsPathsNoEscape: true,
});
})
.filter(element => filterJSFile(element, platform, exclude));
}
function combineSchemasInFileList(fileList, platform, exclude) {
const expandedFileList = expandDirectoriesIntoFiles(
fileList,
platform,
exclude,
);
const combined = combineSchemas(expandedFileList);
if (Object.keys(combined.modules).length === 0) {
console.error(
'No modules to process in combine-js-to-schema-cli. If this is unexpected, please check if you set up your NativeComponent correctly. See combine-js-to-schema.js for how codegen finds modules.',
);
}
return combined;
}
function combineSchemasInFileListAndWriteToFile(
fileList,
platform,
outfile,
exclude,
) {
const combined = combineSchemasInFileList(fileList, platform, exclude);
const formattedSchema = JSON.stringify(combined, null, 2);
fs.writeFileSync(outfile, formattedSchema);
}
module.exports = {
combineSchemas,
combineSchemasInFileList,
combineSchemasInFileListAndWriteToFile,
};

View File

@@ -0,0 +1,107 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
import type {SchemaType} from '../../CodegenSchema.js';
const {FlowParser} = require('../../parsers/flow/parser');
const {TypeScriptParser} = require('../../parsers/typescript/parser');
const {filterJSFile} = require('./combine-utils');
const fs = require('fs');
const glob = require('glob');
const path = require('path');
const flowParser = new FlowParser();
const typescriptParser = new TypeScriptParser();
function combineSchemas(files: Array<string>): SchemaType {
return files.reduce(
(merged, filename) => {
const contents = fs.readFileSync(filename, 'utf8');
if (
contents &&
(/export\s+default\s+\(?codegenNativeComponent</.test(contents) ||
/extends TurboModule/.test(contents))
) {
const isTypeScript =
path.extname(filename) === '.ts' || path.extname(filename) === '.tsx';
const parser = isTypeScript ? typescriptParser : flowParser;
const schema = parser.parseFile(filename);
if (schema && schema.modules) {
merged.modules = {...merged.modules, ...schema.modules};
}
}
return merged;
},
{modules: {}},
);
}
function expandDirectoriesIntoFiles(
fileList: Array<string>,
platform: ?string,
exclude: ?RegExp,
): Array<string> {
return fileList
.flatMap(file => {
if (!fs.lstatSync(file).isDirectory()) {
return [file];
}
const filePattern = path.sep === '\\' ? file.replace(/\\/g, '/') : file;
return glob.sync(`${filePattern}/**/*.{js,ts,tsx}`, {
nodir: true,
// TODO: This will remove the need of slash substitution above for Windows,
// but it requires glob@v9+; with the package currenlty relying on
// glob@7.1.1; and flow-typed repo not having definitions for glob@9+.
// windowsPathsNoEscape: true,
});
})
.filter(element => filterJSFile(element, platform, exclude));
}
function combineSchemasInFileList(
fileList: Array<string>,
platform: ?string,
exclude: ?RegExp,
): SchemaType {
const expandedFileList = expandDirectoriesIntoFiles(
fileList,
platform,
exclude,
);
const combined = combineSchemas(expandedFileList);
if (Object.keys(combined.modules).length === 0) {
console.error(
'No modules to process in combine-js-to-schema-cli. If this is unexpected, please check if you set up your NativeComponent correctly. See combine-js-to-schema.js for how codegen finds modules.',
);
}
return combined;
}
function combineSchemasInFileListAndWriteToFile(
fileList: Array<string>,
platform: ?string,
outfile: string,
exclude: ?RegExp,
): void {
const combined = combineSchemasInFileList(fileList, platform, exclude);
const formattedSchema = JSON.stringify(combined, null, 2);
fs.writeFileSync(outfile, formattedSchema);
}
module.exports = {
combineSchemas,
combineSchemasInFileList,
combineSchemasInFileListAndWriteToFile,
};

View File

@@ -0,0 +1,77 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const assert = require('assert');
const fs = require('fs');
const yargs = require('yargs');
const argv = yargs
.option('p', {
alias: 'platform',
type: 'string',
demandOption: true,
})
.option('o', {
alias: 'output',
})
.option('s', {
alias: 'schema-query',
})
.parseSync();
const platform = argv.platform.toLowerCase();
const output = argv.output;
const schemaQuery = argv.s;
if (!['ios', 'android'].includes(platform)) {
throw new Error(`Invalid platform ${platform}`);
}
if (!schemaQuery.startsWith('@')) {
throw new Error(
"The argument provided to --schema-query must be a filename that starts with '@'.",
);
}
const schemaQueryOutputFile = schemaQuery.replace(/^@/, '');
const schemaQueryOutput = fs.readFileSync(schemaQueryOutputFile, 'utf8');
const schemaFiles = schemaQueryOutput.split(' ');
const modules = {};
const specNameToFile = {};
for (const file of schemaFiles) {
const schema = JSON.parse(fs.readFileSync(file, 'utf8'));
if (schema.modules) {
for (const specName in schema.modules) {
const module = schema.modules[specName];
if (modules[specName]) {
assert.deepEqual(
module,
modules[specName],
`App contained two specs with the same file name '${specName}'. Schemas: ${specNameToFile[specName]}, ${file}. Please rename one of the specs.`,
);
}
if (
module.excludedPlatforms &&
module.excludedPlatforms.indexOf(platform) >= 0
) {
continue;
}
modules[specName] = module;
specNameToFile[specName] = file;
}
}
}
fs.writeFileSync(
output,
JSON.stringify(
{
modules,
},
null,
2,
),
);

View File

@@ -0,0 +1,87 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
import type {
ComponentSchema,
NativeModuleSchema,
SchemaType,
} from '../../CodegenSchema.js';
const assert = require('assert');
const fs = require('fs');
const yargs = require('yargs');
const argv = yargs
.option('p', {
alias: 'platform',
type: 'string',
demandOption: true,
})
.option('o', {
alias: 'output',
})
.option('s', {
alias: 'schema-query',
})
.parseSync();
const platform: string = argv.platform.toLowerCase();
const output: string = argv.output;
const schemaQuery: string = argv.s;
if (!['ios', 'android'].includes(platform)) {
throw new Error(`Invalid platform ${platform}`);
}
if (!schemaQuery.startsWith('@')) {
throw new Error(
"The argument provided to --schema-query must be a filename that starts with '@'.",
);
}
const schemaQueryOutputFile = schemaQuery.replace(/^@/, '');
const schemaQueryOutput = fs.readFileSync(schemaQueryOutputFile, 'utf8');
const schemaFiles = schemaQueryOutput.split(' ');
const modules: {
[hasteModuleName: string]: NativeModuleSchema | ComponentSchema,
} = {};
const specNameToFile: {[hasteModuleName: string]: string} = {};
for (const file of schemaFiles) {
const schema: SchemaType = JSON.parse(fs.readFileSync(file, 'utf8'));
if (schema.modules) {
for (const specName in schema.modules) {
const module = schema.modules[specName];
if (modules[specName]) {
assert.deepEqual(
module,
modules[specName],
`App contained two specs with the same file name '${specName}'. Schemas: ${specNameToFile[specName]}, ${file}. Please rename one of the specs.`,
);
}
if (
module.excludedPlatforms &&
module.excludedPlatforms.indexOf(platform) >= 0
) {
continue;
}
modules[specName] = module;
specNameToFile[specName] = file;
}
}
}
fs.writeFileSync(output, JSON.stringify({modules}, null, 2));

View File

@@ -0,0 +1,56 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
* @oncall react_native
*/
'use strict';
const path = require('path');
/**
* This function is used by the CLI to decide whether a JS/TS file has to be processed or not by the Codegen.
* Parameters:
* - file: the path to the file
* - currentPlatform: the current platform for which we are creating the specs
* Returns: `true` if the file can be used to generate some code; `false` otherwise
*
*/
function filterJSFile(file, currentPlatform, excludeRegExp) {
const isSpecFile = /^(Native.+|.+NativeComponent)/.test(path.basename(file));
const isNotNativeUIManager = !file.endsWith('NativeUIManager.js');
const isNotTest = !file.includes('__tests');
const isNotExcluded = excludeRegExp == null || !excludeRegExp.test(file);
const isNotTSTypeDefinition = !file.endsWith('.d.ts');
const isValidCandidate =
isSpecFile &&
isNotNativeUIManager &&
isNotExcluded &&
isNotTest &&
isNotTSTypeDefinition;
const filenameComponents = path.basename(file).split('.');
const isPlatformAgnostic = filenameComponents.length === 2;
if (currentPlatform == null) {
// need to accept only files that are platform agnostic
return isValidCandidate && isPlatformAgnostic;
}
// If a platform is passed, accept both platform agnostic specs...
if (isPlatformAgnostic) {
return isValidCandidate;
}
// ...and specs that share the same platform as the one passed.
// specfiles must follow the pattern: <filename>[.<platform>].(js|ts|tsx)
const filePlatform =
filenameComponents.length > 2 ? filenameComponents[1] : 'unknown';
return isValidCandidate && currentPlatform === filePlatform;
}
module.exports = {
filterJSFile,
};

View File

@@ -0,0 +1,64 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
'use strict';
const path = require('path');
/**
* This function is used by the CLI to decide whether a JS/TS file has to be processed or not by the Codegen.
* Parameters:
* - file: the path to the file
* - currentPlatform: the current platform for which we are creating the specs
* Returns: `true` if the file can be used to generate some code; `false` otherwise
*
*/
function filterJSFile(
file: string,
currentPlatform: ?string,
excludeRegExp: ?RegExp,
): boolean {
const isSpecFile = /^(Native.+|.+NativeComponent)/.test(path.basename(file));
const isNotNativeUIManager = !file.endsWith('NativeUIManager.js');
const isNotTest = !file.includes('__tests');
const isNotExcluded = excludeRegExp == null || !excludeRegExp.test(file);
const isNotTSTypeDefinition = !file.endsWith('.d.ts');
const isValidCandidate =
isSpecFile &&
isNotNativeUIManager &&
isNotExcluded &&
isNotTest &&
isNotTSTypeDefinition;
const filenameComponents = path.basename(file).split('.');
const isPlatformAgnostic = filenameComponents.length === 2;
if (currentPlatform == null) {
// need to accept only files that are platform agnostic
return isValidCandidate && isPlatformAgnostic;
}
// If a platform is passed, accept both platform agnostic specs...
if (isPlatformAgnostic) {
return isValidCandidate;
}
// ...and specs that share the same platform as the one passed.
// specfiles must follow the pattern: <filename>[.<platform>].(js|ts|tsx)
const filePlatform =
filenameComponents.length > 2 ? filenameComponents[1] : 'unknown';
return isValidCandidate && currentPlatform === filePlatform;
}
module.exports = {
filterJSFile,
};

View File

@@ -0,0 +1,65 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
/**
* This generates all possible outputs by executing all available generators.
*/
'use strict';
const RNCodegen = require('../../generators/RNCodegen.js');
const fs = require('fs');
const mkdirp = require('mkdirp');
const args = process.argv.slice(2);
if (args.length < 3) {
throw new Error(
`Expected to receive path to schema, library name, output directory and module spec name. Received ${args.join(
', ',
)}`,
);
}
const schemaPath = args[0];
const libraryName = args[1];
const outputDirectory = args[2];
const packageName = args[3];
const assumeNonnull = args[4] === 'true' || args[4] === 'True';
const schemaText = fs.readFileSync(schemaPath, 'utf-8');
if (schemaText == null) {
throw new Error(`Can't find schema at ${schemaPath}`);
}
mkdirp.sync(outputDirectory);
let schema;
try {
schema = JSON.parse(schemaText);
} catch (err) {
throw new Error(`Can't parse schema to JSON. ${schemaPath}`);
}
RNCodegen.generate(
{
libraryName,
schema,
outputDirectory,
packageName,
assumeNonnull,
},
{
generators: [
'descriptors',
'events',
'props',
'states',
'tests',
'shadow-nodes',
'modulesAndroid',
'modulesCxx',
'modulesIOS',
],
},
);

View File

@@ -0,0 +1,66 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
/**
* This generates all possible outputs by executing all available generators.
*/
'use strict';
const RNCodegen = require('../../generators/RNCodegen.js');
const fs = require('fs');
const mkdirp = require('mkdirp');
const args = process.argv.slice(2);
if (args.length < 3) {
throw new Error(
`Expected to receive path to schema, library name, output directory and module spec name. Received ${args.join(
', ',
)}`,
);
}
const schemaPath = args[0];
const libraryName = args[1];
const outputDirectory = args[2];
const packageName = args[3];
const assumeNonnull = args[4] === 'true' || args[4] === 'True';
const schemaText = fs.readFileSync(schemaPath, 'utf-8');
if (schemaText == null) {
throw new Error(`Can't find schema at ${schemaPath}`);
}
mkdirp.sync(outputDirectory);
let schema;
try {
schema = JSON.parse(schemaText);
} catch (err) {
throw new Error(`Can't parse schema to JSON. ${schemaPath}`);
}
RNCodegen.generate(
{libraryName, schema, outputDirectory, packageName, assumeNonnull},
{
generators: [
'descriptors',
'events',
'props',
'states',
'tests',
'shadow-nodes',
'modulesAndroid',
'modulesCxx',
'modulesIOS',
],
},
);

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
* @oncall react_native
*/
'use strict';
function _toArray(arr) {
return (
_arrayWithHoles(arr) ||
_iterableToArray(arr) ||
_unsupportedIterableToArray(arr) ||
_nonIterableRest()
);
}
function _nonIterableRest() {
throw new TypeError(
'Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.',
);
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === 'string') return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === 'Object' && o.constructor) n = o.constructor.name;
if (n === 'Map' || n === 'Set') return Array.from(o);
if (n === 'Arguments' || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))
return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _iterableToArray(iter) {
if (
(typeof Symbol !== 'undefined' && iter[Symbol.iterator] != null) ||
iter['@@iterator'] != null
)
return Array.from(iter);
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
const parseFiles = require('./parser.js');
const _process$argv$slice = process.argv.slice(2),
_process$argv$slice2 = _toArray(_process$argv$slice),
fileList = _process$argv$slice2.slice(0);
parseFiles(fileList);

View File

@@ -0,0 +1,18 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
'use strict';
const parseFiles = require('./parser.js');
const [...fileList] = process.argv.slice(2);
parseFiles(fileList);

View File

@@ -0,0 +1,28 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const _require = require('../../parsers/flow/parser'),
FlowParser = _require.FlowParser;
const _require2 = require('../../parsers/typescript/parser'),
TypeScriptParser = _require2.TypeScriptParser;
const path = require('path');
const flowParser = new FlowParser();
const typescriptParser = new TypeScriptParser();
function parseFiles(files) {
files.forEach(filename => {
const isTypeScript =
path.extname(filename) === '.ts' || path.extname(filename) === '.tsx';
const parser = isTypeScript ? typescriptParser : flowParser;
console.log(filename, JSON.stringify(parser.parseFile(filename), null, 2));
});
}
module.exports = parseFiles;

View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
const {FlowParser} = require('../../parsers/flow/parser');
const {TypeScriptParser} = require('../../parsers/typescript/parser');
const path = require('path');
const flowParser = new FlowParser();
const typescriptParser = new TypeScriptParser();
function parseFiles(files: Array<string>) {
files.forEach(filename => {
const isTypeScript =
path.extname(filename) === '.ts' || path.extname(filename) === '.tsx';
const parser = isTypeScript ? typescriptParser : flowParser;
console.log(filename, JSON.stringify(parser.parseFile(filename), null, 2));
});
}
module.exports = parseFiles;

View File

@@ -0,0 +1,15 @@
#!/bin/bash
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
set -e
set -u
THIS_DIR=$(cd -P "$(dirname "$(realpath "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd)
# shellcheck source=xplat/js/env-utils/setup_env_vars.sh
source "$THIS_DIR/../../../../../../env-utils/setup_env_vars.sh"
exec "$FLOW_NODE_BINARY" "$THIS_DIR/parser.js" "$@"

View File

@@ -0,0 +1,99 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import type { SchemaType } from '../CodegenSchema';
export type FilesOutput = Map<string, string>;
export type LibraryGeneratorFunction = (libraryName: string, schema: SchemaType, packageName: string | undefined, assumeNonnull: boolean) => FilesOutput;
export type SchemaGeneratorFunction = (schemas: { [key: string]: SchemaType }) => FilesOutput;
export type ViewGeneratorFunction = (libraryName: string, schema: SchemaType) => FilesOutput;
type LibraryGeneratorNames =
| 'generateComponentDescriptorH'
| 'generateComponentDescriptorCpp'
| 'generateComponentHObjCpp'
| 'generateEventEmitterCpp'
| 'generateEventEmitterH'
| 'generatePropsCpp'
| 'generatePropsH'
| 'generateStateCpp'
| 'generateStateH'
| 'generateModuleH'
| 'generateModuleCpp'
| 'generateModuleObjCpp'
| 'generateModuleJavaSpec'
| 'generateModuleJniCpp'
| 'generateModuleJniH'
| 'generatePropsJavaInterface'
| 'generatePropsJavaDelegate'
| 'generateTests'
| 'generateShadowNodeCpp'
| 'generateShadowNodeH'
;
type SchemaGeneratorNames =
| 'generateThirdPartyFabricComponentsProviderObjCpp'
| 'generateThirdPartyFabricComponentsProviderH'
;
type ViewGeneratorNames =
| 'generateViewConfigJs'
;
export type AllGenerators =
& { readonly [key in LibraryGeneratorNames]: LibraryGeneratorFunction; }
& { readonly [key in SchemaGeneratorNames]: SchemaGeneratorFunction; }
& { readonly [key in ViewGeneratorNames]: ViewGeneratorFunction; }
;
export type LibraryGenerators =
| 'componentsAndroid'
| 'componentsIOS'
| 'descriptors'
| 'events'
| 'props'
| 'states'
| 'tests'
| 'shadow-nodes'
| 'modulesAndroid'
| 'modulesCxx'
| 'modulesIOS'
;
export type SchemaGenerators =
| 'providerIOS'
;
export interface LibraryOptions {
libraryName: string;
schema: SchemaType;
outputDirectory: string;
packageName?: string | undefined;
assumeNonnull: boolean;
}
export interface LibraryConfig {
generators: LibraryGenerators[];
test?: boolean | undefined;
}
export interface SchemasOptions {
schemas: { [key: string]: SchemaType };
outputDirectory: string;
}
export interface SchemasConfig {
generators: SchemaGenerators[];
test?: boolean | undefined;
}
export declare const allGenerators: AllGenerators;
export declare const libraryGenerators: { readonly [key in LibraryGenerators]: LibraryGeneratorFunction };
export declare const schemaGenerators: { readonly [key in SchemaGenerators]: SchemaGeneratorFunction };
export declare function generate(options: LibraryOptions, config: LibraryConfig): boolean;
export declare function generateFromSchemas(options: SchemasOptions, config: SchemasConfig): boolean;
export declare function generateViewConfig(options: LibraryOptions): string;

View File

@@ -0,0 +1,263 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
/*
TODO:
- ViewConfigs should spread in View's valid attributes
*/
const schemaValidator = require('../SchemaValidator.js');
const generateComponentDescriptorCpp = require('./components/GenerateComponentDescriptorCpp.js');
const generateComponentDescriptorH = require('./components/GenerateComponentDescriptorH.js');
const generateComponentHObjCpp = require('./components/GenerateComponentHObjCpp.js');
const generateEventEmitterCpp = require('./components/GenerateEventEmitterCpp.js');
const generateEventEmitterH = require('./components/GenerateEventEmitterH.js');
const generatePropsCpp = require('./components/GeneratePropsCpp.js');
const generatePropsH = require('./components/GeneratePropsH.js');
const generatePropsJavaDelegate = require('./components/GeneratePropsJavaDelegate.js');
const generatePropsJavaInterface = require('./components/GeneratePropsJavaInterface.js');
const generateShadowNodeCpp = require('./components/GenerateShadowNodeCpp.js');
const generateShadowNodeH = require('./components/GenerateShadowNodeH.js');
const generateStateCpp = require('./components/GenerateStateCpp.js');
const generateStateH = require('./components/GenerateStateH.js');
const generateTests = require('./components/GenerateTests.js');
const generateThirdPartyFabricComponentsProviderH = require('./components/GenerateThirdPartyFabricComponentsProviderH.js');
const generateThirdPartyFabricComponentsProviderObjCpp = require('./components/GenerateThirdPartyFabricComponentsProviderObjCpp.js');
const generateViewConfigJs = require('./components/GenerateViewConfigJs.js');
const generateModuleCpp = require('./modules/GenerateModuleCpp.js');
const generateModuleH = require('./modules/GenerateModuleH.js');
const generateModuleJavaSpec = require('./modules/GenerateModuleJavaSpec.js');
const generateModuleJniCpp = require('./modules/GenerateModuleJniCpp.js');
const generateModuleJniH = require('./modules/GenerateModuleJniH.js');
const generateModuleObjCpp = require('./modules/GenerateModuleObjCpp');
const fs = require('fs');
const path = require('path');
const ALL_GENERATORS = {
generateComponentDescriptorH: generateComponentDescriptorH.generate,
generateComponentDescriptorCpp: generateComponentDescriptorCpp.generate,
generateComponentHObjCpp: generateComponentHObjCpp.generate,
generateEventEmitterCpp: generateEventEmitterCpp.generate,
generateEventEmitterH: generateEventEmitterH.generate,
generatePropsCpp: generatePropsCpp.generate,
generatePropsH: generatePropsH.generate,
generateStateCpp: generateStateCpp.generate,
generateStateH: generateStateH.generate,
generateModuleH: generateModuleH.generate,
generateModuleCpp: generateModuleCpp.generate,
generateModuleObjCpp: generateModuleObjCpp.generate,
generateModuleJavaSpec: generateModuleJavaSpec.generate,
generateModuleJniCpp: generateModuleJniCpp.generate,
generateModuleJniH: generateModuleJniH.generate,
generatePropsJavaInterface: generatePropsJavaInterface.generate,
generatePropsJavaDelegate: generatePropsJavaDelegate.generate,
generateTests: generateTests.generate,
generateShadowNodeCpp: generateShadowNodeCpp.generate,
generateShadowNodeH: generateShadowNodeH.generate,
generateThirdPartyFabricComponentsProviderObjCpp:
generateThirdPartyFabricComponentsProviderObjCpp.generate,
generateThirdPartyFabricComponentsProviderH:
generateThirdPartyFabricComponentsProviderH.generate,
generateViewConfigJs: generateViewConfigJs.generate,
};
const LIBRARY_GENERATORS = {
descriptors: [generateComponentDescriptorH.generate],
events: [generateEventEmitterCpp.generate, generateEventEmitterH.generate],
states: [generateStateCpp.generate, generateStateH.generate],
props: [
generateComponentHObjCpp.generate,
generatePropsCpp.generate,
generatePropsH.generate,
generatePropsJavaInterface.generate,
generatePropsJavaDelegate.generate,
],
// TODO: Refactor this to consolidate various C++ output variation instead of forking per platform.
componentsAndroid: [
// JNI/C++ files
generateComponentDescriptorH.generate,
generateComponentDescriptorCpp.generate,
generateEventEmitterCpp.generate,
generateEventEmitterH.generate,
generatePropsCpp.generate,
generatePropsH.generate,
generateStateCpp.generate,
generateStateH.generate,
generateShadowNodeCpp.generate,
generateShadowNodeH.generate,
// Java files
generatePropsJavaInterface.generate,
generatePropsJavaDelegate.generate,
],
componentsIOS: [
generateComponentDescriptorH.generate,
generateComponentDescriptorCpp.generate,
generateEventEmitterCpp.generate,
generateEventEmitterH.generate,
generateComponentHObjCpp.generate,
generatePropsCpp.generate,
generatePropsH.generate,
generateStateCpp.generate,
generateStateH.generate,
generateShadowNodeCpp.generate,
generateShadowNodeH.generate,
],
modulesAndroid: [
generateModuleJniCpp.generate,
generateModuleJniH.generate,
generateModuleJavaSpec.generate,
],
modulesCxx: [generateModuleCpp.generate, generateModuleH.generate],
modulesIOS: [generateModuleObjCpp.generate],
tests: [generateTests.generate],
'shadow-nodes': [
generateShadowNodeCpp.generate,
generateShadowNodeH.generate,
],
};
const SCHEMAS_GENERATORS = {
providerIOS: [
generateThirdPartyFabricComponentsProviderObjCpp.generate,
generateThirdPartyFabricComponentsProviderH.generate,
],
};
function writeMapToFiles(map) {
let success = true;
map.forEach(file => {
try {
const location = path.join(file.outputDir, file.name);
const dirName = path.dirname(location);
if (!fs.existsSync(dirName)) {
fs.mkdirSync(dirName, {
recursive: true,
});
}
fs.writeFileSync(location, file.content);
} catch (error) {
success = false;
console.error(`Failed to write ${file.name} to ${file.outputDir}`, error);
}
});
return success;
}
function checkFilesForChanges(generated) {
let hasChanged = false;
generated.forEach(file => {
const location = path.join(file.outputDir, file.name);
const currentContents = fs.readFileSync(location, 'utf8');
if (currentContents !== file.content) {
console.error(`- ${file.name} has changed`);
hasChanged = true;
}
});
return !hasChanged;
}
function checkOrWriteFiles(generatedFiles, test) {
if (test === true) {
return checkFilesForChanges(generatedFiles);
}
return writeMapToFiles(generatedFiles);
}
module.exports = {
allGenerators: ALL_GENERATORS,
libraryGenerators: LIBRARY_GENERATORS,
schemaGenerators: SCHEMAS_GENERATORS,
generate(
{
libraryName,
schema,
outputDirectory,
packageName,
assumeNonnull,
useLocalIncludePaths,
},
{generators, test},
) {
schemaValidator.validate(schema);
const defaultHeaderPrefix = 'react/renderer/components';
const headerPrefix =
useLocalIncludePaths === true
? ''
: `${defaultHeaderPrefix}/${libraryName}/`;
function composePath(intermediate) {
return path.join(outputDirectory, intermediate, libraryName);
}
const componentIOSOutput = composePath(
useLocalIncludePaths === true ? '' : defaultHeaderPrefix,
);
const modulesIOSOutput = composePath('./');
const outputFoldersForGenerators = {
componentsIOS: componentIOSOutput,
modulesIOS: modulesIOSOutput,
descriptors: outputDirectory,
events: outputDirectory,
props: outputDirectory,
states: outputDirectory,
componentsAndroid: outputDirectory,
modulesAndroid: outputDirectory,
modulesCxx: outputDirectory,
tests: outputDirectory,
'shadow-nodes': outputDirectory,
};
const generatedFiles = [];
for (const name of generators) {
for (const generator of LIBRARY_GENERATORS[name]) {
generator(
libraryName,
schema,
packageName,
assumeNonnull,
headerPrefix,
).forEach((contents, fileName) => {
generatedFiles.push({
name: fileName,
content: contents,
outputDir: outputFoldersForGenerators[name],
});
});
}
}
return checkOrWriteFiles(generatedFiles, test);
},
generateFromSchemas(
{schemas, outputDirectory, supportedApplePlatforms},
{generators, test},
) {
Object.keys(schemas).forEach(libraryName =>
schemaValidator.validate(schemas[libraryName]),
);
const generatedFiles = [];
for (const name of generators) {
for (const generator of SCHEMAS_GENERATORS[name]) {
generator(schemas, supportedApplePlatforms).forEach(
(contents, fileName) => {
generatedFiles.push({
name: fileName,
content: contents,
outputDir: outputDirectory,
});
},
);
}
}
return checkOrWriteFiles(generatedFiles, test);
},
generateViewConfig({libraryName, schema}) {
schemaValidator.validate(schema);
const result = generateViewConfigJs
.generate(libraryName, schema)
.values()
.next();
if (typeof result.value !== 'string') {
throw new Error(`Failed to generate view config for ${libraryName}`);
}
return result.value;
},
};

View File

@@ -0,0 +1,335 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
/*
TODO:
- ViewConfigs should spread in View's valid attributes
*/
import type {SchemaType} from '../CodegenSchema';
const schemaValidator = require('../SchemaValidator.js');
const generateComponentDescriptorCpp = require('./components/GenerateComponentDescriptorCpp.js');
const generateComponentDescriptorH = require('./components/GenerateComponentDescriptorH.js');
const generateComponentHObjCpp = require('./components/GenerateComponentHObjCpp.js');
const generateEventEmitterCpp = require('./components/GenerateEventEmitterCpp.js');
const generateEventEmitterH = require('./components/GenerateEventEmitterH.js');
const generatePropsCpp = require('./components/GeneratePropsCpp.js');
const generatePropsH = require('./components/GeneratePropsH.js');
const generatePropsJavaDelegate = require('./components/GeneratePropsJavaDelegate.js');
const generatePropsJavaInterface = require('./components/GeneratePropsJavaInterface.js');
const generateShadowNodeCpp = require('./components/GenerateShadowNodeCpp.js');
const generateShadowNodeH = require('./components/GenerateShadowNodeH.js');
const generateStateCpp = require('./components/GenerateStateCpp.js');
const generateStateH = require('./components/GenerateStateH.js');
const generateTests = require('./components/GenerateTests.js');
const generateThirdPartyFabricComponentsProviderH = require('./components/GenerateThirdPartyFabricComponentsProviderH.js');
const generateThirdPartyFabricComponentsProviderObjCpp = require('./components/GenerateThirdPartyFabricComponentsProviderObjCpp.js');
const generateViewConfigJs = require('./components/GenerateViewConfigJs.js');
const generateModuleCpp = require('./modules/GenerateModuleCpp.js');
const generateModuleH = require('./modules/GenerateModuleH.js');
const generateModuleJavaSpec = require('./modules/GenerateModuleJavaSpec.js');
const generateModuleJniCpp = require('./modules/GenerateModuleJniCpp.js');
const generateModuleJniH = require('./modules/GenerateModuleJniH.js');
const generateModuleObjCpp = require('./modules/GenerateModuleObjCpp');
const fs = require('fs');
const path = require('path');
const ALL_GENERATORS = {
generateComponentDescriptorH: generateComponentDescriptorH.generate,
generateComponentDescriptorCpp: generateComponentDescriptorCpp.generate,
generateComponentHObjCpp: generateComponentHObjCpp.generate,
generateEventEmitterCpp: generateEventEmitterCpp.generate,
generateEventEmitterH: generateEventEmitterH.generate,
generatePropsCpp: generatePropsCpp.generate,
generatePropsH: generatePropsH.generate,
generateStateCpp: generateStateCpp.generate,
generateStateH: generateStateH.generate,
generateModuleH: generateModuleH.generate,
generateModuleCpp: generateModuleCpp.generate,
generateModuleObjCpp: generateModuleObjCpp.generate,
generateModuleJavaSpec: generateModuleJavaSpec.generate,
generateModuleJniCpp: generateModuleJniCpp.generate,
generateModuleJniH: generateModuleJniH.generate,
generatePropsJavaInterface: generatePropsJavaInterface.generate,
generatePropsJavaDelegate: generatePropsJavaDelegate.generate,
generateTests: generateTests.generate,
generateShadowNodeCpp: generateShadowNodeCpp.generate,
generateShadowNodeH: generateShadowNodeH.generate,
generateThirdPartyFabricComponentsProviderObjCpp:
generateThirdPartyFabricComponentsProviderObjCpp.generate,
generateThirdPartyFabricComponentsProviderH:
generateThirdPartyFabricComponentsProviderH.generate,
generateViewConfigJs: generateViewConfigJs.generate,
};
type LibraryOptions = $ReadOnly<{
libraryName: string,
schema: SchemaType,
outputDirectory: string,
packageName?: string, // Some platforms have a notion of package, which should be configurable.
assumeNonnull: boolean,
useLocalIncludePaths?: boolean,
}>;
type SchemasOptions = $ReadOnly<{
schemas: {[string]: SchemaType},
outputDirectory: string,
supportedApplePlatforms?: {[string]: {[string]: boolean}},
}>;
type LibraryGenerators =
| 'componentsAndroid'
| 'componentsIOS'
| 'descriptors'
| 'events'
| 'props'
| 'states'
| 'tests'
| 'shadow-nodes'
| 'modulesAndroid'
| 'modulesCxx'
| 'modulesIOS';
type SchemasGenerators = 'providerIOS';
type LibraryConfig = $ReadOnly<{
generators: Array<LibraryGenerators>,
test?: boolean,
}>;
type SchemasConfig = $ReadOnly<{
generators: Array<SchemasGenerators>,
test?: boolean,
}>;
const LIBRARY_GENERATORS = {
descriptors: [generateComponentDescriptorH.generate],
events: [generateEventEmitterCpp.generate, generateEventEmitterH.generate],
states: [generateStateCpp.generate, generateStateH.generate],
props: [
generateComponentHObjCpp.generate,
generatePropsCpp.generate,
generatePropsH.generate,
generatePropsJavaInterface.generate,
generatePropsJavaDelegate.generate,
],
// TODO: Refactor this to consolidate various C++ output variation instead of forking per platform.
componentsAndroid: [
// JNI/C++ files
generateComponentDescriptorH.generate,
generateComponentDescriptorCpp.generate,
generateEventEmitterCpp.generate,
generateEventEmitterH.generate,
generatePropsCpp.generate,
generatePropsH.generate,
generateStateCpp.generate,
generateStateH.generate,
generateShadowNodeCpp.generate,
generateShadowNodeH.generate,
// Java files
generatePropsJavaInterface.generate,
generatePropsJavaDelegate.generate,
],
componentsIOS: [
generateComponentDescriptorH.generate,
generateComponentDescriptorCpp.generate,
generateEventEmitterCpp.generate,
generateEventEmitterH.generate,
generateComponentHObjCpp.generate,
generatePropsCpp.generate,
generatePropsH.generate,
generateStateCpp.generate,
generateStateH.generate,
generateShadowNodeCpp.generate,
generateShadowNodeH.generate,
],
modulesAndroid: [
generateModuleJniCpp.generate,
generateModuleJniH.generate,
generateModuleJavaSpec.generate,
],
modulesCxx: [generateModuleCpp.generate, generateModuleH.generate],
modulesIOS: [generateModuleObjCpp.generate],
tests: [generateTests.generate],
'shadow-nodes': [
generateShadowNodeCpp.generate,
generateShadowNodeH.generate,
],
};
const SCHEMAS_GENERATORS = {
providerIOS: [
generateThirdPartyFabricComponentsProviderObjCpp.generate,
generateThirdPartyFabricComponentsProviderH.generate,
],
};
type CodeGenFile = {
name: string,
content: string,
outputDir: string,
};
function writeMapToFiles(map: Array<CodeGenFile>) {
let success = true;
map.forEach(file => {
try {
const location = path.join(file.outputDir, file.name);
const dirName = path.dirname(location);
if (!fs.existsSync(dirName)) {
fs.mkdirSync(dirName, {recursive: true});
}
fs.writeFileSync(location, file.content);
} catch (error) {
success = false;
console.error(`Failed to write ${file.name} to ${file.outputDir}`, error);
}
});
return success;
}
function checkFilesForChanges(generated: Array<CodeGenFile>): boolean {
let hasChanged = false;
generated.forEach(file => {
const location = path.join(file.outputDir, file.name);
const currentContents = fs.readFileSync(location, 'utf8');
if (currentContents !== file.content) {
console.error(`- ${file.name} has changed`);
hasChanged = true;
}
});
return !hasChanged;
}
function checkOrWriteFiles(
generatedFiles: Array<CodeGenFile>,
test: void | boolean,
): boolean {
if (test === true) {
return checkFilesForChanges(generatedFiles);
}
return writeMapToFiles(generatedFiles);
}
module.exports = {
allGenerators: ALL_GENERATORS,
libraryGenerators: LIBRARY_GENERATORS,
schemaGenerators: SCHEMAS_GENERATORS,
generate(
{
libraryName,
schema,
outputDirectory,
packageName,
assumeNonnull,
useLocalIncludePaths,
}: LibraryOptions,
{generators, test}: LibraryConfig,
): boolean {
schemaValidator.validate(schema);
const defaultHeaderPrefix = 'react/renderer/components';
const headerPrefix =
useLocalIncludePaths === true
? ''
: `${defaultHeaderPrefix}/${libraryName}/`;
function composePath(intermediate: string) {
return path.join(outputDirectory, intermediate, libraryName);
}
const componentIOSOutput = composePath(
useLocalIncludePaths === true ? '' : defaultHeaderPrefix,
);
const modulesIOSOutput = composePath('./');
const outputFoldersForGenerators = {
componentsIOS: componentIOSOutput,
modulesIOS: modulesIOSOutput,
descriptors: outputDirectory,
events: outputDirectory,
props: outputDirectory,
states: outputDirectory,
componentsAndroid: outputDirectory,
modulesAndroid: outputDirectory,
modulesCxx: outputDirectory,
tests: outputDirectory,
'shadow-nodes': outputDirectory,
};
const generatedFiles: Array<CodeGenFile> = [];
for (const name of generators) {
for (const generator of LIBRARY_GENERATORS[name]) {
generator(
libraryName,
schema,
packageName,
assumeNonnull,
headerPrefix,
).forEach((contents: string, fileName: string) => {
generatedFiles.push({
name: fileName,
content: contents,
outputDir: outputFoldersForGenerators[name],
});
});
}
}
return checkOrWriteFiles(generatedFiles, test);
},
generateFromSchemas(
{schemas, outputDirectory, supportedApplePlatforms}: SchemasOptions,
{generators, test}: SchemasConfig,
): boolean {
Object.keys(schemas).forEach(libraryName =>
schemaValidator.validate(schemas[libraryName]),
);
const generatedFiles: Array<CodeGenFile> = [];
for (const name of generators) {
for (const generator of SCHEMAS_GENERATORS[name]) {
generator(schemas, supportedApplePlatforms).forEach(
(contents: string, fileName: string) => {
generatedFiles.push({
name: fileName,
content: contents,
outputDir: outputDirectory,
});
},
);
}
}
return checkOrWriteFiles(generatedFiles, test);
},
generateViewConfig({libraryName, schema}: LibraryOptions): string {
schemaValidator.validate(schema);
const result = generateViewConfigJs
.generate(libraryName, schema)
.values()
.next();
if (typeof result.value !== 'string') {
throw new Error(`Failed to generate view config for ${libraryName}`);
}
return result.value;
},
};

View File

@@ -0,0 +1,17 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
* @oncall react_native
*/
function wrapOptional(type, isRequired) {
return isRequired ? type : `std::optional<${type}>`;
}
module.exports = {
wrapOptional,
};

View File

@@ -0,0 +1,18 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
* @oncall react_native
*/
function wrapOptional(type: string, isRequired: boolean): string {
return isRequired ? type : `std::optional<${type}>`;
}
module.exports = {
wrapOptional,
};

View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
* @oncall react_native
*/
const objectTypeForPrimitiveType = {
boolean: 'Boolean',
double: 'Double',
float: 'Float',
int: 'Integer',
};
function wrapOptional(type, isRequired) {
var _objectTypeForPrimiti;
return isRequired
? type
: `@Nullable ${
(_objectTypeForPrimiti = objectTypeForPrimitiveType[type]) !== null &&
_objectTypeForPrimiti !== void 0
? _objectTypeForPrimiti
: type
}`;
}
module.exports = {
wrapOptional,
};

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
* @oncall react_native
*/
const objectTypeForPrimitiveType = {
boolean: 'Boolean',
double: 'Double',
float: 'Float',
int: 'Integer',
};
function wrapOptional(type: string, isRequired: boolean): string {
return isRequired
? type
: `@Nullable ${objectTypeForPrimitiveType[type] ?? type}`;
}
module.exports = {
wrapOptional,
};

View File

@@ -0,0 +1,17 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
* @oncall react_native
*/
function wrapOptional(type, isRequired) {
return isRequired ? type : `${type} _Nullable`;
}
module.exports = {
wrapOptional,
};

View File

@@ -0,0 +1,18 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
* @oncall react_native
*/
function wrapOptional(type: string, isRequired: boolean): string {
return isRequired ? type : `${type} _Nullable`;
}
module.exports = {
wrapOptional,
};

View File

@@ -0,0 +1,47 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
function capitalize(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
function indent(nice, spaces) {
return nice
.split('\n')
.map((line, index) => {
if (line.length === 0 || index === 0) {
return line;
}
const emptySpaces = new Array(spaces + 1).join(' ');
return emptySpaces + line;
})
.join('\n');
}
function toPascalCase(inString) {
if (inString.length === 0) {
return inString;
}
return inString[0].toUpperCase() + inString.slice(1);
}
function toSafeCppString(input) {
return input.split('-').map(toPascalCase).join('');
}
function getEnumName(moduleName, origEnumName) {
const uppercasedPropName = toSafeCppString(origEnumName);
return `${moduleName}${uppercasedPropName}`;
}
module.exports = {
capitalize,
indent,
toPascalCase,
toSafeCppString,
getEnumName,
};

View File

@@ -0,0 +1,53 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
function capitalize(string: string): string {
return string.charAt(0).toUpperCase() + string.slice(1);
}
function indent(nice: string, spaces: number): string {
return nice
.split('\n')
.map((line, index) => {
if (line.length === 0 || index === 0) {
return line;
}
const emptySpaces = new Array<mixed>(spaces + 1).join(' ');
return emptySpaces + line;
})
.join('\n');
}
function toPascalCase(inString: string): string {
if (inString.length === 0) {
return inString;
}
return inString[0].toUpperCase() + inString.slice(1);
}
function toSafeCppString(input: string): string {
return input.split('-').map(toPascalCase).join('');
}
function getEnumName(moduleName: string, origEnumName: string): string {
const uppercasedPropName = toSafeCppString(origEnumName);
return `${moduleName}${uppercasedPropName}`;
}
module.exports = {
capitalize,
indent,
toPascalCase,
toSafeCppString,
getEnumName,
};

View File

@@ -0,0 +1,80 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const SCHEMA_WITH_TM_AND_FC = {
modules: {
ColoredView: {
type: 'Component',
components: {
ColoredView: {
extendsProps: [
{
type: 'ReactNativeBuiltInType',
knownTypeName: 'ReactNativeCoreViewProps',
},
],
events: [],
props: [
{
name: 'color',
optional: false,
typeAnnotation: {
type: 'StringTypeAnnotation',
default: null,
},
},
],
commands: [],
},
},
},
NativeCalculator: {
type: 'NativeModule',
aliasMap: {},
enumMap: {},
spec: {
properties: [
{
name: 'add',
optional: false,
typeAnnotation: {
type: 'FunctionTypeAnnotation',
returnTypeAnnotation: {
type: 'PromiseTypeAnnotation',
},
params: [
{
name: 'a',
optional: false,
typeAnnotation: {
type: 'NumberTypeAnnotation',
},
},
{
name: 'b',
optional: false,
typeAnnotation: {
type: 'NumberTypeAnnotation',
},
},
],
},
},
],
},
moduleName: 'Calculator',
},
},
};
module.exports = {
all: SCHEMA_WITH_TM_AND_FC,
};

View File

@@ -0,0 +1,83 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
import type {SchemaType} from '../../CodegenSchema.js';
const SCHEMA_WITH_TM_AND_FC: SchemaType = {
modules: {
ColoredView: {
type: 'Component',
components: {
ColoredView: {
extendsProps: [
{
type: 'ReactNativeBuiltInType',
knownTypeName: 'ReactNativeCoreViewProps',
},
],
events: [],
props: [
{
name: 'color',
optional: false,
typeAnnotation: {
type: 'StringTypeAnnotation',
default: null,
},
},
],
commands: [],
},
},
},
NativeCalculator: {
type: 'NativeModule',
aliasMap: {},
enumMap: {},
spec: {
properties: [
{
name: 'add',
optional: false,
typeAnnotation: {
type: 'FunctionTypeAnnotation',
returnTypeAnnotation: {
type: 'PromiseTypeAnnotation',
},
params: [
{
name: 'a',
optional: false,
typeAnnotation: {
type: 'NumberTypeAnnotation',
},
},
{
name: 'b',
optional: false,
typeAnnotation: {
type: 'NumberTypeAnnotation',
},
},
],
},
},
],
},
moduleName: 'Calculator',
},
},
};
module.exports = {
all: SCHEMA_WITH_TM_AND_FC,
};

View File

@@ -0,0 +1,228 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const _require = require('../Utils'),
getEnumName = _require.getEnumName;
const _require2 = require('./CppHelpers.js'),
generateStructName = _require2.generateStructName,
getCppTypeForAnnotation = _require2.getCppTypeForAnnotation,
getEnumMaskName = _require2.getEnumMaskName,
getImports = _require2.getImports;
function getNativeTypeFromAnnotation(componentName, prop, nameParts) {
const typeAnnotation = prop.typeAnnotation;
switch (typeAnnotation.type) {
case 'BooleanTypeAnnotation':
case 'StringTypeAnnotation':
case 'Int32TypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
return getCppTypeForAnnotation(typeAnnotation.type);
case 'ReservedPropTypeAnnotation':
switch (typeAnnotation.name) {
case 'ColorPrimitive':
return 'SharedColor';
case 'ImageSourcePrimitive':
return 'ImageSource';
case 'ImageRequestPrimitive':
return 'ImageRequest';
case 'PointPrimitive':
return 'Point';
case 'EdgeInsetsPrimitive':
return 'EdgeInsets';
case 'DimensionPrimitive':
return 'YGValue';
default:
typeAnnotation.name;
throw new Error('Received unknown ReservedPropTypeAnnotation');
}
case 'ArrayTypeAnnotation': {
const arrayType = typeAnnotation.elementType.type;
if (arrayType === 'ArrayTypeAnnotation') {
return `std::vector<${getNativeTypeFromAnnotation(
componentName,
{
typeAnnotation: typeAnnotation.elementType,
name: '',
},
nameParts.concat([prop.name]),
)}>`;
}
if (arrayType === 'ObjectTypeAnnotation') {
const structName = generateStructName(
componentName,
nameParts.concat([prop.name]),
);
return `std::vector<${structName}>`;
}
if (arrayType === 'StringEnumTypeAnnotation') {
const enumName = getEnumName(componentName, prop.name);
return getEnumMaskName(enumName);
}
const itemAnnotation = getNativeTypeFromAnnotation(
componentName,
{
typeAnnotation: typeAnnotation.elementType,
name: componentName,
},
nameParts.concat([prop.name]),
);
return `std::vector<${itemAnnotation}>`;
}
case 'ObjectTypeAnnotation': {
return generateStructName(componentName, nameParts.concat([prop.name]));
}
case 'StringEnumTypeAnnotation':
return getEnumName(componentName, prop.name);
case 'Int32EnumTypeAnnotation':
return getEnumName(componentName, prop.name);
case 'MixedTypeAnnotation':
return 'folly::dynamic';
default:
typeAnnotation;
throw new Error(
`Received invalid typeAnnotation for ${componentName} prop ${prop.name}, received ${typeAnnotation.type}`,
);
}
}
/// This function process some types if we need to customize them
/// For example, the ImageSource and the reserved types could be trasformed into
/// const address instead of using them as plain types.
function convertTypesToConstAddressIfNeeded(type, convertibleTypes) {
if (convertibleTypes.has(type)) {
return `${type} const &`;
}
return type;
}
function convertValueToSharedPointerWithMove(type, value, convertibleTypes) {
if (convertibleTypes.has(type)) {
return `std::make_shared<${type}>(std::move(${value}))`;
}
return value;
}
function convertVariableToSharedPointer(type, convertibleTypes) {
if (convertibleTypes.has(type)) {
return `std::shared_ptr<${type}>`;
}
return type;
}
function convertVariableToPointer(type, value, convertibleTypes) {
if (convertibleTypes.has(type)) {
return `*${value}`;
}
return value;
}
const convertCtorParamToAddressType = type => {
const typesToConvert = new Set();
typesToConvert.add('ImageSource');
return convertTypesToConstAddressIfNeeded(type, typesToConvert);
};
const convertCtorInitToSharedPointers = (type, value) => {
const typesToConvert = new Set();
typesToConvert.add('ImageRequest');
return convertValueToSharedPointerWithMove(type, value, typesToConvert);
};
const convertGettersReturnTypeToAddressType = type => {
const typesToConvert = new Set();
typesToConvert.add('ImageRequest');
return convertTypesToConstAddressIfNeeded(type, typesToConvert);
};
const convertVarTypeToSharedPointer = type => {
const typesToConvert = new Set();
typesToConvert.add('ImageRequest');
return convertVariableToSharedPointer(type, typesToConvert);
};
const convertVarValueToPointer = (type, value) => {
const typesToConvert = new Set();
typesToConvert.add('ImageRequest');
return convertVariableToPointer(type, value, typesToConvert);
};
function getLocalImports(properties) {
const imports = new Set();
function addImportsForNativeName(name) {
switch (name) {
case 'ColorPrimitive':
imports.add('#include <react/renderer/graphics/Color.h>');
return;
case 'ImageSourcePrimitive':
imports.add('#include <react/renderer/imagemanager/primitives.h>');
return;
case 'ImageRequestPrimitive':
imports.add('#include <react/renderer/imagemanager/ImageRequest.h>');
return;
case 'PointPrimitive':
imports.add('#include <react/renderer/graphics/Point.h>');
return;
case 'EdgeInsetsPrimitive':
imports.add('#include <react/renderer/graphics/RectangleEdges.h>');
return;
case 'DimensionPrimitive':
imports.add('#include <yoga/Yoga.h>');
return;
default:
name;
throw new Error(`Invalid ReservedPropTypeAnnotation name, got ${name}`);
}
}
properties.forEach(prop => {
const typeAnnotation = prop.typeAnnotation;
if (typeAnnotation.type === 'ReservedPropTypeAnnotation') {
addImportsForNativeName(typeAnnotation.name);
}
if (typeAnnotation.type === 'ArrayTypeAnnotation') {
imports.add('#include <vector>');
if (typeAnnotation.elementType.type === 'StringEnumTypeAnnotation') {
imports.add('#include <cinttypes>');
}
}
if (
typeAnnotation.type === 'ArrayTypeAnnotation' &&
typeAnnotation.elementType.type === 'ReservedPropTypeAnnotation'
) {
addImportsForNativeName(typeAnnotation.elementType.name);
}
if (
typeAnnotation.type === 'ArrayTypeAnnotation' &&
typeAnnotation.elementType.type === 'ObjectTypeAnnotation'
) {
imports.add('#include <react/renderer/core/propsConversions.h>');
const objectProps = typeAnnotation.elementType.properties;
// $FlowFixMe[incompatible-call] the type is guaranteed to be ObjectTypeAnnotation<PropTypeAnnotation>
const objectImports = getImports(objectProps);
// $FlowFixMe[incompatible-call] the type is guaranteed to be ObjectTypeAnnotation<PropTypeAnnotation>
const localImports = getLocalImports(objectProps);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
objectImports.forEach(imports.add, imports);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
localImports.forEach(imports.add, imports);
}
if (typeAnnotation.type === 'ObjectTypeAnnotation') {
imports.add('#include <react/renderer/core/propsConversions.h>');
const objectImports = getImports(typeAnnotation.properties);
const localImports = getLocalImports(typeAnnotation.properties);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
objectImports.forEach(imports.add, imports);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
localImports.forEach(imports.add, imports);
}
});
return imports;
}
module.exports = {
getNativeTypeFromAnnotation,
convertCtorParamToAddressType,
convertGettersReturnTypeToAddressType,
convertCtorInitToSharedPointers,
convertVarTypeToSharedPointer,
convertVarValueToPointer,
getLocalImports,
};

View File

@@ -0,0 +1,314 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {NamedShape, PropTypeAnnotation} from '../../CodegenSchema';
import type {
BooleanTypeAnnotation,
DoubleTypeAnnotation,
FloatTypeAnnotation,
Int32TypeAnnotation,
ObjectTypeAnnotation,
ReservedPropTypeAnnotation,
StringTypeAnnotation,
} from '../../CodegenSchema';
const {getEnumName} = require('../Utils');
const {
generateStructName,
getCppTypeForAnnotation,
getEnumMaskName,
getImports,
} = require('./CppHelpers.js');
function getNativeTypeFromAnnotation(
componentName: string,
prop:
| NamedShape<PropTypeAnnotation>
| {
name: string,
typeAnnotation:
| $FlowFixMe
| DoubleTypeAnnotation
| FloatTypeAnnotation
| BooleanTypeAnnotation
| Int32TypeAnnotation
| StringTypeAnnotation
| ObjectTypeAnnotation<PropTypeAnnotation>
| ReservedPropTypeAnnotation
| {
+default: string,
+options: $ReadOnlyArray<string>,
+type: 'StringEnumTypeAnnotation',
}
| {
+elementType: ObjectTypeAnnotation<PropTypeAnnotation>,
+type: 'ArrayTypeAnnotation',
},
},
nameParts: $ReadOnlyArray<string>,
): string {
const typeAnnotation = prop.typeAnnotation;
switch (typeAnnotation.type) {
case 'BooleanTypeAnnotation':
case 'StringTypeAnnotation':
case 'Int32TypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
return getCppTypeForAnnotation(typeAnnotation.type);
case 'ReservedPropTypeAnnotation':
switch (typeAnnotation.name) {
case 'ColorPrimitive':
return 'SharedColor';
case 'ImageSourcePrimitive':
return 'ImageSource';
case 'ImageRequestPrimitive':
return 'ImageRequest';
case 'PointPrimitive':
return 'Point';
case 'EdgeInsetsPrimitive':
return 'EdgeInsets';
case 'DimensionPrimitive':
return 'YGValue';
default:
(typeAnnotation.name: empty);
throw new Error('Received unknown ReservedPropTypeAnnotation');
}
case 'ArrayTypeAnnotation': {
const arrayType = typeAnnotation.elementType.type;
if (arrayType === 'ArrayTypeAnnotation') {
return `std::vector<${getNativeTypeFromAnnotation(
componentName,
{typeAnnotation: typeAnnotation.elementType, name: ''},
nameParts.concat([prop.name]),
)}>`;
}
if (arrayType === 'ObjectTypeAnnotation') {
const structName = generateStructName(
componentName,
nameParts.concat([prop.name]),
);
return `std::vector<${structName}>`;
}
if (arrayType === 'StringEnumTypeAnnotation') {
const enumName = getEnumName(componentName, prop.name);
return getEnumMaskName(enumName);
}
const itemAnnotation = getNativeTypeFromAnnotation(
componentName,
{
typeAnnotation: typeAnnotation.elementType,
name: componentName,
},
nameParts.concat([prop.name]),
);
return `std::vector<${itemAnnotation}>`;
}
case 'ObjectTypeAnnotation': {
return generateStructName(componentName, nameParts.concat([prop.name]));
}
case 'StringEnumTypeAnnotation':
return getEnumName(componentName, prop.name);
case 'Int32EnumTypeAnnotation':
return getEnumName(componentName, prop.name);
case 'MixedTypeAnnotation':
return 'folly::dynamic';
default:
(typeAnnotation: empty);
throw new Error(
`Received invalid typeAnnotation for ${componentName} prop ${prop.name}, received ${typeAnnotation.type}`,
);
}
}
/// This function process some types if we need to customize them
/// For example, the ImageSource and the reserved types could be trasformed into
/// const address instead of using them as plain types.
function convertTypesToConstAddressIfNeeded(
type: string,
convertibleTypes: Set<string>,
): string {
if (convertibleTypes.has(type)) {
return `${type} const &`;
}
return type;
}
function convertValueToSharedPointerWithMove(
type: string,
value: string,
convertibleTypes: Set<string>,
): string {
if (convertibleTypes.has(type)) {
return `std::make_shared<${type}>(std::move(${value}))`;
}
return value;
}
function convertVariableToSharedPointer(
type: string,
convertibleTypes: Set<string>,
): string {
if (convertibleTypes.has(type)) {
return `std::shared_ptr<${type}>`;
}
return type;
}
function convertVariableToPointer(
type: string,
value: string,
convertibleTypes: Set<string>,
): string {
if (convertibleTypes.has(type)) {
return `*${value}`;
}
return value;
}
const convertCtorParamToAddressType = (type: string): string => {
const typesToConvert: Set<string> = new Set();
typesToConvert.add('ImageSource');
return convertTypesToConstAddressIfNeeded(type, typesToConvert);
};
const convertCtorInitToSharedPointers = (
type: string,
value: string,
): string => {
const typesToConvert: Set<string> = new Set();
typesToConvert.add('ImageRequest');
return convertValueToSharedPointerWithMove(type, value, typesToConvert);
};
const convertGettersReturnTypeToAddressType = (type: string): string => {
const typesToConvert: Set<string> = new Set();
typesToConvert.add('ImageRequest');
return convertTypesToConstAddressIfNeeded(type, typesToConvert);
};
const convertVarTypeToSharedPointer = (type: string): string => {
const typesToConvert: Set<string> = new Set();
typesToConvert.add('ImageRequest');
return convertVariableToSharedPointer(type, typesToConvert);
};
const convertVarValueToPointer = (type: string, value: string): string => {
const typesToConvert: Set<string> = new Set();
typesToConvert.add('ImageRequest');
return convertVariableToPointer(type, value, typesToConvert);
};
function getLocalImports(
properties: $ReadOnlyArray<NamedShape<PropTypeAnnotation>>,
): Set<string> {
const imports: Set<string> = new Set();
function addImportsForNativeName(
name:
| 'ColorPrimitive'
| 'EdgeInsetsPrimitive'
| 'ImageSourcePrimitive'
| 'PointPrimitive'
| 'ImageRequestPrimitive'
| 'DimensionPrimitive',
) {
switch (name) {
case 'ColorPrimitive':
imports.add('#include <react/renderer/graphics/Color.h>');
return;
case 'ImageSourcePrimitive':
imports.add('#include <react/renderer/imagemanager/primitives.h>');
return;
case 'ImageRequestPrimitive':
imports.add('#include <react/renderer/imagemanager/ImageRequest.h>');
return;
case 'PointPrimitive':
imports.add('#include <react/renderer/graphics/Point.h>');
return;
case 'EdgeInsetsPrimitive':
imports.add('#include <react/renderer/graphics/RectangleEdges.h>');
return;
case 'DimensionPrimitive':
imports.add('#include <yoga/Yoga.h>');
return;
default:
(name: empty);
throw new Error(`Invalid ReservedPropTypeAnnotation name, got ${name}`);
}
}
properties.forEach(prop => {
const typeAnnotation = prop.typeAnnotation;
if (typeAnnotation.type === 'ReservedPropTypeAnnotation') {
addImportsForNativeName(typeAnnotation.name);
}
if (typeAnnotation.type === 'ArrayTypeAnnotation') {
imports.add('#include <vector>');
if (typeAnnotation.elementType.type === 'StringEnumTypeAnnotation') {
imports.add('#include <cinttypes>');
}
}
if (
typeAnnotation.type === 'ArrayTypeAnnotation' &&
typeAnnotation.elementType.type === 'ReservedPropTypeAnnotation'
) {
addImportsForNativeName(typeAnnotation.elementType.name);
}
if (
typeAnnotation.type === 'ArrayTypeAnnotation' &&
typeAnnotation.elementType.type === 'ObjectTypeAnnotation'
) {
imports.add('#include <react/renderer/core/propsConversions.h>');
const objectProps = typeAnnotation.elementType.properties;
// $FlowFixMe[incompatible-call] the type is guaranteed to be ObjectTypeAnnotation<PropTypeAnnotation>
const objectImports = getImports(objectProps);
// $FlowFixMe[incompatible-call] the type is guaranteed to be ObjectTypeAnnotation<PropTypeAnnotation>
const localImports = getLocalImports(objectProps);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
objectImports.forEach(imports.add, imports);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
localImports.forEach(imports.add, imports);
}
if (typeAnnotation.type === 'ObjectTypeAnnotation') {
imports.add('#include <react/renderer/core/propsConversions.h>');
const objectImports = getImports(typeAnnotation.properties);
const localImports = getLocalImports(typeAnnotation.properties);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
objectImports.forEach(imports.add, imports);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
localImports.forEach(imports.add, imports);
}
});
return imports;
}
module.exports = {
getNativeTypeFromAnnotation,
convertCtorParamToAddressType,
convertGettersReturnTypeToAddressType,
convertCtorInitToSharedPointers,
convertVarTypeToSharedPointer,
convertVarValueToPointer,
getLocalImports,
};

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
const APPLE_PLATFORMS_MACRO_MAP = {
ios: 'TARGET_OS_IOS',
macos: 'TARGET_OS_OSX',
tvos: 'TARGET_OS_TV',
visionos: 'TARGET_OS_VISION',
};
/**
* Adds compiler macros to the file template to exclude unsupported platforms.
*/
function generateSupportedApplePlatformsMacro(
fileTemplate,
supportedPlatformsMap,
) {
if (!supportedPlatformsMap) {
return fileTemplate;
}
// According to Podspec Syntax Reference, when `platform` or `deployment_target` is not specified, it defaults to all platforms.
// https://guides.cocoapods.org/syntax/podspec.html#platform
const everyPlatformIsUnsupported = Object.keys(supportedPlatformsMap).every(
platform => supportedPlatformsMap[platform] === false,
);
if (everyPlatformIsUnsupported) {
return fileTemplate;
}
const compilerMacroString = Object.keys(supportedPlatformsMap)
.reduce((acc, platform) => {
if (!supportedPlatformsMap[platform]) {
return [...acc, `!${APPLE_PLATFORMS_MACRO_MAP[platform]}`];
}
return acc;
}, [])
.join(' && ');
if (!compilerMacroString) {
return fileTemplate;
}
return `#if ${compilerMacroString}
${fileTemplate}
#endif
`;
}
module.exports = {
generateSupportedApplePlatformsMacro,
};

View File

@@ -0,0 +1,60 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
const APPLE_PLATFORMS_MACRO_MAP = {
ios: 'TARGET_OS_IOS',
macos: 'TARGET_OS_OSX',
tvos: 'TARGET_OS_TV',
visionos: 'TARGET_OS_VISION',
};
/**
* Adds compiler macros to the file template to exclude unsupported platforms.
*/
function generateSupportedApplePlatformsMacro(
fileTemplate: string,
supportedPlatformsMap: ?{[string]: boolean},
): string {
if (!supportedPlatformsMap) {
return fileTemplate;
}
// According to Podspec Syntax Reference, when `platform` or `deployment_target` is not specified, it defaults to all platforms.
// https://guides.cocoapods.org/syntax/podspec.html#platform
const everyPlatformIsUnsupported = Object.keys(supportedPlatformsMap).every(
platform => supportedPlatformsMap[platform] === false,
);
if (everyPlatformIsUnsupported) {
return fileTemplate;
}
const compilerMacroString = Object.keys(supportedPlatformsMap)
.reduce((acc: string[], platform) => {
if (!supportedPlatformsMap[platform]) {
return [...acc, `!${APPLE_PLATFORMS_MACRO_MAP[platform]}`];
}
return acc;
}, [])
.join(' && ');
if (!compilerMacroString) {
return fileTemplate;
}
return `#if ${compilerMacroString}
${fileTemplate}
#endif
`;
}
module.exports = {
generateSupportedApplePlatformsMacro,
};

View File

@@ -0,0 +1,232 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const _require = require('../Utils'),
getEnumName = _require.getEnumName,
toSafeCppString = _require.toSafeCppString;
function toIntEnumValueName(propName, value) {
return `${toSafeCppString(propName)}${value}`;
}
function getCppTypeForAnnotation(type) {
switch (type) {
case 'BooleanTypeAnnotation':
return 'bool';
case 'StringTypeAnnotation':
return 'std::string';
case 'Int32TypeAnnotation':
return 'int';
case 'DoubleTypeAnnotation':
return 'double';
case 'FloatTypeAnnotation':
return 'Float';
case 'MixedTypeAnnotation':
return 'folly::dynamic';
default:
type;
throw new Error(`Received invalid typeAnnotation ${type}`);
}
}
function getCppArrayTypeForAnnotation(typeElement, structParts) {
switch (typeElement.type) {
case 'BooleanTypeAnnotation':
case 'StringTypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
case 'Int32TypeAnnotation':
case 'MixedTypeAnnotation':
return `std::vector<${getCppTypeForAnnotation(typeElement.type)}>`;
case 'StringEnumTypeAnnotation':
case 'ObjectTypeAnnotation':
if (!structParts) {
throw new Error(
`Trying to generate the event emitter for an Array of ${typeElement.type} without informations to generate the generic type`,
);
}
return `std::vector<${generateEventStructName(structParts)}>`;
case 'ArrayTypeAnnotation':
return `std::vector<${getCppArrayTypeForAnnotation(
typeElement.elementType,
structParts,
)}>`;
default:
throw new Error(
`Can't determine array type with typeElement: ${JSON.stringify(
typeElement,
null,
2,
)}`,
);
}
}
function getImports(properties) {
const imports = new Set();
function addImportsForNativeName(name) {
switch (name) {
case 'ColorPrimitive':
return;
case 'PointPrimitive':
return;
case 'EdgeInsetsPrimitive':
return;
case 'ImageRequestPrimitive':
return;
case 'ImageSourcePrimitive':
imports.add('#include <react/renderer/components/image/conversions.h>');
return;
case 'DimensionPrimitive':
imports.add('#include <react/renderer/components/view/conversions.h>');
return;
default:
name;
throw new Error(`Invalid name, got ${name}`);
}
}
properties.forEach(prop => {
const typeAnnotation = prop.typeAnnotation;
if (typeAnnotation.type === 'ReservedPropTypeAnnotation') {
addImportsForNativeName(typeAnnotation.name);
}
if (
typeAnnotation.type === 'ArrayTypeAnnotation' &&
typeAnnotation.elementType.type === 'ReservedPropTypeAnnotation'
) {
addImportsForNativeName(typeAnnotation.elementType.name);
}
if (typeAnnotation.type === 'MixedTypeAnnotation') {
imports.add('#include <folly/dynamic.h>');
}
if (typeAnnotation.type === 'ObjectTypeAnnotation') {
const objectImports = getImports(typeAnnotation.properties);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
objectImports.forEach(imports.add, imports);
}
});
return imports;
}
function generateEventStructName(parts = []) {
return parts.map(toSafeCppString).join('');
}
function generateStructName(componentName, parts = []) {
const additional = parts.map(toSafeCppString).join('');
return `${componentName}${additional}Struct`;
}
function getEnumMaskName(enumName) {
return `${enumName}Mask`;
}
function getDefaultInitializerString(componentName, prop) {
const defaultValue = convertDefaultTypeToString(componentName, prop);
return `{${defaultValue}}`;
}
function convertDefaultTypeToString(componentName, prop) {
const typeAnnotation = prop.typeAnnotation;
switch (typeAnnotation.type) {
case 'BooleanTypeAnnotation':
if (typeAnnotation.default == null) {
return '';
}
return String(typeAnnotation.default);
case 'StringTypeAnnotation':
if (typeAnnotation.default == null) {
return '';
}
return `"${typeAnnotation.default}"`;
case 'Int32TypeAnnotation':
return String(typeAnnotation.default);
case 'DoubleTypeAnnotation':
const defaultDoubleVal = typeAnnotation.default;
return parseInt(defaultDoubleVal, 10) === defaultDoubleVal
? typeAnnotation.default.toFixed(1)
: String(typeAnnotation.default);
case 'FloatTypeAnnotation':
const defaultFloatVal = typeAnnotation.default;
if (defaultFloatVal == null) {
return '';
}
return parseInt(defaultFloatVal, 10) === defaultFloatVal
? defaultFloatVal.toFixed(1)
: String(typeAnnotation.default);
case 'ReservedPropTypeAnnotation':
switch (typeAnnotation.name) {
case 'ColorPrimitive':
return '';
case 'ImageSourcePrimitive':
return '';
case 'ImageRequestPrimitive':
return '';
case 'PointPrimitive':
return '';
case 'EdgeInsetsPrimitive':
return '';
case 'DimensionPrimitive':
return '';
default:
typeAnnotation.name;
throw new Error(
`Unsupported type annotation: ${typeAnnotation.name}`,
);
}
case 'ArrayTypeAnnotation': {
const elementType = typeAnnotation.elementType;
switch (elementType.type) {
case 'StringEnumTypeAnnotation':
if (elementType.default == null) {
throw new Error(
'A default is required for array StringEnumTypeAnnotation',
);
}
const enumName = getEnumName(componentName, prop.name);
const enumMaskName = getEnumMaskName(enumName);
const defaultValue = `${enumName}::${toSafeCppString(
elementType.default,
)}`;
return `static_cast<${enumMaskName}>(${defaultValue})`;
default:
return '';
}
}
case 'ObjectTypeAnnotation': {
return '';
}
case 'StringEnumTypeAnnotation':
return `${getEnumName(componentName, prop.name)}::${toSafeCppString(
typeAnnotation.default,
)}`;
case 'Int32EnumTypeAnnotation':
return `${getEnumName(componentName, prop.name)}::${toIntEnumValueName(
prop.name,
typeAnnotation.default,
)}`;
case 'MixedTypeAnnotation':
return '';
default:
typeAnnotation;
throw new Error(`Unsupported type annotation: ${typeAnnotation.type}`);
}
}
const IncludeTemplate = ({headerPrefix, file}) => {
if (headerPrefix === '') {
return `#include "${file}"`;
}
return `#include <${headerPrefix}${file}>`;
};
module.exports = {
getDefaultInitializerString,
convertDefaultTypeToString,
getCppArrayTypeForAnnotation,
getCppTypeForAnnotation,
getEnumMaskName,
getImports,
toIntEnumValueName,
generateStructName,
generateEventStructName,
IncludeTemplate,
};

View File

@@ -0,0 +1,291 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {
EventTypeAnnotation,
NamedShape,
PropTypeAnnotation,
} from '../../CodegenSchema';
const {getEnumName, toSafeCppString} = require('../Utils');
function toIntEnumValueName(propName: string, value: number): string {
return `${toSafeCppString(propName)}${value}`;
}
function getCppTypeForAnnotation(
type:
| 'BooleanTypeAnnotation'
| 'StringTypeAnnotation'
| 'Int32TypeAnnotation'
| 'DoubleTypeAnnotation'
| 'FloatTypeAnnotation'
| 'MixedTypeAnnotation',
): string {
switch (type) {
case 'BooleanTypeAnnotation':
return 'bool';
case 'StringTypeAnnotation':
return 'std::string';
case 'Int32TypeAnnotation':
return 'int';
case 'DoubleTypeAnnotation':
return 'double';
case 'FloatTypeAnnotation':
return 'Float';
case 'MixedTypeAnnotation':
return 'folly::dynamic';
default:
(type: empty);
throw new Error(`Received invalid typeAnnotation ${type}`);
}
}
function getCppArrayTypeForAnnotation(
typeElement: EventTypeAnnotation,
structParts?: string[],
): string {
switch (typeElement.type) {
case 'BooleanTypeAnnotation':
case 'StringTypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
case 'Int32TypeAnnotation':
case 'MixedTypeAnnotation':
return `std::vector<${getCppTypeForAnnotation(typeElement.type)}>`;
case 'StringEnumTypeAnnotation':
case 'ObjectTypeAnnotation':
if (!structParts) {
throw new Error(
`Trying to generate the event emitter for an Array of ${typeElement.type} without informations to generate the generic type`,
);
}
return `std::vector<${generateEventStructName(structParts)}>`;
case 'ArrayTypeAnnotation':
return `std::vector<${getCppArrayTypeForAnnotation(
typeElement.elementType,
structParts,
)}>`;
default:
throw new Error(
`Can't determine array type with typeElement: ${JSON.stringify(
typeElement,
null,
2,
)}`,
);
}
}
function getImports(
properties:
| $ReadOnlyArray<NamedShape<PropTypeAnnotation>>
| $ReadOnlyArray<NamedShape<EventTypeAnnotation>>,
): Set<string> {
const imports: Set<string> = new Set();
function addImportsForNativeName(
name:
| 'ColorPrimitive'
| 'EdgeInsetsPrimitive'
| 'ImageRequestPrimitive'
| 'ImageSourcePrimitive'
| 'PointPrimitive'
| 'DimensionPrimitive',
) {
switch (name) {
case 'ColorPrimitive':
return;
case 'PointPrimitive':
return;
case 'EdgeInsetsPrimitive':
return;
case 'ImageRequestPrimitive':
return;
case 'ImageSourcePrimitive':
imports.add('#include <react/renderer/components/image/conversions.h>');
return;
case 'DimensionPrimitive':
imports.add('#include <react/renderer/components/view/conversions.h>');
return;
default:
(name: empty);
throw new Error(`Invalid name, got ${name}`);
}
}
properties.forEach(prop => {
const typeAnnotation = prop.typeAnnotation;
if (typeAnnotation.type === 'ReservedPropTypeAnnotation') {
addImportsForNativeName(typeAnnotation.name);
}
if (
typeAnnotation.type === 'ArrayTypeAnnotation' &&
typeAnnotation.elementType.type === 'ReservedPropTypeAnnotation'
) {
addImportsForNativeName(typeAnnotation.elementType.name);
}
if (typeAnnotation.type === 'MixedTypeAnnotation') {
imports.add('#include <folly/dynamic.h>');
}
if (typeAnnotation.type === 'ObjectTypeAnnotation') {
const objectImports = getImports(typeAnnotation.properties);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
objectImports.forEach(imports.add, imports);
}
});
return imports;
}
function generateEventStructName(parts: $ReadOnlyArray<string> = []): string {
return parts.map(toSafeCppString).join('');
}
function generateStructName(
componentName: string,
parts: $ReadOnlyArray<string> = [],
): string {
const additional = parts.map(toSafeCppString).join('');
return `${componentName}${additional}Struct`;
}
function getEnumMaskName(enumName: string): string {
return `${enumName}Mask`;
}
function getDefaultInitializerString(
componentName: string,
prop: NamedShape<PropTypeAnnotation>,
): string {
const defaultValue = convertDefaultTypeToString(componentName, prop);
return `{${defaultValue}}`;
}
function convertDefaultTypeToString(
componentName: string,
prop: NamedShape<PropTypeAnnotation>,
): string {
const typeAnnotation = prop.typeAnnotation;
switch (typeAnnotation.type) {
case 'BooleanTypeAnnotation':
if (typeAnnotation.default == null) {
return '';
}
return String(typeAnnotation.default);
case 'StringTypeAnnotation':
if (typeAnnotation.default == null) {
return '';
}
return `"${typeAnnotation.default}"`;
case 'Int32TypeAnnotation':
return String(typeAnnotation.default);
case 'DoubleTypeAnnotation':
const defaultDoubleVal = typeAnnotation.default;
return parseInt(defaultDoubleVal, 10) === defaultDoubleVal
? typeAnnotation.default.toFixed(1)
: String(typeAnnotation.default);
case 'FloatTypeAnnotation':
const defaultFloatVal = typeAnnotation.default;
if (defaultFloatVal == null) {
return '';
}
return parseInt(defaultFloatVal, 10) === defaultFloatVal
? defaultFloatVal.toFixed(1)
: String(typeAnnotation.default);
case 'ReservedPropTypeAnnotation':
switch (typeAnnotation.name) {
case 'ColorPrimitive':
return '';
case 'ImageSourcePrimitive':
return '';
case 'ImageRequestPrimitive':
return '';
case 'PointPrimitive':
return '';
case 'EdgeInsetsPrimitive':
return '';
case 'DimensionPrimitive':
return '';
default:
(typeAnnotation.name: empty);
throw new Error(
`Unsupported type annotation: ${typeAnnotation.name}`,
);
}
case 'ArrayTypeAnnotation': {
const elementType = typeAnnotation.elementType;
switch (elementType.type) {
case 'StringEnumTypeAnnotation':
if (elementType.default == null) {
throw new Error(
'A default is required for array StringEnumTypeAnnotation',
);
}
const enumName = getEnumName(componentName, prop.name);
const enumMaskName = getEnumMaskName(enumName);
const defaultValue = `${enumName}::${toSafeCppString(
elementType.default,
)}`;
return `static_cast<${enumMaskName}>(${defaultValue})`;
default:
return '';
}
}
case 'ObjectTypeAnnotation': {
return '';
}
case 'StringEnumTypeAnnotation':
return `${getEnumName(componentName, prop.name)}::${toSafeCppString(
typeAnnotation.default,
)}`;
case 'Int32EnumTypeAnnotation':
return `${getEnumName(componentName, prop.name)}::${toIntEnumValueName(
prop.name,
typeAnnotation.default,
)}`;
case 'MixedTypeAnnotation':
return '';
default:
(typeAnnotation: empty);
throw new Error(`Unsupported type annotation: ${typeAnnotation.type}`);
}
}
const IncludeTemplate = ({
headerPrefix,
file,
}: {
headerPrefix: string,
file: string,
}): string => {
if (headerPrefix === '') {
return `#include "${file}"`;
}
return `#include <${headerPrefix}${file}>`;
};
module.exports = {
getDefaultInitializerString,
convertDefaultTypeToString,
getCppArrayTypeForAnnotation,
getCppTypeForAnnotation,
getEnumMaskName,
getImports,
toIntEnumValueName,
generateStructName,
generateEventStructName,
IncludeTemplate,
};

View File

@@ -0,0 +1,89 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const _require = require('./CppHelpers'),
IncludeTemplate = _require.IncludeTemplate;
// File path -> contents
const FileTemplate = ({libraryName, componentRegistrations, headerPrefix}) => `
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GenerateComponentDescriptorCpp.js
*/
${IncludeTemplate({
headerPrefix,
file: 'ComponentDescriptors.h',
})}
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
namespace facebook::react {
void ${libraryName}_registerComponentDescriptorsFromCodegen(
std::shared_ptr<const ComponentDescriptorProviderRegistry> registry) {
${componentRegistrations}
}
} // namespace facebook::react
`;
const ComponentRegistrationTemplate = ({className}) =>
`
registry->add(concreteComponentDescriptorProvider<${className}ComponentDescriptor>());
`.trim();
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
) {
const fileName = 'ComponentDescriptors.cpp';
const componentRegistrations = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const components = module.components;
// No components in this module
if (components == null) {
return null;
}
return Object.keys(components)
.map(componentName => {
if (components[componentName].interfaceOnly === true) {
return;
}
return ComponentRegistrationTemplate({
className: componentName,
});
})
.join('\n');
})
.filter(Boolean)
.join('\n');
const replacedTemplate = FileTemplate({
libraryName,
componentRegistrations,
headerPrefix:
headerPrefix !== null && headerPrefix !== void 0 ? headerPrefix : '',
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,101 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {SchemaType} from '../../CodegenSchema';
const {IncludeTemplate} = require('./CppHelpers');
// File path -> contents
type FilesOutput = Map<string, string>;
const FileTemplate = ({
libraryName,
componentRegistrations,
headerPrefix,
}: {
libraryName: string,
componentRegistrations: string,
headerPrefix: string,
}) => `
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GenerateComponentDescriptorCpp.js
*/
${IncludeTemplate({headerPrefix, file: 'ComponentDescriptors.h'})}
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
namespace facebook::react {
void ${libraryName}_registerComponentDescriptorsFromCodegen(
std::shared_ptr<const ComponentDescriptorProviderRegistry> registry) {
${componentRegistrations}
}
} // namespace facebook::react
`;
const ComponentRegistrationTemplate = ({className}: {className: string}) =>
`
registry->add(concreteComponentDescriptorProvider<${className}ComponentDescriptor>());
`.trim();
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
): FilesOutput {
const fileName = 'ComponentDescriptors.cpp';
const componentRegistrations = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const {components} = module;
// No components in this module
if (components == null) {
return null;
}
return Object.keys(components)
.map(componentName => {
if (components[componentName].interfaceOnly === true) {
return;
}
return ComponentRegistrationTemplate({className: componentName});
})
.join('\n');
})
.filter(Boolean)
.join('\n');
const replacedTemplate = FileTemplate({
libraryName,
componentRegistrations,
headerPrefix: headerPrefix ?? '',
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,91 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const _require = require('./CppHelpers'),
IncludeTemplate = _require.IncludeTemplate;
// File path -> contents
const FileTemplate = ({libraryName, componentDefinitions, headerPrefix}) => `
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GenerateComponentDescriptorH.js
*/
#pragma once
${IncludeTemplate({
headerPrefix,
file: 'ShadowNodes.h',
})}
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
namespace facebook::react {
${componentDefinitions}
void ${libraryName}_registerComponentDescriptorsFromCodegen(
std::shared_ptr<const ComponentDescriptorProviderRegistry> registry);
} // namespace facebook::react
`;
const ComponentDefinitionTemplate = ({className}) =>
`
using ${className}ComponentDescriptor = ConcreteComponentDescriptor<${className}ShadowNode>;
`.trim();
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
) {
const fileName = 'ComponentDescriptors.h';
const componentDefinitions = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const components = module.components;
// No components in this module
if (components == null) {
return null;
}
return Object.keys(components)
.map(componentName => {
if (components[componentName].interfaceOnly === true) {
return;
}
return ComponentDefinitionTemplate({
className: componentName,
});
})
.join('\n');
})
.filter(Boolean)
.join('\n');
const replacedTemplate = FileTemplate({
libraryName,
componentDefinitions,
headerPrefix:
headerPrefix !== null && headerPrefix !== void 0 ? headerPrefix : '',
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,103 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {SchemaType} from '../../CodegenSchema';
const {IncludeTemplate} = require('./CppHelpers');
// File path -> contents
type FilesOutput = Map<string, string>;
const FileTemplate = ({
libraryName,
componentDefinitions,
headerPrefix,
}: {
libraryName: string,
componentDefinitions: string,
headerPrefix: string,
}) => `
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GenerateComponentDescriptorH.js
*/
#pragma once
${IncludeTemplate({headerPrefix, file: 'ShadowNodes.h'})}
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
namespace facebook::react {
${componentDefinitions}
void ${libraryName}_registerComponentDescriptorsFromCodegen(
std::shared_ptr<const ComponentDescriptorProviderRegistry> registry);
} // namespace facebook::react
`;
const ComponentDefinitionTemplate = ({className}: {className: string}) =>
`
using ${className}ComponentDescriptor = ConcreteComponentDescriptor<${className}ShadowNode>;
`.trim();
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
): FilesOutput {
const fileName = 'ComponentDescriptors.h';
const componentDefinitions = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const {components} = module;
// No components in this module
if (components == null) {
return null;
}
return Object.keys(components)
.map(componentName => {
if (components[componentName].interfaceOnly === true) {
return;
}
return ComponentDefinitionTemplate({className: componentName});
})
.join('\n');
})
.filter(Boolean)
.join('\n');
const replacedTemplate = FileTemplate({
libraryName,
componentDefinitions,
headerPrefix: headerPrefix ?? '',
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,341 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
function getOrdinalNumber(num) {
switch (num) {
case 1:
return '1st';
case 2:
return '2nd';
case 3:
return '3rd';
}
if (num <= 20) {
return `${num}th`;
}
return 'unknown';
}
const ProtocolTemplate = ({componentName, methods}) =>
`
@protocol RCT${componentName}ViewProtocol <NSObject>
${methods}
@end
`.trim();
const CommandHandlerIfCaseConvertArgTemplate = ({
componentName,
expectedKind,
argNumber,
argNumberString,
expectedKindString,
argConversion,
}) =>
`
NSObject *arg${argNumber} = args[${argNumber}];
#if RCT_DEBUG
if (!RCTValidateTypeOfViewCommandArgument(arg${argNumber}, ${expectedKind}, @"${expectedKindString}", @"${componentName}", commandName, @"${argNumberString}")) {
return;
}
#endif
${argConversion}
`.trim();
const CommandHandlerIfCaseTemplate = ({
componentName,
commandName,
numArgs,
convertArgs,
commandCall,
}) =>
`
if ([commandName isEqualToString:@"${commandName}"]) {
#if RCT_DEBUG
if ([args count] != ${numArgs}) {
RCTLogError(@"%@ command %@ received %d arguments, expected %d.", @"${componentName}", commandName, (int)[args count], ${numArgs});
return;
}
#endif
${convertArgs}
${commandCall}
return;
}
`.trim();
const CommandHandlerTemplate = ({componentName, ifCases}) =>
`
RCT_EXTERN inline void RCT${componentName}HandleCommand(
id<RCT${componentName}ViewProtocol> componentView,
NSString const *commandName,
NSArray const *args)
{
${ifCases}
#if RCT_DEBUG
RCTLogError(@"%@ received command %@, which is not a supported command.", @"${componentName}", commandName);
#endif
}
`.trim();
const FileTemplate = ({componentContent}) =>
`
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GenerateComponentHObjCpp.js
*/
#import <Foundation/Foundation.h>
#import <React/RCTDefines.h>
#import <React/RCTLog.h>
NS_ASSUME_NONNULL_BEGIN
${componentContent}
NS_ASSUME_NONNULL_END
`.trim();
function getObjCParamType(param) {
const typeAnnotation = param.typeAnnotation;
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return 'double';
default:
typeAnnotation.name;
throw new Error(`Receieved invalid type: ${typeAnnotation.name}`);
}
case 'BooleanTypeAnnotation':
return 'BOOL';
case 'DoubleTypeAnnotation':
return 'double';
case 'FloatTypeAnnotation':
return 'float';
case 'Int32TypeAnnotation':
return 'NSInteger';
case 'StringTypeAnnotation':
return 'NSString *';
case 'ArrayTypeAnnotation':
return 'const NSArray *';
default:
typeAnnotation.type;
throw new Error('Received invalid param type annotation');
}
}
function getObjCExpectedKindParamType(param) {
const typeAnnotation = param.typeAnnotation;
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return '[NSNumber class]';
default:
typeAnnotation.name;
throw new Error(`Receieved invalid type: ${typeAnnotation.name}`);
}
case 'BooleanTypeAnnotation':
return '[NSNumber class]';
case 'DoubleTypeAnnotation':
return '[NSNumber class]';
case 'FloatTypeAnnotation':
return '[NSNumber class]';
case 'Int32TypeAnnotation':
return '[NSNumber class]';
case 'StringTypeAnnotation':
return '[NSString class]';
case 'ArrayTypeAnnotation':
return '[NSArray class]';
default:
typeAnnotation.type;
throw new Error('Received invalid param type annotation');
}
}
function getReadableExpectedKindParamType(param) {
const typeAnnotation = param.typeAnnotation;
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return 'double';
default:
typeAnnotation.name;
throw new Error(`Receieved invalid type: ${typeAnnotation.name}`);
}
case 'BooleanTypeAnnotation':
return 'boolean';
case 'DoubleTypeAnnotation':
return 'double';
case 'FloatTypeAnnotation':
return 'float';
case 'Int32TypeAnnotation':
return 'number';
case 'StringTypeAnnotation':
return 'string';
case 'ArrayTypeAnnotation':
return 'array';
default:
typeAnnotation.type;
throw new Error('Received invalid param type annotation');
}
}
function getObjCRightHandAssignmentParamType(param, index) {
const typeAnnotation = param.typeAnnotation;
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return `[(NSNumber *)arg${index} doubleValue]`;
default:
typeAnnotation.name;
throw new Error(`Receieved invalid type: ${typeAnnotation.name}`);
}
case 'BooleanTypeAnnotation':
return `[(NSNumber *)arg${index} boolValue]`;
case 'DoubleTypeAnnotation':
return `[(NSNumber *)arg${index} doubleValue]`;
case 'FloatTypeAnnotation':
return `[(NSNumber *)arg${index} floatValue]`;
case 'Int32TypeAnnotation':
return `[(NSNumber *)arg${index} intValue]`;
case 'StringTypeAnnotation':
return `(NSString *)arg${index}`;
case 'ArrayTypeAnnotation':
return `(NSArray *)arg${index}`;
default:
typeAnnotation.type;
throw new Error('Received invalid param type annotation');
}
}
function generateProtocol(component, componentName) {
const methods = component.commands
.map(command => {
const params = command.typeAnnotation.params;
const paramString =
params.length === 0
? ''
: params
.map((param, index) => {
const objCType = getObjCParamType(param);
return `${index === 0 ? '' : param.name}:(${objCType})${
param.name
}`;
})
.join(' ');
return `- (void)${command.name}${paramString};`;
})
.join('\n')
.trim();
return ProtocolTemplate({
componentName,
methods,
});
}
function generateConvertAndValidateParam(param, index, componentName) {
const leftSideType = getObjCParamType(param);
const expectedKind = getObjCExpectedKindParamType(param);
const expectedKindString = getReadableExpectedKindParamType(param);
const argConversion = `${leftSideType} ${
param.name
} = ${getObjCRightHandAssignmentParamType(param, index)};`;
return CommandHandlerIfCaseConvertArgTemplate({
componentName,
argConversion,
argNumber: index,
argNumberString: getOrdinalNumber(index + 1),
expectedKind,
expectedKindString,
});
}
function generateCommandIfCase(command, componentName) {
const params = command.typeAnnotation.params;
const convertArgs = params
.map((param, index) =>
generateConvertAndValidateParam(param, index, componentName),
)
.join('\n\n')
.trim();
const commandCallArgs =
params.length === 0
? ''
: params
.map((param, index) => {
return `${index === 0 ? '' : param.name}:${param.name}`;
})
.join(' ');
const commandCall = `[componentView ${command.name}${commandCallArgs}];`;
return CommandHandlerIfCaseTemplate({
componentName,
commandName: command.name,
numArgs: params.length,
convertArgs,
commandCall,
});
}
function generateCommandHandler(component, componentName) {
if (component.commands.length === 0) {
return null;
}
const ifCases = component.commands
.map(command => generateCommandIfCase(command, componentName))
.join('\n\n');
return CommandHandlerTemplate({
componentName,
ifCases,
});
}
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
) {
const fileName = 'RCTComponentViewHelpers.h';
const componentContent = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const components = module.components;
// No components in this module
if (components == null) {
return null;
}
return Object.keys(components)
.filter(componentName => {
const component = components[componentName];
return !(
component.excludedPlatforms &&
component.excludedPlatforms.includes('iOS')
);
})
.map(componentName => {
return [
generateProtocol(components[componentName], componentName),
generateCommandHandler(components[componentName], componentName),
]
.join('\n\n')
.trim();
})
.join('\n\n');
})
.filter(Boolean)
.join('\n\n');
const replacedTemplate = FileTemplate({
componentContent,
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,427 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {
CommandParamTypeAnnotation,
CommandTypeAnnotation,
ComponentShape,
NamedShape,
SchemaType,
} from '../../CodegenSchema';
type FilesOutput = Map<string, string>;
function getOrdinalNumber(num: number): string {
switch (num) {
case 1:
return '1st';
case 2:
return '2nd';
case 3:
return '3rd';
}
if (num <= 20) {
return `${num}th`;
}
return 'unknown';
}
const ProtocolTemplate = ({
componentName,
methods,
}: {
componentName: string,
methods: string,
}) =>
`
@protocol RCT${componentName}ViewProtocol <NSObject>
${methods}
@end
`.trim();
const CommandHandlerIfCaseConvertArgTemplate = ({
componentName,
expectedKind,
argNumber,
argNumberString,
expectedKindString,
argConversion,
}: {
componentName: string,
expectedKind: string,
argNumber: number,
argNumberString: string,
expectedKindString: string,
argConversion: string,
}) =>
`
NSObject *arg${argNumber} = args[${argNumber}];
#if RCT_DEBUG
if (!RCTValidateTypeOfViewCommandArgument(arg${argNumber}, ${expectedKind}, @"${expectedKindString}", @"${componentName}", commandName, @"${argNumberString}")) {
return;
}
#endif
${argConversion}
`.trim();
const CommandHandlerIfCaseTemplate = ({
componentName,
commandName,
numArgs,
convertArgs,
commandCall,
}: {
componentName: string,
commandName: string,
numArgs: number,
convertArgs: string,
commandCall: string,
}) =>
`
if ([commandName isEqualToString:@"${commandName}"]) {
#if RCT_DEBUG
if ([args count] != ${numArgs}) {
RCTLogError(@"%@ command %@ received %d arguments, expected %d.", @"${componentName}", commandName, (int)[args count], ${numArgs});
return;
}
#endif
${convertArgs}
${commandCall}
return;
}
`.trim();
const CommandHandlerTemplate = ({
componentName,
ifCases,
}: {
componentName: string,
ifCases: string,
}) =>
`
RCT_EXTERN inline void RCT${componentName}HandleCommand(
id<RCT${componentName}ViewProtocol> componentView,
NSString const *commandName,
NSArray const *args)
{
${ifCases}
#if RCT_DEBUG
RCTLogError(@"%@ received command %@, which is not a supported command.", @"${componentName}", commandName);
#endif
}
`.trim();
const FileTemplate = ({componentContent}: {componentContent: string}) =>
`
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GenerateComponentHObjCpp.js
*/
#import <Foundation/Foundation.h>
#import <React/RCTDefines.h>
#import <React/RCTLog.h>
NS_ASSUME_NONNULL_BEGIN
${componentContent}
NS_ASSUME_NONNULL_END
`.trim();
type Param = NamedShape<CommandParamTypeAnnotation>;
function getObjCParamType(param: Param): string {
const {typeAnnotation} = param;
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return 'double';
default:
(typeAnnotation.name: empty);
throw new Error(`Receieved invalid type: ${typeAnnotation.name}`);
}
case 'BooleanTypeAnnotation':
return 'BOOL';
case 'DoubleTypeAnnotation':
return 'double';
case 'FloatTypeAnnotation':
return 'float';
case 'Int32TypeAnnotation':
return 'NSInteger';
case 'StringTypeAnnotation':
return 'NSString *';
case 'ArrayTypeAnnotation':
return 'const NSArray *';
default:
(typeAnnotation.type: empty);
throw new Error('Received invalid param type annotation');
}
}
function getObjCExpectedKindParamType(param: Param): string {
const {typeAnnotation} = param;
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return '[NSNumber class]';
default:
(typeAnnotation.name: empty);
throw new Error(`Receieved invalid type: ${typeAnnotation.name}`);
}
case 'BooleanTypeAnnotation':
return '[NSNumber class]';
case 'DoubleTypeAnnotation':
return '[NSNumber class]';
case 'FloatTypeAnnotation':
return '[NSNumber class]';
case 'Int32TypeAnnotation':
return '[NSNumber class]';
case 'StringTypeAnnotation':
return '[NSString class]';
case 'ArrayTypeAnnotation':
return '[NSArray class]';
default:
(typeAnnotation.type: empty);
throw new Error('Received invalid param type annotation');
}
}
function getReadableExpectedKindParamType(param: Param): string {
const {typeAnnotation} = param;
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return 'double';
default:
(typeAnnotation.name: empty);
throw new Error(`Receieved invalid type: ${typeAnnotation.name}`);
}
case 'BooleanTypeAnnotation':
return 'boolean';
case 'DoubleTypeAnnotation':
return 'double';
case 'FloatTypeAnnotation':
return 'float';
case 'Int32TypeAnnotation':
return 'number';
case 'StringTypeAnnotation':
return 'string';
case 'ArrayTypeAnnotation':
return 'array';
default:
(typeAnnotation.type: empty);
throw new Error('Received invalid param type annotation');
}
}
function getObjCRightHandAssignmentParamType(
param: Param,
index: number,
): string {
const {typeAnnotation} = param;
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return `[(NSNumber *)arg${index} doubleValue]`;
default:
(typeAnnotation.name: empty);
throw new Error(`Receieved invalid type: ${typeAnnotation.name}`);
}
case 'BooleanTypeAnnotation':
return `[(NSNumber *)arg${index} boolValue]`;
case 'DoubleTypeAnnotation':
return `[(NSNumber *)arg${index} doubleValue]`;
case 'FloatTypeAnnotation':
return `[(NSNumber *)arg${index} floatValue]`;
case 'Int32TypeAnnotation':
return `[(NSNumber *)arg${index} intValue]`;
case 'StringTypeAnnotation':
return `(NSString *)arg${index}`;
case 'ArrayTypeAnnotation':
return `(NSArray *)arg${index}`;
default:
(typeAnnotation.type: empty);
throw new Error('Received invalid param type annotation');
}
}
function generateProtocol(
component: ComponentShape,
componentName: string,
): string {
const methods = component.commands
.map(command => {
const params = command.typeAnnotation.params;
const paramString =
params.length === 0
? ''
: params
.map((param, index) => {
const objCType = getObjCParamType(param);
return `${index === 0 ? '' : param.name}:(${objCType})${
param.name
}`;
})
.join(' ');
return `- (void)${command.name}${paramString};`;
})
.join('\n')
.trim();
return ProtocolTemplate({
componentName,
methods,
});
}
function generateConvertAndValidateParam(
param: Param,
index: number,
componentName: string,
): string {
const leftSideType = getObjCParamType(param);
const expectedKind = getObjCExpectedKindParamType(param);
const expectedKindString = getReadableExpectedKindParamType(param);
const argConversion = `${leftSideType} ${
param.name
} = ${getObjCRightHandAssignmentParamType(param, index)};`;
return CommandHandlerIfCaseConvertArgTemplate({
componentName,
argConversion,
argNumber: index,
argNumberString: getOrdinalNumber(index + 1),
expectedKind,
expectedKindString,
});
}
function generateCommandIfCase(
command: NamedShape<CommandTypeAnnotation>,
componentName: string,
) {
const params = command.typeAnnotation.params;
const convertArgs = params
.map((param, index) =>
generateConvertAndValidateParam(param, index, componentName),
)
.join('\n\n')
.trim();
const commandCallArgs =
params.length === 0
? ''
: params
.map((param, index) => {
return `${index === 0 ? '' : param.name}:${param.name}`;
})
.join(' ');
const commandCall = `[componentView ${command.name}${commandCallArgs}];`;
return CommandHandlerIfCaseTemplate({
componentName,
commandName: command.name,
numArgs: params.length,
convertArgs,
commandCall,
});
}
function generateCommandHandler(
component: ComponentShape,
componentName: string,
): ?string {
if (component.commands.length === 0) {
return null;
}
const ifCases = component.commands
.map(command => generateCommandIfCase(command, componentName))
.join('\n\n');
return CommandHandlerTemplate({
componentName,
ifCases,
});
}
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
): FilesOutput {
const fileName = 'RCTComponentViewHelpers.h';
const componentContent = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const {components} = module;
// No components in this module
if (components == null) {
return null;
}
return Object.keys(components)
.filter(componentName => {
const component = components[componentName];
return !(
component.excludedPlatforms &&
component.excludedPlatforms.includes('iOS')
);
})
.map(componentName => {
return [
generateProtocol(components[componentName], componentName),
generateCommandHandler(components[componentName], componentName),
]
.join('\n\n')
.trim();
})
.join('\n\n');
})
.filter(Boolean)
.join('\n\n');
const replacedTemplate = FileTemplate({
componentContent,
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,399 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const _require = require('../Utils'),
indent = _require.indent;
const _require2 = require('./CppHelpers'),
IncludeTemplate = _require2.IncludeTemplate,
generateEventStructName = _require2.generateEventStructName;
// File path -> contents
const FileTemplate = ({events, extraIncludes, headerPrefix}) => `
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GenerateEventEmitterCpp.js
*/
${IncludeTemplate({
headerPrefix,
file: 'EventEmitters.h',
})}
${[...extraIncludes].join('\n')}
namespace facebook::react {
${events}
} // namespace facebook::react
`;
const ComponentTemplate = ({
className,
eventName,
structName,
dispatchEventName,
implementation,
}) => {
const capture = implementation.includes('$event')
? '$event=std::move($event)'
: '';
return `
void ${className}EventEmitter::${eventName}(${structName} $event) const {
dispatchEvent("${dispatchEventName}", [${capture}](jsi::Runtime &runtime) {
${implementation}
});
}
`;
};
const BasicComponentTemplate = ({className, eventName, dispatchEventName}) =>
`
void ${className}EventEmitter::${eventName}() const {
dispatchEvent("${dispatchEventName}");
}
`.trim();
function generateSetter(
variableName,
propertyName,
propertyParts,
usingEvent,
valueMapper = value => value,
) {
const eventChain = usingEvent
? `$event.${[...propertyParts, propertyName].join('.')}`
: [propertyParts, propertyName].join('.');
return `${variableName}.setProperty(runtime, "${propertyName}", ${valueMapper(
eventChain,
)});`;
}
function generateObjectSetter(
variableName,
propertyName,
propertyParts,
typeAnnotation,
extraIncludes,
usingEvent,
) {
return `
{
auto ${propertyName} = jsi::Object(runtime);
${indent(
generateSetters(
propertyName,
typeAnnotation.properties,
propertyParts.concat([propertyName]),
extraIncludes,
usingEvent,
),
2,
)}
${variableName}.setProperty(runtime, "${propertyName}", ${propertyName});
}
`.trim();
}
function setValueAtIndex(
propertyName,
indexVariable,
loopLocalVariable,
mappingFunction = value => value,
) {
return `${propertyName}.setValueAtIndex(runtime, ${indexVariable}++, ${mappingFunction(
loopLocalVariable,
)});`;
}
function generateArraySetter(
variableName,
propertyName,
propertyParts,
elementType,
extraIncludes,
usingEvent,
) {
const eventChain = usingEvent
? `$event.${[...propertyParts, propertyName].join('.')}`
: [propertyParts, propertyName].join('.');
const indexVar = `${propertyName}Index`;
const innerLoopVar = `${propertyName}Value`;
return `
auto ${propertyName} = jsi::Array(runtime, ${eventChain}.size());
size_t ${indexVar} = 0;
for (auto ${innerLoopVar} : ${eventChain}) {
${handleArrayElementType(
elementType,
propertyName,
indexVar,
innerLoopVar,
propertyParts,
extraIncludes,
usingEvent,
)}
}
${variableName}.setProperty(runtime, "${propertyName}", ${propertyName});
`;
}
function handleArrayElementType(
elementType,
propertyName,
indexVariable,
loopLocalVariable,
propertyParts,
extraIncludes,
usingEvent,
) {
switch (elementType.type) {
case 'BooleanTypeAnnotation':
return setValueAtIndex(
propertyName,
indexVariable,
loopLocalVariable,
val => `(bool)${val}`,
);
case 'StringTypeAnnotation':
case 'Int32TypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
return setValueAtIndex(propertyName, indexVariable, loopLocalVariable);
case 'MixedTypeAnnotation':
return setValueAtIndex(
propertyName,
indexVariable,
loopLocalVariable,
val => `jsi::valueFromDynamic(runtime, ${val})`,
);
case 'StringEnumTypeAnnotation':
return setValueAtIndex(
propertyName,
indexVariable,
loopLocalVariable,
val => `toString(${val})`,
);
case 'ObjectTypeAnnotation':
return convertObjectTypeArray(
propertyName,
indexVariable,
loopLocalVariable,
propertyParts,
elementType,
extraIncludes,
);
case 'ArrayTypeAnnotation':
return convertArrayTypeArray(
propertyName,
indexVariable,
loopLocalVariable,
propertyParts,
elementType,
extraIncludes,
usingEvent,
);
default:
throw new Error(
`Received invalid elementType for array ${elementType.type}`,
);
}
}
function convertObjectTypeArray(
propertyName,
indexVariable,
loopLocalVariable,
propertyParts,
objectTypeAnnotation,
extraIncludes,
) {
return `auto ${propertyName}Object = jsi::Object(runtime);
${generateSetters(
`${propertyName}Object`,
objectTypeAnnotation.properties,
[].concat([loopLocalVariable]),
extraIncludes,
false,
)}
${setValueAtIndex(propertyName, indexVariable, `${propertyName}Object`)}`;
}
function convertArrayTypeArray(
propertyName,
indexVariable,
loopLocalVariable,
propertyParts,
eventTypeAnnotation,
extraIncludes,
usingEvent,
) {
if (eventTypeAnnotation.type !== 'ArrayTypeAnnotation') {
throw new Error(
`Inconsistent eventTypeAnnotation received. Expected type = 'ArrayTypeAnnotation'; received = ${eventTypeAnnotation.type}`,
);
}
return `auto ${propertyName}Array = jsi::Array(runtime, ${loopLocalVariable}.size());
size_t ${indexVariable}Internal = 0;
for (auto ${loopLocalVariable}Internal : ${loopLocalVariable}) {
${handleArrayElementType(
eventTypeAnnotation.elementType,
`${propertyName}Array`,
`${indexVariable}Internal`,
`${loopLocalVariable}Internal`,
propertyParts,
extraIncludes,
usingEvent,
)}
}
${setValueAtIndex(propertyName, indexVariable, `${propertyName}Array`)}`;
}
function generateSetters(
parentPropertyName,
properties,
propertyParts,
extraIncludes,
usingEvent = true,
) {
const propSetters = properties
.map(eventProperty => {
const typeAnnotation = eventProperty.typeAnnotation;
switch (typeAnnotation.type) {
case 'BooleanTypeAnnotation':
case 'StringTypeAnnotation':
case 'Int32TypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
return generateSetter(
parentPropertyName,
eventProperty.name,
propertyParts,
usingEvent,
);
case 'MixedTypeAnnotation':
extraIncludes.add('#include <jsi/JSIDynamic.h>');
return generateSetter(
parentPropertyName,
eventProperty.name,
propertyParts,
usingEvent,
prop => `jsi::valueFromDynamic(runtime, ${prop})`,
);
case 'StringEnumTypeAnnotation':
return generateSetter(
parentPropertyName,
eventProperty.name,
propertyParts,
usingEvent,
prop => `toString(${prop})`,
);
case 'ObjectTypeAnnotation':
return generateObjectSetter(
parentPropertyName,
eventProperty.name,
propertyParts,
typeAnnotation,
extraIncludes,
usingEvent,
);
case 'ArrayTypeAnnotation':
return generateArraySetter(
parentPropertyName,
eventProperty.name,
propertyParts,
typeAnnotation.elementType,
extraIncludes,
usingEvent,
);
default:
typeAnnotation.type;
throw new Error(
`Received invalid event property type ${typeAnnotation.type}`,
);
}
})
.join('\n');
return propSetters;
}
function generateEvent(componentName, event, extraIncludes) {
// This is a gross hack necessary because native code is sending
// events named things like topChange to JS which is then converted back to
// call the onChange prop. We should be consistent throughout the system.
// In order to migrate to this new system we have to support the current
// naming scheme. We should delete this once we are able to control this name
// throughout the system.
const dispatchEventName =
event.paperTopLevelNameDeprecated != null
? event.paperTopLevelNameDeprecated
: `${event.name[2].toLowerCase()}${event.name.slice(3)}`;
if (event.typeAnnotation.argument) {
const implementation = `
auto $payload = jsi::Object(runtime);
${generateSetters(
'$payload',
event.typeAnnotation.argument.properties,
[],
extraIncludes,
)}
return $payload;
`.trim();
if (!event.name.startsWith('on')) {
throw new Error('Expected the event name to start with `on`');
}
return ComponentTemplate({
className: componentName,
eventName: event.name,
dispatchEventName,
structName: generateEventStructName([event.name]),
implementation,
});
}
return BasicComponentTemplate({
className: componentName,
eventName: event.name,
dispatchEventName,
});
}
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
) {
const moduleComponents = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const components = module.components;
// No components in this module
if (components == null) {
return null;
}
return components;
})
.filter(Boolean)
.reduce((acc, components) => Object.assign(acc, components), {});
const extraIncludes = new Set();
const componentEmitters = Object.keys(moduleComponents)
.map(componentName => {
const component = moduleComponents[componentName];
return component.events
.map(event => generateEvent(componentName, event, extraIncludes))
.join('\n');
})
.join('\n');
const fileName = 'EventEmitters.cpp';
const replacedTemplate = FileTemplate({
events: componentEmitters,
extraIncludes,
headerPrefix:
headerPrefix !== null && headerPrefix !== void 0 ? headerPrefix : '',
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,454 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {EventTypeShape} from '../../CodegenSchema';
import type {
ComponentShape,
EventTypeAnnotation,
NamedShape,
ObjectTypeAnnotation,
SchemaType,
} from '../../CodegenSchema';
const {indent} = require('../Utils');
const {IncludeTemplate, generateEventStructName} = require('./CppHelpers');
// File path -> contents
type FilesOutput = Map<string, string>;
type ComponentCollection = $ReadOnly<{
[component: string]: ComponentShape,
...
}>;
const FileTemplate = ({
events,
extraIncludes,
headerPrefix,
}: {
events: string,
extraIncludes: Set<string>,
headerPrefix: string,
}) => `
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GenerateEventEmitterCpp.js
*/
${IncludeTemplate({headerPrefix, file: 'EventEmitters.h'})}
${[...extraIncludes].join('\n')}
namespace facebook::react {
${events}
} // namespace facebook::react
`;
const ComponentTemplate = ({
className,
eventName,
structName,
dispatchEventName,
implementation,
}: {
className: string,
eventName: string,
structName: string,
dispatchEventName: string,
implementation: string,
}) => {
const capture = implementation.includes('$event')
? '$event=std::move($event)'
: '';
return `
void ${className}EventEmitter::${eventName}(${structName} $event) const {
dispatchEvent("${dispatchEventName}", [${capture}](jsi::Runtime &runtime) {
${implementation}
});
}
`;
};
const BasicComponentTemplate = ({
className,
eventName,
dispatchEventName,
}: {
className: string,
eventName: string,
dispatchEventName: string,
}) =>
`
void ${className}EventEmitter::${eventName}() const {
dispatchEvent("${dispatchEventName}");
}
`.trim();
function generateSetter(
variableName: string,
propertyName: string,
propertyParts: $ReadOnlyArray<string>,
usingEvent: boolean,
valueMapper: string => string = value => value,
) {
const eventChain = usingEvent
? `$event.${[...propertyParts, propertyName].join('.')}`
: [propertyParts, propertyName].join('.');
return `${variableName}.setProperty(runtime, "${propertyName}", ${valueMapper(
eventChain,
)});`;
}
function generateObjectSetter(
variableName: string,
propertyName: string,
propertyParts: $ReadOnlyArray<string>,
typeAnnotation: ObjectTypeAnnotation<EventTypeAnnotation>,
extraIncludes: Set<string>,
usingEvent: boolean,
) {
return `
{
auto ${propertyName} = jsi::Object(runtime);
${indent(
generateSetters(
propertyName,
typeAnnotation.properties,
propertyParts.concat([propertyName]),
extraIncludes,
usingEvent,
),
2,
)}
${variableName}.setProperty(runtime, "${propertyName}", ${propertyName});
}
`.trim();
}
function setValueAtIndex(
propertyName: string,
indexVariable: string,
loopLocalVariable: string,
mappingFunction: string => string = value => value,
) {
return `${propertyName}.setValueAtIndex(runtime, ${indexVariable}++, ${mappingFunction(
loopLocalVariable,
)});`;
}
function generateArraySetter(
variableName: string,
propertyName: string,
propertyParts: $ReadOnlyArray<string>,
elementType: EventTypeAnnotation,
extraIncludes: Set<string>,
usingEvent: boolean,
): string {
const eventChain = usingEvent
? `$event.${[...propertyParts, propertyName].join('.')}`
: [propertyParts, propertyName].join('.');
const indexVar = `${propertyName}Index`;
const innerLoopVar = `${propertyName}Value`;
return `
auto ${propertyName} = jsi::Array(runtime, ${eventChain}.size());
size_t ${indexVar} = 0;
for (auto ${innerLoopVar} : ${eventChain}) {
${handleArrayElementType(
elementType,
propertyName,
indexVar,
innerLoopVar,
propertyParts,
extraIncludes,
usingEvent,
)}
}
${variableName}.setProperty(runtime, "${propertyName}", ${propertyName});
`;
}
function handleArrayElementType(
elementType: EventTypeAnnotation,
propertyName: string,
indexVariable: string,
loopLocalVariable: string,
propertyParts: $ReadOnlyArray<string>,
extraIncludes: Set<string>,
usingEvent: boolean,
): string {
switch (elementType.type) {
case 'BooleanTypeAnnotation':
return setValueAtIndex(
propertyName,
indexVariable,
loopLocalVariable,
val => `(bool)${val}`,
);
case 'StringTypeAnnotation':
case 'Int32TypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
return setValueAtIndex(propertyName, indexVariable, loopLocalVariable);
case 'MixedTypeAnnotation':
return setValueAtIndex(
propertyName,
indexVariable,
loopLocalVariable,
val => `jsi::valueFromDynamic(runtime, ${val})`,
);
case 'StringEnumTypeAnnotation':
return setValueAtIndex(
propertyName,
indexVariable,
loopLocalVariable,
val => `toString(${val})`,
);
case 'ObjectTypeAnnotation':
return convertObjectTypeArray(
propertyName,
indexVariable,
loopLocalVariable,
propertyParts,
elementType,
extraIncludes,
);
case 'ArrayTypeAnnotation':
return convertArrayTypeArray(
propertyName,
indexVariable,
loopLocalVariable,
propertyParts,
elementType,
extraIncludes,
usingEvent,
);
default:
throw new Error(
`Received invalid elementType for array ${elementType.type}`,
);
}
}
function convertObjectTypeArray(
propertyName: string,
indexVariable: string,
loopLocalVariable: string,
propertyParts: $ReadOnlyArray<string>,
objectTypeAnnotation: ObjectTypeAnnotation<EventTypeAnnotation>,
extraIncludes: Set<string>,
): string {
return `auto ${propertyName}Object = jsi::Object(runtime);
${generateSetters(
`${propertyName}Object`,
objectTypeAnnotation.properties,
[].concat([loopLocalVariable]),
extraIncludes,
false,
)}
${setValueAtIndex(propertyName, indexVariable, `${propertyName}Object`)}`;
}
function convertArrayTypeArray(
propertyName: string,
indexVariable: string,
loopLocalVariable: string,
propertyParts: $ReadOnlyArray<string>,
eventTypeAnnotation: EventTypeAnnotation,
extraIncludes: Set<string>,
usingEvent: boolean,
): string {
if (eventTypeAnnotation.type !== 'ArrayTypeAnnotation') {
throw new Error(
`Inconsistent eventTypeAnnotation received. Expected type = 'ArrayTypeAnnotation'; received = ${eventTypeAnnotation.type}`,
);
}
return `auto ${propertyName}Array = jsi::Array(runtime, ${loopLocalVariable}.size());
size_t ${indexVariable}Internal = 0;
for (auto ${loopLocalVariable}Internal : ${loopLocalVariable}) {
${handleArrayElementType(
eventTypeAnnotation.elementType,
`${propertyName}Array`,
`${indexVariable}Internal`,
`${loopLocalVariable}Internal`,
propertyParts,
extraIncludes,
usingEvent,
)}
}
${setValueAtIndex(propertyName, indexVariable, `${propertyName}Array`)}`;
}
function generateSetters(
parentPropertyName: string,
properties: $ReadOnlyArray<NamedShape<EventTypeAnnotation>>,
propertyParts: $ReadOnlyArray<string>,
extraIncludes: Set<string>,
usingEvent: boolean = true,
): string {
const propSetters = properties
.map(eventProperty => {
const {typeAnnotation} = eventProperty;
switch (typeAnnotation.type) {
case 'BooleanTypeAnnotation':
case 'StringTypeAnnotation':
case 'Int32TypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
return generateSetter(
parentPropertyName,
eventProperty.name,
propertyParts,
usingEvent,
);
case 'MixedTypeAnnotation':
extraIncludes.add('#include <jsi/JSIDynamic.h>');
return generateSetter(
parentPropertyName,
eventProperty.name,
propertyParts,
usingEvent,
prop => `jsi::valueFromDynamic(runtime, ${prop})`,
);
case 'StringEnumTypeAnnotation':
return generateSetter(
parentPropertyName,
eventProperty.name,
propertyParts,
usingEvent,
prop => `toString(${prop})`,
);
case 'ObjectTypeAnnotation':
return generateObjectSetter(
parentPropertyName,
eventProperty.name,
propertyParts,
typeAnnotation,
extraIncludes,
usingEvent,
);
case 'ArrayTypeAnnotation':
return generateArraySetter(
parentPropertyName,
eventProperty.name,
propertyParts,
typeAnnotation.elementType,
extraIncludes,
usingEvent,
);
default:
(typeAnnotation.type: empty);
throw new Error(
`Received invalid event property type ${typeAnnotation.type}`,
);
}
})
.join('\n');
return propSetters;
}
function generateEvent(
componentName: string,
event: EventTypeShape,
extraIncludes: Set<string>,
): string {
// This is a gross hack necessary because native code is sending
// events named things like topChange to JS which is then converted back to
// call the onChange prop. We should be consistent throughout the system.
// In order to migrate to this new system we have to support the current
// naming scheme. We should delete this once we are able to control this name
// throughout the system.
const dispatchEventName =
event.paperTopLevelNameDeprecated != null
? event.paperTopLevelNameDeprecated
: `${event.name[2].toLowerCase()}${event.name.slice(3)}`;
if (event.typeAnnotation.argument) {
const implementation = `
auto $payload = jsi::Object(runtime);
${generateSetters(
'$payload',
event.typeAnnotation.argument.properties,
[],
extraIncludes,
)}
return $payload;
`.trim();
if (!event.name.startsWith('on')) {
throw new Error('Expected the event name to start with `on`');
}
return ComponentTemplate({
className: componentName,
eventName: event.name,
dispatchEventName,
structName: generateEventStructName([event.name]),
implementation,
});
}
return BasicComponentTemplate({
className: componentName,
eventName: event.name,
dispatchEventName,
});
}
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
): FilesOutput {
const moduleComponents: ComponentCollection = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const {components} = module;
// No components in this module
if (components == null) {
return null;
}
return components;
})
.filter(Boolean)
.reduce((acc, components) => Object.assign(acc, components), {});
const extraIncludes = new Set<string>();
const componentEmitters = Object.keys(moduleComponents)
.map(componentName => {
const component = moduleComponents[componentName];
return component.events
.map(event => generateEvent(componentName, event, extraIncludes))
.join('\n');
})
.join('\n');
const fileName = 'EventEmitters.cpp';
const replacedTemplate = FileTemplate({
events: componentEmitters,
extraIncludes,
headerPrefix: headerPrefix ?? '',
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,280 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const _require = require('../Utils'),
indent = _require.indent,
toSafeCppString = _require.toSafeCppString;
const _require2 = require('./CppHelpers'),
generateEventStructName = _require2.generateEventStructName,
getCppArrayTypeForAnnotation = _require2.getCppArrayTypeForAnnotation,
getCppTypeForAnnotation = _require2.getCppTypeForAnnotation,
getImports = _require2.getImports;
const nullthrows = require('nullthrows');
// File path -> contents
const FileTemplate = ({componentEmitters, extraIncludes}) => `
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GenerateEventEmitterH.js
*/
#pragma once
#include <react/renderer/components/view/ViewEventEmitter.h>
${[...extraIncludes].join('\n')}
namespace facebook::react {
${componentEmitters}
} // namespace facebook::react
`;
const ComponentTemplate = ({className, structs, events}) =>
`
class ${className}EventEmitter : public ViewEventEmitter {
public:
using ViewEventEmitter::ViewEventEmitter;
${structs}
${events}
};
`.trim();
const StructTemplate = ({structName, fields}) =>
`
struct ${structName} {
${fields}
};
`.trim();
const EnumTemplate = ({enumName, values, toCases}) =>
`enum class ${enumName} {
${values}
};
static char const *toString(const ${enumName} value) {
switch (value) {
${toCases}
}
}
`.trim();
function getNativeTypeFromAnnotation(componentName, eventProperty, nameParts) {
const type = eventProperty.typeAnnotation.type;
switch (type) {
case 'BooleanTypeAnnotation':
case 'StringTypeAnnotation':
case 'Int32TypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
case 'MixedTypeAnnotation':
return getCppTypeForAnnotation(type);
case 'StringEnumTypeAnnotation':
case 'ObjectTypeAnnotation':
return generateEventStructName([...nameParts, eventProperty.name]);
case 'ArrayTypeAnnotation':
const eventTypeAnnotation = eventProperty.typeAnnotation;
if (eventTypeAnnotation.type !== 'ArrayTypeAnnotation') {
throw new Error(
"Inconsistent Codegen state: type was ArrayTypeAnnotation at the beginning of the body and now it isn't",
);
}
return getCppArrayTypeForAnnotation(eventTypeAnnotation.elementType, [
...nameParts,
eventProperty.name,
]);
default:
type;
throw new Error(`Received invalid event property type ${type}`);
}
}
function generateEnum(structs, options, nameParts) {
const structName = generateEventStructName(nameParts);
const fields = options
.map((option, index) => `${toSafeCppString(option)}`)
.join(',\n ');
const toCases = options
.map(
option =>
`case ${structName}::${toSafeCppString(option)}: return "${option}";`,
)
.join('\n' + ' ');
structs.set(
structName,
EnumTemplate({
enumName: structName,
values: fields,
toCases: toCases,
}),
);
}
function handleGenerateStructForArray(
structs,
name,
componentName,
elementType,
nameParts,
) {
if (elementType.type === 'ObjectTypeAnnotation') {
generateStruct(
structs,
componentName,
nameParts.concat([name]),
nullthrows(elementType.properties),
);
} else if (elementType.type === 'StringEnumTypeAnnotation') {
generateEnum(structs, elementType.options, nameParts.concat([name]));
} else if (elementType.type === 'ArrayTypeAnnotation') {
handleGenerateStructForArray(
structs,
name,
componentName,
elementType.elementType,
nameParts,
);
}
}
function generateStruct(structs, componentName, nameParts, properties) {
const structNameParts = nameParts;
const structName = generateEventStructName(structNameParts);
const fields = properties
.map(property => {
return `${getNativeTypeFromAnnotation(
componentName,
property,
structNameParts,
)} ${property.name};`;
})
.join('\n' + ' ');
properties.forEach(property => {
const name = property.name,
typeAnnotation = property.typeAnnotation;
switch (typeAnnotation.type) {
case 'BooleanTypeAnnotation':
case 'StringTypeAnnotation':
case 'Int32TypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
case 'MixedTypeAnnotation':
return;
case 'ArrayTypeAnnotation':
handleGenerateStructForArray(
structs,
name,
componentName,
typeAnnotation.elementType,
nameParts,
);
return;
case 'ObjectTypeAnnotation':
generateStruct(
structs,
componentName,
nameParts.concat([name]),
nullthrows(typeAnnotation.properties),
);
return;
case 'StringEnumTypeAnnotation':
generateEnum(structs, typeAnnotation.options, nameParts.concat([name]));
return;
default:
typeAnnotation.type;
throw new Error(
`Received invalid event property type ${typeAnnotation.type}`,
);
}
});
structs.set(
structName,
StructTemplate({
structName,
fields,
}),
);
}
function generateStructs(componentName, component) {
const structs = new Map();
component.events.forEach(event => {
if (event.typeAnnotation.argument) {
generateStruct(
structs,
componentName,
[event.name],
event.typeAnnotation.argument.properties,
);
}
});
return Array.from(structs.values()).join('\n\n');
}
function generateEvent(componentName, event) {
if (event.typeAnnotation.argument) {
const structName = generateEventStructName([event.name]);
return `void ${event.name}(${structName} value) const;`;
}
return `void ${event.name}() const;`;
}
function generateEvents(componentName, component) {
return component.events
.map(event => generateEvent(componentName, event))
.join('\n\n' + ' ');
}
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
) {
const moduleComponents = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return null;
}
const components = module.components;
// No components in this module
if (components == null) {
return null;
}
return components;
})
.filter(Boolean)
.reduce((acc, components) => Object.assign(acc, components), {});
const extraIncludes = new Set();
const componentEmitters = Object.keys(moduleComponents)
.map(componentName => {
const component = moduleComponents[componentName];
component.events.forEach(event => {
if (event.typeAnnotation.argument) {
const argIncludes = getImports(
event.typeAnnotation.argument.properties,
);
// $FlowFixMe[method-unbinding]
argIncludes.forEach(extraIncludes.add, extraIncludes);
}
});
const replacedTemplate = ComponentTemplate({
className: componentName,
structs: indent(generateStructs(componentName, component), 2),
events: generateEvents(componentName, component),
});
return replacedTemplate;
})
.join('\n');
const fileName = 'EventEmitters.h';
const replacedTemplate = FileTemplate({
componentEmitters,
extraIncludes,
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,367 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
import type {
ComponentShape,
EventTypeAnnotation,
EventTypeShape,
NamedShape,
SchemaType,
} from '../../CodegenSchema';
const {indent, toSafeCppString} = require('../Utils');
const {
generateEventStructName,
getCppArrayTypeForAnnotation,
getCppTypeForAnnotation,
getImports,
} = require('./CppHelpers');
const nullthrows = require('nullthrows');
// File path -> contents
type FilesOutput = Map<string, string>;
type StructsMap = Map<string, string>;
type ComponentCollection = $ReadOnly<{
[component: string]: ComponentShape,
...
}>;
const FileTemplate = ({
componentEmitters,
extraIncludes,
}: {
componentEmitters: string,
extraIncludes: Set<string>,
}) => `
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GenerateEventEmitterH.js
*/
#pragma once
#include <react/renderer/components/view/ViewEventEmitter.h>
${[...extraIncludes].join('\n')}
namespace facebook::react {
${componentEmitters}
} // namespace facebook::react
`;
const ComponentTemplate = ({
className,
structs,
events,
}: {
className: string,
structs: string,
events: string,
}) =>
`
class ${className}EventEmitter : public ViewEventEmitter {
public:
using ViewEventEmitter::ViewEventEmitter;
${structs}
${events}
};
`.trim();
const StructTemplate = ({
structName,
fields,
}: {
structName: string,
fields: string,
}) =>
`
struct ${structName} {
${fields}
};
`.trim();
const EnumTemplate = ({
enumName,
values,
toCases,
}: {
enumName: string,
values: string,
toCases: string,
}) =>
`enum class ${enumName} {
${values}
};
static char const *toString(const ${enumName} value) {
switch (value) {
${toCases}
}
}
`.trim();
function getNativeTypeFromAnnotation(
componentName: string,
eventProperty: NamedShape<EventTypeAnnotation>,
nameParts: $ReadOnlyArray<string>,
): string {
const {type} = eventProperty.typeAnnotation;
switch (type) {
case 'BooleanTypeAnnotation':
case 'StringTypeAnnotation':
case 'Int32TypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
case 'MixedTypeAnnotation':
return getCppTypeForAnnotation(type);
case 'StringEnumTypeAnnotation':
case 'ObjectTypeAnnotation':
return generateEventStructName([...nameParts, eventProperty.name]);
case 'ArrayTypeAnnotation':
const eventTypeAnnotation = eventProperty.typeAnnotation;
if (eventTypeAnnotation.type !== 'ArrayTypeAnnotation') {
throw new Error(
"Inconsistent Codegen state: type was ArrayTypeAnnotation at the beginning of the body and now it isn't",
);
}
return getCppArrayTypeForAnnotation(eventTypeAnnotation.elementType, [
...nameParts,
eventProperty.name,
]);
default:
(type: empty);
throw new Error(`Received invalid event property type ${type}`);
}
}
function generateEnum(
structs: StructsMap,
options: $ReadOnlyArray<string>,
nameParts: Array<string>,
) {
const structName = generateEventStructName(nameParts);
const fields = options
.map((option, index) => `${toSafeCppString(option)}`)
.join(',\n ');
const toCases = options
.map(
option =>
`case ${structName}::${toSafeCppString(option)}: return "${option}";`,
)
.join('\n' + ' ');
structs.set(
structName,
EnumTemplate({
enumName: structName,
values: fields,
toCases: toCases,
}),
);
}
function handleGenerateStructForArray(
structs: StructsMap,
name: string,
componentName: string,
elementType: EventTypeAnnotation,
nameParts: $ReadOnlyArray<string>,
): void {
if (elementType.type === 'ObjectTypeAnnotation') {
generateStruct(
structs,
componentName,
nameParts.concat([name]),
nullthrows(elementType.properties),
);
} else if (elementType.type === 'StringEnumTypeAnnotation') {
generateEnum(structs, elementType.options, nameParts.concat([name]));
} else if (elementType.type === 'ArrayTypeAnnotation') {
handleGenerateStructForArray(
structs,
name,
componentName,
elementType.elementType,
nameParts,
);
}
}
function generateStruct(
structs: StructsMap,
componentName: string,
nameParts: $ReadOnlyArray<string>,
properties: $ReadOnlyArray<NamedShape<EventTypeAnnotation>>,
): void {
const structNameParts = nameParts;
const structName = generateEventStructName(structNameParts);
const fields = properties
.map(property => {
return `${getNativeTypeFromAnnotation(
componentName,
property,
structNameParts,
)} ${property.name};`;
})
.join('\n' + ' ');
properties.forEach(property => {
const {name, typeAnnotation} = property;
switch (typeAnnotation.type) {
case 'BooleanTypeAnnotation':
case 'StringTypeAnnotation':
case 'Int32TypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
case 'MixedTypeAnnotation':
return;
case 'ArrayTypeAnnotation':
handleGenerateStructForArray(
structs,
name,
componentName,
typeAnnotation.elementType,
nameParts,
);
return;
case 'ObjectTypeAnnotation':
generateStruct(
structs,
componentName,
nameParts.concat([name]),
nullthrows(typeAnnotation.properties),
);
return;
case 'StringEnumTypeAnnotation':
generateEnum(structs, typeAnnotation.options, nameParts.concat([name]));
return;
default:
(typeAnnotation.type: empty);
throw new Error(
`Received invalid event property type ${typeAnnotation.type}`,
);
}
});
structs.set(
structName,
StructTemplate({
structName,
fields,
}),
);
}
function generateStructs(
componentName: string,
component: ComponentShape,
): string {
const structs: StructsMap = new Map();
component.events.forEach(event => {
if (event.typeAnnotation.argument) {
generateStruct(
structs,
componentName,
[event.name],
event.typeAnnotation.argument.properties,
);
}
});
return Array.from(structs.values()).join('\n\n');
}
function generateEvent(componentName: string, event: EventTypeShape): string {
if (event.typeAnnotation.argument) {
const structName = generateEventStructName([event.name]);
return `void ${event.name}(${structName} value) const;`;
}
return `void ${event.name}() const;`;
}
function generateEvents(
componentName: string,
component: ComponentShape,
): string {
return component.events
.map(event => generateEvent(componentName, event))
.join('\n\n' + ' ');
}
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
): FilesOutput {
const moduleComponents: ComponentCollection = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return null;
}
const {components} = module;
// No components in this module
if (components == null) {
return null;
}
return components;
})
.filter(Boolean)
.reduce((acc, components) => Object.assign(acc, components), {});
const extraIncludes = new Set<string>();
const componentEmitters = Object.keys(moduleComponents)
.map(componentName => {
const component = moduleComponents[componentName];
component.events.forEach(event => {
if (event.typeAnnotation.argument) {
const argIncludes = getImports(
event.typeAnnotation.argument.properties,
);
// $FlowFixMe[method-unbinding]
argIncludes.forEach(extraIncludes.add, extraIncludes);
}
});
const replacedTemplate = ComponentTemplate({
className: componentName,
structs: indent(generateStructs(componentName, component), 2),
events: generateEvents(componentName, component),
});
return replacedTemplate;
})
.join('\n');
const fileName = 'EventEmitters.h';
const replacedTemplate = FileTemplate({
componentEmitters,
extraIncludes,
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,135 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const _require = require('./CppHelpers'),
IncludeTemplate = _require.IncludeTemplate,
convertDefaultTypeToString = _require.convertDefaultTypeToString,
getImports = _require.getImports;
// File path -> contents
const FileTemplate = ({imports, componentClasses, headerPrefix}) => `
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GeneratePropsCpp.js
*/
${IncludeTemplate({
headerPrefix,
file: 'Props.h',
})}
${imports}
namespace facebook::react {
${componentClasses}
} // namespace facebook::react
`;
const ComponentTemplate = ({className, extendClasses, props}) =>
`
${className}::${className}(
const PropsParserContext &context,
const ${className} &sourceProps,
const RawProps &rawProps):${extendClasses}
${props}
{}
`.trim();
function generatePropsString(componentName, component) {
return component.props
.map(prop => {
const defaultValue = convertDefaultTypeToString(componentName, prop);
return `${prop.name}(convertRawProp(context, rawProps, "${prop.name}", sourceProps.${prop.name}, {${defaultValue}}))`;
})
.join(',\n' + ' ');
}
function getClassExtendString(component) {
const extendString =
' ' +
component.extendsProps
.map(extendProps => {
switch (extendProps.type) {
case 'ReactNativeBuiltInType':
switch (extendProps.knownTypeName) {
case 'ReactNativeCoreViewProps':
return 'ViewProps(context, sourceProps, rawProps)';
default:
extendProps.knownTypeName;
throw new Error('Invalid knownTypeName');
}
default:
extendProps.type;
throw new Error('Invalid extended type');
}
})
.join(', ') +
`${component.props.length > 0 ? ',' : ''}`;
return extendString;
}
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
) {
const fileName = 'Props.cpp';
const allImports = new Set([
'#include <react/renderer/core/propsConversions.h>',
'#include <react/renderer/core/PropsParserContext.h>',
]);
const componentProps = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const components = module.components;
// No components in this module
if (components == null) {
return null;
}
return Object.keys(components)
.map(componentName => {
const component = components[componentName];
const newName = `${componentName}Props`;
const propsString = generatePropsString(componentName, component);
const extendString = getClassExtendString(component);
const imports = getImports(component.props);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
imports.forEach(allImports.add, allImports);
const replacedTemplate = ComponentTemplate({
className: newName,
extendClasses: extendString,
props: propsString,
});
return replacedTemplate;
})
.join('\n');
})
.filter(Boolean)
.join('\n');
const replacedTemplate = FileTemplate({
componentClasses: componentProps,
imports: Array.from(allImports).sort().join('\n').trim(),
headerPrefix:
headerPrefix !== null && headerPrefix !== void 0 ? headerPrefix : '',
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,165 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {ComponentShape, SchemaType} from '../../CodegenSchema';
const {
IncludeTemplate,
convertDefaultTypeToString,
getImports,
} = require('./CppHelpers');
// File path -> contents
type FilesOutput = Map<string, string>;
const FileTemplate = ({
imports,
componentClasses,
headerPrefix,
}: {
imports: string,
componentClasses: string,
headerPrefix: string,
}) => `
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GeneratePropsCpp.js
*/
${IncludeTemplate({headerPrefix, file: 'Props.h'})}
${imports}
namespace facebook::react {
${componentClasses}
} // namespace facebook::react
`;
const ComponentTemplate = ({
className,
extendClasses,
props,
}: {
className: string,
extendClasses: string,
props: string,
}) =>
`
${className}::${className}(
const PropsParserContext &context,
const ${className} &sourceProps,
const RawProps &rawProps):${extendClasses}
${props}
{}
`.trim();
function generatePropsString(componentName: string, component: ComponentShape) {
return component.props
.map(prop => {
const defaultValue = convertDefaultTypeToString(componentName, prop);
return `${prop.name}(convertRawProp(context, rawProps, "${prop.name}", sourceProps.${prop.name}, {${defaultValue}}))`;
})
.join(',\n' + ' ');
}
function getClassExtendString(component: ComponentShape): string {
const extendString =
' ' +
component.extendsProps
.map(extendProps => {
switch (extendProps.type) {
case 'ReactNativeBuiltInType':
switch (extendProps.knownTypeName) {
case 'ReactNativeCoreViewProps':
return 'ViewProps(context, sourceProps, rawProps)';
default:
(extendProps.knownTypeName: empty);
throw new Error('Invalid knownTypeName');
}
default:
(extendProps.type: empty);
throw new Error('Invalid extended type');
}
})
.join(', ') +
`${component.props.length > 0 ? ',' : ''}`;
return extendString;
}
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
): FilesOutput {
const fileName = 'Props.cpp';
const allImports: Set<string> = new Set([
'#include <react/renderer/core/propsConversions.h>',
'#include <react/renderer/core/PropsParserContext.h>',
]);
const componentProps = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const {components} = module;
// No components in this module
if (components == null) {
return null;
}
return Object.keys(components)
.map(componentName => {
const component = components[componentName];
const newName = `${componentName}Props`;
const propsString = generatePropsString(componentName, component);
const extendString = getClassExtendString(component);
const imports = getImports(component.props);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
imports.forEach(allImports.add, allImports);
const replacedTemplate = ComponentTemplate({
className: newName,
extendClasses: extendString,
props: propsString,
});
return replacedTemplate;
})
.join('\n');
})
.filter(Boolean)
.join('\n');
const replacedTemplate = FileTemplate({
componentClasses: componentProps,
imports: Array.from(allImports).sort().join('\n').trim(),
headerPrefix: headerPrefix ?? '',
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,617 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const _require = require('../Utils'),
getEnumName = _require.getEnumName,
toSafeCppString = _require.toSafeCppString;
const _require2 = require('./ComponentsGeneratorUtils.js'),
getLocalImports = _require2.getLocalImports,
getNativeTypeFromAnnotation = _require2.getNativeTypeFromAnnotation;
const _require3 = require('./CppHelpers.js'),
generateStructName = _require3.generateStructName,
getDefaultInitializerString = _require3.getDefaultInitializerString,
getEnumMaskName = _require3.getEnumMaskName,
toIntEnumValueName = _require3.toIntEnumValueName;
// File path -> contents
const FileTemplate = ({imports, componentClasses}) => `
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GeneratePropsH.js
*/
#pragma once
${imports}
namespace facebook::react {
${componentClasses}
} // namespace facebook::react
`;
const ClassTemplate = ({enums, structs, className, props, extendClasses}) =>
`
${enums}
${structs}
class ${className} final${extendClasses} {
public:
${className}() = default;
${className}(const PropsParserContext& context, const ${className} &sourceProps, const RawProps &rawProps);
#pragma mark - Props
${props}
};
`.trim();
const EnumTemplate = ({enumName, values, fromCases, toCases}) =>
`
enum class ${enumName} { ${values} };
static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, ${enumName} &result) {
auto string = (std::string)value;
${fromCases}
abort();
}
static inline std::string toString(const ${enumName} &value) {
switch (value) {
${toCases}
}
}
`.trim();
const IntEnumTemplate = ({enumName, values, fromCases, toCases}) =>
`
enum class ${enumName} { ${values} };
static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, ${enumName} &result) {
assert(value.hasType<int>());
auto integerValue = (int)value;
switch (integerValue) {${fromCases}
}
abort();
}
static inline std::string toString(const ${enumName} &value) {
switch (value) {
${toCases}
}
}
`.trim();
const StructTemplate = ({structName, fields, fromCases}) =>
`struct ${structName} {
${fields}
};
static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, ${structName} &result) {
auto map = (std::unordered_map<std::string, RawValue>)value;
${fromCases}
}
static inline std::string toString(const ${structName} &value) {
return "[Object ${structName}]";
}
`.trim();
const ArrayConversionFunctionTemplate = ({
structName,
}) => `static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, std::vector<${structName}> &result) {
auto items = (std::vector<RawValue>)value;
for (const auto &item : items) {
${structName} newItem;
fromRawValue(context, item, newItem);
result.emplace_back(newItem);
}
}
`;
const DoubleArrayConversionFunctionTemplate = ({
structName,
}) => `static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, std::vector<std::vector<${structName}>> &result) {
auto items = (std::vector<std::vector<RawValue>>)value;
for (const std::vector<RawValue> &item : items) {
auto nestedArray = std::vector<${structName}>{};
for (const RawValue &nestedItem : item) {
${structName} newItem;
fromRawValue(context, nestedItem, newItem);
nestedArray.emplace_back(newItem);
}
result.emplace_back(nestedArray);
}
}
`;
const ArrayEnumTemplate = ({enumName, enumMask, values, fromCases, toCases}) =>
`
using ${enumMask} = uint32_t;
enum class ${enumName}: ${enumMask} {
${values}
};
constexpr bool operator&(
${enumMask} const lhs,
enum ${enumName} const rhs) {
return lhs & static_cast<${enumMask}>(rhs);
}
constexpr ${enumMask} operator|(
${enumMask} const lhs,
enum ${enumName} const rhs) {
return lhs | static_cast<${enumMask}>(rhs);
}
constexpr void operator|=(
${enumMask} &lhs,
enum ${enumName} const rhs) {
lhs = lhs | static_cast<${enumMask}>(rhs);
}
static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, ${enumMask} &result) {
auto items = std::vector<std::string>{value};
for (const auto &item : items) {
${fromCases}
abort();
}
}
static inline std::string toString(const ${enumMask} &value) {
auto result = std::string{};
auto separator = std::string{", "};
${toCases}
if (!result.empty()) {
result.erase(result.length() - separator.length());
}
return result;
}
`.trim();
function getClassExtendString(component) {
if (component.extendsProps.length === 0) {
throw new Error('Invalid: component.extendsProps is empty');
}
const extendString =
' : ' +
component.extendsProps
.map(extendProps => {
switch (extendProps.type) {
case 'ReactNativeBuiltInType':
switch (extendProps.knownTypeName) {
case 'ReactNativeCoreViewProps':
return 'public ViewProps';
default:
extendProps.knownTypeName;
throw new Error('Invalid knownTypeName');
}
default:
extendProps.type;
throw new Error('Invalid extended type');
}
})
.join(' ');
return extendString;
}
function convertValueToEnumOption(value) {
return toSafeCppString(value);
}
function generateArrayEnumString(componentName, name, options) {
const enumName = getEnumName(componentName, name);
const values = options
.map((option, index) => `${toSafeCppString(option)} = 1 << ${index}`)
.join(',\n ');
const fromCases = options
.map(
option => `if (item == "${option}") {
result |= ${enumName}::${toSafeCppString(option)};
continue;
}`,
)
.join('\n ');
const toCases = options
.map(
option => `if (value & ${enumName}::${toSafeCppString(option)}) {
result += "${option}" + separator;
}`,
)
.join('\n' + ' ');
return ArrayEnumTemplate({
enumName,
enumMask: getEnumMaskName(enumName),
values,
fromCases,
toCases,
});
}
function generateStringEnum(componentName, prop) {
const typeAnnotation = prop.typeAnnotation;
if (typeAnnotation.type === 'StringEnumTypeAnnotation') {
const values = typeAnnotation.options;
const enumName = getEnumName(componentName, prop.name);
const fromCases = values
.map(
value =>
`if (string == "${value}") { result = ${enumName}::${convertValueToEnumOption(
value,
)}; return; }`,
)
.join('\n' + ' ');
const toCases = values
.map(
value =>
`case ${enumName}::${convertValueToEnumOption(
value,
)}: return "${value}";`,
)
.join('\n' + ' ');
return EnumTemplate({
enumName,
values: values.map(toSafeCppString).join(', '),
fromCases: fromCases,
toCases: toCases,
});
}
return '';
}
function generateIntEnum(componentName, prop) {
const typeAnnotation = prop.typeAnnotation;
if (typeAnnotation.type === 'Int32EnumTypeAnnotation') {
const values = typeAnnotation.options;
const enumName = getEnumName(componentName, prop.name);
const fromCases = values
.map(
value => `
case ${value}:
result = ${enumName}::${toIntEnumValueName(prop.name, value)};
return;`,
)
.join('');
const toCases = values
.map(
value =>
`case ${enumName}::${toIntEnumValueName(
prop.name,
value,
)}: return "${value}";`,
)
.join('\n' + ' ');
const valueVariables = values
.map(val => `${toIntEnumValueName(prop.name, val)} = ${val}`)
.join(', ');
return IntEnumTemplate({
enumName,
values: valueVariables,
fromCases,
toCases,
});
}
return '';
}
function generateEnumString(componentName, component) {
return component.props
.map(prop => {
if (
prop.typeAnnotation.type === 'ArrayTypeAnnotation' &&
prop.typeAnnotation.elementType.type === 'StringEnumTypeAnnotation'
) {
return generateArrayEnumString(
componentName,
prop.name,
prop.typeAnnotation.elementType.options,
);
}
if (prop.typeAnnotation.type === 'StringEnumTypeAnnotation') {
return generateStringEnum(componentName, prop);
}
if (prop.typeAnnotation.type === 'Int32EnumTypeAnnotation') {
return generateIntEnum(componentName, prop);
}
if (prop.typeAnnotation.type === 'ObjectTypeAnnotation') {
return prop.typeAnnotation.properties
.map(property => {
if (property.typeAnnotation.type === 'StringEnumTypeAnnotation') {
return generateStringEnum(componentName, property);
} else if (
property.typeAnnotation.type === 'Int32EnumTypeAnnotation'
) {
return generateIntEnum(componentName, property);
}
return null;
})
.filter(Boolean)
.join('\n');
}
})
.filter(Boolean)
.join('\n');
}
function generatePropsString(componentName, props, nameParts) {
return props
.map(prop => {
const nativeType = getNativeTypeFromAnnotation(
componentName,
prop,
nameParts,
);
const defaultInitializer = getDefaultInitializerString(
componentName,
prop,
);
return `${nativeType} ${prop.name}${defaultInitializer};`;
})
.join('\n' + ' ');
}
function getExtendsImports(extendsProps) {
const imports = new Set();
imports.add('#include <react/renderer/core/PropsParserContext.h>');
extendsProps.forEach(extendProps => {
switch (extendProps.type) {
case 'ReactNativeBuiltInType':
switch (extendProps.knownTypeName) {
case 'ReactNativeCoreViewProps':
imports.add(
'#include <react/renderer/components/view/ViewProps.h>',
);
return;
default:
extendProps.knownTypeName;
throw new Error('Invalid knownTypeName');
}
default:
extendProps.type;
throw new Error('Invalid extended type');
}
});
return imports;
}
function generateStructsForComponent(componentName, component) {
const structs = generateStructs(componentName, component.props, []);
const structArray = Array.from(structs.values());
if (structArray.length < 1) {
return '';
}
return structArray.join('\n\n');
}
function generateStructs(componentName, properties, nameParts) {
const structs = new Map();
properties.forEach(prop => {
const typeAnnotation = prop.typeAnnotation;
if (typeAnnotation.type === 'ObjectTypeAnnotation') {
// Recursively visit all of the object properties.
// Note: this is depth first so that the nested structs are ordered first.
const elementProperties = typeAnnotation.properties;
const nestedStructs = generateStructs(
componentName,
elementProperties,
nameParts.concat([prop.name]),
);
nestedStructs.forEach(function (value, key) {
structs.set(key, value);
});
generateStruct(
structs,
componentName,
nameParts.concat([prop.name]),
typeAnnotation.properties,
);
}
if (
prop.typeAnnotation.type === 'ArrayTypeAnnotation' &&
prop.typeAnnotation.elementType.type === 'ObjectTypeAnnotation'
) {
// Recursively visit all of the object properties.
// Note: this is depth first so that the nested structs are ordered first.
const elementProperties = prop.typeAnnotation.elementType.properties;
const nestedStructs = generateStructs(
componentName,
elementProperties,
nameParts.concat([prop.name]),
);
nestedStructs.forEach(function (value, key) {
structs.set(key, value);
});
// Generate this struct and its conversion function.
generateStruct(
structs,
componentName,
nameParts.concat([prop.name]),
elementProperties,
);
// Generate the conversion function for std:vector<Object>.
// Note: This needs to be at the end since it references the struct above.
structs.set(
`${[componentName, ...nameParts.concat([prop.name])].join(
'',
)}ArrayStruct`,
ArrayConversionFunctionTemplate({
structName: generateStructName(
componentName,
nameParts.concat([prop.name]),
),
}),
);
}
if (
prop.typeAnnotation.type === 'ArrayTypeAnnotation' &&
prop.typeAnnotation.elementType.type === 'ArrayTypeAnnotation' &&
prop.typeAnnotation.elementType.elementType.type ===
'ObjectTypeAnnotation'
) {
// Recursively visit all of the object properties.
// Note: this is depth first so that the nested structs are ordered first.
const elementProperties =
prop.typeAnnotation.elementType.elementType.properties;
const nestedStructs = generateStructs(
componentName,
elementProperties,
nameParts.concat([prop.name]),
);
nestedStructs.forEach(function (value, key) {
structs.set(key, value);
});
// Generate this struct and its conversion function.
generateStruct(
structs,
componentName,
nameParts.concat([prop.name]),
elementProperties,
);
// Generate the conversion function for std:vector<Object>.
// Note: This needs to be at the end since it references the struct above.
structs.set(
`${[componentName, ...nameParts.concat([prop.name])].join(
'',
)}ArrayArrayStruct`,
DoubleArrayConversionFunctionTemplate({
structName: generateStructName(
componentName,
nameParts.concat([prop.name]),
),
}),
);
}
});
return structs;
}
function generateStruct(structs, componentName, nameParts, properties) {
const structNameParts = nameParts;
const structName = generateStructName(componentName, structNameParts);
const fields = generatePropsString(
componentName,
properties,
structNameParts,
);
properties.forEach(property => {
const name = property.name;
switch (property.typeAnnotation.type) {
case 'BooleanTypeAnnotation':
return;
case 'StringTypeAnnotation':
return;
case 'Int32TypeAnnotation':
return;
case 'DoubleTypeAnnotation':
return;
case 'FloatTypeAnnotation':
return;
case 'ReservedPropTypeAnnotation':
return;
case 'ArrayTypeAnnotation':
return;
case 'StringEnumTypeAnnotation':
return;
case 'Int32EnumTypeAnnotation':
return;
case 'ObjectTypeAnnotation':
const props = property.typeAnnotation.properties;
if (props == null) {
throw new Error(
`Properties are expected for ObjectTypeAnnotation (see ${name} in ${componentName})`,
);
}
generateStruct(structs, componentName, nameParts.concat([name]), props);
return;
case 'MixedTypeAnnotation':
return;
default:
property.typeAnnotation.type;
throw new Error(
`Received invalid component property type ${property.typeAnnotation.type}`,
);
}
});
const fromCases = properties
.map(property => {
const variable = 'tmp_' + property.name;
return `auto ${variable} = map.find("${property.name}");
if (${variable} != map.end()) {
fromRawValue(context, ${variable}->second, result.${property.name});
}`;
})
.join('\n ');
structs.set(
structName,
StructTemplate({
structName,
fields,
fromCases,
}),
);
}
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
) {
const fileName = 'Props.h';
const allImports = new Set();
const componentClasses = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const components = module.components;
// No components in this module
if (components == null) {
return null;
}
return Object.keys(components)
.map(componentName => {
const component = components[componentName];
const newName = `${componentName}Props`;
const structString = generateStructsForComponent(
componentName,
component,
);
const enumString = generateEnumString(componentName, component);
const propsString = generatePropsString(
componentName,
component.props,
[],
);
const extendString = getClassExtendString(component);
const extendsImports = getExtendsImports(component.extendsProps);
const imports = getLocalImports(component.props);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
extendsImports.forEach(allImports.add, allImports);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
imports.forEach(allImports.add, allImports);
const replacedTemplate = ClassTemplate({
enums: enumString,
structs: structString,
className: newName,
extendClasses: extendString,
props: propsString,
});
return replacedTemplate;
})
.join('\n\n');
})
.filter(Boolean)
.join('\n\n');
const replacedTemplate = FileTemplate({
componentClasses,
imports: Array.from(allImports).sort().join('\n'),
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,777 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {ComponentShape} from '../../CodegenSchema';
import type {
ExtendsPropsShape,
NamedShape,
PropTypeAnnotation,
SchemaType,
} from '../../CodegenSchema';
const {getEnumName, toSafeCppString} = require('../Utils');
const {
getLocalImports,
getNativeTypeFromAnnotation,
} = require('./ComponentsGeneratorUtils.js');
const {
generateStructName,
getDefaultInitializerString,
getEnumMaskName,
toIntEnumValueName,
} = require('./CppHelpers.js');
// File path -> contents
type FilesOutput = Map<string, string>;
type StructsMap = Map<string, string>;
const FileTemplate = ({
imports,
componentClasses,
}: {
imports: string,
componentClasses: string,
}) => `
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GeneratePropsH.js
*/
#pragma once
${imports}
namespace facebook::react {
${componentClasses}
} // namespace facebook::react
`;
const ClassTemplate = ({
enums,
structs,
className,
props,
extendClasses,
}: {
enums: string,
structs: string,
className: string,
props: string,
extendClasses: string,
}) =>
`
${enums}
${structs}
class ${className} final${extendClasses} {
public:
${className}() = default;
${className}(const PropsParserContext& context, const ${className} &sourceProps, const RawProps &rawProps);
#pragma mark - Props
${props}
};
`.trim();
const EnumTemplate = ({
enumName,
values,
fromCases,
toCases,
}: {
enumName: string,
values: string,
fromCases: string,
toCases: string,
}) =>
`
enum class ${enumName} { ${values} };
static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, ${enumName} &result) {
auto string = (std::string)value;
${fromCases}
abort();
}
static inline std::string toString(const ${enumName} &value) {
switch (value) {
${toCases}
}
}
`.trim();
const IntEnumTemplate = ({
enumName,
values,
fromCases,
toCases,
}: {
enumName: string,
values: string,
fromCases: string,
toCases: string,
}) =>
`
enum class ${enumName} { ${values} };
static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, ${enumName} &result) {
assert(value.hasType<int>());
auto integerValue = (int)value;
switch (integerValue) {${fromCases}
}
abort();
}
static inline std::string toString(const ${enumName} &value) {
switch (value) {
${toCases}
}
}
`.trim();
const StructTemplate = ({
structName,
fields,
fromCases,
}: {
structName: string,
fields: string,
fromCases: string,
}) =>
`struct ${structName} {
${fields}
};
static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, ${structName} &result) {
auto map = (std::unordered_map<std::string, RawValue>)value;
${fromCases}
}
static inline std::string toString(const ${structName} &value) {
return "[Object ${structName}]";
}
`.trim();
const ArrayConversionFunctionTemplate = ({
structName,
}: {
structName: string,
}) => `static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, std::vector<${structName}> &result) {
auto items = (std::vector<RawValue>)value;
for (const auto &item : items) {
${structName} newItem;
fromRawValue(context, item, newItem);
result.emplace_back(newItem);
}
}
`;
const DoubleArrayConversionFunctionTemplate = ({
structName,
}: {
structName: string,
}) => `static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, std::vector<std::vector<${structName}>> &result) {
auto items = (std::vector<std::vector<RawValue>>)value;
for (const std::vector<RawValue> &item : items) {
auto nestedArray = std::vector<${structName}>{};
for (const RawValue &nestedItem : item) {
${structName} newItem;
fromRawValue(context, nestedItem, newItem);
nestedArray.emplace_back(newItem);
}
result.emplace_back(nestedArray);
}
}
`;
const ArrayEnumTemplate = ({
enumName,
enumMask,
values,
fromCases,
toCases,
}: {
enumName: string,
enumMask: string,
values: string,
fromCases: string,
toCases: string,
}) =>
`
using ${enumMask} = uint32_t;
enum class ${enumName}: ${enumMask} {
${values}
};
constexpr bool operator&(
${enumMask} const lhs,
enum ${enumName} const rhs) {
return lhs & static_cast<${enumMask}>(rhs);
}
constexpr ${enumMask} operator|(
${enumMask} const lhs,
enum ${enumName} const rhs) {
return lhs | static_cast<${enumMask}>(rhs);
}
constexpr void operator|=(
${enumMask} &lhs,
enum ${enumName} const rhs) {
lhs = lhs | static_cast<${enumMask}>(rhs);
}
static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, ${enumMask} &result) {
auto items = std::vector<std::string>{value};
for (const auto &item : items) {
${fromCases}
abort();
}
}
static inline std::string toString(const ${enumMask} &value) {
auto result = std::string{};
auto separator = std::string{", "};
${toCases}
if (!result.empty()) {
result.erase(result.length() - separator.length());
}
return result;
}
`.trim();
function getClassExtendString(component: ComponentShape): string {
if (component.extendsProps.length === 0) {
throw new Error('Invalid: component.extendsProps is empty');
}
const extendString =
' : ' +
component.extendsProps
.map(extendProps => {
switch (extendProps.type) {
case 'ReactNativeBuiltInType':
switch (extendProps.knownTypeName) {
case 'ReactNativeCoreViewProps':
return 'public ViewProps';
default:
(extendProps.knownTypeName: empty);
throw new Error('Invalid knownTypeName');
}
default:
(extendProps.type: empty);
throw new Error('Invalid extended type');
}
})
.join(' ');
return extendString;
}
function convertValueToEnumOption(value: string): string {
return toSafeCppString(value);
}
function generateArrayEnumString(
componentName: string,
name: string,
options: $ReadOnlyArray<string>,
): string {
const enumName = getEnumName(componentName, name);
const values = options
.map((option, index) => `${toSafeCppString(option)} = 1 << ${index}`)
.join(',\n ');
const fromCases = options
.map(
option =>
`if (item == "${option}") {
result |= ${enumName}::${toSafeCppString(option)};
continue;
}`,
)
.join('\n ');
const toCases = options
.map(
option =>
`if (value & ${enumName}::${toSafeCppString(option)}) {
result += "${option}" + separator;
}`,
)
.join('\n' + ' ');
return ArrayEnumTemplate({
enumName,
enumMask: getEnumMaskName(enumName),
values,
fromCases,
toCases,
});
}
function generateStringEnum(
componentName: string,
prop: NamedShape<PropTypeAnnotation>,
) {
const typeAnnotation = prop.typeAnnotation;
if (typeAnnotation.type === 'StringEnumTypeAnnotation') {
const values: $ReadOnlyArray<string> = typeAnnotation.options;
const enumName = getEnumName(componentName, prop.name);
const fromCases = values
.map(
value =>
`if (string == "${value}") { result = ${enumName}::${convertValueToEnumOption(
value,
)}; return; }`,
)
.join('\n' + ' ');
const toCases = values
.map(
value =>
`case ${enumName}::${convertValueToEnumOption(
value,
)}: return "${value}";`,
)
.join('\n' + ' ');
return EnumTemplate({
enumName,
values: values.map(toSafeCppString).join(', '),
fromCases: fromCases,
toCases: toCases,
});
}
return '';
}
function generateIntEnum(
componentName: string,
prop: NamedShape<PropTypeAnnotation>,
) {
const typeAnnotation = prop.typeAnnotation;
if (typeAnnotation.type === 'Int32EnumTypeAnnotation') {
const values: $ReadOnlyArray<number> = typeAnnotation.options;
const enumName = getEnumName(componentName, prop.name);
const fromCases = values
.map(
value =>
`
case ${value}:
result = ${enumName}::${toIntEnumValueName(prop.name, value)};
return;`,
)
.join('');
const toCases = values
.map(
value =>
`case ${enumName}::${toIntEnumValueName(
prop.name,
value,
)}: return "${value}";`,
)
.join('\n' + ' ');
const valueVariables = values
.map(val => `${toIntEnumValueName(prop.name, val)} = ${val}`)
.join(', ');
return IntEnumTemplate({
enumName,
values: valueVariables,
fromCases,
toCases,
});
}
return '';
}
function generateEnumString(
componentName: string,
component: ComponentShape,
): string {
return component.props
.map(prop => {
if (
prop.typeAnnotation.type === 'ArrayTypeAnnotation' &&
prop.typeAnnotation.elementType.type === 'StringEnumTypeAnnotation'
) {
return generateArrayEnumString(
componentName,
prop.name,
prop.typeAnnotation.elementType.options,
);
}
if (prop.typeAnnotation.type === 'StringEnumTypeAnnotation') {
return generateStringEnum(componentName, prop);
}
if (prop.typeAnnotation.type === 'Int32EnumTypeAnnotation') {
return generateIntEnum(componentName, prop);
}
if (prop.typeAnnotation.type === 'ObjectTypeAnnotation') {
return prop.typeAnnotation.properties
.map(property => {
if (property.typeAnnotation.type === 'StringEnumTypeAnnotation') {
return generateStringEnum(componentName, property);
} else if (
property.typeAnnotation.type === 'Int32EnumTypeAnnotation'
) {
return generateIntEnum(componentName, property);
}
return null;
})
.filter(Boolean)
.join('\n');
}
})
.filter(Boolean)
.join('\n');
}
function generatePropsString(
componentName: string,
props: $ReadOnlyArray<NamedShape<PropTypeAnnotation>>,
nameParts: $ReadOnlyArray<string>,
) {
return props
.map(prop => {
const nativeType = getNativeTypeFromAnnotation(
componentName,
prop,
nameParts,
);
const defaultInitializer = getDefaultInitializerString(
componentName,
prop,
);
return `${nativeType} ${prop.name}${defaultInitializer};`;
})
.join('\n' + ' ');
}
function getExtendsImports(
extendsProps: $ReadOnlyArray<ExtendsPropsShape>,
): Set<string> {
const imports: Set<string> = new Set();
imports.add('#include <react/renderer/core/PropsParserContext.h>');
extendsProps.forEach(extendProps => {
switch (extendProps.type) {
case 'ReactNativeBuiltInType':
switch (extendProps.knownTypeName) {
case 'ReactNativeCoreViewProps':
imports.add(
'#include <react/renderer/components/view/ViewProps.h>',
);
return;
default:
(extendProps.knownTypeName: empty);
throw new Error('Invalid knownTypeName');
}
default:
(extendProps.type: empty);
throw new Error('Invalid extended type');
}
});
return imports;
}
function generateStructsForComponent(
componentName: string,
component: ComponentShape,
): string {
const structs = generateStructs(componentName, component.props, []);
const structArray = Array.from(structs.values());
if (structArray.length < 1) {
return '';
}
return structArray.join('\n\n');
}
function generateStructs(
componentName: string,
properties: $ReadOnlyArray<NamedShape<PropTypeAnnotation>>,
nameParts: Array<string>,
): StructsMap {
const structs: StructsMap = new Map();
properties.forEach(prop => {
const typeAnnotation = prop.typeAnnotation;
if (typeAnnotation.type === 'ObjectTypeAnnotation') {
// Recursively visit all of the object properties.
// Note: this is depth first so that the nested structs are ordered first.
const elementProperties = typeAnnotation.properties;
const nestedStructs = generateStructs(
componentName,
elementProperties,
nameParts.concat([prop.name]),
);
nestedStructs.forEach(function (value, key) {
structs.set(key, value);
});
generateStruct(
structs,
componentName,
nameParts.concat([prop.name]),
typeAnnotation.properties,
);
}
if (
prop.typeAnnotation.type === 'ArrayTypeAnnotation' &&
prop.typeAnnotation.elementType.type === 'ObjectTypeAnnotation'
) {
// Recursively visit all of the object properties.
// Note: this is depth first so that the nested structs are ordered first.
const elementProperties = prop.typeAnnotation.elementType.properties;
const nestedStructs = generateStructs(
componentName,
elementProperties,
nameParts.concat([prop.name]),
);
nestedStructs.forEach(function (value, key) {
structs.set(key, value);
});
// Generate this struct and its conversion function.
generateStruct(
structs,
componentName,
nameParts.concat([prop.name]),
elementProperties,
);
// Generate the conversion function for std:vector<Object>.
// Note: This needs to be at the end since it references the struct above.
structs.set(
`${[componentName, ...nameParts.concat([prop.name])].join(
'',
)}ArrayStruct`,
ArrayConversionFunctionTemplate({
structName: generateStructName(
componentName,
nameParts.concat([prop.name]),
),
}),
);
}
if (
prop.typeAnnotation.type === 'ArrayTypeAnnotation' &&
prop.typeAnnotation.elementType.type === 'ArrayTypeAnnotation' &&
prop.typeAnnotation.elementType.elementType.type ===
'ObjectTypeAnnotation'
) {
// Recursively visit all of the object properties.
// Note: this is depth first so that the nested structs are ordered first.
const elementProperties =
prop.typeAnnotation.elementType.elementType.properties;
const nestedStructs = generateStructs(
componentName,
elementProperties,
nameParts.concat([prop.name]),
);
nestedStructs.forEach(function (value, key) {
structs.set(key, value);
});
// Generate this struct and its conversion function.
generateStruct(
structs,
componentName,
nameParts.concat([prop.name]),
elementProperties,
);
// Generate the conversion function for std:vector<Object>.
// Note: This needs to be at the end since it references the struct above.
structs.set(
`${[componentName, ...nameParts.concat([prop.name])].join(
'',
)}ArrayArrayStruct`,
DoubleArrayConversionFunctionTemplate({
structName: generateStructName(
componentName,
nameParts.concat([prop.name]),
),
}),
);
}
});
return structs;
}
function generateStruct(
structs: StructsMap,
componentName: string,
nameParts: $ReadOnlyArray<string>,
properties: $ReadOnlyArray<NamedShape<PropTypeAnnotation>>,
): void {
const structNameParts = nameParts;
const structName = generateStructName(componentName, structNameParts);
const fields = generatePropsString(
componentName,
properties,
structNameParts,
);
properties.forEach((property: NamedShape<PropTypeAnnotation>) => {
const name = property.name;
switch (property.typeAnnotation.type) {
case 'BooleanTypeAnnotation':
return;
case 'StringTypeAnnotation':
return;
case 'Int32TypeAnnotation':
return;
case 'DoubleTypeAnnotation':
return;
case 'FloatTypeAnnotation':
return;
case 'ReservedPropTypeAnnotation':
return;
case 'ArrayTypeAnnotation':
return;
case 'StringEnumTypeAnnotation':
return;
case 'Int32EnumTypeAnnotation':
return;
case 'ObjectTypeAnnotation':
const props = property.typeAnnotation.properties;
if (props == null) {
throw new Error(
`Properties are expected for ObjectTypeAnnotation (see ${name} in ${componentName})`,
);
}
generateStruct(structs, componentName, nameParts.concat([name]), props);
return;
case 'MixedTypeAnnotation':
return;
default:
(property.typeAnnotation.type: empty);
throw new Error(
`Received invalid component property type ${property.typeAnnotation.type}`,
);
}
});
const fromCases = properties
.map(property => {
const variable = 'tmp_' + property.name;
return `auto ${variable} = map.find("${property.name}");
if (${variable} != map.end()) {
fromRawValue(context, ${variable}->second, result.${property.name});
}`;
})
.join('\n ');
structs.set(
structName,
StructTemplate({
structName,
fields,
fromCases,
}),
);
}
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
): FilesOutput {
const fileName = 'Props.h';
const allImports: Set<string> = new Set();
const componentClasses = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const {components} = module;
// No components in this module
if (components == null) {
return null;
}
return Object.keys(components)
.map(componentName => {
const component = components[componentName];
const newName = `${componentName}Props`;
const structString = generateStructsForComponent(
componentName,
component,
);
const enumString = generateEnumString(componentName, component);
const propsString = generatePropsString(
componentName,
component.props,
[],
);
const extendString = getClassExtendString(component);
const extendsImports = getExtendsImports(component.extendsProps);
const imports = getLocalImports(component.props);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
extendsImports.forEach(allImports.add, allImports);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
imports.forEach(allImports.add, allImports);
const replacedTemplate = ClassTemplate({
enums: enumString,
structs: structString,
className: newName,
extendClasses: extendString,
props: propsString,
});
return replacedTemplate;
})
.join('\n\n');
})
.filter(Boolean)
.join('\n\n');
const replacedTemplate = FileTemplate({
componentClasses,
imports: Array.from(allImports).sort().join('\n'),
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,299 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const _require = require('./JavaHelpers'),
getDelegateJavaClassName = _require.getDelegateJavaClassName,
getImports = _require.getImports,
getInterfaceJavaClassName = _require.getInterfaceJavaClassName,
toSafeJavaString = _require.toSafeJavaString;
// File path -> contents
const FileTemplate = ({
packageName,
imports,
className,
extendClasses,
interfaceClassName,
methods,
}) => `/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GeneratePropsJavaDelegate.js
*/
package ${packageName};
${imports}
public class ${className}<T extends ${extendClasses}, U extends BaseViewManagerInterface<T> & ${interfaceClassName}<T>> extends BaseViewManagerDelegate<T, U> {
public ${className}(U viewManager) {
super(viewManager);
}
${methods}
}
`;
const PropSetterTemplate = ({propCases}) =>
`
@Override
public void setProperty(T view, String propName, @Nullable Object value) {
${propCases}
}
`.trim();
const CommandsTemplate = ({commandCases}) =>
`
@Override
public void receiveCommand(T view, String commandName, ReadableArray args) {
switch (commandName) {
${commandCases}
}
}
`.trim();
function getJavaValueForProp(prop, componentName) {
const typeAnnotation = prop.typeAnnotation;
switch (typeAnnotation.type) {
case 'BooleanTypeAnnotation':
if (typeAnnotation.default === null) {
return 'value == null ? null : (Boolean) value';
} else {
return `value == null ? ${typeAnnotation.default.toString()} : (boolean) value`;
}
case 'StringTypeAnnotation':
const defaultValueString =
typeAnnotation.default === null
? 'null'
: `"${typeAnnotation.default}"`;
return `value == null ? ${defaultValueString} : (String) value`;
case 'Int32TypeAnnotation':
return `value == null ? ${typeAnnotation.default} : ((Double) value).intValue()`;
case 'DoubleTypeAnnotation':
if (prop.optional) {
return `value == null ? ${typeAnnotation.default}f : ((Double) value).doubleValue()`;
} else {
return 'value == null ? Double.NaN : ((Double) value).doubleValue()';
}
case 'FloatTypeAnnotation':
if (typeAnnotation.default === null) {
return 'value == null ? null : ((Double) value).floatValue()';
} else if (prop.optional) {
return `value == null ? ${typeAnnotation.default}f : ((Double) value).floatValue()`;
} else {
return 'value == null ? Float.NaN : ((Double) value).floatValue()';
}
case 'ReservedPropTypeAnnotation':
switch (typeAnnotation.name) {
case 'ColorPrimitive':
return 'ColorPropConverter.getColor(value, view.getContext())';
case 'ImageSourcePrimitive':
return '(ReadableMap) value';
case 'ImageRequestPrimitive':
return '(ReadableMap) value';
case 'PointPrimitive':
return '(ReadableMap) value';
case 'EdgeInsetsPrimitive':
return '(ReadableMap) value';
case 'DimensionPrimitive':
return 'DimensionPropConverter.getDimension(value)';
default:
typeAnnotation.name;
throw new Error('Received unknown ReservedPropTypeAnnotation');
}
case 'ArrayTypeAnnotation': {
return '(ReadableArray) value';
}
case 'ObjectTypeAnnotation': {
return '(ReadableMap) value';
}
case 'StringEnumTypeAnnotation':
return '(String) value';
case 'Int32EnumTypeAnnotation':
return `value == null ? ${typeAnnotation.default} : ((Double) value).intValue()`;
case 'MixedTypeAnnotation':
return 'new DynamicFromObject(value)';
default:
typeAnnotation;
throw new Error('Received invalid typeAnnotation');
}
}
function generatePropCasesString(component, componentName) {
if (component.props.length === 0) {
return 'super.setProperty(view, propName, value);';
}
const cases = component.props
.map(prop => {
return `case "${prop.name}":
mViewManager.set${toSafeJavaString(
prop.name,
)}(view, ${getJavaValueForProp(prop, componentName)});
break;`;
})
.join('\n' + ' ');
return `switch (propName) {
${cases}
default:
super.setProperty(view, propName, value);
}`;
}
function getCommandArgJavaType(param, index) {
const typeAnnotation = param.typeAnnotation;
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return `args.getDouble(${index})`;
default:
typeAnnotation.name;
throw new Error(`Receieved invalid type: ${typeAnnotation.name}`);
}
case 'BooleanTypeAnnotation':
return `args.getBoolean(${index})`;
case 'DoubleTypeAnnotation':
return `args.getDouble(${index})`;
case 'FloatTypeAnnotation':
return `(float) args.getDouble(${index})`;
case 'Int32TypeAnnotation':
return `args.getInt(${index})`;
case 'StringTypeAnnotation':
return `args.getString(${index})`;
case 'ArrayTypeAnnotation':
return `args.getArray(${index})`;
default:
typeAnnotation.type;
throw new Error(`Receieved invalid type: ${typeAnnotation.type}`);
}
}
function getCommandArguments(command) {
return [
'view',
...command.typeAnnotation.params.map(getCommandArgJavaType),
].join(', ');
}
function generateCommandCasesString(component, componentName) {
if (component.commands.length === 0) {
return null;
}
const commandMethods = component.commands
.map(command => {
return `case "${command.name}":
mViewManager.${toSafeJavaString(
command.name,
false,
)}(${getCommandArguments(command)});
break;`;
})
.join('\n' + ' ');
return commandMethods;
}
function getClassExtendString(component) {
const extendString = component.extendsProps
.map(extendProps => {
switch (extendProps.type) {
case 'ReactNativeBuiltInType':
switch (extendProps.knownTypeName) {
case 'ReactNativeCoreViewProps':
return 'View';
default:
extendProps.knownTypeName;
throw new Error('Invalid knownTypeName');
}
default:
extendProps.type;
throw new Error('Invalid extended type');
}
})
.join('');
return extendString;
}
function getDelegateImports(component) {
const imports = getImports(component, 'delegate');
// The delegate needs ReadableArray for commands always.
// The interface doesn't always need it
if (component.commands.length > 0) {
imports.add('import com.facebook.react.bridge.ReadableArray;');
}
imports.add('import androidx.annotation.Nullable;');
imports.add('import com.facebook.react.uimanager.BaseViewManagerDelegate;');
imports.add('import com.facebook.react.uimanager.BaseViewManagerInterface;');
return imports;
}
function generateMethods(propsString, commandsString) {
return [
PropSetterTemplate({
propCases: propsString,
}),
commandsString != null
? CommandsTemplate({
commandCases: commandsString,
})
: '',
]
.join('\n\n ')
.trimRight();
}
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
) {
// TODO: This doesn't support custom package name yet.
const normalizedPackageName = 'com.facebook.react.viewmanagers';
const outputDir = `java/${normalizedPackageName.replace(/\./g, '/')}`;
const files = new Map();
Object.keys(schema.modules).forEach(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const components = module.components;
// No components in this module
if (components == null) {
return;
}
return Object.keys(components)
.filter(componentName => {
const component = components[componentName];
return !(
component.excludedPlatforms &&
component.excludedPlatforms.includes('android')
);
})
.forEach(componentName => {
const component = components[componentName];
const className = getDelegateJavaClassName(componentName);
const interfaceClassName = getInterfaceJavaClassName(componentName);
const imports = getDelegateImports(component);
const propsString = generatePropCasesString(component, componentName);
const commandsString = generateCommandCasesString(
component,
componentName,
);
const extendString = getClassExtendString(component);
const replacedTemplate = FileTemplate({
imports: Array.from(imports).sort().join('\n'),
packageName: normalizedPackageName,
className,
extendClasses: extendString,
methods: generateMethods(propsString, commandsString),
interfaceClassName: interfaceClassName,
});
files.set(`${outputDir}/${className}.java`, replacedTemplate);
});
});
return files;
},
};

View File

@@ -0,0 +1,355 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {CommandParamTypeAnnotation} from '../../CodegenSchema';
import type {
CommandTypeAnnotation,
ComponentShape,
NamedShape,
PropTypeAnnotation,
SchemaType,
} from '../../CodegenSchema';
const {
getDelegateJavaClassName,
getImports,
getInterfaceJavaClassName,
toSafeJavaString,
} = require('./JavaHelpers');
// File path -> contents
type FilesOutput = Map<string, string>;
const FileTemplate = ({
packageName,
imports,
className,
extendClasses,
interfaceClassName,
methods,
}: {
packageName: string,
imports: string,
className: string,
extendClasses: string,
interfaceClassName: string,
methods: string,
}) => `/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GeneratePropsJavaDelegate.js
*/
package ${packageName};
${imports}
public class ${className}<T extends ${extendClasses}, U extends BaseViewManagerInterface<T> & ${interfaceClassName}<T>> extends BaseViewManagerDelegate<T, U> {
public ${className}(U viewManager) {
super(viewManager);
}
${methods}
}
`;
const PropSetterTemplate = ({propCases}: {propCases: string}) =>
`
@Override
public void setProperty(T view, String propName, @Nullable Object value) {
${propCases}
}
`.trim();
const CommandsTemplate = ({commandCases}: {commandCases: string}) =>
`
@Override
public void receiveCommand(T view, String commandName, ReadableArray args) {
switch (commandName) {
${commandCases}
}
}
`.trim();
function getJavaValueForProp(
prop: NamedShape<PropTypeAnnotation>,
componentName: string,
): string {
const typeAnnotation = prop.typeAnnotation;
switch (typeAnnotation.type) {
case 'BooleanTypeAnnotation':
if (typeAnnotation.default === null) {
return 'value == null ? null : (Boolean) value';
} else {
return `value == null ? ${typeAnnotation.default.toString()} : (boolean) value`;
}
case 'StringTypeAnnotation':
const defaultValueString =
typeAnnotation.default === null
? 'null'
: `"${typeAnnotation.default}"`;
return `value == null ? ${defaultValueString} : (String) value`;
case 'Int32TypeAnnotation':
return `value == null ? ${typeAnnotation.default} : ((Double) value).intValue()`;
case 'DoubleTypeAnnotation':
if (prop.optional) {
return `value == null ? ${typeAnnotation.default}f : ((Double) value).doubleValue()`;
} else {
return 'value == null ? Double.NaN : ((Double) value).doubleValue()';
}
case 'FloatTypeAnnotation':
if (typeAnnotation.default === null) {
return 'value == null ? null : ((Double) value).floatValue()';
} else if (prop.optional) {
return `value == null ? ${typeAnnotation.default}f : ((Double) value).floatValue()`;
} else {
return 'value == null ? Float.NaN : ((Double) value).floatValue()';
}
case 'ReservedPropTypeAnnotation':
switch (typeAnnotation.name) {
case 'ColorPrimitive':
return 'ColorPropConverter.getColor(value, view.getContext())';
case 'ImageSourcePrimitive':
return '(ReadableMap) value';
case 'ImageRequestPrimitive':
return '(ReadableMap) value';
case 'PointPrimitive':
return '(ReadableMap) value';
case 'EdgeInsetsPrimitive':
return '(ReadableMap) value';
case 'DimensionPrimitive':
return 'DimensionPropConverter.getDimension(value)';
default:
(typeAnnotation.name: empty);
throw new Error('Received unknown ReservedPropTypeAnnotation');
}
case 'ArrayTypeAnnotation': {
return '(ReadableArray) value';
}
case 'ObjectTypeAnnotation': {
return '(ReadableMap) value';
}
case 'StringEnumTypeAnnotation':
return '(String) value';
case 'Int32EnumTypeAnnotation':
return `value == null ? ${typeAnnotation.default} : ((Double) value).intValue()`;
case 'MixedTypeAnnotation':
return 'new DynamicFromObject(value)';
default:
(typeAnnotation: empty);
throw new Error('Received invalid typeAnnotation');
}
}
function generatePropCasesString(
component: ComponentShape,
componentName: string,
) {
if (component.props.length === 0) {
return 'super.setProperty(view, propName, value);';
}
const cases = component.props
.map(prop => {
return `case "${prop.name}":
mViewManager.set${toSafeJavaString(
prop.name,
)}(view, ${getJavaValueForProp(prop, componentName)});
break;`;
})
.join('\n' + ' ');
return `switch (propName) {
${cases}
default:
super.setProperty(view, propName, value);
}`;
}
function getCommandArgJavaType(
param: NamedShape<CommandParamTypeAnnotation>,
index: number,
) {
const {typeAnnotation} = param;
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return `args.getDouble(${index})`;
default:
(typeAnnotation.name: empty);
throw new Error(`Receieved invalid type: ${typeAnnotation.name}`);
}
case 'BooleanTypeAnnotation':
return `args.getBoolean(${index})`;
case 'DoubleTypeAnnotation':
return `args.getDouble(${index})`;
case 'FloatTypeAnnotation':
return `(float) args.getDouble(${index})`;
case 'Int32TypeAnnotation':
return `args.getInt(${index})`;
case 'StringTypeAnnotation':
return `args.getString(${index})`;
case 'ArrayTypeAnnotation':
return `args.getArray(${index})`;
default:
(typeAnnotation.type: empty);
throw new Error(`Receieved invalid type: ${typeAnnotation.type}`);
}
}
function getCommandArguments(
command: NamedShape<CommandTypeAnnotation>,
): string {
return [
'view',
...command.typeAnnotation.params.map(getCommandArgJavaType),
].join(', ');
}
function generateCommandCasesString(
component: ComponentShape,
componentName: string,
) {
if (component.commands.length === 0) {
return null;
}
const commandMethods = component.commands
.map(command => {
return `case "${command.name}":
mViewManager.${toSafeJavaString(
command.name,
false,
)}(${getCommandArguments(command)});
break;`;
})
.join('\n' + ' ');
return commandMethods;
}
function getClassExtendString(component: ComponentShape): string {
const extendString = component.extendsProps
.map(extendProps => {
switch (extendProps.type) {
case 'ReactNativeBuiltInType':
switch (extendProps.knownTypeName) {
case 'ReactNativeCoreViewProps':
return 'View';
default:
(extendProps.knownTypeName: empty);
throw new Error('Invalid knownTypeName');
}
default:
(extendProps.type: empty);
throw new Error('Invalid extended type');
}
})
.join('');
return extendString;
}
function getDelegateImports(component: ComponentShape) {
const imports = getImports(component, 'delegate');
// The delegate needs ReadableArray for commands always.
// The interface doesn't always need it
if (component.commands.length > 0) {
imports.add('import com.facebook.react.bridge.ReadableArray;');
}
imports.add('import androidx.annotation.Nullable;');
imports.add('import com.facebook.react.uimanager.BaseViewManagerDelegate;');
imports.add('import com.facebook.react.uimanager.BaseViewManagerInterface;');
return imports;
}
function generateMethods(
propsString: string,
commandsString: null | string,
): string {
return [
PropSetterTemplate({propCases: propsString}),
commandsString != null
? CommandsTemplate({commandCases: commandsString})
: '',
]
.join('\n\n ')
.trimRight();
}
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
): FilesOutput {
// TODO: This doesn't support custom package name yet.
const normalizedPackageName = 'com.facebook.react.viewmanagers';
const outputDir = `java/${normalizedPackageName.replace(/\./g, '/')}`;
const files = new Map<string, string>();
Object.keys(schema.modules).forEach(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const {components} = module;
// No components in this module
if (components == null) {
return;
}
return Object.keys(components)
.filter(componentName => {
const component = components[componentName];
return !(
component.excludedPlatforms &&
component.excludedPlatforms.includes('android')
);
})
.forEach(componentName => {
const component = components[componentName];
const className = getDelegateJavaClassName(componentName);
const interfaceClassName = getInterfaceJavaClassName(componentName);
const imports = getDelegateImports(component);
const propsString = generatePropCasesString(component, componentName);
const commandsString = generateCommandCasesString(
component,
componentName,
);
const extendString = getClassExtendString(component);
const replacedTemplate = FileTemplate({
imports: Array.from(imports).sort().join('\n'),
packageName: normalizedPackageName,
className,
extendClasses: extendString,
methods: generateMethods(propsString, commandsString),
interfaceClassName: interfaceClassName,
});
files.set(`${outputDir}/${className}.java`, replacedTemplate);
});
});
return files;
},
};

View File

@@ -0,0 +1,250 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const _require = require('./JavaHelpers'),
getImports = _require.getImports,
getInterfaceJavaClassName = _require.getInterfaceJavaClassName,
toSafeJavaString = _require.toSafeJavaString;
// File path -> contents
const FileTemplate = ({
packageName,
imports,
className,
extendClasses,
methods,
}) => `/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GeneratePropsJavaInterface.js
*/
package ${packageName};
${imports}
public interface ${className}<T extends ${extendClasses}> {
${methods}
}
`;
function addNullable(imports) {
imports.add('import androidx.annotation.Nullable;');
}
function getJavaValueForProp(prop, imports) {
const typeAnnotation = prop.typeAnnotation;
switch (typeAnnotation.type) {
case 'BooleanTypeAnnotation':
if (typeAnnotation.default === null) {
addNullable(imports);
return '@Nullable Boolean value';
} else {
return 'boolean value';
}
case 'StringTypeAnnotation':
addNullable(imports);
return '@Nullable String value';
case 'Int32TypeAnnotation':
return 'int value';
case 'DoubleTypeAnnotation':
return 'double value';
case 'FloatTypeAnnotation':
if (typeAnnotation.default === null) {
addNullable(imports);
return '@Nullable Float value';
} else {
return 'float value';
}
case 'ReservedPropTypeAnnotation':
switch (typeAnnotation.name) {
case 'ColorPrimitive':
addNullable(imports);
return '@Nullable Integer value';
case 'ImageSourcePrimitive':
addNullable(imports);
return '@Nullable ReadableMap value';
case 'ImageRequestPrimitive':
addNullable(imports);
return '@Nullable ReadableMap value';
case 'PointPrimitive':
addNullable(imports);
return '@Nullable ReadableMap value';
case 'EdgeInsetsPrimitive':
addNullable(imports);
return '@Nullable ReadableMap value';
case 'DimensionPrimitive':
addNullable(imports);
return '@Nullable YogaValue value';
default:
typeAnnotation.name;
throw new Error('Received unknown ReservedPropTypeAnnotation');
}
case 'ArrayTypeAnnotation': {
addNullable(imports);
return '@Nullable ReadableArray value';
}
case 'ObjectTypeAnnotation': {
addNullable(imports);
return '@Nullable ReadableMap value';
}
case 'StringEnumTypeAnnotation':
addNullable(imports);
return '@Nullable String value';
case 'Int32EnumTypeAnnotation':
addNullable(imports);
return '@Nullable Integer value';
case 'MixedTypeAnnotation':
return 'Dynamic value';
default:
typeAnnotation;
throw new Error('Received invalid typeAnnotation');
}
}
function generatePropsString(component, imports) {
if (component.props.length === 0) {
return '// No props';
}
return component.props
.map(prop => {
return `void set${toSafeJavaString(
prop.name,
)}(T view, ${getJavaValueForProp(prop, imports)});`;
})
.join('\n' + ' ');
}
function getCommandArgJavaType(param) {
const typeAnnotation = param.typeAnnotation;
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return 'double';
default:
typeAnnotation.name;
throw new Error(`Receieved invalid type: ${typeAnnotation.name}`);
}
case 'BooleanTypeAnnotation':
return 'boolean';
case 'DoubleTypeAnnotation':
return 'double';
case 'FloatTypeAnnotation':
return 'float';
case 'Int32TypeAnnotation':
return 'int';
case 'StringTypeAnnotation':
return 'String';
case 'ArrayTypeAnnotation':
return 'ReadableArray';
default:
typeAnnotation.type;
throw new Error('Receieved invalid typeAnnotation');
}
}
function getCommandArguments(command, componentName) {
return [
'T view',
...command.typeAnnotation.params.map(param => {
const commandArgJavaType = getCommandArgJavaType(param);
return `${commandArgJavaType} ${param.name}`;
}),
].join(', ');
}
function generateCommandsString(component, componentName) {
return component.commands
.map(command => {
const safeJavaName = toSafeJavaString(command.name, false);
return `void ${safeJavaName}(${getCommandArguments(
command,
componentName,
)});`;
})
.join('\n' + ' ');
}
function getClassExtendString(component) {
const extendString = component.extendsProps
.map(extendProps => {
switch (extendProps.type) {
case 'ReactNativeBuiltInType':
switch (extendProps.knownTypeName) {
case 'ReactNativeCoreViewProps':
return 'View';
default:
extendProps.knownTypeName;
throw new Error('Invalid knownTypeName');
}
default:
extendProps.type;
throw new Error('Invalid extended type');
}
})
.join('');
return extendString;
}
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
) {
// TODO: This doesn't support custom package name yet.
const normalizedPackageName = 'com.facebook.react.viewmanagers';
const outputDir = `java/${normalizedPackageName.replace(/\./g, '/')}`;
const files = new Map();
Object.keys(schema.modules).forEach(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const components = module.components;
// No components in this module
if (components == null) {
return;
}
return Object.keys(components)
.filter(componentName => {
const component = components[componentName];
return !(
component.excludedPlatforms &&
component.excludedPlatforms.includes('android')
);
})
.forEach(componentName => {
const component = components[componentName];
const className = getInterfaceJavaClassName(componentName);
const imports = getImports(component, 'interface');
const propsString = generatePropsString(component, imports);
const commandsString = generateCommandsString(
component,
componentName,
);
const extendString = getClassExtendString(component);
const replacedTemplate = FileTemplate({
imports: Array.from(imports).sort().join('\n'),
packageName: normalizedPackageName,
className,
extendClasses: extendString,
methods: [propsString, commandsString]
.join('\n' + ' ')
.trimRight(),
});
files.set(`${outputDir}/${className}.java`, replacedTemplate);
});
});
return files;
},
};

View File

@@ -0,0 +1,296 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {CommandParamTypeAnnotation} from '../../CodegenSchema';
import type {
CommandTypeAnnotation,
ComponentShape,
NamedShape,
PropTypeAnnotation,
SchemaType,
} from '../../CodegenSchema';
const {
getImports,
getInterfaceJavaClassName,
toSafeJavaString,
} = require('./JavaHelpers');
// File path -> contents
type FilesOutput = Map<string, string>;
const FileTemplate = ({
packageName,
imports,
className,
extendClasses,
methods,
}: {
packageName: string,
imports: string,
className: string,
extendClasses: string,
methods: string,
}) => `/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GeneratePropsJavaInterface.js
*/
package ${packageName};
${imports}
public interface ${className}<T extends ${extendClasses}> {
${methods}
}
`;
function addNullable(imports: Set<string>) {
imports.add('import androidx.annotation.Nullable;');
}
function getJavaValueForProp(
prop: NamedShape<PropTypeAnnotation>,
imports: Set<string>,
): string {
const typeAnnotation = prop.typeAnnotation;
switch (typeAnnotation.type) {
case 'BooleanTypeAnnotation':
if (typeAnnotation.default === null) {
addNullable(imports);
return '@Nullable Boolean value';
} else {
return 'boolean value';
}
case 'StringTypeAnnotation':
addNullable(imports);
return '@Nullable String value';
case 'Int32TypeAnnotation':
return 'int value';
case 'DoubleTypeAnnotation':
return 'double value';
case 'FloatTypeAnnotation':
if (typeAnnotation.default === null) {
addNullable(imports);
return '@Nullable Float value';
} else {
return 'float value';
}
case 'ReservedPropTypeAnnotation':
switch (typeAnnotation.name) {
case 'ColorPrimitive':
addNullable(imports);
return '@Nullable Integer value';
case 'ImageSourcePrimitive':
addNullable(imports);
return '@Nullable ReadableMap value';
case 'ImageRequestPrimitive':
addNullable(imports);
return '@Nullable ReadableMap value';
case 'PointPrimitive':
addNullable(imports);
return '@Nullable ReadableMap value';
case 'EdgeInsetsPrimitive':
addNullable(imports);
return '@Nullable ReadableMap value';
case 'DimensionPrimitive':
addNullable(imports);
return '@Nullable YogaValue value';
default:
(typeAnnotation.name: empty);
throw new Error('Received unknown ReservedPropTypeAnnotation');
}
case 'ArrayTypeAnnotation': {
addNullable(imports);
return '@Nullable ReadableArray value';
}
case 'ObjectTypeAnnotation': {
addNullable(imports);
return '@Nullable ReadableMap value';
}
case 'StringEnumTypeAnnotation':
addNullable(imports);
return '@Nullable String value';
case 'Int32EnumTypeAnnotation':
addNullable(imports);
return '@Nullable Integer value';
case 'MixedTypeAnnotation':
return 'Dynamic value';
default:
(typeAnnotation: empty);
throw new Error('Received invalid typeAnnotation');
}
}
function generatePropsString(component: ComponentShape, imports: Set<string>) {
if (component.props.length === 0) {
return '// No props';
}
return component.props
.map(prop => {
return `void set${toSafeJavaString(
prop.name,
)}(T view, ${getJavaValueForProp(prop, imports)});`;
})
.join('\n' + ' ');
}
function getCommandArgJavaType(param: NamedShape<CommandParamTypeAnnotation>) {
const {typeAnnotation} = param;
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return 'double';
default:
(typeAnnotation.name: empty);
throw new Error(`Receieved invalid type: ${typeAnnotation.name}`);
}
case 'BooleanTypeAnnotation':
return 'boolean';
case 'DoubleTypeAnnotation':
return 'double';
case 'FloatTypeAnnotation':
return 'float';
case 'Int32TypeAnnotation':
return 'int';
case 'StringTypeAnnotation':
return 'String';
case 'ArrayTypeAnnotation':
return 'ReadableArray';
default:
(typeAnnotation.type: empty);
throw new Error('Receieved invalid typeAnnotation');
}
}
function getCommandArguments(
command: NamedShape<CommandTypeAnnotation>,
componentName: string,
): string {
return [
'T view',
...command.typeAnnotation.params.map(param => {
const commandArgJavaType = getCommandArgJavaType(param);
return `${commandArgJavaType} ${param.name}`;
}),
].join(', ');
}
function generateCommandsString(
component: ComponentShape,
componentName: string,
) {
return component.commands
.map(command => {
const safeJavaName = toSafeJavaString(command.name, false);
return `void ${safeJavaName}(${getCommandArguments(
command,
componentName,
)});`;
})
.join('\n' + ' ');
}
function getClassExtendString(component: ComponentShape): string {
const extendString = component.extendsProps
.map(extendProps => {
switch (extendProps.type) {
case 'ReactNativeBuiltInType':
switch (extendProps.knownTypeName) {
case 'ReactNativeCoreViewProps':
return 'View';
default:
(extendProps.knownTypeName: empty);
throw new Error('Invalid knownTypeName');
}
default:
(extendProps.type: empty);
throw new Error('Invalid extended type');
}
})
.join('');
return extendString;
}
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
): FilesOutput {
// TODO: This doesn't support custom package name yet.
const normalizedPackageName = 'com.facebook.react.viewmanagers';
const outputDir = `java/${normalizedPackageName.replace(/\./g, '/')}`;
const files = new Map<string, string>();
Object.keys(schema.modules).forEach(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const {components} = module;
// No components in this module
if (components == null) {
return;
}
return Object.keys(components)
.filter(componentName => {
const component = components[componentName];
return !(
component.excludedPlatforms &&
component.excludedPlatforms.includes('android')
);
})
.forEach(componentName => {
const component = components[componentName];
const className = getInterfaceJavaClassName(componentName);
const imports = getImports(component, 'interface');
const propsString = generatePropsString(component, imports);
const commandsString = generateCommandsString(
component,
componentName,
);
const extendString = getClassExtendString(component);
const replacedTemplate = FileTemplate({
imports: Array.from(imports).sort().join('\n'),
packageName: normalizedPackageName,
className,
extendClasses: extendString,
methods: [propsString, commandsString]
.join('\n' + ' ')
.trimRight(),
});
files.set(`${outputDir}/${className}.java`, replacedTemplate);
});
});
return files;
},
};

View File

@@ -0,0 +1,156 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
enumerableOnly &&
(symbols = symbols.filter(function (sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
})),
keys.push.apply(keys, symbols);
}
return keys;
}
function _objectSpread(target) {
for (var i = 1; i < arguments.length; i++) {
var source = null != arguments[i] ? arguments[i] : {};
i % 2
? ownKeys(Object(source), !0).forEach(function (key) {
_defineProperty(target, key, source[key]);
})
: Object.getOwnPropertyDescriptors
? Object.defineProperties(
target,
Object.getOwnPropertyDescriptors(source),
)
: ownKeys(Object(source)).forEach(function (key) {
Object.defineProperty(
target,
key,
Object.getOwnPropertyDescriptor(source, key),
);
});
}
return target;
}
function _defineProperty(obj, key, value) {
key = _toPropertyKey(key);
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true,
});
} else {
obj[key] = value;
}
return obj;
}
function _toPropertyKey(arg) {
var key = _toPrimitive(arg, 'string');
return typeof key === 'symbol' ? key : String(key);
}
function _toPrimitive(input, hint) {
if (typeof input !== 'object' || input === null) return input;
var prim = input[Symbol.toPrimitive];
if (prim !== undefined) {
var res = prim.call(input, hint || 'default');
if (typeof res !== 'object') return res;
throw new TypeError('@@toPrimitive must return a primitive value.');
}
return (hint === 'string' ? String : Number)(input);
}
const _require = require('../../Utils'),
capitalize = _require.capitalize;
class PojoCollector {
constructor() {
_defineProperty(this, '_pojos', new Map());
}
process(namespace, pojoName, typeAnnotation) {
switch (typeAnnotation.type) {
case 'ObjectTypeAnnotation': {
this._insertPojo(namespace, pojoName, typeAnnotation);
return {
type: 'PojoTypeAliasTypeAnnotation',
name: pojoName,
};
}
case 'ArrayTypeAnnotation': {
const arrayTypeAnnotation = typeAnnotation;
const elementType = arrayTypeAnnotation.elementType;
const pojoElementType = (() => {
switch (elementType.type) {
case 'ObjectTypeAnnotation': {
this._insertPojo(namespace, `${pojoName}Element`, elementType);
return {
type: 'PojoTypeAliasTypeAnnotation',
name: `${pojoName}Element`,
};
}
case 'ArrayTypeAnnotation': {
const objectTypeAnnotation = elementType.elementType;
this._insertPojo(
namespace,
`${pojoName}ElementElement`,
objectTypeAnnotation,
);
return {
type: 'ArrayTypeAnnotation',
elementType: {
type: 'PojoTypeAliasTypeAnnotation',
name: `${pojoName}ElementElement`,
},
};
}
default: {
return elementType;
}
}
})();
return {
type: 'ArrayTypeAnnotation',
elementType: pojoElementType,
};
}
default:
return typeAnnotation;
}
}
_insertPojo(namespace, pojoName, objectTypeAnnotation) {
const properties = objectTypeAnnotation.properties.map(property => {
const propertyPojoName = pojoName + capitalize(property.name);
return _objectSpread(
_objectSpread({}, property),
{},
{
typeAnnotation: this.process(
namespace,
propertyPojoName,
property.typeAnnotation,
),
},
);
});
this._pojos.set(pojoName, {
name: pojoName,
namespace,
properties,
});
}
getAllPojos() {
return [...this._pojos.values()];
}
}
module.exports = PojoCollector;

View File

@@ -0,0 +1,187 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {
ArrayTypeAnnotation,
BooleanTypeAnnotation,
DoubleTypeAnnotation,
FloatTypeAnnotation,
Int32TypeAnnotation,
MixedTypeAnnotation,
NamedShape,
ObjectTypeAnnotation,
PropTypeAnnotation,
ReservedPropTypeAnnotation,
StringTypeAnnotation,
} from '../../../CodegenSchema';
const {capitalize} = require('../../Utils');
export type Pojo = {
name: string,
namespace: string,
properties: $ReadOnlyArray<PojoProperty>,
};
export type PojoProperty = NamedShape<PojoTypeAnnotation>;
export type PojoTypeAliasAnnotation = {
type: 'PojoTypeAliasTypeAnnotation',
name: string,
};
export type PojoTypeAnnotation =
| $ReadOnly<{
type: 'BooleanTypeAnnotation',
default: boolean | null,
}>
| $ReadOnly<{
type: 'StringTypeAnnotation',
default: string | null,
}>
| $ReadOnly<{
type: 'DoubleTypeAnnotation',
default: number,
}>
| $ReadOnly<{
type: 'FloatTypeAnnotation',
default: number | null,
}>
| $ReadOnly<{
type: 'Int32TypeAnnotation',
default: number,
}>
| $ReadOnly<{
type: 'StringEnumTypeAnnotation',
default: string,
options: $ReadOnlyArray<string>,
}>
| $ReadOnly<{
type: 'Int32EnumTypeAnnotation',
default: number,
options: $ReadOnlyArray<number>,
}>
| ReservedPropTypeAnnotation
| PojoTypeAliasAnnotation
| $ReadOnly<{
type: 'ArrayTypeAnnotation',
elementType:
| BooleanTypeAnnotation
| StringTypeAnnotation
| DoubleTypeAnnotation
| FloatTypeAnnotation
| Int32TypeAnnotation
| $ReadOnly<{
type: 'StringEnumTypeAnnotation',
default: string,
options: $ReadOnlyArray<string>,
}>
| PojoTypeAliasAnnotation
| ReservedPropTypeAnnotation
| $ReadOnly<{
type: 'ArrayTypeAnnotation',
elementType: PojoTypeAliasAnnotation,
}>,
}>
| MixedTypeAnnotation;
class PojoCollector {
_pojos: Map<string, Pojo> = new Map();
process(
namespace: string,
pojoName: string,
typeAnnotation: PropTypeAnnotation,
): PojoTypeAnnotation {
switch (typeAnnotation.type) {
case 'ObjectTypeAnnotation': {
this._insertPojo(namespace, pojoName, typeAnnotation);
return {
type: 'PojoTypeAliasTypeAnnotation',
name: pojoName,
};
}
case 'ArrayTypeAnnotation': {
const arrayTypeAnnotation = typeAnnotation;
const elementType: $PropertyType<ArrayTypeAnnotation, 'elementType'> =
arrayTypeAnnotation.elementType;
const pojoElementType = (() => {
switch (elementType.type) {
case 'ObjectTypeAnnotation': {
this._insertPojo(namespace, `${pojoName}Element`, elementType);
return {
type: 'PojoTypeAliasTypeAnnotation',
name: `${pojoName}Element`,
};
}
case 'ArrayTypeAnnotation': {
const {elementType: objectTypeAnnotation} = elementType;
this._insertPojo(
namespace,
`${pojoName}ElementElement`,
objectTypeAnnotation,
);
return {
type: 'ArrayTypeAnnotation',
elementType: {
type: 'PojoTypeAliasTypeAnnotation',
name: `${pojoName}ElementElement`,
},
};
}
default: {
return elementType;
}
}
})();
return {
type: 'ArrayTypeAnnotation',
elementType: pojoElementType,
};
}
default:
return typeAnnotation;
}
}
_insertPojo(
namespace: string,
pojoName: string,
objectTypeAnnotation: ObjectTypeAnnotation<PropTypeAnnotation>,
) {
const properties = objectTypeAnnotation.properties.map(property => {
const propertyPojoName = pojoName + capitalize(property.name);
return {
...property,
typeAnnotation: this.process(
namespace,
propertyPojoName,
property.typeAnnotation,
),
};
});
this._pojos.set(pojoName, {
name: pojoName,
namespace,
properties,
});
}
getAllPojos(): $ReadOnlyArray<Pojo> {
return [...this._pojos.values()];
}
}
module.exports = PojoCollector;

View File

@@ -0,0 +1,66 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const _require = require('../../Utils'),
capitalize = _require.capitalize;
const PojoCollector = require('./PojoCollector');
const _require2 = require('./serializePojo'),
serializePojo = _require2.serializePojo;
module.exports = {
generate(libraryName, schema, packageName) {
const pojoCollector = new PojoCollector();
const basePackageName = 'com.facebook.react.viewmanagers';
Object.keys(schema.modules).forEach(hasteModuleName => {
const module = schema.modules[hasteModuleName];
if (module.type !== 'Component') {
return;
}
const components = module.components;
// No components in this module
if (components == null) {
return null;
}
Object.keys(components)
.filter(componentName => {
const component = components[componentName];
return !(
component.excludedPlatforms &&
component.excludedPlatforms.includes('android')
);
})
.forEach(componentName => {
const component = components[componentName];
if (component == null) {
return;
}
const props = component.props;
pojoCollector.process(
capitalize(hasteModuleName),
`${capitalize(componentName)}Props`,
{
type: 'ObjectTypeAnnotation',
properties: props,
},
);
});
});
const pojoDir = basePackageName.split('.').join('/');
return new Map(
pojoCollector.getAllPojos().map(pojo => {
return [
`java/${pojoDir}/${pojo.namespace}/${pojo.name}.java`,
serializePojo(pojo, basePackageName),
];
}),
);
},
};

View File

@@ -0,0 +1,80 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {SchemaType} from '../../../CodegenSchema';
const {capitalize} = require('../../Utils');
const PojoCollector = require('./PojoCollector');
const {serializePojo} = require('./serializePojo');
type FilesOutput = Map<string, string>;
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
): FilesOutput {
const pojoCollector = new PojoCollector();
const basePackageName = 'com.facebook.react.viewmanagers';
Object.keys(schema.modules).forEach(hasteModuleName => {
const module = schema.modules[hasteModuleName];
if (module.type !== 'Component') {
return;
}
const {components} = module;
// No components in this module
if (components == null) {
return null;
}
Object.keys(components)
.filter(componentName => {
const component = components[componentName];
return !(
component.excludedPlatforms &&
component.excludedPlatforms.includes('android')
);
})
.forEach(componentName => {
const component = components[componentName];
if (component == null) {
return;
}
const {props} = component;
pojoCollector.process(
capitalize(hasteModuleName),
`${capitalize(componentName)}Props`,
{
type: 'ObjectTypeAnnotation',
properties: props,
},
);
});
});
const pojoDir = basePackageName.split('.').join('/');
return new Map(
pojoCollector.getAllPojos().map(pojo => {
return [
`java/${pojoDir}/${pojo.namespace}/${pojo.name}.java`,
serializePojo(pojo, basePackageName),
];
}),
);
},
};

View File

@@ -0,0 +1,286 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const _require = require('../../Utils'),
capitalize = _require.capitalize;
function toJavaType(typeAnnotation, addImport) {
const importNullable = () => addImport('androidx.annotation.Nullable');
const importReadableMap = () =>
addImport('com.facebook.react.bridge.ReadableMap');
const importArrayList = () => addImport('java.util.ArrayList');
const importYogaValue = () => addImport('com.facebook.yoga.YogaValue');
const importDynamic = () => addImport('com.facebook.react.bridge.Dynamic');
switch (typeAnnotation.type) {
/**
* Primitives
*/
case 'BooleanTypeAnnotation': {
if (typeAnnotation.default === null) {
importNullable();
return '@Nullable Boolean';
} else {
return 'boolean';
}
}
case 'StringTypeAnnotation': {
importNullable();
return '@Nullable String';
}
case 'DoubleTypeAnnotation': {
return 'double';
}
case 'FloatTypeAnnotation': {
if (typeAnnotation.default === null) {
importNullable();
return '@Nullable Float';
} else {
return 'float';
}
}
case 'Int32TypeAnnotation': {
return 'int';
}
/**
* Enums
*/
// TODO: Make StringEnumTypeAnnotation type-safe?
case 'StringEnumTypeAnnotation':
importNullable();
return '@Nullable String';
// TODO: Make Int32EnumTypeAnnotation type-safe?
case 'Int32EnumTypeAnnotation':
importNullable();
return '@Nullable Integer';
/**
* Reserved types
*/
case 'ReservedPropTypeAnnotation': {
switch (typeAnnotation.name) {
case 'ColorPrimitive':
importNullable();
return '@Nullable Integer';
// TODO: Make ImageSourcePrimitive type-safe
case 'ImageSourcePrimitive':
importNullable();
importReadableMap();
return '@Nullable ReadableMap';
// TODO: Make ImageRequestPrimitive type-safe
case 'ImageRequestPrimitive':
importNullable();
importReadableMap();
return '@Nullable ReadableMap';
// TODO: Make PointPrimitive type-safe
case 'PointPrimitive':
importNullable();
importReadableMap();
return '@Nullable ReadableMap';
// TODO: Make EdgeInsetsPrimitive type-safe
case 'EdgeInsetsPrimitive':
importNullable();
importReadableMap();
return '@Nullable ReadableMap';
case 'DimensionPrimitive':
importNullable();
importYogaValue();
return '@Nullable YogaValue';
default:
typeAnnotation.name;
throw new Error(
`Received unknown ReservedPropTypeAnnotation ${typeAnnotation.name}`,
);
}
}
/**
* Other Pojo objects
*/
case 'PojoTypeAliasTypeAnnotation': {
return typeAnnotation.name;
}
/**
* Arrays
*/
case 'ArrayTypeAnnotation': {
const elementType = typeAnnotation.elementType;
const elementTypeString = (() => {
switch (elementType.type) {
/**
* Primitives
*/
case 'BooleanTypeAnnotation': {
return 'Boolean';
}
case 'StringTypeAnnotation': {
return 'String';
}
case 'DoubleTypeAnnotation': {
return 'Double';
}
case 'FloatTypeAnnotation': {
return 'Float';
}
case 'Int32TypeAnnotation': {
return 'Integer';
}
/**
* Enums
*/
// TODO: Make StringEnums type-safe in Pojos
case 'StringEnumTypeAnnotation': {
return 'String';
}
/**
* Other Pojo objects
*/
case 'PojoTypeAliasTypeAnnotation': {
return elementType.name;
}
/**
* Reserved types
*/
case 'ReservedPropTypeAnnotation': {
switch (elementType.name) {
case 'ColorPrimitive':
return 'Integer';
// TODO: Make ImageSourcePrimitive type-safe
case 'ImageSourcePrimitive':
importReadableMap();
return 'ReadableMap';
// TODO: Make ImageRequestPrimitive type-safe
case 'ImageRequestPrimitive':
importReadableMap();
return 'ReadableMap';
// TODO: Make PointPrimitive type-safe
case 'PointPrimitive':
importReadableMap();
return 'ReadableMap';
// TODO: Make EdgeInsetsPrimitive type-safe
case 'EdgeInsetsPrimitive':
importReadableMap();
return 'ReadableMap';
case 'DimensionPrimitive':
importYogaValue();
return 'YogaValue';
default:
elementType.name;
throw new Error(
`Received unknown ReservedPropTypeAnnotation ${elementType.name}`,
);
}
}
// Arrays
case 'ArrayTypeAnnotation': {
const pojoTypeAliasTypeAnnotation = elementType.elementType;
importArrayList();
return `ArrayList<${pojoTypeAliasTypeAnnotation.name}>`;
}
default: {
elementType.type;
throw new Error(
`Unrecognized PojoTypeAnnotation Array element type annotation '${typeAnnotation.type}'`,
);
}
}
})();
importArrayList();
return `ArrayList<${elementTypeString}>`;
}
case 'MixedTypeAnnotation': {
importDynamic();
return 'Dynamic';
}
default: {
typeAnnotation.type;
throw new Error(
`Unrecognized PojoTypeAnnotation '${typeAnnotation.type}'`,
);
}
}
}
function toJavaMemberName(property) {
return `m${capitalize(property.name)}`;
}
function toJavaMemberDeclaration(property, addImport) {
const type = toJavaType(property.typeAnnotation, addImport);
const memberName = toJavaMemberName(property);
return `private ${type} ${memberName};`;
}
function toJavaGetter(property, addImport) {
const type = toJavaType(property.typeAnnotation, addImport);
const getterName = `get${capitalize(property.name)}`;
const memberName = toJavaMemberName(property);
addImport('com.facebook.proguard.annotations.DoNotStrip');
return `@DoNotStrip
public ${type} ${getterName}() {
return ${memberName};
}`;
}
function serializePojo(pojo, basePackageName) {
const importSet = new Set();
const addImport = $import => {
importSet.add($import);
};
addImport('com.facebook.proguard.annotations.DoNotStrip');
const indent = ' '.repeat(2);
const members = pojo.properties
.map(property => toJavaMemberDeclaration(property, addImport))
.map(member => `${indent}${member}`)
.join('\n');
const getters = pojo.properties
.map(property => toJavaGetter(property, addImport))
.map(getter =>
getter
.split('\n')
.map(line => `${indent}${line}`)
.join('\n'),
)
.join('\n');
const imports = [...importSet]
.map($import => `import ${$import};`)
.sort()
.join('\n');
return `/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* ${'@'}generated by codegen project: GeneratePropsJavaPojo.js
*/
package ${basePackageName}.${pojo.namespace};
${imports === '' ? '' : `\n${imports}\n`}
@DoNotStrip
public class ${pojo.name} {
${members}
${getters}
}
`;
}
module.exports = {
serializePojo,
};

View File

@@ -0,0 +1,315 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {Pojo, PojoProperty, PojoTypeAnnotation} from './PojoCollector';
const {capitalize} = require('../../Utils');
type ImportCollector = ($import: string) => void;
function toJavaType(
typeAnnotation: PojoTypeAnnotation,
addImport: ImportCollector,
): string {
const importNullable = () => addImport('androidx.annotation.Nullable');
const importReadableMap = () =>
addImport('com.facebook.react.bridge.ReadableMap');
const importArrayList = () => addImport('java.util.ArrayList');
const importYogaValue = () => addImport('com.facebook.yoga.YogaValue');
const importDynamic = () => addImport('com.facebook.react.bridge.Dynamic');
switch (typeAnnotation.type) {
/**
* Primitives
*/
case 'BooleanTypeAnnotation': {
if (typeAnnotation.default === null) {
importNullable();
return '@Nullable Boolean';
} else {
return 'boolean';
}
}
case 'StringTypeAnnotation': {
importNullable();
return '@Nullable String';
}
case 'DoubleTypeAnnotation': {
return 'double';
}
case 'FloatTypeAnnotation': {
if (typeAnnotation.default === null) {
importNullable();
return '@Nullable Float';
} else {
return 'float';
}
}
case 'Int32TypeAnnotation': {
return 'int';
}
/**
* Enums
*/
// TODO: Make StringEnumTypeAnnotation type-safe?
case 'StringEnumTypeAnnotation':
importNullable();
return '@Nullable String';
// TODO: Make Int32EnumTypeAnnotation type-safe?
case 'Int32EnumTypeAnnotation':
importNullable();
return '@Nullable Integer';
/**
* Reserved types
*/
case 'ReservedPropTypeAnnotation': {
switch (typeAnnotation.name) {
case 'ColorPrimitive':
importNullable();
return '@Nullable Integer';
// TODO: Make ImageSourcePrimitive type-safe
case 'ImageSourcePrimitive':
importNullable();
importReadableMap();
return '@Nullable ReadableMap';
// TODO: Make ImageRequestPrimitive type-safe
case 'ImageRequestPrimitive':
importNullable();
importReadableMap();
return '@Nullable ReadableMap';
// TODO: Make PointPrimitive type-safe
case 'PointPrimitive':
importNullable();
importReadableMap();
return '@Nullable ReadableMap';
// TODO: Make EdgeInsetsPrimitive type-safe
case 'EdgeInsetsPrimitive':
importNullable();
importReadableMap();
return '@Nullable ReadableMap';
case 'DimensionPrimitive':
importNullable();
importYogaValue();
return '@Nullable YogaValue';
default:
(typeAnnotation.name: empty);
throw new Error(
`Received unknown ReservedPropTypeAnnotation ${typeAnnotation.name}`,
);
}
}
/**
* Other Pojo objects
*/
case 'PojoTypeAliasTypeAnnotation': {
return typeAnnotation.name;
}
/**
* Arrays
*/
case 'ArrayTypeAnnotation': {
const {elementType} = typeAnnotation;
const elementTypeString = (() => {
switch (elementType.type) {
/**
* Primitives
*/
case 'BooleanTypeAnnotation': {
return 'Boolean';
}
case 'StringTypeAnnotation': {
return 'String';
}
case 'DoubleTypeAnnotation': {
return 'Double';
}
case 'FloatTypeAnnotation': {
return 'Float';
}
case 'Int32TypeAnnotation': {
return 'Integer';
}
/**
* Enums
*/
// TODO: Make StringEnums type-safe in Pojos
case 'StringEnumTypeAnnotation': {
return 'String';
}
/**
* Other Pojo objects
*/
case 'PojoTypeAliasTypeAnnotation': {
return elementType.name;
}
/**
* Reserved types
*/
case 'ReservedPropTypeAnnotation': {
switch (elementType.name) {
case 'ColorPrimitive':
return 'Integer';
// TODO: Make ImageSourcePrimitive type-safe
case 'ImageSourcePrimitive':
importReadableMap();
return 'ReadableMap';
// TODO: Make ImageRequestPrimitive type-safe
case 'ImageRequestPrimitive':
importReadableMap();
return 'ReadableMap';
// TODO: Make PointPrimitive type-safe
case 'PointPrimitive':
importReadableMap();
return 'ReadableMap';
// TODO: Make EdgeInsetsPrimitive type-safe
case 'EdgeInsetsPrimitive':
importReadableMap();
return 'ReadableMap';
case 'DimensionPrimitive':
importYogaValue();
return 'YogaValue';
default:
(elementType.name: empty);
throw new Error(
`Received unknown ReservedPropTypeAnnotation ${elementType.name}`,
);
}
}
// Arrays
case 'ArrayTypeAnnotation': {
const {elementType: pojoTypeAliasTypeAnnotation} = elementType;
importArrayList();
return `ArrayList<${pojoTypeAliasTypeAnnotation.name}>`;
}
default: {
(elementType.type: empty);
throw new Error(
`Unrecognized PojoTypeAnnotation Array element type annotation '${typeAnnotation.type}'`,
);
}
}
})();
importArrayList();
return `ArrayList<${elementTypeString}>`;
}
case 'MixedTypeAnnotation': {
importDynamic();
return 'Dynamic';
}
default: {
(typeAnnotation.type: empty);
throw new Error(
`Unrecognized PojoTypeAnnotation '${typeAnnotation.type}'`,
);
}
}
}
function toJavaMemberName(property: PojoProperty): string {
return `m${capitalize(property.name)}`;
}
function toJavaMemberDeclaration(
property: PojoProperty,
addImport: ImportCollector,
): string {
const type = toJavaType(property.typeAnnotation, addImport);
const memberName = toJavaMemberName(property);
return `private ${type} ${memberName};`;
}
function toJavaGetter(property: PojoProperty, addImport: ImportCollector) {
const type = toJavaType(property.typeAnnotation, addImport);
const getterName = `get${capitalize(property.name)}`;
const memberName = toJavaMemberName(property);
addImport('com.facebook.proguard.annotations.DoNotStrip');
return `@DoNotStrip
public ${type} ${getterName}() {
return ${memberName};
}`;
}
function serializePojo(pojo: Pojo, basePackageName: string): string {
const importSet: Set<string> = new Set();
const addImport = ($import: string) => {
importSet.add($import);
};
addImport('com.facebook.proguard.annotations.DoNotStrip');
const indent = ' '.repeat(2);
const members = pojo.properties
.map(property => toJavaMemberDeclaration(property, addImport))
.map(member => `${indent}${member}`)
.join('\n');
const getters = pojo.properties
.map(property => toJavaGetter(property, addImport))
.map(getter =>
getter
.split('\n')
.map(line => `${indent}${line}`)
.join('\n'),
)
.join('\n');
const imports = [...importSet]
.map($import => `import ${$import};`)
.sort()
.join('\n');
return `/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* ${'@'}generated by codegen project: GeneratePropsJavaPojo.js
*/
package ${basePackageName}.${pojo.namespace};
${imports === '' ? '' : `\n${imports}\n`}
@DoNotStrip
public class ${pojo.name} {
${members}
${getters}
}
`;
}
module.exports = {serializePojo};

View File

@@ -0,0 +1,84 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const _require = require('./CppHelpers'),
IncludeTemplate = _require.IncludeTemplate;
// File path -> contents
const FileTemplate = ({componentNames, headerPrefix}) => `
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GenerateShadowNodeCpp.js
*/
${IncludeTemplate({
headerPrefix,
file: 'ShadowNodes.h',
})}
namespace facebook::react {
${componentNames}
} // namespace facebook::react
`;
const ComponentTemplate = ({className}) =>
`
extern const char ${className}ComponentName[] = "${className}";
`.trim();
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
) {
const fileName = 'ShadowNodes.cpp';
const componentNames = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const components = module.components;
// No components in this module
if (components == null) {
return null;
}
return Object.keys(components)
.map(componentName => {
if (components[componentName].interfaceOnly === true) {
return;
}
const replacedTemplate = ComponentTemplate({
className: componentName,
});
return replacedTemplate;
})
.join('\n');
})
.filter(Boolean)
.join('\n');
const replacedTemplate = FileTemplate({
componentNames,
headerPrefix:
headerPrefix !== null && headerPrefix !== void 0 ? headerPrefix : '',
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,96 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {SchemaType} from '../../CodegenSchema';
const {IncludeTemplate} = require('./CppHelpers');
// File path -> contents
type FilesOutput = Map<string, string>;
const FileTemplate = ({
componentNames,
headerPrefix,
}: {
componentNames: string,
headerPrefix: string,
}) => `
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GenerateShadowNodeCpp.js
*/
${IncludeTemplate({headerPrefix, file: 'ShadowNodes.h'})}
namespace facebook::react {
${componentNames}
} // namespace facebook::react
`;
const ComponentTemplate = ({className}: {className: string}) =>
`
extern const char ${className}ComponentName[] = "${className}";
`.trim();
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
): FilesOutput {
const fileName = 'ShadowNodes.cpp';
const componentNames = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const {components} = module;
// No components in this module
if (components == null) {
return null;
}
return Object.keys(components)
.map(componentName => {
if (components[componentName].interfaceOnly === true) {
return;
}
const replacedTemplate = ComponentTemplate({
className: componentName,
});
return replacedTemplate;
})
.join('\n');
})
.filter(Boolean)
.join('\n');
const replacedTemplate = FileTemplate({
componentNames,
headerPrefix: headerPrefix ?? '',
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,107 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const _require = require('./CppHelpers'),
IncludeTemplate = _require.IncludeTemplate;
// File path -> contents
const FileTemplate = ({componentClasses, headerPrefix}) => `
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GenerateShadowNodeH.js
*/
#pragma once
${IncludeTemplate({
headerPrefix,
file: 'EventEmitters.h',
})}
${IncludeTemplate({
headerPrefix,
file: 'Props.h',
})}
${IncludeTemplate({
headerPrefix,
file: 'States.h',
})}
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include <jsi/jsi.h>
namespace facebook::react {
${componentClasses}
} // namespace facebook::react
`;
const ComponentTemplate = ({className, eventEmitter}) =>
`
JSI_EXPORT extern const char ${className}ComponentName[];
/*
* \`ShadowNode\` for <${className}> component.
*/
using ${className}ShadowNode = ConcreteViewShadowNode<
${className}ComponentName,
${className}Props${eventEmitter},
${className}State>;
`.trim();
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
) {
const fileName = 'ShadowNodes.h';
const moduleResults = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const components = module.components;
// No components in this module
if (components == null) {
return null;
}
return Object.keys(components)
.map(componentName => {
const component = components[componentName];
if (component.interfaceOnly === true) {
return;
}
const eventEmitter = `,\n ${componentName}EventEmitter`;
const replacedTemplate = ComponentTemplate({
className: componentName,
eventEmitter,
});
return replacedTemplate;
})
.join('\n\n');
})
.filter(Boolean)
.join('\n\n');
const replacedTemplate = FileTemplate({
componentClasses: moduleResults,
headerPrefix:
headerPrefix !== null && headerPrefix !== void 0 ? headerPrefix : '',
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,121 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {SchemaType} from '../../CodegenSchema';
const {IncludeTemplate} = require('./CppHelpers');
// File path -> contents
type FilesOutput = Map<string, string>;
const FileTemplate = ({
componentClasses,
headerPrefix,
}: {
componentClasses: string,
headerPrefix: string,
}) => `
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GenerateShadowNodeH.js
*/
#pragma once
${IncludeTemplate({headerPrefix, file: 'EventEmitters.h'})}
${IncludeTemplate({headerPrefix, file: 'Props.h'})}
${IncludeTemplate({headerPrefix, file: 'States.h'})}
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include <jsi/jsi.h>
namespace facebook::react {
${componentClasses}
} // namespace facebook::react
`;
const ComponentTemplate = ({
className,
eventEmitter,
}: {
className: string,
eventEmitter: string,
}) =>
`
JSI_EXPORT extern const char ${className}ComponentName[];
/*
* \`ShadowNode\` for <${className}> component.
*/
using ${className}ShadowNode = ConcreteViewShadowNode<
${className}ComponentName,
${className}Props${eventEmitter},
${className}State>;
`.trim();
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
): FilesOutput {
const fileName = 'ShadowNodes.h';
const moduleResults = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const {components} = module;
// No components in this module
if (components == null) {
return null;
}
return Object.keys(components)
.map(componentName => {
const component = components[componentName];
if (component.interfaceOnly === true) {
return;
}
const eventEmitter = `,\n ${componentName}EventEmitter`;
const replacedTemplate = ComponentTemplate({
className: componentName,
eventEmitter,
});
return replacedTemplate;
})
.join('\n\n');
})
.filter(Boolean)
.join('\n\n');
const replacedTemplate = FileTemplate({
componentClasses: moduleResults,
headerPrefix: headerPrefix ?? '',
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,81 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const _require = require('./CppHelpers'),
IncludeTemplate = _require.IncludeTemplate;
// File path -> contents
const FileTemplate = ({stateClasses, headerPrefix}) => `
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GenerateStateCpp.js
*/
${IncludeTemplate({
headerPrefix,
file: 'States.h',
})}
namespace facebook::react {
${stateClasses}
} // namespace facebook::react
`;
const StateTemplate = ({stateName}) => '';
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
) {
const fileName = 'States.cpp';
const stateClasses = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const components = module.components;
// No components in this module
if (components == null) {
return null;
}
return Object.keys(components)
.map(componentName => {
const component = components[componentName];
if (component.interfaceOnly === true) {
return null;
}
return StateTemplate({
stateName: `${componentName}State`,
});
})
.filter(Boolean)
.join('\n');
})
.filter(Boolean)
.join('\n');
const replacedTemplate = FileTemplate({
stateClasses,
headerPrefix:
headerPrefix !== null && headerPrefix !== void 0 ? headerPrefix : '',
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,93 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {SchemaType} from '../../CodegenSchema';
const {IncludeTemplate} = require('./CppHelpers');
// File path -> contents
type FilesOutput = Map<string, string>;
const FileTemplate = ({
stateClasses,
headerPrefix,
}: {
stateClasses: string,
headerPrefix: string,
}) => `
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GenerateStateCpp.js
*/
${IncludeTemplate({headerPrefix, file: 'States.h'})}
namespace facebook::react {
${stateClasses}
} // namespace facebook::react
`;
const StateTemplate = ({stateName}: {stateName: string}) => '';
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
): FilesOutput {
const fileName = 'States.cpp';
const stateClasses = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const {components} = module;
// No components in this module
if (components == null) {
return null;
}
return Object.keys(components)
.map(componentName => {
const component = components[componentName];
if (component.interfaceOnly === true) {
return null;
}
return StateTemplate({
stateName: `${componentName}State`,
});
})
.filter(Boolean)
.join('\n');
})
.filter(Boolean)
.join('\n');
const replacedTemplate = FileTemplate({
stateClasses,
headerPrefix: headerPrefix ?? '',
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,97 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
// File path -> contents
const FileTemplate = ({libraryName, stateClasses}) =>
`
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GenerateStateH.js
*/
#pragma once
#ifdef ANDROID
#include <folly/dynamic.h>
#include <react/renderer/mapbuffer/MapBuffer.h>
#include <react/renderer/mapbuffer/MapBufferBuilder.h>
#endif
namespace facebook::react {
${stateClasses}
} // namespace facebook::react
`.trim();
const StateTemplate = ({stateName}) =>
`
class ${stateName}State {
public:
${stateName}State() = default;
#ifdef ANDROID
${stateName}State(${stateName}State const &previousState, folly::dynamic data){};
folly::dynamic getDynamic() const {
return {};
};
MapBuffer getMapBuffer() const {
return MapBufferBuilder::EMPTY();
};
#endif
};
`.trim();
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
) {
const fileName = 'States.h';
const stateClasses = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const components = module.components;
// No components in this module
if (components == null) {
return null;
}
return Object.keys(components)
.map(componentName => {
const component = components[componentName];
if (component.interfaceOnly === true) {
return null;
}
return StateTemplate({
stateName: componentName,
});
})
.filter(Boolean)
.join('\n\n');
})
.filter(Boolean)
.join('\n\n');
const template = FileTemplate({
libraryName,
stateClasses,
});
return new Map([[fileName, template]]);
},
};

View File

@@ -0,0 +1,110 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {SchemaType} from '../../CodegenSchema';
// File path -> contents
type FilesOutput = Map<string, string>;
const FileTemplate = ({
libraryName,
stateClasses,
}: {
libraryName: string,
stateClasses: string,
}) =>
`
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GenerateStateH.js
*/
#pragma once
#ifdef ANDROID
#include <folly/dynamic.h>
#include <react/renderer/mapbuffer/MapBuffer.h>
#include <react/renderer/mapbuffer/MapBufferBuilder.h>
#endif
namespace facebook::react {
${stateClasses}
} // namespace facebook::react
`.trim();
const StateTemplate = ({stateName}: {stateName: string}) =>
`
class ${stateName}State {
public:
${stateName}State() = default;
#ifdef ANDROID
${stateName}State(${stateName}State const &previousState, folly::dynamic data){};
folly::dynamic getDynamic() const {
return {};
};
MapBuffer getMapBuffer() const {
return MapBufferBuilder::EMPTY();
};
#endif
};
`.trim();
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
): FilesOutput {
const fileName = 'States.h';
const stateClasses = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const {components} = module;
// No components in this module
if (components == null) {
return null;
}
return Object.keys(components)
.map(componentName => {
const component = components[componentName];
if (component.interfaceOnly === true) {
return null;
}
return StateTemplate({stateName: componentName});
})
.filter(Boolean)
.join('\n\n');
})
.filter(Boolean)
.join('\n\n');
const template = FileTemplate({
libraryName,
stateClasses,
});
return new Map([[fileName, template]]);
},
};

View File

@@ -0,0 +1,172 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const _require = require('../Utils'),
toSafeCppString = _require.toSafeCppString;
const _require2 = require('./CppHelpers'),
getImports = _require2.getImports;
const FileTemplate = ({libraryName, imports, componentTests}) =>
`
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GenerateTests.js
* */
#include <gtest/gtest.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/components/${libraryName}/Props.h>
${imports}
using namespace facebook::react;
${componentTests}
`.trim();
const TestTemplate = ({componentName, testName, propName, propValue}) => `
TEST(${componentName}_${testName}, etc) {
auto propParser = RawPropsParser();
propParser.prepare<${componentName}>();
auto const &sourceProps = ${componentName}();
auto const &rawProps = RawProps(folly::dynamic::object("${propName}", ${propValue}));
ContextContainer contextContainer{};
PropsParserContext parserContext{-1, contextContainer};
rawProps.parse(propParser, parserContext);
${componentName}(parserContext, sourceProps, rawProps);
}
`;
function getTestCasesForProp(propName, typeAnnotation) {
const cases = [];
if (typeAnnotation.type === 'StringEnumTypeAnnotation') {
typeAnnotation.options.forEach(option =>
cases.push({
propName,
testName: `${propName}_${toSafeCppString(option)}`,
propValue: option,
}),
);
} else if (typeAnnotation.type === 'StringTypeAnnotation') {
cases.push({
propName,
propValue:
typeAnnotation.default != null && typeAnnotation.default !== ''
? typeAnnotation.default
: 'foo',
});
} else if (typeAnnotation.type === 'BooleanTypeAnnotation') {
cases.push({
propName: propName,
propValue: typeAnnotation.default != null ? typeAnnotation.default : true,
});
// $FlowFixMe[incompatible-type]
} else if (typeAnnotation.type === 'IntegerTypeAnnotation') {
cases.push({
propName,
propValue: typeAnnotation.default || 10,
});
} else if (typeAnnotation.type === 'FloatTypeAnnotation') {
cases.push({
propName,
propValue: typeAnnotation.default != null ? typeAnnotation.default : 0.1,
});
} else if (typeAnnotation.type === 'ReservedPropTypeAnnotation') {
if (typeAnnotation.name === 'ColorPrimitive') {
cases.push({
propName,
propValue: 1,
});
} else if (typeAnnotation.name === 'PointPrimitive') {
cases.push({
propName,
propValue: 'folly::dynamic::object("x", 1)("y", 1)',
raw: true,
});
} else if (typeAnnotation.name === 'ImageSourcePrimitive') {
cases.push({
propName,
propValue: 'folly::dynamic::object("url", "testurl")',
raw: true,
});
}
}
return cases;
}
function generateTestsString(name, component) {
function createTest({testName, propName, propValue, raw = false}) {
const value =
!raw && typeof propValue === 'string' ? `"${propValue}"` : propValue;
return TestTemplate({
componentName: name,
testName: testName != null ? testName : propName,
propName,
propValue: String(value),
});
}
const testCases = component.props.reduce((cases, prop) => {
return cases.concat(getTestCasesForProp(prop.name, prop.typeAnnotation));
}, []);
const baseTest = {
testName: 'DoesNotDie',
propName: 'xx_invalid_xx',
propValue: 'xx_invalid_xx',
};
return [baseTest, ...testCases].map(createTest).join('');
}
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
) {
const fileName = 'Tests.cpp';
const allImports = new Set([
'#include <react/renderer/core/propsConversions.h>',
'#include <react/renderer/core/RawProps.h>',
'#include <react/renderer/core/RawPropsParser.h>',
]);
const componentTests = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const components = module.components;
if (components == null) {
return null;
}
return Object.keys(components)
.map(componentName => {
const component = components[componentName];
const name = `${componentName}Props`;
const imports = getImports(component.props);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
imports.forEach(allImports.add, allImports);
return generateTestsString(name, component);
})
.join('');
})
.filter(Boolean)
.join('');
const imports = Array.from(allImports).sort().join('\n').trim();
const replacedTemplate = FileTemplate({
imports,
libraryName,
componentTests,
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,221 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {ComponentShape, PropTypeAnnotation} from '../../CodegenSchema';
import type {SchemaType} from '../../CodegenSchema';
const {toSafeCppString} = require('../Utils');
const {getImports} = require('./CppHelpers');
type FilesOutput = Map<string, string>;
type PropValueType = string | number | boolean;
type TestCase = $ReadOnly<{
propName: string,
propValue: ?PropValueType,
testName?: string,
raw?: boolean,
}>;
const FileTemplate = ({
libraryName,
imports,
componentTests,
}: {
libraryName: string,
imports: string,
componentTests: string,
}) =>
`
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GenerateTests.js
* */
#include <gtest/gtest.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/components/${libraryName}/Props.h>
${imports}
using namespace facebook::react;
${componentTests}
`.trim();
const TestTemplate = ({
componentName,
testName,
propName,
propValue,
}: {
componentName: string,
testName: string,
propName: string,
propValue: string,
}) => `
TEST(${componentName}_${testName}, etc) {
auto propParser = RawPropsParser();
propParser.prepare<${componentName}>();
auto const &sourceProps = ${componentName}();
auto const &rawProps = RawProps(folly::dynamic::object("${propName}", ${propValue}));
ContextContainer contextContainer{};
PropsParserContext parserContext{-1, contextContainer};
rawProps.parse(propParser, parserContext);
${componentName}(parserContext, sourceProps, rawProps);
}
`;
function getTestCasesForProp(
propName: string,
typeAnnotation: PropTypeAnnotation,
) {
const cases = [];
if (typeAnnotation.type === 'StringEnumTypeAnnotation') {
typeAnnotation.options.forEach(option =>
cases.push({
propName,
testName: `${propName}_${toSafeCppString(option)}`,
propValue: option,
}),
);
} else if (typeAnnotation.type === 'StringTypeAnnotation') {
cases.push({
propName,
propValue:
typeAnnotation.default != null && typeAnnotation.default !== ''
? typeAnnotation.default
: 'foo',
});
} else if (typeAnnotation.type === 'BooleanTypeAnnotation') {
cases.push({
propName: propName,
propValue: typeAnnotation.default != null ? typeAnnotation.default : true,
});
// $FlowFixMe[incompatible-type]
} else if (typeAnnotation.type === 'IntegerTypeAnnotation') {
cases.push({
propName,
propValue: typeAnnotation.default || 10,
});
} else if (typeAnnotation.type === 'FloatTypeAnnotation') {
cases.push({
propName,
propValue: typeAnnotation.default != null ? typeAnnotation.default : 0.1,
});
} else if (typeAnnotation.type === 'ReservedPropTypeAnnotation') {
if (typeAnnotation.name === 'ColorPrimitive') {
cases.push({
propName,
propValue: 1,
});
} else if (typeAnnotation.name === 'PointPrimitive') {
cases.push({
propName,
propValue: 'folly::dynamic::object("x", 1)("y", 1)',
raw: true,
});
} else if (typeAnnotation.name === 'ImageSourcePrimitive') {
cases.push({
propName,
propValue: 'folly::dynamic::object("url", "testurl")',
raw: true,
});
}
}
return cases;
}
function generateTestsString(name: string, component: ComponentShape) {
function createTest({testName, propName, propValue, raw = false}: TestCase) {
const value =
!raw && typeof propValue === 'string' ? `"${propValue}"` : propValue;
return TestTemplate({
componentName: name,
testName: testName != null ? testName : propName,
propName,
propValue: String(value),
});
}
const testCases = component.props.reduce((cases: Array<TestCase>, prop) => {
return cases.concat(getTestCasesForProp(prop.name, prop.typeAnnotation));
}, []);
const baseTest = {
testName: 'DoesNotDie',
propName: 'xx_invalid_xx',
propValue: 'xx_invalid_xx',
};
return [baseTest, ...testCases].map(createTest).join('');
}
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
): FilesOutput {
const fileName = 'Tests.cpp';
const allImports = new Set([
'#include <react/renderer/core/propsConversions.h>',
'#include <react/renderer/core/RawProps.h>',
'#include <react/renderer/core/RawPropsParser.h>',
]);
const componentTests = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const {components} = module;
if (components == null) {
return null;
}
return Object.keys(components)
.map(componentName => {
const component = components[componentName];
const name = `${componentName}Props`;
const imports = getImports(component.props);
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
imports.forEach(allImports.add, allImports);
return generateTestsString(name, component);
})
.join('');
})
.filter(Boolean)
.join('');
const imports = Array.from(allImports).sort().join('\n').trim();
const replacedTemplate = FileTemplate({
imports,
libraryName,
componentTests,
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,108 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const _require = require('./ComponentsProviderUtils'),
generateSupportedApplePlatformsMacro =
_require.generateSupportedApplePlatformsMacro;
// File path -> contents
const FileTemplate = ({lookupFuncs}) => `
/*
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by GenerateRCTThirdPartyFabricComponentsProviderH
*/
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wreturn-type-c-linkage"
#import <React/RCTComponentViewProtocol.h>
#ifdef __cplusplus
extern "C" {
#endif
Class<RCTComponentViewProtocol> RCTThirdPartyFabricComponentsProvider(const char *name);
#if RCT_NEW_ARCH_ENABLED
#ifndef RCT_DYNAMIC_FRAMEWORKS
${lookupFuncs}
#endif
#endif
#ifdef __cplusplus
}
#endif
#pragma GCC diagnostic pop
`;
const LookupFuncTemplate = ({className, libraryName}) =>
`
Class<RCTComponentViewProtocol> ${className}Cls(void) __attribute__((used)); // ${libraryName}
`.trim();
module.exports = {
generate(schemas, supportedApplePlatforms) {
const fileName = 'RCTThirdPartyFabricComponentsProvider.h';
const lookupFuncs = Object.keys(schemas)
.map(libraryName => {
const schema = schemas[libraryName];
const librarySupportedApplePlatforms =
supportedApplePlatforms === null || supportedApplePlatforms === void 0
? void 0
: supportedApplePlatforms[libraryName];
const generatedLookup = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const components = module.components;
// No components in this module
if (components == null) {
return null;
}
return Object.keys(components)
.filter(componentName => {
const component = components[componentName];
return !(
component.excludedPlatforms &&
component.excludedPlatforms.includes('iOS')
);
})
.map(componentName => {
return LookupFuncTemplate({
className: componentName,
libraryName,
});
})
.join('\n');
})
.filter(Boolean)
.join('\n');
return generateSupportedApplePlatformsMacro(
generatedLookup,
librarySupportedApplePlatforms,
);
})
.join('\n');
const replacedTemplate = FileTemplate({
lookupFuncs,
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,126 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {SchemaType} from '../../CodegenSchema';
const {
generateSupportedApplePlatformsMacro,
} = require('./ComponentsProviderUtils');
// File path -> contents
type FilesOutput = Map<string, string>;
const FileTemplate = ({lookupFuncs}: {lookupFuncs: string}) => `
/*
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by GenerateRCTThirdPartyFabricComponentsProviderH
*/
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wreturn-type-c-linkage"
#import <React/RCTComponentViewProtocol.h>
#ifdef __cplusplus
extern "C" {
#endif
Class<RCTComponentViewProtocol> RCTThirdPartyFabricComponentsProvider(const char *name);
#if RCT_NEW_ARCH_ENABLED
#ifndef RCT_DYNAMIC_FRAMEWORKS
${lookupFuncs}
#endif
#endif
#ifdef __cplusplus
}
#endif
#pragma GCC diagnostic pop
`;
const LookupFuncTemplate = ({
className,
libraryName,
}: {
className: string,
libraryName: string,
}) =>
`
Class<RCTComponentViewProtocol> ${className}Cls(void) __attribute__((used)); // ${libraryName}
`.trim();
module.exports = {
generate(
schemas: {[string]: SchemaType},
supportedApplePlatforms?: {[string]: {[string]: boolean}},
): FilesOutput {
const fileName = 'RCTThirdPartyFabricComponentsProvider.h';
const lookupFuncs = Object.keys(schemas)
.map(libraryName => {
const schema = schemas[libraryName];
const librarySupportedApplePlatforms =
supportedApplePlatforms?.[libraryName];
const generatedLookup = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const {components} = module;
// No components in this module
if (components == null) {
return null;
}
return Object.keys(components)
.filter(componentName => {
const component = components[componentName];
return !(
component.excludedPlatforms &&
component.excludedPlatforms.includes('iOS')
);
})
.map(componentName => {
return LookupFuncTemplate({
className: componentName,
libraryName,
});
})
.join('\n');
})
.filter(Boolean)
.join('\n');
return generateSupportedApplePlatformsMacro(
generatedLookup,
librarySupportedApplePlatforms,
);
})
.join('\n');
const replacedTemplate = FileTemplate({
lookupFuncs,
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,106 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const _require = require('./ComponentsProviderUtils'),
generateSupportedApplePlatformsMacro =
_require.generateSupportedApplePlatformsMacro;
// File path -> contents
const FileTemplate = ({lookupMap}) => `
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by GenerateRCTThirdPartyFabricComponentsProviderCpp
*/
// OSS-compatibility layer
#import "RCTThirdPartyFabricComponentsProvider.h"
#import <string>
#import <unordered_map>
Class<RCTComponentViewProtocol> RCTThirdPartyFabricComponentsProvider(const char *name) {
static std::unordered_map<std::string, Class (*)(void)> sFabricComponentsClassMap = {
#if RCT_NEW_ARCH_ENABLED
#ifndef RCT_DYNAMIC_FRAMEWORKS
${lookupMap}
#endif
#endif
};
auto p = sFabricComponentsClassMap.find(name);
if (p != sFabricComponentsClassMap.end()) {
auto classFunc = p->second;
return classFunc();
}
return nil;
}
`;
const LookupMapTemplate = ({className, libraryName}) => `
{"${className}", ${className}Cls}, // ${libraryName}`;
module.exports = {
generate(schemas, supportedApplePlatforms) {
const fileName = 'RCTThirdPartyFabricComponentsProvider.mm';
const lookupMap = Object.keys(schemas)
.map(libraryName => {
const schema = schemas[libraryName];
const librarySupportedApplePlatforms =
supportedApplePlatforms === null || supportedApplePlatforms === void 0
? void 0
: supportedApplePlatforms[libraryName];
const generatedLookup = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const components = module.components;
// No components in this module
if (components == null) {
return null;
}
const componentTemplates = Object.keys(components)
.filter(componentName => {
const component = components[componentName];
return !(
component.excludedPlatforms &&
component.excludedPlatforms.includes('iOS')
);
})
.map(componentName => {
const replacedTemplate = LookupMapTemplate({
className: componentName,
libraryName,
});
return replacedTemplate;
});
return componentTemplates.length > 0 ? componentTemplates : null;
})
.filter(Boolean)
.join('\n');
return generateSupportedApplePlatformsMacro(
generatedLookup,
librarySupportedApplePlatforms,
);
})
.join('\n');
const replacedTemplate = FileTemplate({
lookupMap,
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,125 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {SchemaType} from '../../CodegenSchema';
const {
generateSupportedApplePlatformsMacro,
} = require('./ComponentsProviderUtils');
// File path -> contents
type FilesOutput = Map<string, string>;
const FileTemplate = ({lookupMap}: {lookupMap: string}) => `
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by GenerateRCTThirdPartyFabricComponentsProviderCpp
*/
// OSS-compatibility layer
#import "RCTThirdPartyFabricComponentsProvider.h"
#import <string>
#import <unordered_map>
Class<RCTComponentViewProtocol> RCTThirdPartyFabricComponentsProvider(const char *name) {
static std::unordered_map<std::string, Class (*)(void)> sFabricComponentsClassMap = {
#if RCT_NEW_ARCH_ENABLED
#ifndef RCT_DYNAMIC_FRAMEWORKS
${lookupMap}
#endif
#endif
};
auto p = sFabricComponentsClassMap.find(name);
if (p != sFabricComponentsClassMap.end()) {
auto classFunc = p->second;
return classFunc();
}
return nil;
}
`;
const LookupMapTemplate = ({
className,
libraryName,
}: {
className: string,
libraryName: string,
}) => `
{"${className}", ${className}Cls}, // ${libraryName}`;
module.exports = {
generate(
schemas: {[string]: SchemaType},
supportedApplePlatforms?: {[string]: {[string]: boolean}},
): FilesOutput {
const fileName = 'RCTThirdPartyFabricComponentsProvider.mm';
const lookupMap = Object.keys(schemas)
.map(libraryName => {
const schema = schemas[libraryName];
const librarySupportedApplePlatforms =
supportedApplePlatforms?.[libraryName];
const generatedLookup = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const {components} = module;
// No components in this module
if (components == null) {
return null;
}
const componentTemplates = Object.keys(components)
.filter(componentName => {
const component = components[componentName];
return !(
component.excludedPlatforms &&
component.excludedPlatforms.includes('iOS')
);
})
.map(componentName => {
const replacedTemplate = LookupMapTemplate({
className: componentName,
libraryName,
});
return replacedTemplate;
});
return componentTemplates.length > 0 ? componentTemplates : null;
})
.filter(Boolean)
.join('\n');
return generateSupportedApplePlatformsMacro(
generatedLookup,
librarySupportedApplePlatforms,
);
})
.join('\n');
const replacedTemplate = FileTemplate({lookupMap});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,414 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const j = require('jscodeshift');
// File path -> contents
const FileTemplate = ({imports, componentConfig}) => `
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @flow
*
* ${'@'}generated by codegen project: GenerateViewConfigJs.js
*/
'use strict';
${imports}
${componentConfig}
`;
// We use this to add to a set. Need to make sure we aren't importing
// this multiple times.
const UIMANAGER_IMPORT = 'const {UIManager} = require("react-native")';
function getReactDiffProcessValue(typeAnnotation) {
switch (typeAnnotation.type) {
case 'BooleanTypeAnnotation':
case 'StringTypeAnnotation':
case 'Int32TypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
case 'ObjectTypeAnnotation':
case 'StringEnumTypeAnnotation':
case 'Int32EnumTypeAnnotation':
case 'MixedTypeAnnotation':
return j.literal(true);
case 'ReservedPropTypeAnnotation':
switch (typeAnnotation.name) {
case 'ColorPrimitive':
return j.template
.expression`{ process: require('react-native/Libraries/StyleSheet/processColor').default }`;
case 'ImageSourcePrimitive':
return j.template
.expression`{ process: require('react-native/Libraries/Image/resolveAssetSource') }`;
case 'ImageRequestPrimitive':
throw new Error('ImageRequest should not be used in props');
case 'PointPrimitive':
return j.template
.expression`{ diff: require('react-native/Libraries/Utilities/differ/pointsDiffer') }`;
case 'EdgeInsetsPrimitive':
return j.template
.expression`{ diff: require('react-native/Libraries/Utilities/differ/insetsDiffer') }`;
case 'DimensionPrimitive':
return j.literal(true);
default:
typeAnnotation.name;
throw new Error(
`Received unknown native typeAnnotation: "${typeAnnotation.name}"`,
);
}
case 'ArrayTypeAnnotation':
if (typeAnnotation.elementType.type === 'ReservedPropTypeAnnotation') {
switch (typeAnnotation.elementType.name) {
case 'ColorPrimitive':
return j.template
.expression`{ process: require('react-native/Libraries/StyleSheet/processColorArray') }`;
case 'ImageSourcePrimitive':
case 'PointPrimitive':
case 'EdgeInsetsPrimitive':
case 'DimensionPrimitive':
return j.literal(true);
default:
throw new Error(
`Received unknown array native typeAnnotation: "${typeAnnotation.elementType.name}"`,
);
}
}
return j.literal(true);
default:
typeAnnotation;
throw new Error(
`Received unknown typeAnnotation: "${typeAnnotation.type}"`,
);
}
}
const ComponentTemplate = ({
componentName,
paperComponentName,
paperComponentNameDeprecated,
}) => {
const nativeComponentName =
paperComponentName !== null && paperComponentName !== void 0
? paperComponentName
: componentName;
return `
let nativeComponentName = '${nativeComponentName}';
${
paperComponentNameDeprecated != null
? DeprecatedComponentNameCheckTemplate({
componentName,
paperComponentNameDeprecated,
})
: ''
}
export const __INTERNAL_VIEW_CONFIG = VIEW_CONFIG;
export default NativeComponentRegistry.get(nativeComponentName, () => __INTERNAL_VIEW_CONFIG);
`.trim();
};
// Check whether the native component exists in the app binary.
// Old getViewManagerConfig() checks for the existance of the native Paper view manager. Not available in Bridgeless.
// New hasViewManagerConfig() queries Fabrics native component registry directly.
const DeprecatedComponentNameCheckTemplate = ({
componentName,
paperComponentNameDeprecated,
}) =>
`
if (UIManager.hasViewManagerConfig('${componentName}')) {
nativeComponentName = '${componentName}';
} else if (UIManager.hasViewManagerConfig('${paperComponentNameDeprecated}')) {
nativeComponentName = '${paperComponentNameDeprecated}';
} else {
throw new Error('Failed to find native component for either "${componentName}" or "${paperComponentNameDeprecated}"');
}
`.trim();
// Replicates the behavior of RCTNormalizeInputEventName in RCTEventDispatcher.m
function normalizeInputEventName(name) {
if (name.startsWith('on')) {
return name.replace(/^on/, 'top');
} else if (!name.startsWith('top')) {
return `top${name[0].toUpperCase()}${name.slice(1)}`;
}
return name;
}
// Replicates the behavior of viewConfig in RCTComponentData.m
function getValidAttributesForEvents(events, imports) {
imports.add(
"const {ConditionallyIgnoredEventHandlers} = require('react-native/Libraries/NativeComponent/ViewConfigIgnore');",
);
const validAttributes = j.objectExpression(
events.map(eventType => {
return j.property('init', j.identifier(eventType.name), j.literal(true));
}),
);
return j.callExpression(j.identifier('ConditionallyIgnoredEventHandlers'), [
validAttributes,
]);
}
function generateBubblingEventInfo(event, nameOveride) {
return j.property(
'init',
j.identifier(normalizeInputEventName(nameOveride || event.name)),
j.objectExpression([
j.property(
'init',
j.identifier('phasedRegistrationNames'),
j.objectExpression([
j.property(
'init',
j.identifier('captured'),
j.literal(`${event.name}Capture`),
),
j.property('init', j.identifier('bubbled'), j.literal(event.name)),
]),
),
]),
);
}
function generateDirectEventInfo(event, nameOveride) {
return j.property(
'init',
j.identifier(normalizeInputEventName(nameOveride || event.name)),
j.objectExpression([
j.property(
'init',
j.identifier('registrationName'),
j.literal(event.name),
),
]),
);
}
function buildViewConfig(schema, componentName, component, imports) {
const componentProps = component.props;
const componentEvents = component.events;
component.extendsProps.forEach(extendProps => {
switch (extendProps.type) {
case 'ReactNativeBuiltInType':
switch (extendProps.knownTypeName) {
case 'ReactNativeCoreViewProps':
imports.add(
"const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry');",
);
return;
default:
extendProps.knownTypeName;
throw new Error('Invalid knownTypeName');
}
default:
extendProps.type;
throw new Error('Invalid extended type');
}
});
const validAttributes = j.objectExpression([
...componentProps.map(schemaProp => {
return j.property(
'init',
j.identifier(schemaProp.name),
getReactDiffProcessValue(schemaProp.typeAnnotation),
);
}),
...(componentEvents.length > 0
? [
j.spreadProperty(
getValidAttributesForEvents(componentEvents, imports),
),
]
: []),
]);
const bubblingEventNames = component.events
.filter(event => event.bubblingType === 'bubble')
.reduce((bubblingEvents, event) => {
// We add in the deprecated paper name so that it is in the view config.
// This means either the old event name or the new event name can fire
// and be sent to the listener until the old top level name is removed.
if (event.paperTopLevelNameDeprecated) {
bubblingEvents.push(
generateBubblingEventInfo(event, event.paperTopLevelNameDeprecated),
);
} else {
bubblingEvents.push(generateBubblingEventInfo(event));
}
return bubblingEvents;
}, []);
const bubblingEvents =
bubblingEventNames.length > 0
? j.property(
'init',
j.identifier('bubblingEventTypes'),
j.objectExpression(bubblingEventNames),
)
: null;
const directEventNames = component.events
.filter(event => event.bubblingType === 'direct')
.reduce((directEvents, event) => {
// We add in the deprecated paper name so that it is in the view config.
// This means either the old event name or the new event name can fire
// and be sent to the listener until the old top level name is removed.
if (event.paperTopLevelNameDeprecated) {
directEvents.push(
generateDirectEventInfo(event, event.paperTopLevelNameDeprecated),
);
} else {
directEvents.push(generateDirectEventInfo(event));
}
return directEvents;
}, []);
const directEvents =
directEventNames.length > 0
? j.property(
'init',
j.identifier('directEventTypes'),
j.objectExpression(directEventNames),
)
: null;
const properties = [
j.property(
'init',
j.identifier('uiViewClassName'),
j.literal(componentName),
),
bubblingEvents,
directEvents,
j.property('init', j.identifier('validAttributes'), validAttributes),
].filter(Boolean);
return j.objectExpression(properties);
}
function buildCommands(schema, componentName, component, imports) {
const commands = component.commands;
if (commands.length === 0) {
return null;
}
imports.add(
'const {dispatchCommand} = require("react-native/Libraries/ReactNative/RendererProxy");',
);
const properties = commands.map(command => {
const commandName = command.name;
const params = command.typeAnnotation.params;
const commandNameLiteral = j.literal(commandName);
const commandNameIdentifier = j.identifier(commandName);
const arrayParams = j.arrayExpression(
params.map(param => {
return j.identifier(param.name);
}),
);
const expression = j.template
.expression`dispatchCommand(ref, ${commandNameLiteral}, ${arrayParams})`;
const functionParams = params.map(param => {
return j.identifier(param.name);
});
const property = j.property(
'init',
commandNameIdentifier,
j.functionExpression(
null,
[j.identifier('ref'), ...functionParams],
j.blockStatement([j.expressionStatement(expression)]),
),
);
property.method = true;
return property;
});
return j.exportNamedDeclaration(
j.variableDeclaration('const', [
j.variableDeclarator(
j.identifier('Commands'),
j.objectExpression(properties),
),
]),
);
}
module.exports = {
generate(libraryName, schema) {
try {
const fileName = `${libraryName}NativeViewConfig.js`;
const imports = new Set();
const moduleResults = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const components = module.components;
return Object.keys(components)
.map(componentName => {
var _component$paperCompo;
const component = components[componentName];
if (component.paperComponentNameDeprecated) {
imports.add(UIMANAGER_IMPORT);
}
const replacedTemplate = ComponentTemplate({
componentName,
paperComponentName: component.paperComponentName,
paperComponentNameDeprecated:
component.paperComponentNameDeprecated,
});
const replacedSourceRoot = j.withParser('flow')(replacedTemplate);
const paperComponentName =
(_component$paperCompo = component.paperComponentName) !==
null && _component$paperCompo !== void 0
? _component$paperCompo
: componentName;
replacedSourceRoot
.find(j.Identifier, {
name: 'VIEW_CONFIG',
})
.replaceWith(
buildViewConfig(
schema,
paperComponentName,
component,
imports,
),
);
const commands = buildCommands(
schema,
paperComponentName,
component,
imports,
);
if (commands) {
replacedSourceRoot
.find(j.ExportDefaultDeclaration)
.insertAfter(j(commands).toSource());
}
const replacedSource = replacedSourceRoot.toSource({
quote: 'single',
trailingComma: true,
});
return replacedSource;
})
.join('\n\n');
})
.filter(Boolean)
.join('\n\n');
const replacedTemplate = FileTemplate({
componentConfig: moduleResults,
imports: Array.from(imports).sort().join('\n'),
});
return new Map([[fileName, replacedTemplate]]);
} catch (error) {
console.error(`\nError parsing schema for ${libraryName}\n`);
console.error(JSON.stringify(schema));
throw error;
}
},
};

View File

@@ -0,0 +1,488 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
import type {
ComponentShape,
EventTypeShape,
PropTypeAnnotation,
} from '../../CodegenSchema';
import type {SchemaType} from '../../CodegenSchema';
const j = require('jscodeshift');
// File path -> contents
type FilesOutput = Map<string, string>;
const FileTemplate = ({
imports,
componentConfig,
}: {
imports: string,
componentConfig: string,
}) => `
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @flow
*
* ${'@'}generated by codegen project: GenerateViewConfigJs.js
*/
'use strict';
${imports}
${componentConfig}
`;
// We use this to add to a set. Need to make sure we aren't importing
// this multiple times.
const UIMANAGER_IMPORT = 'const {UIManager} = require("react-native")';
function getReactDiffProcessValue(typeAnnotation: PropTypeAnnotation) {
switch (typeAnnotation.type) {
case 'BooleanTypeAnnotation':
case 'StringTypeAnnotation':
case 'Int32TypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
case 'ObjectTypeAnnotation':
case 'StringEnumTypeAnnotation':
case 'Int32EnumTypeAnnotation':
case 'MixedTypeAnnotation':
return j.literal(true);
case 'ReservedPropTypeAnnotation':
switch (typeAnnotation.name) {
case 'ColorPrimitive':
return j.template
.expression`{ process: require('react-native/Libraries/StyleSheet/processColor').default }`;
case 'ImageSourcePrimitive':
return j.template
.expression`{ process: require('react-native/Libraries/Image/resolveAssetSource') }`;
case 'ImageRequestPrimitive':
throw new Error('ImageRequest should not be used in props');
case 'PointPrimitive':
return j.template
.expression`{ diff: require('react-native/Libraries/Utilities/differ/pointsDiffer') }`;
case 'EdgeInsetsPrimitive':
return j.template
.expression`{ diff: require('react-native/Libraries/Utilities/differ/insetsDiffer') }`;
case 'DimensionPrimitive':
return j.literal(true);
default:
(typeAnnotation.name: empty);
throw new Error(
`Received unknown native typeAnnotation: "${typeAnnotation.name}"`,
);
}
case 'ArrayTypeAnnotation':
if (typeAnnotation.elementType.type === 'ReservedPropTypeAnnotation') {
switch (typeAnnotation.elementType.name) {
case 'ColorPrimitive':
return j.template
.expression`{ process: require('react-native/Libraries/StyleSheet/processColorArray') }`;
case 'ImageSourcePrimitive':
case 'PointPrimitive':
case 'EdgeInsetsPrimitive':
case 'DimensionPrimitive':
return j.literal(true);
default:
throw new Error(
`Received unknown array native typeAnnotation: "${typeAnnotation.elementType.name}"`,
);
}
}
return j.literal(true);
default:
(typeAnnotation: empty);
throw new Error(
`Received unknown typeAnnotation: "${typeAnnotation.type}"`,
);
}
}
const ComponentTemplate = ({
componentName,
paperComponentName,
paperComponentNameDeprecated,
}: {
componentName: string,
paperComponentName: ?string,
paperComponentNameDeprecated: ?string,
}) => {
const nativeComponentName = paperComponentName ?? componentName;
return `
let nativeComponentName = '${nativeComponentName}';
${
paperComponentNameDeprecated != null
? DeprecatedComponentNameCheckTemplate({
componentName,
paperComponentNameDeprecated,
})
: ''
}
export const __INTERNAL_VIEW_CONFIG = VIEW_CONFIG;
export default NativeComponentRegistry.get(nativeComponentName, () => __INTERNAL_VIEW_CONFIG);
`.trim();
};
// Check whether the native component exists in the app binary.
// Old getViewManagerConfig() checks for the existance of the native Paper view manager. Not available in Bridgeless.
// New hasViewManagerConfig() queries Fabrics native component registry directly.
const DeprecatedComponentNameCheckTemplate = ({
componentName,
paperComponentNameDeprecated,
}: {
componentName: string,
paperComponentNameDeprecated: string,
}) =>
`
if (UIManager.hasViewManagerConfig('${componentName}')) {
nativeComponentName = '${componentName}';
} else if (UIManager.hasViewManagerConfig('${paperComponentNameDeprecated}')) {
nativeComponentName = '${paperComponentNameDeprecated}';
} else {
throw new Error('Failed to find native component for either "${componentName}" or "${paperComponentNameDeprecated}"');
}
`.trim();
// Replicates the behavior of RCTNormalizeInputEventName in RCTEventDispatcher.m
function normalizeInputEventName(name: string) {
if (name.startsWith('on')) {
return name.replace(/^on/, 'top');
} else if (!name.startsWith('top')) {
return `top${name[0].toUpperCase()}${name.slice(1)}`;
}
return name;
}
// Replicates the behavior of viewConfig in RCTComponentData.m
function getValidAttributesForEvents(
events: $ReadOnlyArray<EventTypeShape>,
imports: Set<string>,
) {
imports.add(
"const {ConditionallyIgnoredEventHandlers} = require('react-native/Libraries/NativeComponent/ViewConfigIgnore');",
);
const validAttributes = j.objectExpression(
events.map(eventType => {
return j.property('init', j.identifier(eventType.name), j.literal(true));
}),
);
return j.callExpression(j.identifier('ConditionallyIgnoredEventHandlers'), [
validAttributes,
]);
}
function generateBubblingEventInfo(
event: EventTypeShape,
nameOveride: void | string,
) {
return j.property(
'init',
j.identifier(normalizeInputEventName(nameOveride || event.name)),
j.objectExpression([
j.property(
'init',
j.identifier('phasedRegistrationNames'),
j.objectExpression([
j.property(
'init',
j.identifier('captured'),
j.literal(`${event.name}Capture`),
),
j.property('init', j.identifier('bubbled'), j.literal(event.name)),
]),
),
]),
);
}
function generateDirectEventInfo(
event: EventTypeShape,
nameOveride: void | string,
) {
return j.property(
'init',
j.identifier(normalizeInputEventName(nameOveride || event.name)),
j.objectExpression([
j.property(
'init',
j.identifier('registrationName'),
j.literal(event.name),
),
]),
);
}
function buildViewConfig(
schema: SchemaType,
componentName: string,
component: ComponentShape,
imports: Set<string>,
) {
const componentProps = component.props;
const componentEvents = component.events;
component.extendsProps.forEach(extendProps => {
switch (extendProps.type) {
case 'ReactNativeBuiltInType':
switch (extendProps.knownTypeName) {
case 'ReactNativeCoreViewProps':
imports.add(
"const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry');",
);
return;
default:
(extendProps.knownTypeName: empty);
throw new Error('Invalid knownTypeName');
}
default:
(extendProps.type: empty);
throw new Error('Invalid extended type');
}
});
const validAttributes = j.objectExpression([
...componentProps.map(schemaProp => {
return j.property(
'init',
j.identifier(schemaProp.name),
getReactDiffProcessValue(schemaProp.typeAnnotation),
);
}),
...(componentEvents.length > 0
? [
j.spreadProperty(
getValidAttributesForEvents(componentEvents, imports),
),
]
: []),
]);
const bubblingEventNames = component.events
.filter(event => event.bubblingType === 'bubble')
.reduce((bubblingEvents: Array<any>, event) => {
// We add in the deprecated paper name so that it is in the view config.
// This means either the old event name or the new event name can fire
// and be sent to the listener until the old top level name is removed.
if (event.paperTopLevelNameDeprecated) {
bubblingEvents.push(
generateBubblingEventInfo(event, event.paperTopLevelNameDeprecated),
);
} else {
bubblingEvents.push(generateBubblingEventInfo(event));
}
return bubblingEvents;
}, []);
const bubblingEvents =
bubblingEventNames.length > 0
? j.property(
'init',
j.identifier('bubblingEventTypes'),
j.objectExpression(bubblingEventNames),
)
: null;
const directEventNames = component.events
.filter(event => event.bubblingType === 'direct')
.reduce((directEvents: Array<any>, event) => {
// We add in the deprecated paper name so that it is in the view config.
// This means either the old event name or the new event name can fire
// and be sent to the listener until the old top level name is removed.
if (event.paperTopLevelNameDeprecated) {
directEvents.push(
generateDirectEventInfo(event, event.paperTopLevelNameDeprecated),
);
} else {
directEvents.push(generateDirectEventInfo(event));
}
return directEvents;
}, []);
const directEvents =
directEventNames.length > 0
? j.property(
'init',
j.identifier('directEventTypes'),
j.objectExpression(directEventNames),
)
: null;
const properties = [
j.property(
'init',
j.identifier('uiViewClassName'),
j.literal(componentName),
),
bubblingEvents,
directEvents,
j.property('init', j.identifier('validAttributes'), validAttributes),
].filter(Boolean);
return j.objectExpression(properties);
}
function buildCommands(
schema: SchemaType,
componentName: string,
component: ComponentShape,
imports: Set<string>,
) {
const commands = component.commands;
if (commands.length === 0) {
return null;
}
imports.add(
'const {dispatchCommand} = require("react-native/Libraries/ReactNative/RendererProxy");',
);
const properties = commands.map(command => {
const commandName = command.name;
const params = command.typeAnnotation.params;
const commandNameLiteral = j.literal(commandName);
const commandNameIdentifier = j.identifier(commandName);
const arrayParams = j.arrayExpression(
params.map(param => {
return j.identifier(param.name);
}),
);
const expression = j.template
.expression`dispatchCommand(ref, ${commandNameLiteral}, ${arrayParams})`;
const functionParams = params.map(param => {
return j.identifier(param.name);
});
const property = j.property(
'init',
commandNameIdentifier,
j.functionExpression(
null,
[j.identifier('ref'), ...functionParams],
j.blockStatement([j.expressionStatement(expression)]),
),
);
property.method = true;
return property;
});
return j.exportNamedDeclaration(
j.variableDeclaration('const', [
j.variableDeclarator(
j.identifier('Commands'),
j.objectExpression(properties),
),
]),
);
}
module.exports = {
generate(libraryName: string, schema: SchemaType): FilesOutput {
try {
const fileName = `${libraryName}NativeViewConfig.js`;
const imports: Set<string> = new Set();
const moduleResults = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
}
const {components} = module;
return Object.keys(components)
.map((componentName: string) => {
const component = components[componentName];
if (component.paperComponentNameDeprecated) {
imports.add(UIMANAGER_IMPORT);
}
const replacedTemplate = ComponentTemplate({
componentName,
paperComponentName: component.paperComponentName,
paperComponentNameDeprecated:
component.paperComponentNameDeprecated,
});
const replacedSourceRoot = j.withParser('flow')(replacedTemplate);
const paperComponentName =
component.paperComponentName ?? componentName;
replacedSourceRoot
.find(j.Identifier, {
name: 'VIEW_CONFIG',
})
.replaceWith(
buildViewConfig(
schema,
paperComponentName,
component,
imports,
),
);
const commands = buildCommands(
schema,
paperComponentName,
component,
imports,
);
if (commands) {
replacedSourceRoot
.find(j.ExportDefaultDeclaration)
.insertAfter(j(commands).toSource());
}
const replacedSource: string = replacedSourceRoot.toSource({
quote: 'single',
trailingComma: true,
});
return replacedSource;
})
.join('\n\n');
})
.filter(Boolean)
.join('\n\n');
const replacedTemplate = FileTemplate({
componentConfig: moduleResults,
imports: Array.from(imports).sort().join('\n'),
});
return new Map([[fileName, replacedTemplate]]);
} catch (error) {
console.error(`\nError parsing schema for ${libraryName}\n`);
console.error(JSON.stringify(schema));
throw error;
}
},
};

View File

@@ -0,0 +1,111 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
function upperCaseFirst(inString) {
return inString[0].toUpperCase() + inString.slice(1);
}
function getInterfaceJavaClassName(componentName) {
return `${componentName.replace(/^RCT/, '')}ManagerInterface`;
}
function getDelegateJavaClassName(componentName) {
return `${componentName.replace(/^RCT/, '')}ManagerDelegate`;
}
function toSafeJavaString(input, shouldUpperCaseFirst) {
const parts = input.split('-');
if (shouldUpperCaseFirst === false) {
return parts.join('');
}
return parts.map(upperCaseFirst).join('');
}
function getImports(component, type) {
const imports = new Set();
component.extendsProps.forEach(extendProps => {
switch (extendProps.type) {
case 'ReactNativeBuiltInType':
switch (extendProps.knownTypeName) {
case 'ReactNativeCoreViewProps':
imports.add('import android.view.View;');
return;
default:
extendProps.knownTypeName;
throw new Error('Invalid knownTypeName');
}
default:
extendProps.type;
throw new Error('Invalid extended type');
}
});
function addImportsForNativeName(name) {
switch (name) {
case 'ColorPrimitive':
if (type === 'delegate') {
imports.add('import com.facebook.react.bridge.ColorPropConverter;');
}
return;
case 'ImageSourcePrimitive':
imports.add('import com.facebook.react.bridge.ReadableMap;');
return;
case 'PointPrimitive':
imports.add('import com.facebook.react.bridge.ReadableMap;');
return;
case 'EdgeInsetsPrimitive':
imports.add('import com.facebook.react.bridge.ReadableMap;');
return;
case 'DimensionPrimitive':
if (type === 'delegate') {
imports.add(
'import com.facebook.react.bridge.DimensionPropConverter;',
);
} else {
imports.add('import com.facebook.yoga.YogaValue;');
}
return;
default:
name;
throw new Error(`Invalid ReservedPropTypeAnnotation name, got ${name}`);
}
}
component.props.forEach(prop => {
const typeAnnotation = prop.typeAnnotation;
if (typeAnnotation.type === 'ReservedPropTypeAnnotation') {
addImportsForNativeName(typeAnnotation.name);
}
if (typeAnnotation.type === 'ArrayTypeAnnotation') {
imports.add('import com.facebook.react.bridge.ReadableArray;');
}
if (typeAnnotation.type === 'ObjectTypeAnnotation') {
imports.add('import com.facebook.react.bridge.ReadableMap;');
}
if (typeAnnotation.type === 'MixedTypeAnnotation') {
if (type === 'delegate') {
imports.add('import com.facebook.react.bridge.DynamicFromObject;');
} else {
imports.add('import com.facebook.react.bridge.Dynamic;');
}
}
});
component.commands.forEach(command => {
command.typeAnnotation.params.forEach(param => {
const cmdParamType = param.typeAnnotation.type;
if (cmdParamType === 'ArrayTypeAnnotation') {
imports.add('import com.facebook.react.bridge.ReadableArray;');
}
});
});
return imports;
}
module.exports = {
getInterfaceJavaClassName,
getDelegateJavaClassName,
toSafeJavaString,
getImports,
};

View File

@@ -0,0 +1,147 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {ComponentShape} from '../../CodegenSchema';
function upperCaseFirst(inString: string): string {
return inString[0].toUpperCase() + inString.slice(1);
}
function getInterfaceJavaClassName(componentName: string): string {
return `${componentName.replace(/^RCT/, '')}ManagerInterface`;
}
function getDelegateJavaClassName(componentName: string): string {
return `${componentName.replace(/^RCT/, '')}ManagerDelegate`;
}
function toSafeJavaString(
input: string,
shouldUpperCaseFirst?: boolean,
): string {
const parts = input.split('-');
if (shouldUpperCaseFirst === false) {
return parts.join('');
}
return parts.map(upperCaseFirst).join('');
}
function getImports(
component: ComponentShape,
type: 'interface' | 'delegate',
): Set<string> {
const imports: Set<string> = new Set();
component.extendsProps.forEach(extendProps => {
switch (extendProps.type) {
case 'ReactNativeBuiltInType':
switch (extendProps.knownTypeName) {
case 'ReactNativeCoreViewProps':
imports.add('import android.view.View;');
return;
default:
(extendProps.knownTypeName: empty);
throw new Error('Invalid knownTypeName');
}
default:
(extendProps.type: empty);
throw new Error('Invalid extended type');
}
});
function addImportsForNativeName(
name:
| 'ColorPrimitive'
| 'EdgeInsetsPrimitive'
| 'ImageSourcePrimitive'
| 'PointPrimitive'
| 'DimensionPrimitive'
| $TEMPORARY$string<'ColorPrimitive'>
| $TEMPORARY$string<'EdgeInsetsPrimitive'>
| $TEMPORARY$string<'ImageSourcePrimitive'>
| $TEMPORARY$string<'PointPrimitive'>
| $TEMPORARY$string<'DimensionPrimitive'>,
) {
switch (name) {
case 'ColorPrimitive':
if (type === 'delegate') {
imports.add('import com.facebook.react.bridge.ColorPropConverter;');
}
return;
case 'ImageSourcePrimitive':
imports.add('import com.facebook.react.bridge.ReadableMap;');
return;
case 'PointPrimitive':
imports.add('import com.facebook.react.bridge.ReadableMap;');
return;
case 'EdgeInsetsPrimitive':
imports.add('import com.facebook.react.bridge.ReadableMap;');
return;
case 'DimensionPrimitive':
if (type === 'delegate') {
imports.add(
'import com.facebook.react.bridge.DimensionPropConverter;',
);
} else {
imports.add('import com.facebook.yoga.YogaValue;');
}
return;
default:
(name: empty);
throw new Error(`Invalid ReservedPropTypeAnnotation name, got ${name}`);
}
}
component.props.forEach(prop => {
const typeAnnotation = prop.typeAnnotation;
if (typeAnnotation.type === 'ReservedPropTypeAnnotation') {
addImportsForNativeName(typeAnnotation.name);
}
if (typeAnnotation.type === 'ArrayTypeAnnotation') {
imports.add('import com.facebook.react.bridge.ReadableArray;');
}
if (typeAnnotation.type === 'ObjectTypeAnnotation') {
imports.add('import com.facebook.react.bridge.ReadableMap;');
}
if (typeAnnotation.type === 'MixedTypeAnnotation') {
if (type === 'delegate') {
imports.add('import com.facebook.react.bridge.DynamicFromObject;');
} else {
imports.add('import com.facebook.react.bridge.Dynamic;');
}
}
});
component.commands.forEach(command => {
command.typeAnnotation.params.forEach(param => {
const cmdParamType = param.typeAnnotation.type;
if (cmdParamType === 'ArrayTypeAnnotation') {
imports.add('import com.facebook.react.bridge.ReadableArray;');
}
});
});
return imports;
}
module.exports = {
getInterfaceJavaClassName,
getDelegateJavaClassName,
toSafeJavaString,
getImports,
};

View File

@@ -0,0 +1,308 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
function _slicedToArray(arr, i) {
return (
_arrayWithHoles(arr) ||
_iterableToArrayLimit(arr, i) ||
_unsupportedIterableToArray(arr, i) ||
_nonIterableRest()
);
}
function _nonIterableRest() {
throw new TypeError(
'Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.',
);
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === 'string') return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === 'Object' && o.constructor) n = o.constructor.name;
if (n === 'Map' || n === 'Set') return Array.from(o);
if (n === 'Arguments' || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))
return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _iterableToArrayLimit(arr, i) {
var _i =
null == arr
? null
: ('undefined' != typeof Symbol && arr[Symbol.iterator]) ||
arr['@@iterator'];
if (null != _i) {
var _s,
_e,
_x,
_r,
_arr = [],
_n = !0,
_d = !1;
try {
if (((_x = (_i = _i.call(arr)).next), 0 === i)) {
if (Object(_i) !== _i) return;
_n = !1;
} else
for (
;
!(_n = (_s = _x.call(_i)).done) &&
(_arr.push(_s.value), _arr.length !== i);
_n = !0
);
} catch (err) {
(_d = !0), (_e = err);
} finally {
try {
if (!_n && null != _i.return && ((_r = _i.return()), Object(_r) !== _r))
return;
} finally {
if (_d) throw _e;
}
}
return _arr;
}
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
const _require = require('../../parsers/parsers-commons'),
unwrapNullable = _require.unwrapNullable;
const _require2 = require('./Utils'),
createAliasResolver = _require2.createAliasResolver,
getModules = _require2.getModules;
const HostFunctionTemplate = ({
hasteModuleName,
methodName,
returnTypeAnnotation,
args,
}) => {
const isNullable = returnTypeAnnotation.type === 'NullableTypeAnnotation';
const isVoid = returnTypeAnnotation.type === 'VoidTypeAnnotation';
const methodCallArgs = [' rt', ...args].join(',\n ');
const methodCall = `static_cast<${hasteModuleName}CxxSpecJSI *>(&turboModule)->${methodName}(\n${methodCallArgs}\n )`;
return `static jsi::Value __hostFunction_${hasteModuleName}CxxSpecJSI_${methodName}(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {${
isVoid
? `\n ${methodCall};`
: isNullable
? `\n auto result = ${methodCall};`
: ''
}
return ${
isVoid
? 'jsi::Value::undefined()'
: isNullable
? 'result ? jsi::Value(std::move(*result)) : jsi::Value::null()'
: methodCall
};
}`;
};
const ModuleTemplate = ({
hasteModuleName,
hostFunctions,
moduleName,
methods,
}) => {
return `${hostFunctions.join('\n')}
${hasteModuleName}CxxSpecJSI::${hasteModuleName}CxxSpecJSI(std::shared_ptr<CallInvoker> jsInvoker)
: TurboModule("${moduleName}", jsInvoker) {
${methods
.map(({methodName, paramCount}) => {
return ` methodMap_["${methodName}"] = MethodMetadata {${paramCount}, __hostFunction_${hasteModuleName}CxxSpecJSI_${methodName}};`;
})
.join('\n')}
}`;
};
const FileTemplate = ({libraryName, modules}) => {
return `/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GenerateModuleCpp.js
*/
#include "${libraryName}JSI.h"
namespace facebook::react {
${modules}
} // namespace facebook::react
`;
};
function serializeArg(moduleName, arg, index, resolveAlias, enumMap) {
const nullableTypeAnnotation = arg.typeAnnotation,
optional = arg.optional;
const _unwrapNullable = unwrapNullable(nullableTypeAnnotation),
_unwrapNullable2 = _slicedToArray(_unwrapNullable, 2),
typeAnnotation = _unwrapNullable2[0],
nullable = _unwrapNullable2[1];
const isRequired = !optional && !nullable;
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
function wrap(callback) {
const val = `args[${index}]`;
const expression = callback(val);
if (isRequired) {
return expression;
} else {
let condition = `${val}.isNull() || ${val}.isUndefined()`;
if (optional) {
condition = `count <= ${index} || ${condition}`;
}
return `${condition} ? std::nullopt : std::make_optional(${expression})`;
}
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return wrap(val => `${val}.getNumber()`);
default:
realTypeAnnotation.name;
throw new Error(
`Unknown prop type for "${arg.name}, found: ${realTypeAnnotation.name}"`,
);
}
case 'StringTypeAnnotation':
return wrap(val => `${val}.asString(rt)`);
case 'BooleanTypeAnnotation':
return wrap(val => `${val}.asBool()`);
case 'EnumDeclaration':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrap(val => `${val}.asNumber()`);
case 'StringTypeAnnotation':
return wrap(val => `${val}.asString(rt)`);
default:
throw new Error(
`Unknown enum type for "${arg.name}, found: ${realTypeAnnotation.type}"`,
);
}
case 'NumberTypeAnnotation':
return wrap(val => `${val}.asNumber()`);
case 'FloatTypeAnnotation':
return wrap(val => `${val}.asNumber()`);
case 'DoubleTypeAnnotation':
return wrap(val => `${val}.asNumber()`);
case 'Int32TypeAnnotation':
return wrap(val => `${val}.asNumber()`);
case 'ArrayTypeAnnotation':
return wrap(val => `${val}.asObject(rt).asArray(rt)`);
case 'FunctionTypeAnnotation':
return wrap(val => `${val}.asObject(rt).asFunction(rt)`);
case 'GenericObjectTypeAnnotation':
return wrap(val => `${val}.asObject(rt)`);
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrap(val => `${val}.asNumber()`);
case 'ObjectTypeAnnotation':
return wrap(val => `${val}.asObject(rt)`);
case 'StringTypeAnnotation':
return wrap(val => `${val}.asString(rt)`);
default:
throw new Error(
`Unsupported union member type for param "${arg.name}, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'ObjectTypeAnnotation':
return wrap(val => `${val}.asObject(rt)`);
case 'MixedTypeAnnotation':
return wrap(val => `jsi::Value(rt, ${val})`);
default:
realTypeAnnotation.type;
throw new Error(
`Unknown prop type for "${arg.name}, found: ${realTypeAnnotation.type}"`,
);
}
}
function serializePropertyIntoHostFunction(
moduleName,
hasteModuleName,
property,
resolveAlias,
enumMap,
) {
const _unwrapNullable3 = unwrapNullable(property.typeAnnotation),
_unwrapNullable4 = _slicedToArray(_unwrapNullable3, 1),
propertyTypeAnnotation = _unwrapNullable4[0];
return HostFunctionTemplate({
hasteModuleName,
methodName: property.name,
returnTypeAnnotation: propertyTypeAnnotation.returnTypeAnnotation,
args: propertyTypeAnnotation.params.map((p, i) =>
serializeArg(moduleName, p, i, resolveAlias, enumMap),
),
});
}
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
) {
const nativeModules = getModules(schema);
const modules = Object.keys(nativeModules)
.map(hasteModuleName => {
const nativeModule = nativeModules[hasteModuleName];
const aliasMap = nativeModule.aliasMap,
enumMap = nativeModule.enumMap,
properties = nativeModule.spec.properties,
moduleName = nativeModule.moduleName;
const resolveAlias = createAliasResolver(aliasMap);
const hostFunctions = properties.map(property =>
serializePropertyIntoHostFunction(
moduleName,
hasteModuleName,
property,
resolveAlias,
enumMap,
),
);
return ModuleTemplate({
hasteModuleName,
hostFunctions,
moduleName,
methods: properties.map(
({name: propertyName, typeAnnotation: nullableTypeAnnotation}) => {
const _unwrapNullable5 = unwrapNullable(nullableTypeAnnotation),
_unwrapNullable6 = _slicedToArray(_unwrapNullable5, 1),
params = _unwrapNullable6[0].params;
return {
methodName: propertyName,
paramCount: params.length,
};
},
),
});
})
.join('\n');
const fileName = `${libraryName}JSI-generated.cpp`;
const replacedTemplate = FileTemplate({
modules,
libraryName,
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,285 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {
NamedShape,
NativeModuleEnumMap,
NativeModuleFunctionTypeAnnotation,
NativeModuleParamTypeAnnotation,
NativeModulePropertyShape,
NativeModuleTypeAnnotation,
Nullable,
SchemaType,
} from '../../CodegenSchema';
import type {AliasResolver} from './Utils';
const {unwrapNullable} = require('../../parsers/parsers-commons');
const {createAliasResolver, getModules} = require('./Utils');
type FilesOutput = Map<string, string>;
const HostFunctionTemplate = ({
hasteModuleName,
methodName,
returnTypeAnnotation,
args,
}: $ReadOnly<{
hasteModuleName: string,
methodName: string,
returnTypeAnnotation: Nullable<NativeModuleTypeAnnotation>,
args: Array<string>,
}>) => {
const isNullable = returnTypeAnnotation.type === 'NullableTypeAnnotation';
const isVoid = returnTypeAnnotation.type === 'VoidTypeAnnotation';
const methodCallArgs = [' rt', ...args].join(',\n ');
const methodCall = `static_cast<${hasteModuleName}CxxSpecJSI *>(&turboModule)->${methodName}(\n${methodCallArgs}\n )`;
return `static jsi::Value __hostFunction_${hasteModuleName}CxxSpecJSI_${methodName}(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {${
isVoid
? `\n ${methodCall};`
: isNullable
? `\n auto result = ${methodCall};`
: ''
}
return ${
isVoid
? 'jsi::Value::undefined()'
: isNullable
? 'result ? jsi::Value(std::move(*result)) : jsi::Value::null()'
: methodCall
};
}`;
};
const ModuleTemplate = ({
hasteModuleName,
hostFunctions,
moduleName,
methods,
}: $ReadOnly<{
hasteModuleName: string,
hostFunctions: $ReadOnlyArray<string>,
moduleName: string,
methods: $ReadOnlyArray<$ReadOnly<{methodName: string, paramCount: number}>>,
}>) => {
return `${hostFunctions.join('\n')}
${hasteModuleName}CxxSpecJSI::${hasteModuleName}CxxSpecJSI(std::shared_ptr<CallInvoker> jsInvoker)
: TurboModule("${moduleName}", jsInvoker) {
${methods
.map(({methodName, paramCount}) => {
return ` methodMap_["${methodName}"] = MethodMetadata {${paramCount}, __hostFunction_${hasteModuleName}CxxSpecJSI_${methodName}};`;
})
.join('\n')}
}`;
};
const FileTemplate = ({
libraryName,
modules,
}: $ReadOnly<{
libraryName: string,
modules: string,
}>) => {
return `/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GenerateModuleCpp.js
*/
#include "${libraryName}JSI.h"
namespace facebook::react {
${modules}
} // namespace facebook::react
`;
};
type Param = NamedShape<Nullable<NativeModuleParamTypeAnnotation>>;
function serializeArg(
moduleName: string,
arg: Param,
index: number,
resolveAlias: AliasResolver,
enumMap: NativeModuleEnumMap,
): string {
const {typeAnnotation: nullableTypeAnnotation, optional} = arg;
const [typeAnnotation, nullable] =
unwrapNullable<NativeModuleParamTypeAnnotation>(nullableTypeAnnotation);
const isRequired = !optional && !nullable;
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
function wrap(callback: (val: string) => string) {
const val = `args[${index}]`;
const expression = callback(val);
if (isRequired) {
return expression;
} else {
let condition = `${val}.isNull() || ${val}.isUndefined()`;
if (optional) {
condition = `count <= ${index} || ${condition}`;
}
return `${condition} ? std::nullopt : std::make_optional(${expression})`;
}
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return wrap(val => `${val}.getNumber()`);
default:
(realTypeAnnotation.name: empty);
throw new Error(
`Unknown prop type for "${arg.name}, found: ${realTypeAnnotation.name}"`,
);
}
case 'StringTypeAnnotation':
return wrap(val => `${val}.asString(rt)`);
case 'BooleanTypeAnnotation':
return wrap(val => `${val}.asBool()`);
case 'EnumDeclaration':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrap(val => `${val}.asNumber()`);
case 'StringTypeAnnotation':
return wrap(val => `${val}.asString(rt)`);
default:
throw new Error(
`Unknown enum type for "${arg.name}, found: ${realTypeAnnotation.type}"`,
);
}
case 'NumberTypeAnnotation':
return wrap(val => `${val}.asNumber()`);
case 'FloatTypeAnnotation':
return wrap(val => `${val}.asNumber()`);
case 'DoubleTypeAnnotation':
return wrap(val => `${val}.asNumber()`);
case 'Int32TypeAnnotation':
return wrap(val => `${val}.asNumber()`);
case 'ArrayTypeAnnotation':
return wrap(val => `${val}.asObject(rt).asArray(rt)`);
case 'FunctionTypeAnnotation':
return wrap(val => `${val}.asObject(rt).asFunction(rt)`);
case 'GenericObjectTypeAnnotation':
return wrap(val => `${val}.asObject(rt)`);
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrap(val => `${val}.asNumber()`);
case 'ObjectTypeAnnotation':
return wrap(val => `${val}.asObject(rt)`);
case 'StringTypeAnnotation':
return wrap(val => `${val}.asString(rt)`);
default:
throw new Error(
`Unsupported union member type for param "${arg.name}, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'ObjectTypeAnnotation':
return wrap(val => `${val}.asObject(rt)`);
case 'MixedTypeAnnotation':
return wrap(val => `jsi::Value(rt, ${val})`);
default:
(realTypeAnnotation.type: empty);
throw new Error(
`Unknown prop type for "${arg.name}, found: ${realTypeAnnotation.type}"`,
);
}
}
function serializePropertyIntoHostFunction(
moduleName: string,
hasteModuleName: string,
property: NativeModulePropertyShape,
resolveAlias: AliasResolver,
enumMap: NativeModuleEnumMap,
): string {
const [propertyTypeAnnotation] =
unwrapNullable<NativeModuleFunctionTypeAnnotation>(property.typeAnnotation);
return HostFunctionTemplate({
hasteModuleName,
methodName: property.name,
returnTypeAnnotation: propertyTypeAnnotation.returnTypeAnnotation,
args: propertyTypeAnnotation.params.map((p, i) =>
serializeArg(moduleName, p, i, resolveAlias, enumMap),
),
});
}
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
): FilesOutput {
const nativeModules = getModules(schema);
const modules = Object.keys(nativeModules)
.map((hasteModuleName: string) => {
const nativeModule = nativeModules[hasteModuleName];
const {
aliasMap,
enumMap,
spec: {properties},
moduleName,
} = nativeModule;
const resolveAlias = createAliasResolver(aliasMap);
const hostFunctions = properties.map(property =>
serializePropertyIntoHostFunction(
moduleName,
hasteModuleName,
property,
resolveAlias,
enumMap,
),
);
return ModuleTemplate({
hasteModuleName,
hostFunctions,
moduleName,
methods: properties.map(
({name: propertyName, typeAnnotation: nullableTypeAnnotation}) => {
const [{params}] = unwrapNullable(nullableTypeAnnotation);
return {
methodName: propertyName,
paramCount: params.length,
};
},
),
});
})
.join('\n');
const fileName = `${libraryName}JSI-generated.cpp`;
const replacedTemplate = FileTemplate({
modules,
libraryName,
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,656 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
function _slicedToArray(arr, i) {
return (
_arrayWithHoles(arr) ||
_iterableToArrayLimit(arr, i) ||
_unsupportedIterableToArray(arr, i) ||
_nonIterableRest()
);
}
function _nonIterableRest() {
throw new TypeError(
'Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.',
);
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === 'string') return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === 'Object' && o.constructor) n = o.constructor.name;
if (n === 'Map' || n === 'Set') return Array.from(o);
if (n === 'Arguments' || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))
return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _iterableToArrayLimit(arr, i) {
var _i =
null == arr
? null
: ('undefined' != typeof Symbol && arr[Symbol.iterator]) ||
arr['@@iterator'];
if (null != _i) {
var _s,
_e,
_x,
_r,
_arr = [],
_n = !0,
_d = !1;
try {
if (((_x = (_i = _i.call(arr)).next), 0 === i)) {
if (Object(_i) !== _i) return;
_n = !1;
} else
for (
;
!(_n = (_s = _x.call(_i)).done) &&
(_arr.push(_s.value), _arr.length !== i);
_n = !0
);
} catch (err) {
(_d = !0), (_e = err);
} finally {
try {
if (!_n && null != _i.return && ((_r = _i.return()), Object(_r) !== _r))
return;
} finally {
if (_d) throw _e;
}
}
return _arr;
}
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
const _require = require('../../parsers/parsers-commons'),
unwrapNullable = _require.unwrapNullable;
const _require2 = require('../TypeUtils/Cxx'),
wrapOptional = _require2.wrapOptional;
const _require3 = require('../Utils'),
getEnumName = _require3.getEnumName,
toSafeCppString = _require3.toSafeCppString;
const _require4 = require('../Utils'),
indent = _require4.indent;
const _require5 = require('./Utils'),
createAliasResolver = _require5.createAliasResolver,
getAreEnumMembersInteger = _require5.getAreEnumMembersInteger,
getModules = _require5.getModules,
isArrayRecursiveMember = _require5.isArrayRecursiveMember,
isDirectRecursiveMember = _require5.isDirectRecursiveMember;
const ModuleClassDeclarationTemplate = ({
hasteModuleName,
moduleProperties,
structs,
enums,
}) => {
return `${enums}
${structs}class JSI_EXPORT ${hasteModuleName}CxxSpecJSI : public TurboModule {
protected:
${hasteModuleName}CxxSpecJSI(std::shared_ptr<CallInvoker> jsInvoker);
public:
${indent(moduleProperties.join('\n'), 2)}
};`;
};
const ModuleSpecClassDeclarationTemplate = ({
hasteModuleName,
moduleName,
moduleProperties,
}) => {
return `template <typename T>
class JSI_EXPORT ${hasteModuleName}CxxSpec : public TurboModule {
public:
jsi::Value get(jsi::Runtime &rt, const jsi::PropNameID &propName) override {
return delegate_.get(rt, propName);
}
static constexpr std::string_view kModuleName = "${moduleName}";
protected:
${hasteModuleName}CxxSpec(std::shared_ptr<CallInvoker> jsInvoker)
: TurboModule(std::string{${hasteModuleName}CxxSpec::kModuleName}, jsInvoker),
delegate_(reinterpret_cast<T*>(this), jsInvoker) {}
private:
class Delegate : public ${hasteModuleName}CxxSpecJSI {
public:
Delegate(T *instance, std::shared_ptr<CallInvoker> jsInvoker) :
${hasteModuleName}CxxSpecJSI(std::move(jsInvoker)), instance_(instance) {}
${indent(moduleProperties.join('\n'), 4)}
private:
T *instance_;
};
Delegate delegate_;
};`;
};
const FileTemplate = ({modules}) => {
return `/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GenerateModuleH.js
*/
#pragma once
#include <ReactCommon/TurboModule.h>
#include <react/bridging/Bridging.h>
namespace facebook::react {
${modules.join('\n\n')}
} // namespace facebook::react
`;
};
function translatePrimitiveJSTypeToCpp(
moduleName,
parentObjectAliasName,
nullableTypeAnnotation,
optional,
createErrorMessage,
resolveAlias,
enumMap,
) {
const _unwrapNullable = unwrapNullable(nullableTypeAnnotation),
_unwrapNullable2 = _slicedToArray(_unwrapNullable, 2),
typeAnnotation = _unwrapNullable2[0],
nullable = _unwrapNullable2[1];
const isRecursiveType = isDirectRecursiveMember(
parentObjectAliasName,
nullableTypeAnnotation,
);
const isRequired = (!optional && !nullable) || isRecursiveType;
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return wrapOptional('double', isRequired);
default:
realTypeAnnotation.name;
throw new Error(createErrorMessage(realTypeAnnotation.name));
}
case 'VoidTypeAnnotation':
return 'void';
case 'StringTypeAnnotation':
return wrapOptional('jsi::String', isRequired);
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'DoubleTypeAnnotation':
return wrapOptional('double', isRequired);
case 'FloatTypeAnnotation':
return wrapOptional('double', isRequired);
case 'Int32TypeAnnotation':
return wrapOptional('int', isRequired);
case 'BooleanTypeAnnotation':
return wrapOptional('bool', isRequired);
case 'EnumDeclaration':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('jsi::Value', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('jsi::String', isRequired);
default:
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
case 'GenericObjectTypeAnnotation':
return wrapOptional('jsi::Object', isRequired);
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'ObjectTypeAnnotation':
return wrapOptional('jsi::Object', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('jsi::String', isRequired);
default:
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
case 'ObjectTypeAnnotation':
return wrapOptional('jsi::Object', isRequired);
case 'ArrayTypeAnnotation':
return wrapOptional('jsi::Array', isRequired);
case 'FunctionTypeAnnotation':
return wrapOptional('jsi::Function', isRequired);
case 'PromiseTypeAnnotation':
return wrapOptional('jsi::Value', isRequired);
case 'MixedTypeAnnotation':
return wrapOptional('jsi::Value', isRequired);
default:
realTypeAnnotation.type;
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
}
function createStructsString(moduleName, aliasMap, resolveAlias, enumMap) {
const getCppType = (parentObjectAlias, v) =>
translatePrimitiveJSTypeToCpp(
moduleName,
parentObjectAlias,
v.typeAnnotation,
false,
typeName => `Unsupported type for param "${v.name}". Found: ${typeName}`,
resolveAlias,
enumMap,
);
// TODO: T171006733 [Begin] Remove deprecated Cxx TMs structs after a new release.
return (
Object.keys(aliasMap)
.map(alias => {
const value = aliasMap[alias];
if (value.properties.length === 0) {
return '';
}
const structName = `${moduleName}Base${alias}`;
const structNameNew = `${moduleName}${alias}`;
const templateParameterWithTypename = value.properties
.map((v, i) => `typename P${i}`)
.join(', ');
const templateParameter = value.properties
.map((v, i) => 'P' + i)
.join(', ');
const debugParameterConversion = value.properties
.map(
(v, i) => ` static ${getCppType(alias, v)} ${
v.name
}ToJs(jsi::Runtime &rt, P${i} value) {
return bridging::toJs(rt, value);
}`,
)
.join('\n\n');
return `
#pragma mark - ${structName}
template <${templateParameterWithTypename}>
struct [[deprecated("Use ${structNameNew} instead.")]] ${structName} {
${value.properties.map((v, i) => ' P' + i + ' ' + v.name).join(';\n')};
bool operator==(const ${structName} &other) const {
return ${value.properties
.map(v => `${v.name} == other.${v.name}`)
.join(' && ')};
}
};
template <${templateParameterWithTypename}>
struct [[deprecated("Use ${structNameNew}Bridging instead.")]] ${structName}Bridging {
static ${structName}<${templateParameter}> fromJs(
jsi::Runtime &rt,
const jsi::Object &value,
const std::shared_ptr<CallInvoker> &jsInvoker) {
${structName}<${templateParameter}> result{
${value.properties
.map(
(v, i) =>
` bridging::fromJs<P${i}>(rt, value.getProperty(rt, "${v.name}"), jsInvoker)`,
)
.join(',\n')}};
return result;
}
#ifdef DEBUG
${debugParameterConversion}
#endif
static jsi::Object toJs(
jsi::Runtime &rt,
const ${structName}<${templateParameter}> &value,
const std::shared_ptr<CallInvoker> &jsInvoker) {
auto result = facebook::jsi::Object(rt);
${value.properties
.map((v, i) => {
if (v.optional) {
return ` if (value.${v.name}) {
result.setProperty(rt, "${v.name}", bridging::toJs(rt, value.${v.name}.value(), jsInvoker));
}`;
} else {
return ` result.setProperty(rt, "${v.name}", bridging::toJs(rt, value.${v.name}, jsInvoker));`;
}
})
.join('\n')}
return result;
}
};
`;
})
.join('\n') +
// TODO: T171006733 [End] Remove deprecated Cxx TMs structs after a new release.
Object.keys(aliasMap)
.map(alias => {
const value = aliasMap[alias];
if (value.properties.length === 0) {
return '';
}
const structName = `${moduleName}${alias}`;
const templateParameter = value.properties.filter(
v =>
!isDirectRecursiveMember(alias, v.typeAnnotation) &&
!isArrayRecursiveMember(alias, v.typeAnnotation),
);
const templateParameterWithTypename = templateParameter
.map((v, i) => `typename P${i}`)
.join(', ');
const templateParameterWithoutTypename = templateParameter
.map((v, i) => `P${i}`)
.join(', ');
let i = -1;
const templateMemberTypes = value.properties.map(v => {
if (isDirectRecursiveMember(alias, v.typeAnnotation)) {
return `std::unique_ptr<${structName}<${templateParameterWithoutTypename}>> ${v.name}`;
} else if (isArrayRecursiveMember(alias, v.typeAnnotation)) {
const _unwrapNullable3 = unwrapNullable(v.typeAnnotation),
_unwrapNullable4 = _slicedToArray(_unwrapNullable3, 1),
nullable = _unwrapNullable4[0];
return (
(nullable
? `std::optional<std::vector<${structName}<${templateParameterWithoutTypename}>>>`
: `std::vector<${structName}<${templateParameterWithoutTypename}>>`) +
` ${v.name}`
);
} else {
i++;
return `P${i} ${v.name}`;
}
});
const debugParameterConversion = value.properties
.map(
v => ` static ${getCppType(alias, v)} ${
v.name
}ToJs(jsi::Runtime &rt, decltype(types.${v.name}) value) {
return bridging::toJs(rt, value);
}`,
)
.join('\n\n');
return `
#pragma mark - ${structName}
template <${templateParameterWithTypename}>
struct ${structName} {
${templateMemberTypes.map(v => ' ' + v).join(';\n')};
bool operator==(const ${structName} &other) const {
return ${value.properties
.map(v => `${v.name} == other.${v.name}`)
.join(' && ')};
}
};
template <typename T>
struct ${structName}Bridging {
static T types;
static T fromJs(
jsi::Runtime &rt,
const jsi::Object &value,
const std::shared_ptr<CallInvoker> &jsInvoker) {
T result{
${value.properties
.map(v => {
if (isDirectRecursiveMember(alias, v.typeAnnotation)) {
return ` value.hasProperty(rt, "${v.name}") ? std::make_unique<T>(bridging::fromJs<T>(rt, value.getProperty(rt, "${v.name}"), jsInvoker)) : nullptr`;
} else {
return ` bridging::fromJs<decltype(types.${v.name})>(rt, value.getProperty(rt, "${v.name}"), jsInvoker)`;
}
})
.join(',\n')}};
return result;
}
#ifdef DEBUG
${debugParameterConversion}
#endif
static jsi::Object toJs(
jsi::Runtime &rt,
const T &value,
const std::shared_ptr<CallInvoker> &jsInvoker) {
auto result = facebook::jsi::Object(rt);
${value.properties
.map(v => {
if (isDirectRecursiveMember(alias, v.typeAnnotation)) {
return ` if (value.${v.name}) {
result.setProperty(rt, "${v.name}", bridging::toJs(rt, *value.${v.name}, jsInvoker));
}`;
} else if (v.optional) {
return ` if (value.${v.name}) {
result.setProperty(rt, "${v.name}", bridging::toJs(rt, value.${v.name}.value(), jsInvoker));
}`;
} else {
return ` result.setProperty(rt, "${v.name}", bridging::toJs(rt, value.${v.name}, jsInvoker));`;
}
})
.join('\n')}
return result;
}
};
`;
})
.join('\n')
);
}
const EnumTemplate = ({
enumName,
values,
fromCases,
toCases,
nativeEnumMemberType,
}) => {
const _ref =
nativeEnumMemberType === 'std::string'
? [
'const jsi::String &rawValue',
'std::string value = rawValue.utf8(rt);',
'jsi::String',
]
: [
'const jsi::Value &rawValue',
'double value = (double)rawValue.asNumber();',
'jsi::Value',
],
_ref2 = _slicedToArray(_ref, 3),
fromValue = _ref2[0],
fromValueConversion = _ref2[1],
toValue = _ref2[2];
return `
#pragma mark - ${enumName}
enum class ${enumName} { ${values} };
template <>
struct Bridging<${enumName}> {
static ${enumName} fromJs(jsi::Runtime &rt, ${fromValue}) {
${fromValueConversion}
${fromCases}
}
static ${toValue} toJs(jsi::Runtime &rt, ${enumName} value) {
${toCases}
}
};`;
};
function generateEnum(moduleName, origEnumName, members, memberType) {
const enumName = getEnumName(moduleName, origEnumName);
const nativeEnumMemberType =
memberType === 'StringTypeAnnotation'
? 'std::string'
: getAreEnumMembersInteger(members)
? 'int32_t'
: 'float';
const getMemberValueAppearance = value =>
memberType === 'StringTypeAnnotation'
? `"${value}"`
: `${value}${nativeEnumMemberType === 'float' ? 'f' : ''}`;
const fromCases =
members
.map(
member => `if (value == ${getMemberValueAppearance(member.value)}) {
return ${enumName}::${toSafeCppString(member.name)};
}`,
)
.join(' else ') +
` else {
throw jsi::JSError(rt, "No appropriate enum member found for value");
}`;
const toCases =
members
.map(
member => `if (value == ${enumName}::${toSafeCppString(member.name)}) {
return bridging::toJs(rt, ${getMemberValueAppearance(member.value)});
}`,
)
.join(' else ') +
` else {
throw jsi::JSError(rt, "No appropriate enum member found for enum value");
}`;
return EnumTemplate({
enumName,
values: members.map(member => member.name).join(', '),
fromCases,
toCases,
nativeEnumMemberType,
});
}
function createEnums(moduleName, enumMap, resolveAlias) {
return Object.entries(enumMap)
.map(([enumName, enumNode]) => {
return generateEnum(
moduleName,
enumName,
enumNode.members,
enumNode.memberType,
);
})
.filter(Boolean)
.join('\n');
}
function translatePropertyToCpp(
moduleName,
prop,
resolveAlias,
enumMap,
abstract = false,
) {
const _unwrapNullable5 = unwrapNullable(prop.typeAnnotation),
_unwrapNullable6 = _slicedToArray(_unwrapNullable5, 1),
propTypeAnnotation = _unwrapNullable6[0];
const params = propTypeAnnotation.params.map(
param => `std::move(${param.name})`,
);
const paramTypes = propTypeAnnotation.params.map(param => {
const translatedParam = translatePrimitiveJSTypeToCpp(
moduleName,
null,
param.typeAnnotation,
param.optional,
typeName =>
`Unsupported type for param "${param.name}" in ${prop.name}. Found: ${typeName}`,
resolveAlias,
enumMap,
);
return `${translatedParam} ${param.name}`;
});
const returnType = translatePrimitiveJSTypeToCpp(
moduleName,
null,
propTypeAnnotation.returnTypeAnnotation,
false,
typeName => `Unsupported return type for ${prop.name}. Found: ${typeName}`,
resolveAlias,
enumMap,
);
// The first param will always be the runtime reference.
paramTypes.unshift('jsi::Runtime &rt');
const method = `${returnType} ${prop.name}(${paramTypes.join(', ')})`;
if (abstract) {
return `virtual ${method} = 0;`;
}
return `${method} override {
static_assert(
bridging::getParameterCount(&T::${prop.name}) == ${paramTypes.length},
"Expected ${prop.name}(...) to have ${paramTypes.length} parameters");
return bridging::callFromJs<${returnType}>(
rt, &T::${prop.name}, jsInvoker_, ${['instance_', ...params].join(', ')});
}`;
}
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
) {
const nativeModules = getModules(schema);
const modules = Object.keys(nativeModules).flatMap(hasteModuleName => {
const _nativeModules$hasteM = nativeModules[hasteModuleName],
aliasMap = _nativeModules$hasteM.aliasMap,
enumMap = _nativeModules$hasteM.enumMap,
properties = _nativeModules$hasteM.spec.properties,
moduleName = _nativeModules$hasteM.moduleName;
const resolveAlias = createAliasResolver(aliasMap);
const structs = createStructsString(
moduleName,
aliasMap,
resolveAlias,
enumMap,
);
const enums = createEnums(moduleName, enumMap, resolveAlias);
return [
ModuleClassDeclarationTemplate({
hasteModuleName,
moduleProperties: properties.map(prop =>
translatePropertyToCpp(
moduleName,
prop,
resolveAlias,
enumMap,
true,
),
),
structs,
enums,
}),
ModuleSpecClassDeclarationTemplate({
hasteModuleName,
moduleName,
moduleProperties: properties.map(prop =>
translatePropertyToCpp(moduleName, prop, resolveAlias, enumMap),
),
}),
];
});
const fileName = `${libraryName}JSI.h`;
const replacedTemplate = FileTemplate({
modules,
});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,660 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {
NamedShape,
NativeModuleBaseTypeAnnotation,
} from '../../CodegenSchema';
import type {
NativeModuleAliasMap,
NativeModuleEnumMap,
NativeModuleEnumMembers,
NativeModuleEnumMemberType,
NativeModuleFunctionTypeAnnotation,
NativeModulePropertyShape,
NativeModuleTypeAnnotation,
Nullable,
SchemaType,
} from '../../CodegenSchema';
import type {AliasResolver} from './Utils';
const {unwrapNullable} = require('../../parsers/parsers-commons');
const {wrapOptional} = require('../TypeUtils/Cxx');
const {getEnumName, toSafeCppString} = require('../Utils');
const {indent} = require('../Utils');
const {
createAliasResolver,
getAreEnumMembersInteger,
getModules,
isArrayRecursiveMember,
isDirectRecursiveMember,
} = require('./Utils');
type FilesOutput = Map<string, string>;
const ModuleClassDeclarationTemplate = ({
hasteModuleName,
moduleProperties,
structs,
enums,
}: $ReadOnly<{
hasteModuleName: string,
moduleProperties: string[],
structs: string,
enums: string,
}>) => {
return `${enums}
${structs}class JSI_EXPORT ${hasteModuleName}CxxSpecJSI : public TurboModule {
protected:
${hasteModuleName}CxxSpecJSI(std::shared_ptr<CallInvoker> jsInvoker);
public:
${indent(moduleProperties.join('\n'), 2)}
};`;
};
const ModuleSpecClassDeclarationTemplate = ({
hasteModuleName,
moduleName,
moduleProperties,
}: $ReadOnly<{
hasteModuleName: string,
moduleName: string,
moduleProperties: string[],
}>) => {
return `template <typename T>
class JSI_EXPORT ${hasteModuleName}CxxSpec : public TurboModule {
public:
jsi::Value get(jsi::Runtime &rt, const jsi::PropNameID &propName) override {
return delegate_.get(rt, propName);
}
static constexpr std::string_view kModuleName = "${moduleName}";
protected:
${hasteModuleName}CxxSpec(std::shared_ptr<CallInvoker> jsInvoker)
: TurboModule(std::string{${hasteModuleName}CxxSpec::kModuleName}, jsInvoker),
delegate_(reinterpret_cast<T*>(this), jsInvoker) {}
private:
class Delegate : public ${hasteModuleName}CxxSpecJSI {
public:
Delegate(T *instance, std::shared_ptr<CallInvoker> jsInvoker) :
${hasteModuleName}CxxSpecJSI(std::move(jsInvoker)), instance_(instance) {}
${indent(moduleProperties.join('\n'), 4)}
private:
T *instance_;
};
Delegate delegate_;
};`;
};
const FileTemplate = ({
modules,
}: $ReadOnly<{
modules: string[],
}>) => {
return `/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GenerateModuleH.js
*/
#pragma once
#include <ReactCommon/TurboModule.h>
#include <react/bridging/Bridging.h>
namespace facebook::react {
${modules.join('\n\n')}
} // namespace facebook::react
`;
};
function translatePrimitiveJSTypeToCpp(
moduleName: string,
parentObjectAliasName: ?string,
nullableTypeAnnotation: Nullable<NativeModuleTypeAnnotation>,
optional: boolean,
createErrorMessage: (typeName: string) => string,
resolveAlias: AliasResolver,
enumMap: NativeModuleEnumMap,
) {
const [typeAnnotation, nullable] = unwrapNullable<NativeModuleTypeAnnotation>(
nullableTypeAnnotation,
);
const isRecursiveType = isDirectRecursiveMember(
parentObjectAliasName,
nullableTypeAnnotation,
);
const isRequired = (!optional && !nullable) || isRecursiveType;
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return wrapOptional('double', isRequired);
default:
(realTypeAnnotation.name: empty);
throw new Error(createErrorMessage(realTypeAnnotation.name));
}
case 'VoidTypeAnnotation':
return 'void';
case 'StringTypeAnnotation':
return wrapOptional('jsi::String', isRequired);
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'DoubleTypeAnnotation':
return wrapOptional('double', isRequired);
case 'FloatTypeAnnotation':
return wrapOptional('double', isRequired);
case 'Int32TypeAnnotation':
return wrapOptional('int', isRequired);
case 'BooleanTypeAnnotation':
return wrapOptional('bool', isRequired);
case 'EnumDeclaration':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('jsi::Value', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('jsi::String', isRequired);
default:
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
case 'GenericObjectTypeAnnotation':
return wrapOptional('jsi::Object', isRequired);
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'ObjectTypeAnnotation':
return wrapOptional('jsi::Object', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('jsi::String', isRequired);
default:
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
case 'ObjectTypeAnnotation':
return wrapOptional('jsi::Object', isRequired);
case 'ArrayTypeAnnotation':
return wrapOptional('jsi::Array', isRequired);
case 'FunctionTypeAnnotation':
return wrapOptional('jsi::Function', isRequired);
case 'PromiseTypeAnnotation':
return wrapOptional('jsi::Value', isRequired);
case 'MixedTypeAnnotation':
return wrapOptional('jsi::Value', isRequired);
default:
(realTypeAnnotation.type: empty);
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
}
function createStructsString(
moduleName: string,
aliasMap: NativeModuleAliasMap,
resolveAlias: AliasResolver,
enumMap: NativeModuleEnumMap,
): string {
const getCppType = (
parentObjectAlias: string,
v: NamedShape<Nullable<NativeModuleBaseTypeAnnotation>>,
) =>
translatePrimitiveJSTypeToCpp(
moduleName,
parentObjectAlias,
v.typeAnnotation,
false,
typeName => `Unsupported type for param "${v.name}". Found: ${typeName}`,
resolveAlias,
enumMap,
);
// TODO: T171006733 [Begin] Remove deprecated Cxx TMs structs after a new release.
return (
Object.keys(aliasMap)
.map(alias => {
const value = aliasMap[alias];
if (value.properties.length === 0) {
return '';
}
const structName = `${moduleName}Base${alias}`;
const structNameNew = `${moduleName}${alias}`;
const templateParameterWithTypename = value.properties
.map((v, i) => `typename P${i}`)
.join(', ');
const templateParameter = value.properties
.map((v, i) => 'P' + i)
.join(', ');
const debugParameterConversion = value.properties
.map(
(v, i) => ` static ${getCppType(alias, v)} ${
v.name
}ToJs(jsi::Runtime &rt, P${i} value) {
return bridging::toJs(rt, value);
}`,
)
.join('\n\n');
return `
#pragma mark - ${structName}
template <${templateParameterWithTypename}>
struct [[deprecated("Use ${structNameNew} instead.")]] ${structName} {
${value.properties.map((v, i) => ' P' + i + ' ' + v.name).join(';\n')};
bool operator==(const ${structName} &other) const {
return ${value.properties
.map(v => `${v.name} == other.${v.name}`)
.join(' && ')};
}
};
template <${templateParameterWithTypename}>
struct [[deprecated("Use ${structNameNew}Bridging instead.")]] ${structName}Bridging {
static ${structName}<${templateParameter}> fromJs(
jsi::Runtime &rt,
const jsi::Object &value,
const std::shared_ptr<CallInvoker> &jsInvoker) {
${structName}<${templateParameter}> result{
${value.properties
.map(
(v, i) =>
` bridging::fromJs<P${i}>(rt, value.getProperty(rt, "${v.name}"), jsInvoker)`,
)
.join(',\n')}};
return result;
}
#ifdef DEBUG
${debugParameterConversion}
#endif
static jsi::Object toJs(
jsi::Runtime &rt,
const ${structName}<${templateParameter}> &value,
const std::shared_ptr<CallInvoker> &jsInvoker) {
auto result = facebook::jsi::Object(rt);
${value.properties
.map((v, i) => {
if (v.optional) {
return ` if (value.${v.name}) {
result.setProperty(rt, "${v.name}", bridging::toJs(rt, value.${v.name}.value(), jsInvoker));
}`;
} else {
return ` result.setProperty(rt, "${v.name}", bridging::toJs(rt, value.${v.name}, jsInvoker));`;
}
})
.join('\n')}
return result;
}
};
`;
})
.join('\n') +
// TODO: T171006733 [End] Remove deprecated Cxx TMs structs after a new release.
Object.keys(aliasMap)
.map(alias => {
const value = aliasMap[alias];
if (value.properties.length === 0) {
return '';
}
const structName = `${moduleName}${alias}`;
const templateParameter = value.properties.filter(
v =>
!isDirectRecursiveMember(alias, v.typeAnnotation) &&
!isArrayRecursiveMember(alias, v.typeAnnotation),
);
const templateParameterWithTypename = templateParameter
.map((v, i) => `typename P${i}`)
.join(', ');
const templateParameterWithoutTypename = templateParameter
.map((v, i) => `P${i}`)
.join(', ');
let i = -1;
const templateMemberTypes = value.properties.map(v => {
if (isDirectRecursiveMember(alias, v.typeAnnotation)) {
return `std::unique_ptr<${structName}<${templateParameterWithoutTypename}>> ${v.name}`;
} else if (isArrayRecursiveMember(alias, v.typeAnnotation)) {
const [nullable] = unwrapNullable<NativeModuleTypeAnnotation>(
v.typeAnnotation,
);
return (
(nullable
? `std::optional<std::vector<${structName}<${templateParameterWithoutTypename}>>>`
: `std::vector<${structName}<${templateParameterWithoutTypename}>>`) +
` ${v.name}`
);
} else {
i++;
return `P${i} ${v.name}`;
}
});
const debugParameterConversion = value.properties
.map(
v => ` static ${getCppType(alias, v)} ${
v.name
}ToJs(jsi::Runtime &rt, decltype(types.${v.name}) value) {
return bridging::toJs(rt, value);
}`,
)
.join('\n\n');
return `
#pragma mark - ${structName}
template <${templateParameterWithTypename}>
struct ${structName} {
${templateMemberTypes.map(v => ' ' + v).join(';\n')};
bool operator==(const ${structName} &other) const {
return ${value.properties
.map(v => `${v.name} == other.${v.name}`)
.join(' && ')};
}
};
template <typename T>
struct ${structName}Bridging {
static T types;
static T fromJs(
jsi::Runtime &rt,
const jsi::Object &value,
const std::shared_ptr<CallInvoker> &jsInvoker) {
T result{
${value.properties
.map(v => {
if (isDirectRecursiveMember(alias, v.typeAnnotation)) {
return ` value.hasProperty(rt, "${v.name}") ? std::make_unique<T>(bridging::fromJs<T>(rt, value.getProperty(rt, "${v.name}"), jsInvoker)) : nullptr`;
} else {
return ` bridging::fromJs<decltype(types.${v.name})>(rt, value.getProperty(rt, "${v.name}"), jsInvoker)`;
}
})
.join(',\n')}};
return result;
}
#ifdef DEBUG
${debugParameterConversion}
#endif
static jsi::Object toJs(
jsi::Runtime &rt,
const T &value,
const std::shared_ptr<CallInvoker> &jsInvoker) {
auto result = facebook::jsi::Object(rt);
${value.properties
.map(v => {
if (isDirectRecursiveMember(alias, v.typeAnnotation)) {
return ` if (value.${v.name}) {
result.setProperty(rt, "${v.name}", bridging::toJs(rt, *value.${v.name}, jsInvoker));
}`;
} else if (v.optional) {
return ` if (value.${v.name}) {
result.setProperty(rt, "${v.name}", bridging::toJs(rt, value.${v.name}.value(), jsInvoker));
}`;
} else {
return ` result.setProperty(rt, "${v.name}", bridging::toJs(rt, value.${v.name}, jsInvoker));`;
}
})
.join('\n')}
return result;
}
};
`;
})
.join('\n')
);
}
type NativeEnumMemberValueType = 'std::string' | 'int32_t' | 'float';
const EnumTemplate = ({
enumName,
values,
fromCases,
toCases,
nativeEnumMemberType,
}: {
enumName: string,
values: string,
fromCases: string,
toCases: string,
nativeEnumMemberType: NativeEnumMemberValueType,
}) => {
const [fromValue, fromValueConversion, toValue] =
nativeEnumMemberType === 'std::string'
? [
'const jsi::String &rawValue',
'std::string value = rawValue.utf8(rt);',
'jsi::String',
]
: [
'const jsi::Value &rawValue',
'double value = (double)rawValue.asNumber();',
'jsi::Value',
];
return `
#pragma mark - ${enumName}
enum class ${enumName} { ${values} };
template <>
struct Bridging<${enumName}> {
static ${enumName} fromJs(jsi::Runtime &rt, ${fromValue}) {
${fromValueConversion}
${fromCases}
}
static ${toValue} toJs(jsi::Runtime &rt, ${enumName} value) {
${toCases}
}
};`;
};
function generateEnum(
moduleName: string,
origEnumName: string,
members: NativeModuleEnumMembers,
memberType: NativeModuleEnumMemberType,
): string {
const enumName = getEnumName(moduleName, origEnumName);
const nativeEnumMemberType: NativeEnumMemberValueType =
memberType === 'StringTypeAnnotation'
? 'std::string'
: getAreEnumMembersInteger(members)
? 'int32_t'
: 'float';
const getMemberValueAppearance = (value: string) =>
memberType === 'StringTypeAnnotation'
? `"${value}"`
: `${value}${nativeEnumMemberType === 'float' ? 'f' : ''}`;
const fromCases =
members
.map(
member => `if (value == ${getMemberValueAppearance(member.value)}) {
return ${enumName}::${toSafeCppString(member.name)};
}`,
)
.join(' else ') +
` else {
throw jsi::JSError(rt, "No appropriate enum member found for value");
}`;
const toCases =
members
.map(
member => `if (value == ${enumName}::${toSafeCppString(member.name)}) {
return bridging::toJs(rt, ${getMemberValueAppearance(member.value)});
}`,
)
.join(' else ') +
` else {
throw jsi::JSError(rt, "No appropriate enum member found for enum value");
}`;
return EnumTemplate({
enumName,
values: members.map(member => member.name).join(', '),
fromCases,
toCases,
nativeEnumMemberType,
});
}
function createEnums(
moduleName: string,
enumMap: NativeModuleEnumMap,
resolveAlias: AliasResolver,
): string {
return Object.entries(enumMap)
.map(([enumName, enumNode]) => {
return generateEnum(
moduleName,
enumName,
enumNode.members,
enumNode.memberType,
);
})
.filter(Boolean)
.join('\n');
}
function translatePropertyToCpp(
moduleName: string,
prop: NativeModulePropertyShape,
resolveAlias: AliasResolver,
enumMap: NativeModuleEnumMap,
abstract: boolean = false,
) {
const [propTypeAnnotation] =
unwrapNullable<NativeModuleFunctionTypeAnnotation>(prop.typeAnnotation);
const params = propTypeAnnotation.params.map(
param => `std::move(${param.name})`,
);
const paramTypes = propTypeAnnotation.params.map(param => {
const translatedParam = translatePrimitiveJSTypeToCpp(
moduleName,
null,
param.typeAnnotation,
param.optional,
typeName =>
`Unsupported type for param "${param.name}" in ${prop.name}. Found: ${typeName}`,
resolveAlias,
enumMap,
);
return `${translatedParam} ${param.name}`;
});
const returnType = translatePrimitiveJSTypeToCpp(
moduleName,
null,
propTypeAnnotation.returnTypeAnnotation,
false,
typeName => `Unsupported return type for ${prop.name}. Found: ${typeName}`,
resolveAlias,
enumMap,
);
// The first param will always be the runtime reference.
paramTypes.unshift('jsi::Runtime &rt');
const method = `${returnType} ${prop.name}(${paramTypes.join(', ')})`;
if (abstract) {
return `virtual ${method} = 0;`;
}
return `${method} override {
static_assert(
bridging::getParameterCount(&T::${prop.name}) == ${paramTypes.length},
"Expected ${prop.name}(...) to have ${paramTypes.length} parameters");
return bridging::callFromJs<${returnType}>(
rt, &T::${prop.name}, jsInvoker_, ${['instance_', ...params].join(', ')});
}`;
}
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
): FilesOutput {
const nativeModules = getModules(schema);
const modules = Object.keys(nativeModules).flatMap(hasteModuleName => {
const {
aliasMap,
enumMap,
spec: {properties},
moduleName,
} = nativeModules[hasteModuleName];
const resolveAlias = createAliasResolver(aliasMap);
const structs = createStructsString(
moduleName,
aliasMap,
resolveAlias,
enumMap,
);
const enums = createEnums(moduleName, enumMap, resolveAlias);
return [
ModuleClassDeclarationTemplate({
hasteModuleName,
moduleProperties: properties.map(prop =>
translatePropertyToCpp(
moduleName,
prop,
resolveAlias,
enumMap,
true,
),
),
structs,
enums,
}),
ModuleSpecClassDeclarationTemplate({
hasteModuleName,
moduleName,
moduleProperties: properties.map(prop =>
translatePropertyToCpp(moduleName, prop, resolveAlias, enumMap),
),
}),
];
});
const fileName = `${libraryName}JSI.h`;
const replacedTemplate = FileTemplate({modules});
return new Map([[fileName, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,565 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
function _slicedToArray(arr, i) {
return (
_arrayWithHoles(arr) ||
_iterableToArrayLimit(arr, i) ||
_unsupportedIterableToArray(arr, i) ||
_nonIterableRest()
);
}
function _nonIterableRest() {
throw new TypeError(
'Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.',
);
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === 'string') return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === 'Object' && o.constructor) n = o.constructor.name;
if (n === 'Map' || n === 'Set') return Array.from(o);
if (n === 'Arguments' || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))
return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _iterableToArrayLimit(arr, i) {
var _i =
null == arr
? null
: ('undefined' != typeof Symbol && arr[Symbol.iterator]) ||
arr['@@iterator'];
if (null != _i) {
var _s,
_e,
_x,
_r,
_arr = [],
_n = !0,
_d = !1;
try {
if (((_x = (_i = _i.call(arr)).next), 0 === i)) {
if (Object(_i) !== _i) return;
_n = !1;
} else
for (
;
!(_n = (_s = _x.call(_i)).done) &&
(_arr.push(_s.value), _arr.length !== i);
_n = !0
);
} catch (err) {
(_d = !0), (_e = err);
} finally {
try {
if (!_n && null != _i.return && ((_r = _i.return()), Object(_r) !== _r))
return;
} finally {
if (_d) throw _e;
}
}
return _arr;
}
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
const _require = require('../../parsers/parsers-commons'),
unwrapNullable = _require.unwrapNullable;
const _require2 = require('../TypeUtils/Java'),
wrapOptional = _require2.wrapOptional;
const _require3 = require('./Utils'),
createAliasResolver = _require3.createAliasResolver,
getModules = _require3.getModules;
function FileTemplate(config) {
const packageName = config.packageName,
className = config.className,
jsName = config.jsName,
methods = config.methods,
imports = config.imports;
return `
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GenerateModuleJavaSpec.js
*
* ${'@'}nolint
*/
package ${packageName};
${imports}
public abstract class ${className} extends ReactContextBaseJavaModule implements TurboModule {
public static final String NAME = "${jsName}";
public ${className}(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public @Nonnull String getName() {
return NAME;
}
${methods}
}
`;
}
function MethodTemplate(config) {
const abstract = config.abstract,
methodBody = config.methodBody,
methodJavaAnnotation = config.methodJavaAnnotation,
methodName = config.methodName,
translatedReturnType = config.translatedReturnType,
traversedArgs = config.traversedArgs;
const methodQualifier = abstract ? 'abstract ' : '';
const methodClosing = abstract
? ';'
: methodBody != null && methodBody.length > 0
? ` { ${methodBody} }`
: ' {}';
return ` ${methodJavaAnnotation}
public ${methodQualifier}${translatedReturnType} ${methodName}(${traversedArgs.join(
', ',
)})${methodClosing}`;
}
function translateFunctionParamToJavaType(
param,
createErrorMessage,
resolveAlias,
imports,
) {
const optional = param.optional,
nullableTypeAnnotation = param.typeAnnotation;
const _unwrapNullable = unwrapNullable(nullableTypeAnnotation),
_unwrapNullable2 = _slicedToArray(_unwrapNullable, 2),
typeAnnotation = _unwrapNullable2[0],
nullable = _unwrapNullable2[1];
const isRequired = !optional && !nullable;
if (!isRequired) {
imports.add('javax.annotation.Nullable');
}
// FIXME: support class alias for args
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return wrapOptional('double', isRequired);
default:
realTypeAnnotation.name;
throw new Error(createErrorMessage(realTypeAnnotation.name));
}
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'FloatTypeAnnotation':
return wrapOptional('double', isRequired);
case 'DoubleTypeAnnotation':
return wrapOptional('double', isRequired);
case 'Int32TypeAnnotation':
return wrapOptional('double', isRequired);
case 'BooleanTypeAnnotation':
return wrapOptional('boolean', isRequired);
case 'EnumDeclaration':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
default:
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'ObjectTypeAnnotation':
imports.add('com.facebook.react.bridge.ReadableMap');
return wrapOptional('ReadableMap', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
default:
throw new Error(
`Unsupported union member returning value, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'ObjectTypeAnnotation':
imports.add('com.facebook.react.bridge.ReadableMap');
return wrapOptional('ReadableMap', isRequired);
case 'GenericObjectTypeAnnotation':
// Treat this the same as ObjectTypeAnnotation for now.
imports.add('com.facebook.react.bridge.ReadableMap');
return wrapOptional('ReadableMap', isRequired);
case 'ArrayTypeAnnotation':
imports.add('com.facebook.react.bridge.ReadableArray');
return wrapOptional('ReadableArray', isRequired);
case 'FunctionTypeAnnotation':
imports.add('com.facebook.react.bridge.Callback');
return wrapOptional('Callback', isRequired);
default:
realTypeAnnotation.type;
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
}
function translateFunctionReturnTypeToJavaType(
nullableReturnTypeAnnotation,
createErrorMessage,
resolveAlias,
imports,
) {
const _unwrapNullable3 = unwrapNullable(nullableReturnTypeAnnotation),
_unwrapNullable4 = _slicedToArray(_unwrapNullable3, 2),
returnTypeAnnotation = _unwrapNullable4[0],
nullable = _unwrapNullable4[1];
if (nullable) {
imports.add('javax.annotation.Nullable');
}
const isRequired = !nullable;
// FIXME: support class alias for args
let realTypeAnnotation = returnTypeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return wrapOptional('double', isRequired);
default:
realTypeAnnotation.name;
throw new Error(createErrorMessage(realTypeAnnotation.name));
}
case 'VoidTypeAnnotation':
return 'void';
case 'PromiseTypeAnnotation':
return 'void';
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'FloatTypeAnnotation':
return wrapOptional('double', isRequired);
case 'DoubleTypeAnnotation':
return wrapOptional('double', isRequired);
case 'Int32TypeAnnotation':
return wrapOptional('double', isRequired);
case 'BooleanTypeAnnotation':
return wrapOptional('boolean', isRequired);
case 'EnumDeclaration':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
default:
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
case 'UnionTypeAnnotation':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'ObjectTypeAnnotation':
imports.add('com.facebook.react.bridge.WritableMap');
return wrapOptional('WritableMap', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
default:
throw new Error(
`Unsupported union member returning value, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'ObjectTypeAnnotation':
imports.add('com.facebook.react.bridge.WritableMap');
return wrapOptional('WritableMap', isRequired);
case 'GenericObjectTypeAnnotation':
imports.add('com.facebook.react.bridge.WritableMap');
return wrapOptional('WritableMap', isRequired);
case 'ArrayTypeAnnotation':
imports.add('com.facebook.react.bridge.WritableArray');
return wrapOptional('WritableArray', isRequired);
default:
realTypeAnnotation.type;
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
}
function getFalsyReturnStatementFromReturnType(
nullableReturnTypeAnnotation,
createErrorMessage,
resolveAlias,
) {
const _unwrapNullable5 = unwrapNullable(nullableReturnTypeAnnotation),
_unwrapNullable6 = _slicedToArray(_unwrapNullable5, 2),
returnTypeAnnotation = _unwrapNullable6[0],
nullable = _unwrapNullable6[1];
let realTypeAnnotation = returnTypeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return 'return 0.0;';
default:
realTypeAnnotation.name;
throw new Error(createErrorMessage(realTypeAnnotation.name));
}
case 'VoidTypeAnnotation':
return '';
case 'PromiseTypeAnnotation':
return '';
case 'NumberTypeAnnotation':
return nullable ? 'return null;' : 'return 0;';
case 'FloatTypeAnnotation':
return nullable ? 'return null;' : 'return 0.0;';
case 'DoubleTypeAnnotation':
return nullable ? 'return null;' : 'return 0.0;';
case 'Int32TypeAnnotation':
return nullable ? 'return null;' : 'return 0;';
case 'BooleanTypeAnnotation':
return nullable ? 'return null;' : 'return false;';
case 'EnumDeclaration':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return nullable ? 'return null;' : 'return 0;';
case 'StringTypeAnnotation':
return nullable ? 'return null;' : 'return "";';
default:
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
case 'UnionTypeAnnotation':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return nullable ? 'return null;' : 'return 0;';
case 'ObjectTypeAnnotation':
return 'return null;';
case 'StringTypeAnnotation':
return nullable ? 'return null;' : 'return "";';
default:
throw new Error(
`Unsupported union member returning value, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'StringTypeAnnotation':
return nullable ? 'return null;' : 'return "";';
case 'ObjectTypeAnnotation':
return 'return null;';
case 'GenericObjectTypeAnnotation':
return 'return null;';
case 'ArrayTypeAnnotation':
return 'return null;';
default:
realTypeAnnotation.type;
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
}
// Build special-cased runtime check for getConstants().
function buildGetConstantsMethod(method, imports, resolveAlias) {
const _unwrapNullable7 = unwrapNullable(method.typeAnnotation),
_unwrapNullable8 = _slicedToArray(_unwrapNullable7, 1),
methodTypeAnnotation = _unwrapNullable8[0];
let returnTypeAnnotation = methodTypeAnnotation.returnTypeAnnotation;
if (returnTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
// The return type is an alias, resolve it to get the expected undelying object literal type
returnTypeAnnotation = resolveAlias(returnTypeAnnotation.name);
}
if (returnTypeAnnotation.type === 'ObjectTypeAnnotation') {
const requiredProps = [];
const optionalProps = [];
const rawProperties = returnTypeAnnotation.properties || [];
rawProperties.forEach(p => {
if (p.optional || p.typeAnnotation.type === 'NullableTypeAnnotation') {
optionalProps.push(p.name);
} else {
requiredProps.push(p.name);
}
});
if (requiredProps.length === 0 && optionalProps.length === 0) {
// Nothing to validate during runtime.
return '';
}
imports.add('com.facebook.react.common.build.ReactBuildConfig');
imports.add('java.util.Arrays');
imports.add('java.util.HashSet');
imports.add('java.util.Map');
imports.add('java.util.Set');
imports.add('javax.annotation.Nullable');
const requiredPropsFragment =
requiredProps.length > 0
? `Arrays.asList(
${requiredProps
.sort()
.map(p => `"${p}"`)
.join(',\n ')}
)`
: '';
const optionalPropsFragment =
optionalProps.length > 0
? `Arrays.asList(
${optionalProps
.sort()
.map(p => `"${p}"`)
.join(',\n ')}
)`
: '';
return ` protected abstract Map<String, Object> getTypedExportedConstants();
@Override
@DoNotStrip
public final @Nullable Map<String, Object> getConstants() {
Map<String, Object> constants = getTypedExportedConstants();
if (ReactBuildConfig.DEBUG || ReactBuildConfig.IS_INTERNAL_BUILD) {
Set<String> obligatoryFlowConstants = new HashSet<>(${requiredPropsFragment});
Set<String> optionalFlowConstants = new HashSet<>(${optionalPropsFragment});
Set<String> undeclaredConstants = new HashSet<>(constants.keySet());
undeclaredConstants.removeAll(obligatoryFlowConstants);
undeclaredConstants.removeAll(optionalFlowConstants);
if (!undeclaredConstants.isEmpty()) {
throw new IllegalStateException(String.format("Native Module Flow doesn't declare constants: %s", undeclaredConstants));
}
undeclaredConstants = obligatoryFlowConstants;
undeclaredConstants.removeAll(constants.keySet());
if (!undeclaredConstants.isEmpty()) {
throw new IllegalStateException(String.format("Native Module doesn't fill in constants: %s", undeclaredConstants));
}
}
return constants;
}`;
}
return '';
}
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
) {
const files = new Map();
const nativeModules = getModules(schema);
const normalizedPackageName =
packageName == null ? 'com.facebook.fbreact.specs' : packageName;
const outputDir = `java/${normalizedPackageName.replace(/\./g, '/')}`;
Object.keys(nativeModules).forEach(hasteModuleName => {
const _nativeModules$hasteM = nativeModules[hasteModuleName],
aliasMap = _nativeModules$hasteM.aliasMap,
excludedPlatforms = _nativeModules$hasteM.excludedPlatforms,
moduleName = _nativeModules$hasteM.moduleName,
properties = _nativeModules$hasteM.spec.properties;
if (excludedPlatforms != null && excludedPlatforms.includes('android')) {
return;
}
const resolveAlias = createAliasResolver(aliasMap);
const className = `${hasteModuleName}Spec`;
const imports = new Set([
// Always required.
'com.facebook.react.bridge.ReactApplicationContext',
'com.facebook.react.bridge.ReactContextBaseJavaModule',
'com.facebook.react.bridge.ReactMethod',
'com.facebook.react.turbomodule.core.interfaces.TurboModule',
'com.facebook.proguard.annotations.DoNotStrip',
'javax.annotation.Nonnull',
]);
const methods = properties.map(method => {
if (method.name === 'getConstants') {
return buildGetConstantsMethod(method, imports, resolveAlias);
}
const _unwrapNullable9 = unwrapNullable(method.typeAnnotation),
_unwrapNullable10 = _slicedToArray(_unwrapNullable9, 1),
methodTypeAnnotation = _unwrapNullable10[0];
// Handle return type
const translatedReturnType = translateFunctionReturnTypeToJavaType(
methodTypeAnnotation.returnTypeAnnotation,
typeName =>
`Unsupported return type for method ${method.name}. Found: ${typeName}`,
resolveAlias,
imports,
);
const returningPromise =
methodTypeAnnotation.returnTypeAnnotation.type ===
'PromiseTypeAnnotation';
const isSyncMethod =
methodTypeAnnotation.returnTypeAnnotation.type !==
'VoidTypeAnnotation' && !returningPromise;
// Handle method args
const traversedArgs = methodTypeAnnotation.params.map(param => {
const translatedParam = translateFunctionParamToJavaType(
param,
typeName =>
`Unsupported type for param "${param.name}" in ${method.name}. Found: ${typeName}`,
resolveAlias,
imports,
);
return `${translatedParam} ${param.name}`;
});
if (returningPromise) {
// Promise return type requires an extra arg at the end.
imports.add('com.facebook.react.bridge.Promise');
traversedArgs.push('Promise promise');
}
const methodJavaAnnotation = `@ReactMethod${
isSyncMethod ? '(isBlockingSynchronousMethod = true)' : ''
}\n @DoNotStrip`;
const methodBody = method.optional
? getFalsyReturnStatementFromReturnType(
methodTypeAnnotation.returnTypeAnnotation,
typeName =>
`Cannot build falsy return statement for return type for method ${method.name}. Found: ${typeName}`,
resolveAlias,
)
: null;
return MethodTemplate({
abstract: !method.optional,
methodBody,
methodJavaAnnotation,
methodName: method.name,
translatedReturnType,
traversedArgs,
});
});
files.set(
`${outputDir}/${className}.java`,
FileTemplate({
packageName: normalizedPackageName,
className,
jsName: moduleName,
methods: methods.filter(Boolean).join('\n\n'),
imports: Array.from(imports)
.sort()
.map(p => `import ${p};`)
.join('\n'),
}),
);
});
return files;
},
};

View File

@@ -0,0 +1,551 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {
NamedShape,
NativeModuleFunctionTypeAnnotation,
NativeModuleParamTypeAnnotation,
NativeModulePropertyShape,
NativeModuleReturnTypeAnnotation,
Nullable,
SchemaType,
} from '../../CodegenSchema';
import type {AliasResolver} from './Utils';
const {unwrapNullable} = require('../../parsers/parsers-commons');
const {wrapOptional} = require('../TypeUtils/Java');
const {createAliasResolver, getModules} = require('./Utils');
type FilesOutput = Map<string, string>;
function FileTemplate(
config: $ReadOnly<{
packageName: string,
className: string,
jsName: string,
methods: string,
imports: string,
}>,
): string {
const {packageName, className, jsName, methods, imports} = config;
return `
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GenerateModuleJavaSpec.js
*
* ${'@'}nolint
*/
package ${packageName};
${imports}
public abstract class ${className} extends ReactContextBaseJavaModule implements TurboModule {
public static final String NAME = "${jsName}";
public ${className}(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public @Nonnull String getName() {
return NAME;
}
${methods}
}
`;
}
function MethodTemplate(
config: $ReadOnly<{
abstract: boolean,
methodBody: ?string,
methodJavaAnnotation: string,
methodName: string,
translatedReturnType: string,
traversedArgs: Array<string>,
}>,
): string {
const {
abstract,
methodBody,
methodJavaAnnotation,
methodName,
translatedReturnType,
traversedArgs,
} = config;
const methodQualifier = abstract ? 'abstract ' : '';
const methodClosing = abstract
? ';'
: methodBody != null && methodBody.length > 0
? ` { ${methodBody} }`
: ' {}';
return ` ${methodJavaAnnotation}
public ${methodQualifier}${translatedReturnType} ${methodName}(${traversedArgs.join(
', ',
)})${methodClosing}`;
}
type Param = NamedShape<Nullable<NativeModuleParamTypeAnnotation>>;
function translateFunctionParamToJavaType(
param: Param,
createErrorMessage: (typeName: string) => string,
resolveAlias: AliasResolver,
imports: Set<string>,
): string {
const {optional, typeAnnotation: nullableTypeAnnotation} = param;
const [typeAnnotation, nullable] =
unwrapNullable<NativeModuleParamTypeAnnotation>(nullableTypeAnnotation);
const isRequired = !optional && !nullable;
if (!isRequired) {
imports.add('javax.annotation.Nullable');
}
// FIXME: support class alias for args
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return wrapOptional('double', isRequired);
default:
(realTypeAnnotation.name: empty);
throw new Error(createErrorMessage(realTypeAnnotation.name));
}
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'FloatTypeAnnotation':
return wrapOptional('double', isRequired);
case 'DoubleTypeAnnotation':
return wrapOptional('double', isRequired);
case 'Int32TypeAnnotation':
return wrapOptional('double', isRequired);
case 'BooleanTypeAnnotation':
return wrapOptional('boolean', isRequired);
case 'EnumDeclaration':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
default:
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'ObjectTypeAnnotation':
imports.add('com.facebook.react.bridge.ReadableMap');
return wrapOptional('ReadableMap', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
default:
throw new Error(
`Unsupported union member returning value, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'ObjectTypeAnnotation':
imports.add('com.facebook.react.bridge.ReadableMap');
return wrapOptional('ReadableMap', isRequired);
case 'GenericObjectTypeAnnotation':
// Treat this the same as ObjectTypeAnnotation for now.
imports.add('com.facebook.react.bridge.ReadableMap');
return wrapOptional('ReadableMap', isRequired);
case 'ArrayTypeAnnotation':
imports.add('com.facebook.react.bridge.ReadableArray');
return wrapOptional('ReadableArray', isRequired);
case 'FunctionTypeAnnotation':
imports.add('com.facebook.react.bridge.Callback');
return wrapOptional('Callback', isRequired);
default:
(realTypeAnnotation.type: 'MixedTypeAnnotation');
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
}
function translateFunctionReturnTypeToJavaType(
nullableReturnTypeAnnotation: Nullable<NativeModuleReturnTypeAnnotation>,
createErrorMessage: (typeName: string) => string,
resolveAlias: AliasResolver,
imports: Set<string>,
): string {
const [returnTypeAnnotation, nullable] =
unwrapNullable<NativeModuleReturnTypeAnnotation>(
nullableReturnTypeAnnotation,
);
if (nullable) {
imports.add('javax.annotation.Nullable');
}
const isRequired = !nullable;
// FIXME: support class alias for args
let realTypeAnnotation = returnTypeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return wrapOptional('double', isRequired);
default:
(realTypeAnnotation.name: empty);
throw new Error(createErrorMessage(realTypeAnnotation.name));
}
case 'VoidTypeAnnotation':
return 'void';
case 'PromiseTypeAnnotation':
return 'void';
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'FloatTypeAnnotation':
return wrapOptional('double', isRequired);
case 'DoubleTypeAnnotation':
return wrapOptional('double', isRequired);
case 'Int32TypeAnnotation':
return wrapOptional('double', isRequired);
case 'BooleanTypeAnnotation':
return wrapOptional('boolean', isRequired);
case 'EnumDeclaration':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
default:
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
case 'UnionTypeAnnotation':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'ObjectTypeAnnotation':
imports.add('com.facebook.react.bridge.WritableMap');
return wrapOptional('WritableMap', isRequired);
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
default:
throw new Error(
`Unsupported union member returning value, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'ObjectTypeAnnotation':
imports.add('com.facebook.react.bridge.WritableMap');
return wrapOptional('WritableMap', isRequired);
case 'GenericObjectTypeAnnotation':
imports.add('com.facebook.react.bridge.WritableMap');
return wrapOptional('WritableMap', isRequired);
case 'ArrayTypeAnnotation':
imports.add('com.facebook.react.bridge.WritableArray');
return wrapOptional('WritableArray', isRequired);
default:
(realTypeAnnotation.type: 'MixedTypeAnnotation');
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
}
function getFalsyReturnStatementFromReturnType(
nullableReturnTypeAnnotation: Nullable<NativeModuleReturnTypeAnnotation>,
createErrorMessage: (typeName: string) => string,
resolveAlias: AliasResolver,
): string {
const [returnTypeAnnotation, nullable] =
unwrapNullable<NativeModuleReturnTypeAnnotation>(
nullableReturnTypeAnnotation,
);
let realTypeAnnotation = returnTypeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return 'return 0.0;';
default:
(realTypeAnnotation.name: empty);
throw new Error(createErrorMessage(realTypeAnnotation.name));
}
case 'VoidTypeAnnotation':
return '';
case 'PromiseTypeAnnotation':
return '';
case 'NumberTypeAnnotation':
return nullable ? 'return null;' : 'return 0;';
case 'FloatTypeAnnotation':
return nullable ? 'return null;' : 'return 0.0;';
case 'DoubleTypeAnnotation':
return nullable ? 'return null;' : 'return 0.0;';
case 'Int32TypeAnnotation':
return nullable ? 'return null;' : 'return 0;';
case 'BooleanTypeAnnotation':
return nullable ? 'return null;' : 'return false;';
case 'EnumDeclaration':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return nullable ? 'return null;' : 'return 0;';
case 'StringTypeAnnotation':
return nullable ? 'return null;' : 'return "";';
default:
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
case 'UnionTypeAnnotation':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return nullable ? 'return null;' : 'return 0;';
case 'ObjectTypeAnnotation':
return 'return null;';
case 'StringTypeAnnotation':
return nullable ? 'return null;' : 'return "";';
default:
throw new Error(
`Unsupported union member returning value, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'StringTypeAnnotation':
return nullable ? 'return null;' : 'return "";';
case 'ObjectTypeAnnotation':
return 'return null;';
case 'GenericObjectTypeAnnotation':
return 'return null;';
case 'ArrayTypeAnnotation':
return 'return null;';
default:
(realTypeAnnotation.type: 'MixedTypeAnnotation');
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
}
// Build special-cased runtime check for getConstants().
function buildGetConstantsMethod(
method: NativeModulePropertyShape,
imports: Set<string>,
resolveAlias: AliasResolver,
): string {
const [methodTypeAnnotation] =
unwrapNullable<NativeModuleFunctionTypeAnnotation>(method.typeAnnotation);
let returnTypeAnnotation = methodTypeAnnotation.returnTypeAnnotation;
if (returnTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
// The return type is an alias, resolve it to get the expected undelying object literal type
returnTypeAnnotation = resolveAlias(returnTypeAnnotation.name);
}
if (returnTypeAnnotation.type === 'ObjectTypeAnnotation') {
const requiredProps = [];
const optionalProps = [];
const rawProperties = returnTypeAnnotation.properties || [];
rawProperties.forEach(p => {
if (p.optional || p.typeAnnotation.type === 'NullableTypeAnnotation') {
optionalProps.push(p.name);
} else {
requiredProps.push(p.name);
}
});
if (requiredProps.length === 0 && optionalProps.length === 0) {
// Nothing to validate during runtime.
return '';
}
imports.add('com.facebook.react.common.build.ReactBuildConfig');
imports.add('java.util.Arrays');
imports.add('java.util.HashSet');
imports.add('java.util.Map');
imports.add('java.util.Set');
imports.add('javax.annotation.Nullable');
const requiredPropsFragment =
requiredProps.length > 0
? `Arrays.asList(
${requiredProps
.sort()
.map(p => `"${p}"`)
.join(',\n ')}
)`
: '';
const optionalPropsFragment =
optionalProps.length > 0
? `Arrays.asList(
${optionalProps
.sort()
.map(p => `"${p}"`)
.join(',\n ')}
)`
: '';
return ` protected abstract Map<String, Object> getTypedExportedConstants();
@Override
@DoNotStrip
public final @Nullable Map<String, Object> getConstants() {
Map<String, Object> constants = getTypedExportedConstants();
if (ReactBuildConfig.DEBUG || ReactBuildConfig.IS_INTERNAL_BUILD) {
Set<String> obligatoryFlowConstants = new HashSet<>(${requiredPropsFragment});
Set<String> optionalFlowConstants = new HashSet<>(${optionalPropsFragment});
Set<String> undeclaredConstants = new HashSet<>(constants.keySet());
undeclaredConstants.removeAll(obligatoryFlowConstants);
undeclaredConstants.removeAll(optionalFlowConstants);
if (!undeclaredConstants.isEmpty()) {
throw new IllegalStateException(String.format("Native Module Flow doesn't declare constants: %s", undeclaredConstants));
}
undeclaredConstants = obligatoryFlowConstants;
undeclaredConstants.removeAll(constants.keySet());
if (!undeclaredConstants.isEmpty()) {
throw new IllegalStateException(String.format("Native Module doesn't fill in constants: %s", undeclaredConstants));
}
}
return constants;
}`;
}
return '';
}
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
): FilesOutput {
const files = new Map<string, string>();
const nativeModules = getModules(schema);
const normalizedPackageName =
packageName == null ? 'com.facebook.fbreact.specs' : packageName;
const outputDir = `java/${normalizedPackageName.replace(/\./g, '/')}`;
Object.keys(nativeModules).forEach(hasteModuleName => {
const {
aliasMap,
excludedPlatforms,
moduleName,
spec: {properties},
} = nativeModules[hasteModuleName];
if (excludedPlatforms != null && excludedPlatforms.includes('android')) {
return;
}
const resolveAlias = createAliasResolver(aliasMap);
const className = `${hasteModuleName}Spec`;
const imports: Set<string> = new Set([
// Always required.
'com.facebook.react.bridge.ReactApplicationContext',
'com.facebook.react.bridge.ReactContextBaseJavaModule',
'com.facebook.react.bridge.ReactMethod',
'com.facebook.react.turbomodule.core.interfaces.TurboModule',
'com.facebook.proguard.annotations.DoNotStrip',
'javax.annotation.Nonnull',
]);
const methods = properties.map(method => {
if (method.name === 'getConstants') {
return buildGetConstantsMethod(method, imports, resolveAlias);
}
const [methodTypeAnnotation] =
unwrapNullable<NativeModuleFunctionTypeAnnotation>(
method.typeAnnotation,
);
// Handle return type
const translatedReturnType = translateFunctionReturnTypeToJavaType(
methodTypeAnnotation.returnTypeAnnotation,
typeName =>
`Unsupported return type for method ${method.name}. Found: ${typeName}`,
resolveAlias,
imports,
);
const returningPromise =
methodTypeAnnotation.returnTypeAnnotation.type ===
'PromiseTypeAnnotation';
const isSyncMethod =
methodTypeAnnotation.returnTypeAnnotation.type !==
'VoidTypeAnnotation' && !returningPromise;
// Handle method args
const traversedArgs = methodTypeAnnotation.params.map(param => {
const translatedParam = translateFunctionParamToJavaType(
param,
typeName =>
`Unsupported type for param "${param.name}" in ${method.name}. Found: ${typeName}`,
resolveAlias,
imports,
);
return `${translatedParam} ${param.name}`;
});
if (returningPromise) {
// Promise return type requires an extra arg at the end.
imports.add('com.facebook.react.bridge.Promise');
traversedArgs.push('Promise promise');
}
const methodJavaAnnotation = `@ReactMethod${
isSyncMethod ? '(isBlockingSynchronousMethod = true)' : ''
}\n @DoNotStrip`;
const methodBody = method.optional
? getFalsyReturnStatementFromReturnType(
methodTypeAnnotation.returnTypeAnnotation,
typeName =>
`Cannot build falsy return statement for return type for method ${method.name}. Found: ${typeName}`,
resolveAlias,
)
: null;
return MethodTemplate({
abstract: !method.optional,
methodBody,
methodJavaAnnotation,
methodName: method.name,
translatedReturnType,
traversedArgs,
});
});
files.set(
`${outputDir}/${className}.java`,
FileTemplate({
packageName: normalizedPackageName,
className,
jsName: moduleName,
methods: methods.filter(Boolean).join('\n\n'),
imports: Array.from(imports)
.sort()
.map(p => `import ${p};`)
.join('\n'),
}),
);
});
return files;
},
};

View File

@@ -0,0 +1,509 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
function _slicedToArray(arr, i) {
return (
_arrayWithHoles(arr) ||
_iterableToArrayLimit(arr, i) ||
_unsupportedIterableToArray(arr, i) ||
_nonIterableRest()
);
}
function _nonIterableRest() {
throw new TypeError(
'Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.',
);
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === 'string') return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === 'Object' && o.constructor) n = o.constructor.name;
if (n === 'Map' || n === 'Set') return Array.from(o);
if (n === 'Arguments' || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))
return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _iterableToArrayLimit(arr, i) {
var _i =
null == arr
? null
: ('undefined' != typeof Symbol && arr[Symbol.iterator]) ||
arr['@@iterator'];
if (null != _i) {
var _s,
_e,
_x,
_r,
_arr = [],
_n = !0,
_d = !1;
try {
if (((_x = (_i = _i.call(arr)).next), 0 === i)) {
if (Object(_i) !== _i) return;
_n = !1;
} else
for (
;
!(_n = (_s = _x.call(_i)).done) &&
(_arr.push(_s.value), _arr.length !== i);
_n = !0
);
} catch (err) {
(_d = !0), (_e = err);
} finally {
try {
if (!_n && null != _i.return && ((_r = _i.return()), Object(_r) !== _r))
return;
} finally {
if (_d) throw _e;
}
}
return _arr;
}
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
const _require = require('../../parsers/parsers-commons'),
unwrapNullable = _require.unwrapNullable;
const _require2 = require('./Utils'),
createAliasResolver = _require2.createAliasResolver,
getModules = _require2.getModules;
const HostFunctionTemplate = ({
hasteModuleName,
propertyName,
jniSignature,
jsReturnType,
}) => {
return `static facebook::jsi::Value __hostFunction_${hasteModuleName}SpecJSI_${propertyName}(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, ${jsReturnType}, "${propertyName}", "${jniSignature}", args, count, cachedMethodId);
}`;
};
const ModuleClassConstructorTemplate = ({hasteModuleName, methods}) => {
return `
${hasteModuleName}SpecJSI::${hasteModuleName}SpecJSI(const JavaTurboModule::InitParams &params)
: JavaTurboModule(params) {
${methods
.map(({propertyName, argCount}) => {
return ` methodMap_["${propertyName}"] = MethodMetadata {${argCount}, __hostFunction_${hasteModuleName}SpecJSI_${propertyName}};`;
})
.join('\n')}
}`.trim();
};
const ModuleLookupTemplate = ({moduleName, hasteModuleName}) => {
return ` if (moduleName == "${moduleName}") {
return std::make_shared<${hasteModuleName}SpecJSI>(params);
}`;
};
const FileTemplate = ({libraryName, include, modules, moduleLookups}) => {
return `
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GenerateModuleJniCpp.js
*/
#include ${include}
namespace facebook::react {
${modules}
std::shared_ptr<TurboModule> ${libraryName}_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams &params) {
${moduleLookups.map(ModuleLookupTemplate).join('\n')}
return nullptr;
}
} // namespace facebook::react
`;
};
function translateReturnTypeToKind(nullableTypeAnnotation, resolveAlias) {
const _unwrapNullable = unwrapNullable(nullableTypeAnnotation),
_unwrapNullable2 = _slicedToArray(_unwrapNullable, 1),
typeAnnotation = _unwrapNullable2[0];
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return 'NumberKind';
default:
realTypeAnnotation.name;
throw new Error(
`Invalid ReservedFunctionValueTypeName name, got ${realTypeAnnotation.name}`,
);
}
case 'VoidTypeAnnotation':
return 'VoidKind';
case 'StringTypeAnnotation':
return 'StringKind';
case 'BooleanTypeAnnotation':
return 'BooleanKind';
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return 'NumberKind';
case 'StringTypeAnnotation':
return 'StringKind';
default:
throw new Error(
`Unknown enum prop type for returning value, found: ${realTypeAnnotation.type}"`,
);
}
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return 'NumberKind';
case 'ObjectTypeAnnotation':
return 'ObjectKind';
case 'StringTypeAnnotation':
return 'StringKind';
default:
throw new Error(
`Unsupported union member returning value, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'NumberTypeAnnotation':
return 'NumberKind';
case 'DoubleTypeAnnotation':
return 'NumberKind';
case 'FloatTypeAnnotation':
return 'NumberKind';
case 'Int32TypeAnnotation':
return 'NumberKind';
case 'PromiseTypeAnnotation':
return 'PromiseKind';
case 'GenericObjectTypeAnnotation':
return 'ObjectKind';
case 'ObjectTypeAnnotation':
return 'ObjectKind';
case 'ArrayTypeAnnotation':
return 'ArrayKind';
default:
realTypeAnnotation.type;
throw new Error(
`Unknown prop type for returning value, found: ${realTypeAnnotation.type}"`,
);
}
}
function translateParamTypeToJniType(param, resolveAlias) {
const optional = param.optional,
nullableTypeAnnotation = param.typeAnnotation;
const _unwrapNullable3 = unwrapNullable(nullableTypeAnnotation),
_unwrapNullable4 = _slicedToArray(_unwrapNullable3, 2),
typeAnnotation = _unwrapNullable4[0],
nullable = _unwrapNullable4[1];
const isRequired = !optional && !nullable;
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
default:
realTypeAnnotation.name;
throw new Error(
`Invalid ReservedFunctionValueTypeName name, got ${realTypeAnnotation.name}`,
);
}
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
case 'BooleanTypeAnnotation':
return !isRequired ? 'Ljava/lang/Boolean;' : 'Z';
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
default:
throw new Error(
`Unknown enum prop type for method arg, found: ${realTypeAnnotation.type}"`,
);
}
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'ObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/ReadableMap;';
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
default:
throw new Error(
`Unsupported union prop value, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'NumberTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'DoubleTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'FloatTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'Int32TypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'GenericObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/ReadableMap;';
case 'ObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/ReadableMap;';
case 'ArrayTypeAnnotation':
return 'Lcom/facebook/react/bridge/ReadableArray;';
case 'FunctionTypeAnnotation':
return 'Lcom/facebook/react/bridge/Callback;';
default:
realTypeAnnotation.type;
throw new Error(
`Unknown prop type for method arg, found: ${realTypeAnnotation.type}"`,
);
}
}
function translateReturnTypeToJniType(nullableTypeAnnotation, resolveAlias) {
const _unwrapNullable5 = unwrapNullable(nullableTypeAnnotation),
_unwrapNullable6 = _slicedToArray(_unwrapNullable5, 2),
typeAnnotation = _unwrapNullable6[0],
nullable = _unwrapNullable6[1];
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return nullable ? 'Ljava/lang/Double;' : 'D';
default:
realTypeAnnotation.name;
throw new Error(
`Invalid ReservedFunctionValueTypeName name, got ${realTypeAnnotation.name}`,
);
}
case 'VoidTypeAnnotation':
return 'V';
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
case 'BooleanTypeAnnotation':
return nullable ? 'Ljava/lang/Boolean;' : 'Z';
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
default:
throw new Error(
`Unknown enum prop type for method return type, found: ${realTypeAnnotation.type}"`,
);
}
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'ObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/WritableMap;';
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
default:
throw new Error(
`Unsupported union member type, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'NumberTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'DoubleTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'FloatTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'Int32TypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'PromiseTypeAnnotation':
return 'Lcom/facebook/react/bridge/Promise;';
case 'GenericObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/WritableMap;';
case 'ObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/WritableMap;';
case 'ArrayTypeAnnotation':
return 'Lcom/facebook/react/bridge/WritableArray;';
default:
realTypeAnnotation.type;
throw new Error(
`Unknown prop type for method return type, found: ${realTypeAnnotation.type}"`,
);
}
}
function translateMethodTypeToJniSignature(property, resolveAlias) {
const name = property.name,
typeAnnotation = property.typeAnnotation;
let _unwrapNullable7 = unwrapNullable(typeAnnotation),
_unwrapNullable8 = _slicedToArray(_unwrapNullable7, 1),
_unwrapNullable8$ = _unwrapNullable8[0],
returnTypeAnnotation = _unwrapNullable8$.returnTypeAnnotation,
params = _unwrapNullable8$.params;
params = [...params];
let processedReturnTypeAnnotation = returnTypeAnnotation;
const isPromiseReturn = returnTypeAnnotation.type === 'PromiseTypeAnnotation';
if (isPromiseReturn) {
processedReturnTypeAnnotation = {
type: 'VoidTypeAnnotation',
};
}
const argsSignatureParts = params.map(t => {
return translateParamTypeToJniType(t, resolveAlias);
});
if (isPromiseReturn) {
// Additional promise arg for this case.
argsSignatureParts.push(
translateReturnTypeToJniType(returnTypeAnnotation, resolveAlias),
);
}
const argsSignature = argsSignatureParts.join('');
const returnSignature =
name === 'getConstants'
? 'Ljava/util/Map;'
: translateReturnTypeToJniType(
processedReturnTypeAnnotation,
resolveAlias,
);
return `(${argsSignature})${returnSignature}`;
}
function translateMethodForImplementation(
hasteModuleName,
property,
resolveAlias,
) {
const _unwrapNullable9 = unwrapNullable(property.typeAnnotation),
_unwrapNullable10 = _slicedToArray(_unwrapNullable9, 1),
propertyTypeAnnotation = _unwrapNullable10[0];
const returnTypeAnnotation = propertyTypeAnnotation.returnTypeAnnotation;
if (
property.name === 'getConstants' &&
returnTypeAnnotation.type === 'ObjectTypeAnnotation' &&
returnTypeAnnotation.properties.length === 0
) {
return '';
}
return HostFunctionTemplate({
hasteModuleName,
propertyName: property.name,
jniSignature: translateMethodTypeToJniSignature(property, resolveAlias),
jsReturnType: translateReturnTypeToKind(returnTypeAnnotation, resolveAlias),
});
}
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
) {
const nativeModules = getModules(schema);
const modules = Object.keys(nativeModules)
.filter(hasteModuleName => {
const module = nativeModules[hasteModuleName];
return !(
module.excludedPlatforms != null &&
module.excludedPlatforms.includes('android')
);
})
.sort()
.map(hasteModuleName => {
const _nativeModules$hasteM = nativeModules[hasteModuleName],
aliasMap = _nativeModules$hasteM.aliasMap,
properties = _nativeModules$hasteM.spec.properties;
const resolveAlias = createAliasResolver(aliasMap);
const translatedMethods = properties
.map(property =>
translateMethodForImplementation(
hasteModuleName,
property,
resolveAlias,
),
)
.join('\n\n');
return (
translatedMethods +
'\n\n' +
ModuleClassConstructorTemplate({
hasteModuleName,
methods: properties
.map(({name: propertyName, typeAnnotation}) => {
const _unwrapNullable11 = unwrapNullable(typeAnnotation),
_unwrapNullable12 = _slicedToArray(_unwrapNullable11, 1),
_unwrapNullable12$ = _unwrapNullable12[0],
returnTypeAnnotation =
_unwrapNullable12$.returnTypeAnnotation,
params = _unwrapNullable12$.params;
if (
propertyName === 'getConstants' &&
returnTypeAnnotation.type === 'ObjectTypeAnnotation' &&
returnTypeAnnotation.properties &&
returnTypeAnnotation.properties.length === 0
) {
return null;
}
return {
propertyName,
argCount: params.length,
};
})
.filter(Boolean),
})
);
})
.join('\n');
const moduleLookups = Object.keys(nativeModules)
.filter(hasteModuleName => {
const module = nativeModules[hasteModuleName];
return !(
module.excludedPlatforms != null &&
module.excludedPlatforms.includes('android')
);
})
.sort((a, b) => {
const nameA = nativeModules[a].moduleName;
const nameB = nativeModules[b].moduleName;
if (nameA < nameB) {
return -1;
} else if (nameA > nameB) {
return 1;
}
return 0;
})
.map(hasteModuleName => ({
moduleName: nativeModules[hasteModuleName].moduleName,
hasteModuleName,
}));
const fileName = `${libraryName}-generated.cpp`;
const replacedTemplate = FileTemplate({
modules: modules,
libraryName: libraryName.replace(/-/g, '_'),
moduleLookups,
include: `"${libraryName}.h"`,
});
return new Map([[`jni/${fileName}`, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,522 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {
NamedShape,
NativeModuleFunctionTypeAnnotation,
NativeModuleParamTypeAnnotation,
NativeModulePropertyShape,
NativeModuleReturnTypeAnnotation,
Nullable,
SchemaType,
} from '../../CodegenSchema';
import type {AliasResolver} from './Utils';
const {unwrapNullable} = require('../../parsers/parsers-commons');
const {createAliasResolver, getModules} = require('./Utils');
type FilesOutput = Map<string, string>;
type JSReturnType =
| 'VoidKind'
| 'StringKind'
| 'BooleanKind'
| 'NumberKind'
| 'PromiseKind'
| 'ObjectKind'
| 'ArrayKind';
const HostFunctionTemplate = ({
hasteModuleName,
propertyName,
jniSignature,
jsReturnType,
}: $ReadOnly<{
hasteModuleName: string,
propertyName: string,
jniSignature: string,
jsReturnType: JSReturnType,
}>) => {
return `static facebook::jsi::Value __hostFunction_${hasteModuleName}SpecJSI_${propertyName}(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, ${jsReturnType}, "${propertyName}", "${jniSignature}", args, count, cachedMethodId);
}`;
};
const ModuleClassConstructorTemplate = ({
hasteModuleName,
methods,
}: $ReadOnly<{
hasteModuleName: string,
methods: $ReadOnlyArray<{
propertyName: string,
argCount: number,
}>,
}>) => {
return `
${hasteModuleName}SpecJSI::${hasteModuleName}SpecJSI(const JavaTurboModule::InitParams &params)
: JavaTurboModule(params) {
${methods
.map(({propertyName, argCount}) => {
return ` methodMap_["${propertyName}"] = MethodMetadata {${argCount}, __hostFunction_${hasteModuleName}SpecJSI_${propertyName}};`;
})
.join('\n')}
}`.trim();
};
const ModuleLookupTemplate = ({
moduleName,
hasteModuleName,
}: $ReadOnly<{moduleName: string, hasteModuleName: string}>) => {
return ` if (moduleName == "${moduleName}") {
return std::make_shared<${hasteModuleName}SpecJSI>(params);
}`;
};
const FileTemplate = ({
libraryName,
include,
modules,
moduleLookups,
}: $ReadOnly<{
libraryName: string,
include: string,
modules: string,
moduleLookups: $ReadOnlyArray<{
hasteModuleName: string,
moduleName: string,
}>,
}>) => {
return `
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GenerateModuleJniCpp.js
*/
#include ${include}
namespace facebook::react {
${modules}
std::shared_ptr<TurboModule> ${libraryName}_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams &params) {
${moduleLookups.map(ModuleLookupTemplate).join('\n')}
return nullptr;
}
} // namespace facebook::react
`;
};
function translateReturnTypeToKind(
nullableTypeAnnotation: Nullable<NativeModuleReturnTypeAnnotation>,
resolveAlias: AliasResolver,
): JSReturnType {
const [typeAnnotation] = unwrapNullable<NativeModuleReturnTypeAnnotation>(
nullableTypeAnnotation,
);
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return 'NumberKind';
default:
(realTypeAnnotation.name: empty);
throw new Error(
`Invalid ReservedFunctionValueTypeName name, got ${realTypeAnnotation.name}`,
);
}
case 'VoidTypeAnnotation':
return 'VoidKind';
case 'StringTypeAnnotation':
return 'StringKind';
case 'BooleanTypeAnnotation':
return 'BooleanKind';
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return 'NumberKind';
case 'StringTypeAnnotation':
return 'StringKind';
default:
throw new Error(
`Unknown enum prop type for returning value, found: ${realTypeAnnotation.type}"`,
);
}
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return 'NumberKind';
case 'ObjectTypeAnnotation':
return 'ObjectKind';
case 'StringTypeAnnotation':
return 'StringKind';
default:
throw new Error(
`Unsupported union member returning value, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'NumberTypeAnnotation':
return 'NumberKind';
case 'DoubleTypeAnnotation':
return 'NumberKind';
case 'FloatTypeAnnotation':
return 'NumberKind';
case 'Int32TypeAnnotation':
return 'NumberKind';
case 'PromiseTypeAnnotation':
return 'PromiseKind';
case 'GenericObjectTypeAnnotation':
return 'ObjectKind';
case 'ObjectTypeAnnotation':
return 'ObjectKind';
case 'ArrayTypeAnnotation':
return 'ArrayKind';
default:
(realTypeAnnotation.type: 'MixedTypeAnnotation');
throw new Error(
`Unknown prop type for returning value, found: ${realTypeAnnotation.type}"`,
);
}
}
type Param = NamedShape<Nullable<NativeModuleParamTypeAnnotation>>;
function translateParamTypeToJniType(
param: Param,
resolveAlias: AliasResolver,
): string {
const {optional, typeAnnotation: nullableTypeAnnotation} = param;
const [typeAnnotation, nullable] =
unwrapNullable<NativeModuleParamTypeAnnotation>(nullableTypeAnnotation);
const isRequired = !optional && !nullable;
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
default:
(realTypeAnnotation.name: empty);
throw new Error(
`Invalid ReservedFunctionValueTypeName name, got ${realTypeAnnotation.name}`,
);
}
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
case 'BooleanTypeAnnotation':
return !isRequired ? 'Ljava/lang/Boolean;' : 'Z';
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
default:
throw new Error(
`Unknown enum prop type for method arg, found: ${realTypeAnnotation.type}"`,
);
}
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'ObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/ReadableMap;';
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
default:
throw new Error(
`Unsupported union prop value, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'NumberTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'DoubleTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'FloatTypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'Int32TypeAnnotation':
return !isRequired ? 'Ljava/lang/Double;' : 'D';
case 'GenericObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/ReadableMap;';
case 'ObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/ReadableMap;';
case 'ArrayTypeAnnotation':
return 'Lcom/facebook/react/bridge/ReadableArray;';
case 'FunctionTypeAnnotation':
return 'Lcom/facebook/react/bridge/Callback;';
default:
(realTypeAnnotation.type: 'MixedTypeAnnotation');
throw new Error(
`Unknown prop type for method arg, found: ${realTypeAnnotation.type}"`,
);
}
}
function translateReturnTypeToJniType(
nullableTypeAnnotation: Nullable<NativeModuleReturnTypeAnnotation>,
resolveAlias: AliasResolver,
): string {
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return nullable ? 'Ljava/lang/Double;' : 'D';
default:
(realTypeAnnotation.name: empty);
throw new Error(
`Invalid ReservedFunctionValueTypeName name, got ${realTypeAnnotation.name}`,
);
}
case 'VoidTypeAnnotation':
return 'V';
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
case 'BooleanTypeAnnotation':
return nullable ? 'Ljava/lang/Boolean;' : 'Z';
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
default:
throw new Error(
`Unknown enum prop type for method return type, found: ${realTypeAnnotation.type}"`,
);
}
case 'UnionTypeAnnotation':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'ObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/WritableMap;';
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
default:
throw new Error(
`Unsupported union member type, found: ${realTypeAnnotation.memberType}"`,
);
}
case 'NumberTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'DoubleTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'FloatTypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'Int32TypeAnnotation':
return nullable ? 'Ljava/lang/Double;' : 'D';
case 'PromiseTypeAnnotation':
return 'Lcom/facebook/react/bridge/Promise;';
case 'GenericObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/WritableMap;';
case 'ObjectTypeAnnotation':
return 'Lcom/facebook/react/bridge/WritableMap;';
case 'ArrayTypeAnnotation':
return 'Lcom/facebook/react/bridge/WritableArray;';
default:
(realTypeAnnotation.type: 'MixedTypeAnnotation');
throw new Error(
`Unknown prop type for method return type, found: ${realTypeAnnotation.type}"`,
);
}
}
function translateMethodTypeToJniSignature(
property: NativeModulePropertyShape,
resolveAlias: AliasResolver,
): string {
const {name, typeAnnotation} = property;
let [{returnTypeAnnotation, params}] =
unwrapNullable<NativeModuleFunctionTypeAnnotation>(typeAnnotation);
params = [...params];
let processedReturnTypeAnnotation = returnTypeAnnotation;
const isPromiseReturn = returnTypeAnnotation.type === 'PromiseTypeAnnotation';
if (isPromiseReturn) {
processedReturnTypeAnnotation = {
type: 'VoidTypeAnnotation',
};
}
const argsSignatureParts = params.map(t => {
return translateParamTypeToJniType(t, resolveAlias);
});
if (isPromiseReturn) {
// Additional promise arg for this case.
argsSignatureParts.push(
translateReturnTypeToJniType(returnTypeAnnotation, resolveAlias),
);
}
const argsSignature = argsSignatureParts.join('');
const returnSignature =
name === 'getConstants'
? 'Ljava/util/Map;'
: translateReturnTypeToJniType(
processedReturnTypeAnnotation,
resolveAlias,
);
return `(${argsSignature})${returnSignature}`;
}
function translateMethodForImplementation(
hasteModuleName: string,
property: NativeModulePropertyShape,
resolveAlias: AliasResolver,
): string {
const [propertyTypeAnnotation] =
unwrapNullable<NativeModuleFunctionTypeAnnotation>(property.typeAnnotation);
const {returnTypeAnnotation} = propertyTypeAnnotation;
if (
property.name === 'getConstants' &&
returnTypeAnnotation.type === 'ObjectTypeAnnotation' &&
returnTypeAnnotation.properties.length === 0
) {
return '';
}
return HostFunctionTemplate({
hasteModuleName,
propertyName: property.name,
jniSignature: translateMethodTypeToJniSignature(property, resolveAlias),
jsReturnType: translateReturnTypeToKind(returnTypeAnnotation, resolveAlias),
});
}
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
): FilesOutput {
const nativeModules = getModules(schema);
const modules = Object.keys(nativeModules)
.filter(hasteModuleName => {
const module = nativeModules[hasteModuleName];
return !(
module.excludedPlatforms != null &&
module.excludedPlatforms.includes('android')
);
})
.sort()
.map(hasteModuleName => {
const {
aliasMap,
spec: {properties},
} = nativeModules[hasteModuleName];
const resolveAlias = createAliasResolver(aliasMap);
const translatedMethods = properties
.map(property =>
translateMethodForImplementation(
hasteModuleName,
property,
resolveAlias,
),
)
.join('\n\n');
return (
translatedMethods +
'\n\n' +
ModuleClassConstructorTemplate({
hasteModuleName,
methods: properties
.map(({name: propertyName, typeAnnotation}) => {
const [{returnTypeAnnotation, params}] =
unwrapNullable<NativeModuleFunctionTypeAnnotation>(
typeAnnotation,
);
if (
propertyName === 'getConstants' &&
returnTypeAnnotation.type === 'ObjectTypeAnnotation' &&
returnTypeAnnotation.properties &&
returnTypeAnnotation.properties.length === 0
) {
return null;
}
return {
propertyName,
argCount: params.length,
};
})
.filter(Boolean),
})
);
})
.join('\n');
const moduleLookups: $ReadOnlyArray<{
hasteModuleName: string,
moduleName: string,
}> = Object.keys(nativeModules)
.filter(hasteModuleName => {
const module = nativeModules[hasteModuleName];
return !(
module.excludedPlatforms != null &&
module.excludedPlatforms.includes('android')
);
})
.sort((a, b) => {
const nameA = nativeModules[a].moduleName;
const nameB = nativeModules[b].moduleName;
if (nameA < nameB) {
return -1;
} else if (nameA > nameB) {
return 1;
}
return 0;
})
.map((hasteModuleName: string) => ({
moduleName: nativeModules[hasteModuleName].moduleName,
hasteModuleName,
}));
const fileName = `${libraryName}-generated.cpp`;
const replacedTemplate = FileTemplate({
modules: modules,
libraryName: libraryName.replace(/-/g, '_'),
moduleLookups,
include: `"${libraryName}.h"`,
});
return new Map([[`jni/${fileName}`, replacedTemplate]]);
},
};

View File

@@ -0,0 +1,145 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
const _require = require('./Utils'),
getModules = _require.getModules;
const ModuleClassDeclarationTemplate = ({hasteModuleName}) => {
return `/**
* JNI C++ class for module '${hasteModuleName}'
*/
class JSI_EXPORT ${hasteModuleName}SpecJSI : public JavaTurboModule {
public:
${hasteModuleName}SpecJSI(const JavaTurboModule::InitParams &params);
};
`;
};
const HeaderFileTemplate = ({modules, libraryName}) => {
return `
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GenerateModuleJniH.js
*/
#pragma once
#include <ReactCommon/JavaTurboModule.h>
#include <ReactCommon/TurboModule.h>
#include <jsi/jsi.h>
namespace facebook::react {
${modules}
JSI_EXPORT
std::shared_ptr<TurboModule> ${libraryName}_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams &params);
} // namespace facebook::react
`;
};
// Note: this CMakeLists.txt template includes dependencies for both NativeModule and components.
const CMakeListsTemplate = ({libraryName}) => {
return `# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
file(GLOB react_codegen_SRCS CONFIGURE_DEPENDS *.cpp react/renderer/components/${libraryName}/*.cpp)
add_library(
react_codegen_${libraryName}
SHARED
\${react_codegen_SRCS}
)
target_include_directories(react_codegen_${libraryName} PUBLIC . react/renderer/components/${libraryName})
target_link_libraries(
react_codegen_${libraryName}
fbjni
folly_runtime
glog
jsi
${libraryName !== 'rncore' ? 'react_codegen_rncore' : ''}
react_debug
react_nativemodule_core
react_render_componentregistry
react_render_core
react_render_debug
react_render_graphics
react_render_imagemanager
react_render_mapbuffer
react_utils
rrc_image
rrc_view
turbomodulejsijni
yoga
)
target_compile_options(
react_codegen_${libraryName}
PRIVATE
-DLOG_TAG=\\"ReactNative\\"
-fexceptions
-frtti
-std=c++20
-Wall
)
`;
};
module.exports = {
generate(
libraryName,
schema,
packageName,
assumeNonnull = false,
headerPrefix,
) {
const nativeModules = getModules(schema);
const modules = Object.keys(nativeModules)
.filter(hasteModuleName => {
const module = nativeModules[hasteModuleName];
return !(
module.excludedPlatforms != null &&
module.excludedPlatforms.includes('android')
);
})
.sort()
.map(hasteModuleName =>
ModuleClassDeclarationTemplate({
hasteModuleName,
}),
)
.join('\n');
const fileName = `${libraryName}.h`;
const replacedTemplate = HeaderFileTemplate({
modules: modules,
libraryName: libraryName.replace(/-/g, '_'),
});
return new Map([
[`jni/${fileName}`, replacedTemplate],
[
'jni/CMakeLists.txt',
CMakeListsTemplate({
libraryName: libraryName,
}),
],
]);
},
};

View File

@@ -0,0 +1,150 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {SchemaType} from '../../CodegenSchema';
const {getModules} = require('./Utils');
type FilesOutput = Map<string, string>;
const ModuleClassDeclarationTemplate = ({
hasteModuleName,
}: $ReadOnly<{hasteModuleName: string}>) => {
return `/**
* JNI C++ class for module '${hasteModuleName}'
*/
class JSI_EXPORT ${hasteModuleName}SpecJSI : public JavaTurboModule {
public:
${hasteModuleName}SpecJSI(const JavaTurboModule::InitParams &params);
};
`;
};
const HeaderFileTemplate = ({
modules,
libraryName,
}: $ReadOnly<{modules: string, libraryName: string}>) => {
return `
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* ${'@'}generated by codegen project: GenerateModuleJniH.js
*/
#pragma once
#include <ReactCommon/JavaTurboModule.h>
#include <ReactCommon/TurboModule.h>
#include <jsi/jsi.h>
namespace facebook::react {
${modules}
JSI_EXPORT
std::shared_ptr<TurboModule> ${libraryName}_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams &params);
} // namespace facebook::react
`;
};
// Note: this CMakeLists.txt template includes dependencies for both NativeModule and components.
const CMakeListsTemplate = ({
libraryName,
}: $ReadOnly<{libraryName: string}>) => {
return `# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)
file(GLOB react_codegen_SRCS CONFIGURE_DEPENDS *.cpp react/renderer/components/${libraryName}/*.cpp)
add_library(
react_codegen_${libraryName}
SHARED
\${react_codegen_SRCS}
)
target_include_directories(react_codegen_${libraryName} PUBLIC . react/renderer/components/${libraryName})
target_link_libraries(
react_codegen_${libraryName}
fbjni
folly_runtime
glog
jsi
${libraryName !== 'rncore' ? 'react_codegen_rncore' : ''}
react_debug
react_nativemodule_core
react_render_componentregistry
react_render_core
react_render_debug
react_render_graphics
react_render_imagemanager
react_render_mapbuffer
react_utils
rrc_image
rrc_view
turbomodulejsijni
yoga
)
target_compile_options(
react_codegen_${libraryName}
PRIVATE
-DLOG_TAG=\\"ReactNative\\"
-fexceptions
-frtti
-std=c++20
-Wall
)
`;
};
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
packageName?: string,
assumeNonnull: boolean = false,
headerPrefix?: string,
): FilesOutput {
const nativeModules = getModules(schema);
const modules = Object.keys(nativeModules)
.filter(hasteModuleName => {
const module = nativeModules[hasteModuleName];
return !(
module.excludedPlatforms != null &&
module.excludedPlatforms.includes('android')
);
})
.sort()
.map(hasteModuleName => ModuleClassDeclarationTemplate({hasteModuleName}))
.join('\n');
const fileName = `${libraryName}.h`;
const replacedTemplate = HeaderFileTemplate({
modules: modules,
libraryName: libraryName.replace(/-/g, '_'),
});
return new Map([
[`jni/${fileName}`, replacedTemplate],
['jni/CMakeLists.txt', CMakeListsTemplate({libraryName: libraryName})],
]);
},
};

View File

@@ -0,0 +1,262 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
enumerableOnly &&
(symbols = symbols.filter(function (sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
})),
keys.push.apply(keys, symbols);
}
return keys;
}
function _objectSpread(target) {
for (var i = 1; i < arguments.length; i++) {
var source = null != arguments[i] ? arguments[i] : {};
i % 2
? ownKeys(Object(source), !0).forEach(function (key) {
_defineProperty(target, key, source[key]);
})
: Object.getOwnPropertyDescriptors
? Object.defineProperties(
target,
Object.getOwnPropertyDescriptors(source),
)
: ownKeys(Object(source)).forEach(function (key) {
Object.defineProperty(
target,
key,
Object.getOwnPropertyDescriptor(source, key),
);
});
}
return target;
}
function _slicedToArray(arr, i) {
return (
_arrayWithHoles(arr) ||
_iterableToArrayLimit(arr, i) ||
_unsupportedIterableToArray(arr, i) ||
_nonIterableRest()
);
}
function _nonIterableRest() {
throw new TypeError(
'Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.',
);
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === 'string') return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === 'Object' && o.constructor) n = o.constructor.name;
if (n === 'Map' || n === 'Set') return Array.from(o);
if (n === 'Arguments' || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))
return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _iterableToArrayLimit(arr, i) {
var _i =
null == arr
? null
: ('undefined' != typeof Symbol && arr[Symbol.iterator]) ||
arr['@@iterator'];
if (null != _i) {
var _s,
_e,
_x,
_r,
_arr = [],
_n = !0,
_d = !1;
try {
if (((_x = (_i = _i.call(arr)).next), 0 === i)) {
if (Object(_i) !== _i) return;
_n = !1;
} else
for (
;
!(_n = (_s = _x.call(_i)).done) &&
(_arr.push(_s.value), _arr.length !== i);
_n = !0
);
} catch (err) {
(_d = !0), (_e = err);
} finally {
try {
if (!_n && null != _i.return && ((_r = _i.return()), Object(_r) !== _r))
return;
} finally {
if (_d) throw _e;
}
}
return _arr;
}
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
function _defineProperty(obj, key, value) {
key = _toPropertyKey(key);
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true,
});
} else {
obj[key] = value;
}
return obj;
}
function _toPropertyKey(arg) {
var key = _toPrimitive(arg, 'string');
return typeof key === 'symbol' ? key : String(key);
}
function _toPrimitive(input, hint) {
if (typeof input !== 'object' || input === null) return input;
var prim = input[Symbol.toPrimitive];
if (prim !== undefined) {
var res = prim.call(input, hint || 'default');
if (typeof res !== 'object') return res;
throw new TypeError('@@toPrimitive must return a primitive value.');
}
return (hint === 'string' ? String : Number)(input);
}
const _require = require('../../../parsers/parsers-commons'),
unwrapNullable = _require.unwrapNullable,
wrapNullable = _require.wrapNullable;
const _require2 = require('../../Utils'),
capitalize = _require2.capitalize;
class StructCollector {
constructor() {
_defineProperty(this, '_structs', new Map());
}
process(structName, structContext, resolveAlias, nullableTypeAnnotation) {
const _unwrapNullable = unwrapNullable(nullableTypeAnnotation),
_unwrapNullable2 = _slicedToArray(_unwrapNullable, 2),
typeAnnotation = _unwrapNullable2[0],
nullable = _unwrapNullable2[1];
switch (typeAnnotation.type) {
case 'ObjectTypeAnnotation': {
this._insertStruct(
structName,
structContext,
resolveAlias,
typeAnnotation,
);
return wrapNullable(nullable, {
type: 'TypeAliasTypeAnnotation',
name: structName,
});
}
case 'ArrayTypeAnnotation': {
if (typeAnnotation.elementType == null) {
return wrapNullable(nullable, {
type: 'ArrayTypeAnnotation',
});
}
return wrapNullable(nullable, {
type: 'ArrayTypeAnnotation',
elementType: this.process(
structName + 'Element',
structContext,
resolveAlias,
typeAnnotation.elementType,
),
});
}
case 'TypeAliasTypeAnnotation': {
this._insertAlias(typeAnnotation.name, structContext, resolveAlias);
return wrapNullable(nullable, typeAnnotation);
}
case 'EnumDeclaration':
return wrapNullable(nullable, typeAnnotation);
case 'MixedTypeAnnotation':
throw new Error('Mixed types are unsupported in structs');
case 'UnionTypeAnnotation':
throw new Error('Union types are unsupported in structs');
default: {
return wrapNullable(nullable, typeAnnotation);
}
}
}
_insertAlias(aliasName, structContext, resolveAlias) {
const usedStruct = this._structs.get(aliasName);
if (usedStruct == null) {
this._insertStruct(
aliasName,
structContext,
resolveAlias,
resolveAlias(aliasName),
);
} else if (usedStruct.context !== structContext) {
throw new Error(
`Tried to use alias '${aliasName}' in a getConstants() return type and inside a regular struct.`,
);
}
}
_insertStruct(structName, structContext, resolveAlias, objectTypeAnnotation) {
// $FlowFixMe[missing-type-arg]
const properties = objectTypeAnnotation.properties.map(property => {
const propertyStructName = structName + capitalize(property.name);
return _objectSpread(
_objectSpread({}, property),
{},
{
typeAnnotation: this.process(
propertyStructName,
structContext,
resolveAlias,
property.typeAnnotation,
),
},
);
});
switch (structContext) {
case 'REGULAR':
this._structs.set(structName, {
name: structName,
context: 'REGULAR',
properties: properties,
});
break;
case 'CONSTANTS':
this._structs.set(structName, {
name: structName,
context: 'CONSTANTS',
properties: properties,
});
break;
default:
structContext;
throw new Error(`Detected an invalid struct context: ${structContext}`);
}
}
getAllStructs() {
return [...this._structs.values()];
}
getStruct(name) {
return this._structs.get(name);
}
}
module.exports = {
StructCollector,
};

View File

@@ -0,0 +1,207 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {
NativeModuleArrayTypeAnnotation,
NativeModuleBaseTypeAnnotation,
NativeModuleBooleanTypeAnnotation,
NativeModuleDoubleTypeAnnotation,
NativeModuleEnumDeclaration,
NativeModuleFloatTypeAnnotation,
NativeModuleGenericObjectTypeAnnotation,
NativeModuleInt32TypeAnnotation,
NativeModuleNumberTypeAnnotation,
NativeModuleObjectTypeAnnotation,
NativeModuleStringTypeAnnotation,
NativeModuleTypeAliasTypeAnnotation,
Nullable,
ReservedTypeAnnotation,
} from '../../../CodegenSchema';
import type {AliasResolver} from '../Utils';
const {
unwrapNullable,
wrapNullable,
} = require('../../../parsers/parsers-commons');
const {capitalize} = require('../../Utils');
type StructContext = 'CONSTANTS' | 'REGULAR';
export type RegularStruct = $ReadOnly<{
context: 'REGULAR',
name: string,
properties: $ReadOnlyArray<StructProperty>,
}>;
export type ConstantsStruct = $ReadOnly<{
context: 'CONSTANTS',
name: string,
properties: $ReadOnlyArray<StructProperty>,
}>;
export type Struct = RegularStruct | ConstantsStruct;
export type StructProperty = $ReadOnly<{
name: string,
optional: boolean,
typeAnnotation: Nullable<StructTypeAnnotation>,
}>;
export type StructTypeAnnotation =
| NativeModuleStringTypeAnnotation
| NativeModuleNumberTypeAnnotation
| NativeModuleInt32TypeAnnotation
| NativeModuleDoubleTypeAnnotation
| NativeModuleFloatTypeAnnotation
| NativeModuleBooleanTypeAnnotation
| NativeModuleEnumDeclaration
| NativeModuleGenericObjectTypeAnnotation
| ReservedTypeAnnotation
| NativeModuleTypeAliasTypeAnnotation
| NativeModuleArrayTypeAnnotation<Nullable<StructTypeAnnotation>>;
class StructCollector {
_structs: Map<string, Struct> = new Map();
process(
structName: string,
structContext: StructContext,
resolveAlias: AliasResolver,
nullableTypeAnnotation: Nullable<NativeModuleBaseTypeAnnotation>,
): Nullable<StructTypeAnnotation> {
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
switch (typeAnnotation.type) {
case 'ObjectTypeAnnotation': {
this._insertStruct(
structName,
structContext,
resolveAlias,
typeAnnotation,
);
return wrapNullable(nullable, {
type: 'TypeAliasTypeAnnotation',
name: structName,
});
}
case 'ArrayTypeAnnotation': {
if (typeAnnotation.elementType == null) {
return wrapNullable(nullable, {
type: 'ArrayTypeAnnotation',
});
}
return wrapNullable(nullable, {
type: 'ArrayTypeAnnotation',
elementType: this.process(
structName + 'Element',
structContext,
resolveAlias,
typeAnnotation.elementType,
),
});
}
case 'TypeAliasTypeAnnotation': {
this._insertAlias(typeAnnotation.name, structContext, resolveAlias);
return wrapNullable(nullable, typeAnnotation);
}
case 'EnumDeclaration':
return wrapNullable(nullable, typeAnnotation);
case 'MixedTypeAnnotation':
throw new Error('Mixed types are unsupported in structs');
case 'UnionTypeAnnotation':
throw new Error('Union types are unsupported in structs');
default: {
return wrapNullable(nullable, typeAnnotation);
}
}
}
_insertAlias(
aliasName: string,
structContext: StructContext,
resolveAlias: AliasResolver,
): void {
const usedStruct = this._structs.get(aliasName);
if (usedStruct == null) {
this._insertStruct(
aliasName,
structContext,
resolveAlias,
resolveAlias(aliasName),
);
} else if (usedStruct.context !== structContext) {
throw new Error(
`Tried to use alias '${aliasName}' in a getConstants() return type and inside a regular struct.`,
);
}
}
_insertStruct(
structName: string,
structContext: StructContext,
resolveAlias: AliasResolver,
objectTypeAnnotation: NativeModuleObjectTypeAnnotation,
): void {
// $FlowFixMe[missing-type-arg]
const properties = objectTypeAnnotation.properties.map<
$ReadOnly<{
name: string,
optional: boolean,
typeAnnotation: Nullable<StructTypeAnnotation>,
}>,
>(property => {
const propertyStructName = structName + capitalize(property.name);
return {
...property,
typeAnnotation: this.process(
propertyStructName,
structContext,
resolveAlias,
property.typeAnnotation,
),
};
});
switch (structContext) {
case 'REGULAR':
this._structs.set(structName, {
name: structName,
context: 'REGULAR',
properties: properties,
});
break;
case 'CONSTANTS':
this._structs.set(structName, {
name: structName,
context: 'CONSTANTS',
properties: properties,
});
break;
default:
(structContext: empty);
throw new Error(`Detected an invalid struct context: ${structContext}`);
}
}
getAllStructs(): $ReadOnlyArray<Struct> {
return [...this._structs.values()];
}
getStruct(name: string): ?Struct {
return this._structs.get(name);
}
}
module.exports = {
StructCollector,
};

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
function getSafePropertyName(property) {
if (property.name === 'id') {
return `${property.name}_`;
}
return property.name;
}
function getNamespacedStructName(hasteModuleName, structName) {
return `JS::${hasteModuleName}::${structName}`;
}
module.exports = {
getSafePropertyName,
getNamespacedStructName,
};

View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {StructProperty} from './StructCollector';
function getSafePropertyName(property: StructProperty): string {
if (property.name === 'id') {
return `${property.name}_`;
}
return property.name;
}
function getNamespacedStructName(
hasteModuleName: string,
structName: string,
): string {
return `JS::${hasteModuleName}::${structName}`;
}
module.exports = {
getSafePropertyName,
getNamespacedStructName,
};

View File

@@ -0,0 +1,339 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
'use strict';
function _slicedToArray(arr, i) {
return (
_arrayWithHoles(arr) ||
_iterableToArrayLimit(arr, i) ||
_unsupportedIterableToArray(arr, i) ||
_nonIterableRest()
);
}
function _nonIterableRest() {
throw new TypeError(
'Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.',
);
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === 'string') return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === 'Object' && o.constructor) n = o.constructor.name;
if (n === 'Map' || n === 'Set') return Array.from(o);
if (n === 'Arguments' || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))
return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _iterableToArrayLimit(arr, i) {
var _i =
null == arr
? null
: ('undefined' != typeof Symbol && arr[Symbol.iterator]) ||
arr['@@iterator'];
if (null != _i) {
var _s,
_e,
_x,
_r,
_arr = [],
_n = !0,
_d = !1;
try {
if (((_x = (_i = _i.call(arr)).next), 0 === i)) {
if (Object(_i) !== _i) return;
_n = !1;
} else
for (
;
!(_n = (_s = _x.call(_i)).done) &&
(_arr.push(_s.value), _arr.length !== i);
_n = !0
);
} catch (err) {
(_d = !0), (_e = err);
} finally {
try {
if (!_n && null != _i.return && ((_r = _i.return()), Object(_r) !== _r))
return;
} finally {
if (_d) throw _e;
}
}
return _arr;
}
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
const _require = require('../../../../parsers/parsers-commons'),
unwrapNullable = _require.unwrapNullable;
const _require2 = require('../../../TypeUtils/Cxx'),
wrapCxxOptional = _require2.wrapOptional;
const _require3 = require('../../../TypeUtils/Objective-C'),
wrapObjCOptional = _require3.wrapOptional;
const _require4 = require('../../../Utils'),
capitalize = _require4.capitalize;
const _require5 = require('../Utils'),
getNamespacedStructName = _require5.getNamespacedStructName,
getSafePropertyName = _require5.getSafePropertyName;
const StructTemplate = ({
hasteModuleName,
structName,
builderInputProps,
}) => `namespace JS {
namespace ${hasteModuleName} {
struct ${structName} {
struct Builder {
struct Input {
${builderInputProps}
};
/** Initialize with a set of values */
Builder(const Input i);
/** Initialize with an existing ${structName} */
Builder(${structName} i);
/** Builds the object. Generally used only by the infrastructure. */
NSDictionary *buildUnsafeRawValue() const { return _factory(); };
private:
NSDictionary *(^_factory)(void);
};
static ${structName} fromUnsafeRawValue(NSDictionary *const v) { return {v}; }
NSDictionary *unsafeRawValue() const { return _v; }
private:
${structName}(NSDictionary *const v) : _v(v) {}
NSDictionary *_v;
};
}
}`;
const MethodTemplate = ({
hasteModuleName,
structName,
properties,
}) => `inline JS::${hasteModuleName}::${structName}::Builder::Builder(const Input i) : _factory(^{
NSMutableDictionary *d = [NSMutableDictionary new];
${properties}
return d;
}) {}
inline JS::${hasteModuleName}::${structName}::Builder::Builder(${structName} i) : _factory(^{
return i.unsafeRawValue();
}) {}`;
function toObjCType(
hasteModuleName,
nullableTypeAnnotation,
isOptional = false,
) {
const _unwrapNullable = unwrapNullable(nullableTypeAnnotation),
_unwrapNullable2 = _slicedToArray(_unwrapNullable, 2),
typeAnnotation = _unwrapNullable2[0],
nullable = _unwrapNullable2[1];
const isRequired = !nullable && !isOptional;
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return wrapCxxOptional('double', isRequired);
default:
typeAnnotation.name;
throw new Error(`Unknown prop type, found: ${typeAnnotation.name}"`);
}
case 'StringTypeAnnotation':
return 'NSString *';
case 'NumberTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'FloatTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'Int32TypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'DoubleTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'BooleanTypeAnnotation':
return wrapCxxOptional('bool', isRequired);
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'StringTypeAnnotation':
return 'NSString *';
default:
throw new Error(
`Couldn't convert enum into ObjC type: ${typeAnnotation.type}"`,
);
}
case 'GenericObjectTypeAnnotation':
return wrapObjCOptional('id<NSObject>', isRequired);
case 'ArrayTypeAnnotation':
if (typeAnnotation.elementType == null) {
return wrapObjCOptional('id<NSObject>', isRequired);
}
return wrapCxxOptional(
`std::vector<${toObjCType(
hasteModuleName,
typeAnnotation.elementType,
)}>`,
isRequired,
);
case 'TypeAliasTypeAnnotation':
const structName = capitalize(typeAnnotation.name);
const namespacedStructName = getNamespacedStructName(
hasteModuleName,
structName,
);
return wrapCxxOptional(`${namespacedStructName}::Builder`, isRequired);
default:
typeAnnotation.type;
throw new Error(
`Couldn't convert into ObjC type: ${typeAnnotation.type}"`,
);
}
}
function toObjCValue(
hasteModuleName,
nullableTypeAnnotation,
value,
depth,
isOptional = false,
) {
const _unwrapNullable3 = unwrapNullable(nullableTypeAnnotation),
_unwrapNullable4 = _slicedToArray(_unwrapNullable3, 2),
typeAnnotation = _unwrapNullable4[0],
nullable = _unwrapNullable4[1];
const isRequired = !nullable && !isOptional;
function wrapPrimitive(type) {
return !isRequired
? `${value}.has_value() ? @((${type})${value}.value()) : nil`
: `@(${value})`;
}
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return wrapPrimitive('double');
default:
typeAnnotation.name;
throw new Error(
`Couldn't convert into ObjC type: ${typeAnnotation.type}"`,
);
}
case 'StringTypeAnnotation':
return value;
case 'NumberTypeAnnotation':
return wrapPrimitive('double');
case 'FloatTypeAnnotation':
return wrapPrimitive('double');
case 'Int32TypeAnnotation':
return wrapPrimitive('double');
case 'DoubleTypeAnnotation':
return wrapPrimitive('double');
case 'BooleanTypeAnnotation':
return wrapPrimitive('BOOL');
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapPrimitive('double');
case 'StringTypeAnnotation':
return value;
default:
throw new Error(
`Couldn't convert enum into ObjC value: ${typeAnnotation.type}"`,
);
}
case 'GenericObjectTypeAnnotation':
return value;
case 'ArrayTypeAnnotation':
const elementType = typeAnnotation.elementType;
if (elementType == null) {
return value;
}
const localVarName = `el${'_'.repeat(depth + 1)}`;
const elementObjCType = toObjCType(hasteModuleName, elementType);
const elementObjCValue = toObjCValue(
hasteModuleName,
elementType,
localVarName,
depth + 1,
);
const RCTConvertVecToArray = transformer => {
return `RCTConvert${
!isRequired ? 'Optional' : ''
}VecToArray(${value}, ${transformer})`;
};
return RCTConvertVecToArray(
`^id(${elementObjCType} ${localVarName}) { return ${elementObjCValue}; }`,
);
case 'TypeAliasTypeAnnotation':
return !isRequired
? `${value}.has_value() ? ${value}.value().buildUnsafeRawValue() : nil`
: `${value}.buildUnsafeRawValue()`;
default:
typeAnnotation.type;
throw new Error(
`Couldn't convert into ObjC value: ${typeAnnotation.type}"`,
);
}
}
function serializeConstantsStruct(hasteModuleName, struct) {
const declaration = StructTemplate({
hasteModuleName,
structName: struct.name,
builderInputProps: struct.properties
.map(property => {
const typeAnnotation = property.typeAnnotation,
optional = property.optional;
const safePropName = getSafePropertyName(property);
const objCType = toObjCType(hasteModuleName, typeAnnotation, optional);
if (!optional) {
return `RCTRequired<${objCType}> ${safePropName};`;
}
const space = ' '.repeat(objCType.endsWith('*') ? 0 : 1);
return `${objCType}${space}${safePropName};`;
})
.join('\n '),
});
const methods = MethodTemplate({
hasteModuleName,
structName: struct.name,
properties: struct.properties
.map(property => {
const typeAnnotation = property.typeAnnotation,
optional = property.optional,
propName = property.name;
const safePropName = getSafePropertyName(property);
const objCValue = toObjCValue(
hasteModuleName,
typeAnnotation,
safePropName,
0,
optional,
);
let varDecl = `auto ${safePropName} = i.${safePropName}`;
if (!optional) {
varDecl += '.get()';
}
const assignment = `d[@"${propName}"] = ` + objCValue;
return ` ${varDecl};\n ${assignment};`;
})
.join('\n'),
});
return {
declaration,
methods,
};
}
module.exports = {
serializeConstantsStruct,
};

View File

@@ -0,0 +1,289 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
import type {Nullable} from '../../../../CodegenSchema';
import type {ConstantsStruct, StructTypeAnnotation} from '../StructCollector';
import type {StructSerilizationOutput} from './serializeStruct';
const {unwrapNullable} = require('../../../../parsers/parsers-commons');
const {wrapOptional: wrapCxxOptional} = require('../../../TypeUtils/Cxx');
const {
wrapOptional: wrapObjCOptional,
} = require('../../../TypeUtils/Objective-C');
const {capitalize} = require('../../../Utils');
const {getNamespacedStructName, getSafePropertyName} = require('../Utils');
const StructTemplate = ({
hasteModuleName,
structName,
builderInputProps,
}: $ReadOnly<{
hasteModuleName: string,
structName: string,
builderInputProps: string,
}>) => `namespace JS {
namespace ${hasteModuleName} {
struct ${structName} {
struct Builder {
struct Input {
${builderInputProps}
};
/** Initialize with a set of values */
Builder(const Input i);
/** Initialize with an existing ${structName} */
Builder(${structName} i);
/** Builds the object. Generally used only by the infrastructure. */
NSDictionary *buildUnsafeRawValue() const { return _factory(); };
private:
NSDictionary *(^_factory)(void);
};
static ${structName} fromUnsafeRawValue(NSDictionary *const v) { return {v}; }
NSDictionary *unsafeRawValue() const { return _v; }
private:
${structName}(NSDictionary *const v) : _v(v) {}
NSDictionary *_v;
};
}
}`;
const MethodTemplate = ({
hasteModuleName,
structName,
properties,
}: $ReadOnly<{
hasteModuleName: string,
structName: string,
properties: string,
}>) => `inline JS::${hasteModuleName}::${structName}::Builder::Builder(const Input i) : _factory(^{
NSMutableDictionary *d = [NSMutableDictionary new];
${properties}
return d;
}) {}
inline JS::${hasteModuleName}::${structName}::Builder::Builder(${structName} i) : _factory(^{
return i.unsafeRawValue();
}) {}`;
function toObjCType(
hasteModuleName: string,
nullableTypeAnnotation: Nullable<StructTypeAnnotation>,
isOptional: boolean = false,
): string {
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
const isRequired = !nullable && !isOptional;
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return wrapCxxOptional('double', isRequired);
default:
(typeAnnotation.name: empty);
throw new Error(`Unknown prop type, found: ${typeAnnotation.name}"`);
}
case 'StringTypeAnnotation':
return 'NSString *';
case 'NumberTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'FloatTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'Int32TypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'DoubleTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'BooleanTypeAnnotation':
return wrapCxxOptional('bool', isRequired);
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'StringTypeAnnotation':
return 'NSString *';
default:
throw new Error(
`Couldn't convert enum into ObjC type: ${typeAnnotation.type}"`,
);
}
case 'GenericObjectTypeAnnotation':
return wrapObjCOptional('id<NSObject>', isRequired);
case 'ArrayTypeAnnotation':
if (typeAnnotation.elementType == null) {
return wrapObjCOptional('id<NSObject>', isRequired);
}
return wrapCxxOptional(
`std::vector<${toObjCType(
hasteModuleName,
typeAnnotation.elementType,
)}>`,
isRequired,
);
case 'TypeAliasTypeAnnotation':
const structName = capitalize(typeAnnotation.name);
const namespacedStructName = getNamespacedStructName(
hasteModuleName,
structName,
);
return wrapCxxOptional(`${namespacedStructName}::Builder`, isRequired);
default:
(typeAnnotation.type: empty);
throw new Error(
`Couldn't convert into ObjC type: ${typeAnnotation.type}"`,
);
}
}
function toObjCValue(
hasteModuleName: string,
nullableTypeAnnotation: Nullable<StructTypeAnnotation>,
value: string,
depth: number,
isOptional: boolean = false,
): string {
const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation);
const isRequired = !nullable && !isOptional;
function wrapPrimitive(type: string) {
return !isRequired
? `${value}.has_value() ? @((${type})${value}.value()) : nil`
: `@(${value})`;
}
switch (typeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (typeAnnotation.name) {
case 'RootTag':
return wrapPrimitive('double');
default:
(typeAnnotation.name: empty);
throw new Error(
`Couldn't convert into ObjC type: ${typeAnnotation.type}"`,
);
}
case 'StringTypeAnnotation':
return value;
case 'NumberTypeAnnotation':
return wrapPrimitive('double');
case 'FloatTypeAnnotation':
return wrapPrimitive('double');
case 'Int32TypeAnnotation':
return wrapPrimitive('double');
case 'DoubleTypeAnnotation':
return wrapPrimitive('double');
case 'BooleanTypeAnnotation':
return wrapPrimitive('BOOL');
case 'EnumDeclaration':
switch (typeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrapPrimitive('double');
case 'StringTypeAnnotation':
return value;
default:
throw new Error(
`Couldn't convert enum into ObjC value: ${typeAnnotation.type}"`,
);
}
case 'GenericObjectTypeAnnotation':
return value;
case 'ArrayTypeAnnotation':
const {elementType} = typeAnnotation;
if (elementType == null) {
return value;
}
const localVarName = `el${'_'.repeat(depth + 1)}`;
const elementObjCType = toObjCType(hasteModuleName, elementType);
const elementObjCValue = toObjCValue(
hasteModuleName,
elementType,
localVarName,
depth + 1,
);
const RCTConvertVecToArray = (transformer: string) => {
return `RCTConvert${
!isRequired ? 'Optional' : ''
}VecToArray(${value}, ${transformer})`;
};
return RCTConvertVecToArray(
`^id(${elementObjCType} ${localVarName}) { return ${elementObjCValue}; }`,
);
case 'TypeAliasTypeAnnotation':
return !isRequired
? `${value}.has_value() ? ${value}.value().buildUnsafeRawValue() : nil`
: `${value}.buildUnsafeRawValue()`;
default:
(typeAnnotation.type: empty);
throw new Error(
`Couldn't convert into ObjC value: ${typeAnnotation.type}"`,
);
}
}
function serializeConstantsStruct(
hasteModuleName: string,
struct: ConstantsStruct,
): StructSerilizationOutput {
const declaration = StructTemplate({
hasteModuleName,
structName: struct.name,
builderInputProps: struct.properties
.map(property => {
const {typeAnnotation, optional} = property;
const safePropName = getSafePropertyName(property);
const objCType = toObjCType(hasteModuleName, typeAnnotation, optional);
if (!optional) {
return `RCTRequired<${objCType}> ${safePropName};`;
}
const space = ' '.repeat(objCType.endsWith('*') ? 0 : 1);
return `${objCType}${space}${safePropName};`;
})
.join('\n '),
});
const methods = MethodTemplate({
hasteModuleName,
structName: struct.name,
properties: struct.properties
.map(property => {
const {typeAnnotation, optional, name: propName} = property;
const safePropName = getSafePropertyName(property);
const objCValue = toObjCValue(
hasteModuleName,
typeAnnotation,
safePropName,
0,
optional,
);
let varDecl = `auto ${safePropName} = i.${safePropName}`;
if (!optional) {
varDecl += '.get()';
}
const assignment = `d[@"${propName}"] = ` + objCValue;
return ` ${varDecl};\n ${assignment};`;
})
.join('\n'),
});
return {declaration, methods};
}
module.exports = {
serializeConstantsStruct,
};

Some files were not shown because too many files have changed in this diff Show More