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,21 @@
# @react-native/assets-registry
[![Version][version-badge]][package]
## Installation
```
yarn add --dev @react-native/assets-registry
```
*Note: We're using `yarn` to install deps. Feel free to change commands to use `npm` 3+ and `npx` if you like*
[version-badge]: https://img.shields.io/npm/v/@react-native/assets-registry?style=flat-square
[package]: https://www.npmjs.com/package/@react-native/assets-registry
## Testing
To run the tests in this package, run the following commands from the React Native root folder:
1. `yarn` to install the dependencies. You just need to run this once
2. `yarn jest packages/assets`.

View File

@@ -0,0 +1,22 @@
{
"name": "@react-native/assets-registry",
"version": "0.74.81",
"description": "Asset support code for React Native.",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/facebook/react-native.git",
"directory": "packages/assets"
},
"homepage": "https://github.com/facebook/react-native/tree/HEAD/packages/assets#readme",
"keywords": [
"assets",
"registry",
"react-native",
"support"
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">=18"
}
}

View File

@@ -0,0 +1,92 @@
/**
* 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
* @flow strict
*/
'use strict';
import type {PackagerAsset} from './registry.js';
const androidScaleSuffix = {
'0.75': 'ldpi',
'1': 'mdpi',
'1.5': 'hdpi',
'2': 'xhdpi',
'3': 'xxhdpi',
'4': 'xxxhdpi',
};
const ANDROID_BASE_DENSITY = 160;
/**
* FIXME: using number to represent discrete scale numbers is fragile in essence because of
* floating point numbers imprecision.
*/
function getAndroidAssetSuffix(scale: number): string {
if (scale.toString() in androidScaleSuffix) {
return androidScaleSuffix[scale.toString()];
}
// NOTE: Android Gradle Plugin does not fully support the nnndpi format.
// See https://issuetracker.google.com/issues/72884435
if (Number.isFinite(scale) && scale > 0) {
return Math.round(scale * ANDROID_BASE_DENSITY) + 'dpi';
}
throw new Error('no such scale ' + scale.toString());
}
// See https://developer.android.com/guide/topics/resources/drawable-resource.html
const drawableFileTypes = new Set([
'gif',
'jpeg',
'jpg',
'ktx',
'png',
'svg',
'webp',
'xml',
]);
function getAndroidResourceFolderName(
asset: PackagerAsset,
scale: number,
): string | $TEMPORARY$string<'raw'> {
if (!drawableFileTypes.has(asset.type)) {
return 'raw';
}
const suffix = getAndroidAssetSuffix(scale);
if (!suffix) {
throw new Error(
"Don't know which android drawable suffix to use for scale: " +
scale +
'\nAsset: ' +
JSON.stringify(asset, null, '\t') +
'\nPossible scales are:' +
JSON.stringify(androidScaleSuffix, null, '\t'),
);
}
return 'drawable-' + suffix;
}
function getAndroidResourceIdentifier(asset: PackagerAsset): string {
return (getBasePath(asset) + '/' + asset.name)
.toLowerCase()
.replace(/\//g, '_') // Encode folder structure in file name
.replace(/([^a-z0-9_])/g, '') // Remove illegal chars
.replace(/^assets_/, ''); // Remove "assets_" prefix
}
function getBasePath(asset: PackagerAsset): string {
const basePath = asset.httpServerLocation;
return basePath.startsWith('/') ? basePath.slice(1) : basePath;
}
module.exports = {
getAndroidResourceFolderName,
getAndroidResourceIdentifier,
getBasePath,
};

View File

@@ -0,0 +1,38 @@
/**
* 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 PackagerAsset = {
+__packager_asset: boolean,
+fileSystemLocation: string,
+httpServerLocation: string,
+width: ?number,
+height: ?number,
+scales: Array<number>,
+hash: string,
+name: string,
+type: string,
...
};
const assets: Array<PackagerAsset> = [];
function registerAsset(asset: PackagerAsset): number {
// `push` returns new array length, so the first asset will
// get id 1 (not 0) to make the value truthy
return assets.push(asset);
}
function getAssetByID(assetId: number): PackagerAsset {
return assets[assetId - 1];
}
module.exports = {registerAsset, getAssetByID};

View File

@@ -0,0 +1,21 @@
# @react-native/babel-plugin-codegen
[![Version][version-badge]][package]
## Installation
```
yarn add --dev @babel/core @react-native/babel-plugin-codegen
```
*Note: We're using `yarn` to install deps. Feel free to change commands to use `npm` 3+ and `npx` if you like*
[version-badge]: https://img.shields.io/npm/v/@react-native/babel-plugin-codegen?style=flat-square
[package]: https://www.npmjs.com/package/@react-native/babel-plugin-codegen
## Testing
To run the tests in this package, run the following commands from the React Native root folder:
1. `yarn` to install the dependencies. You just need to run this once
2. `yarn jest packages/babel-plugin-codegen`.

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.
*
* @format
*/
'use strict';
let FlowParser, TypeScriptParser, RNCodegen;
const {basename} = require('path');
try {
FlowParser =
require('@react-native/codegen/src/parsers/flow/parser').FlowParser;
TypeScriptParser =
require('@react-native/codegen/src/parsers/typescript/parser').TypeScriptParser;
RNCodegen = require('@react-native/codegen/src/generators/RNCodegen');
} catch (e) {
// Fallback to lib when source doesn't exit (e.g. when installed as a dev dependency)
FlowParser =
require('@react-native/codegen/lib/parsers/flow/parser').FlowParser;
TypeScriptParser =
require('@react-native/codegen/lib/parsers/typescript/parser').TypeScriptParser;
RNCodegen = require('@react-native/codegen/lib/generators/RNCodegen');
}
const flowParser = new FlowParser();
const typeScriptParser = new TypeScriptParser();
function parseFile(filename, code) {
if (filename.endsWith('js')) {
return flowParser.parseString(code);
}
if (filename.endsWith('ts')) {
return typeScriptParser.parseString(code);
}
throw new Error(
`Unable to parse file '${filename}'. Unsupported filename extension.`,
);
}
function generateViewConfig(filename, code) {
const schema = parseFile(filename, code);
const libraryName = basename(filename).replace(
/NativeComponent\.(js|ts)$/,
'',
);
return RNCodegen.generateViewConfig({
schema,
libraryName,
});
}
function isCodegenDeclaration(declaration) {
if (!declaration) {
return false;
}
if (
declaration.left &&
declaration.left.left &&
declaration.left.left.name === 'codegenNativeComponent'
) {
return true;
} else if (
declaration.callee &&
declaration.callee.name &&
declaration.callee.name === 'codegenNativeComponent'
) {
return true;
} else if (
(declaration.type === 'TypeCastExpression' ||
declaration.type === 'AsExpression') &&
declaration.expression &&
declaration.expression.callee &&
declaration.expression.callee.name &&
declaration.expression.callee.name === 'codegenNativeComponent'
) {
return true;
} else if (
declaration.type === 'TSAsExpression' &&
declaration.expression &&
declaration.expression.callee &&
declaration.expression.callee.name &&
declaration.expression.callee.name === 'codegenNativeComponent'
) {
return true;
}
return false;
}
module.exports = function ({parse, types: t}) {
return {
pre(state) {
this.code = state.code;
this.filename = state.opts.filename;
this.defaultExport = null;
this.commandsExport = null;
this.codeInserted = false;
},
visitor: {
ExportNamedDeclaration(path) {
if (this.codeInserted) {
return;
}
if (
path.node.declaration &&
path.node.declaration.declarations &&
path.node.declaration.declarations[0]
) {
const firstDeclaration = path.node.declaration.declarations[0];
if (firstDeclaration.type === 'VariableDeclarator') {
if (
firstDeclaration.init &&
firstDeclaration.init.type === 'CallExpression' &&
firstDeclaration.init.callee.type === 'Identifier' &&
firstDeclaration.init.callee.name === 'codegenNativeCommands'
) {
if (
firstDeclaration.id.type === 'Identifier' &&
firstDeclaration.id.name !== 'Commands'
) {
throw path.buildCodeFrameError(
"Native commands must be exported with the name 'Commands'",
);
}
this.commandsExport = path;
return;
} else {
if (firstDeclaration.id.name === 'Commands') {
throw path.buildCodeFrameError(
"'Commands' is a reserved export and may only be used to export the result of codegenNativeCommands.",
);
}
}
}
} else if (path.node.specifiers && path.node.specifiers.length > 0) {
path.node.specifiers.forEach(specifier => {
if (
specifier.type === 'ExportSpecifier' &&
specifier.local.type === 'Identifier' &&
specifier.local.name === 'Commands'
) {
throw path.buildCodeFrameError(
"'Commands' is a reserved export and may only be used to export the result of codegenNativeCommands.",
);
}
});
}
},
ExportDefaultDeclaration(path, state) {
if (isCodegenDeclaration(path.node.declaration)) {
this.defaultExport = path;
}
},
Program: {
exit(path) {
if (this.defaultExport) {
const viewConfig = generateViewConfig(this.filename, this.code);
this.defaultExport.replaceWithMultiple(
parse(viewConfig, {
babelrc: false,
browserslistConfigFile: false,
configFile: false,
}).program.body,
);
if (this.commandsExport != null) {
this.commandsExport.remove();
}
this.codeInserted = true;
}
},
},
},
};
};

View File

@@ -0,0 +1,33 @@
{
"name": "@react-native/babel-plugin-codegen",
"version": "0.74.87",
"description": "Babel plugin to generate native module and view manager code for React Native.",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/facebook/react-native.git",
"directory": "packages/babel-plugin-codegen"
},
"homepage": "https://github.com/facebook/react-native/tree/HEAD/packages/babel-plugin-codegen#readme",
"keywords": [
"babel",
"plugin",
"codegen",
"react-native",
"native-modules",
"view-manager"
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">=18"
},
"files": [
"index.js"
],
"dependencies": {
"@react-native/codegen": "0.74.87"
},
"devDependencies": {
"@babel/core": "^7.20.0"
}
}

View File

@@ -0,0 +1,41 @@
# @react-native/babel-preset
Babel presets for [React Native](https://reactnative.dev) applications. React Native itself uses this Babel preset by default when transforming your app's source code.
If you wish to use a custom Babel configuration by writing a `babel.config.js` file in your project's root directory, you must specify all the plugins necessary to transform your code. React Native does not apply its default Babel configuration in this case. So, to make your life easier, you can use this preset to get the default configuration and then specify more plugins that run before it.
## Usage
As mentioned above, you only need to use this preset if you are writing a custom `babel.config.js` file.
### Installation
Install `@react-native/babel-preset` in your app:
with `npm`:
```sh
npm i @react-native/babel-preset --save-dev
```
or with `yarn`:
```sh
yarn add -D @react-native/babel-preset
```
### Configuring Babel
Then, create a file called `babel.config.js` in your project's root directory. The existence of this `babel.config.js` file will tell React Native to use your custom Babel configuration instead of its own. Then load this preset:
```
{
"presets": ["module:@react-native/babel-preset"]
}
```
You can further customize your Babel configuration by specifying plugins and other options. See [Babel's `babel.config.js` documentation](https://babeljs.io/docs/en/config-files/) to learn more.
## Help and Support
If you get stuck configuring Babel, please ask a question on Stack Overflow or find a consultant for help. If you discover a bug, please open up an issue.

View File

@@ -0,0 +1,67 @@
{
"name": "@react-native/babel-preset",
"version": "0.74.87",
"description": "Babel preset for React Native applications",
"main": "src/index.js",
"repository": {
"type": "git",
"url": "git@github.com:facebook/react-native.git"
},
"keywords": [
"babel",
"preset",
"react-native"
],
"license": "MIT",
"dependencies": {
"@babel/core": "^7.20.0",
"@babel/plugin-proposal-async-generator-functions": "^7.0.0",
"@babel/plugin-proposal-class-properties": "^7.18.0",
"@babel/plugin-proposal-export-default-from": "^7.0.0",
"@babel/plugin-proposal-logical-assignment-operators": "^7.18.0",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.0",
"@babel/plugin-proposal-numeric-separator": "^7.0.0",
"@babel/plugin-proposal-object-rest-spread": "^7.20.0",
"@babel/plugin-proposal-optional-catch-binding": "^7.0.0",
"@babel/plugin-proposal-optional-chaining": "^7.20.0",
"@babel/plugin-syntax-dynamic-import": "^7.8.0",
"@babel/plugin-syntax-export-default-from": "^7.0.0",
"@babel/plugin-syntax-flow": "^7.18.0",
"@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0",
"@babel/plugin-syntax-optional-chaining": "^7.0.0",
"@babel/plugin-transform-arrow-functions": "^7.0.0",
"@babel/plugin-transform-async-to-generator": "^7.20.0",
"@babel/plugin-transform-block-scoping": "^7.0.0",
"@babel/plugin-transform-classes": "^7.0.0",
"@babel/plugin-transform-computed-properties": "^7.0.0",
"@babel/plugin-transform-destructuring": "^7.20.0",
"@babel/plugin-transform-flow-strip-types": "^7.20.0",
"@babel/plugin-transform-function-name": "^7.0.0",
"@babel/plugin-transform-literals": "^7.0.0",
"@babel/plugin-transform-modules-commonjs": "^7.0.0",
"@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0",
"@babel/plugin-transform-parameters": "^7.0.0",
"@babel/plugin-transform-private-methods": "^7.22.5",
"@babel/plugin-transform-private-property-in-object": "^7.22.11",
"@babel/plugin-transform-react-display-name": "^7.0.0",
"@babel/plugin-transform-react-jsx": "^7.0.0",
"@babel/plugin-transform-react-jsx-self": "^7.0.0",
"@babel/plugin-transform-react-jsx-source": "^7.0.0",
"@babel/plugin-transform-runtime": "^7.0.0",
"@babel/plugin-transform-shorthand-properties": "^7.0.0",
"@babel/plugin-transform-spread": "^7.0.0",
"@babel/plugin-transform-sticky-regex": "^7.0.0",
"@babel/plugin-transform-typescript": "^7.5.0",
"@babel/plugin-transform-unicode-regex": "^7.0.0",
"@babel/template": "^7.0.0",
"@react-native/babel-plugin-codegen": "0.74.87",
"babel-plugin-transform-flow-enums": "^0.0.2",
"react-refresh": "^0.14.0"
},
"peerDependencies": {
"@babel/core": "*"
},
"engines": {
"node": ">=18"
}
}

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
*/
'use strict';
module.exports = function () {
return {
plugins: [require('react-refresh/babel')],
};
};

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.
*
* @format
* @oncall react_native
*/
// This is the set of modules that React Native publicly exports and that we
// want to require lazily. Keep this list in sync with
// react-native/index.js (though having extra entries here is fairly harmless).
'use strict';
module.exports = new Set([
'AccessibilityInfo',
'ActivityIndicator',
'Button',
'DatePickerIOS',
'DrawerLayoutAndroid',
'FlatList',
'Image',
'ImageBackground',
'InputAccessoryView',
'KeyboardAvoidingView',
'Modal',
'Pressable',
'ProgressBarAndroid',
'ProgressViewIOS',
'SafeAreaView',
'ScrollView',
'SectionList',
'Slider',
'Switch',
'RefreshControl',
'StatusBar',
'Text',
'TextInput',
'Touchable',
'TouchableHighlight',
'TouchableNativeFeedback',
'TouchableOpacity',
'TouchableWithoutFeedback',
'View',
'VirtualizedList',
'VirtualizedSectionList',
// APIs
'ActionSheetIOS',
'Alert',
'Animated',
'Appearance',
'AppRegistry',
'AppState',
'AsyncStorage',
'BackHandler',
'Clipboard',
'DeviceInfo',
'Dimensions',
'Easing',
'ReactNative',
'I18nManager',
'InteractionManager',
'Keyboard',
'LayoutAnimation',
'Linking',
'LogBox',
'NativeEventEmitter',
'PanResponder',
'PermissionsAndroid',
'PixelRatio',
'PushNotificationIOS',
'Settings',
'Share',
'StyleSheet',
'Systrace',
'ToastAndroid',
'TVEventHandler',
'UIManager',
'ReactNative',
'UTFSequence',
'Vibration',
// Plugins
'RCTDeviceEventEmitter',
'RCTNativeAppEventEmitter',
'NativeModules',
'Platform',
'processColor',
'requireNativeComponent',
]);

View File

@@ -0,0 +1,235 @@
/**
* 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 passthroughSyntaxPlugins = require('../passthrough-syntax-plugins');
const lazyImports = require('./lazy-imports');
function isTypeScriptSource(fileName) {
return !!fileName && fileName.endsWith('.ts');
}
function isTSXSource(fileName) {
return !!fileName && fileName.endsWith('.tsx');
}
// use `this.foo = bar` instead of `this.defineProperty('foo', ...)`
const loose = true;
const defaultPlugins = [
[require('@babel/plugin-syntax-flow')],
[require('babel-plugin-transform-flow-enums')],
[require('@babel/plugin-transform-block-scoping')],
[require('@babel/plugin-proposal-class-properties'), {loose}],
[require('@babel/plugin-transform-private-methods'), {loose}],
[require('@babel/plugin-transform-private-property-in-object'), {loose}],
[require('@babel/plugin-syntax-dynamic-import')],
[require('@babel/plugin-syntax-export-default-from')],
...passthroughSyntaxPlugins,
[require('@babel/plugin-transform-unicode-regex')],
];
const getPreset = (src, options) => {
const transformProfile =
(options && options.unstable_transformProfile) || 'default';
const isHermesStable = transformProfile === 'hermes-stable';
const isHermesCanary = transformProfile === 'hermes-canary';
const isHermes = isHermesStable || isHermesCanary;
const isNull = src == null;
const hasClass = isNull || src.indexOf('class') !== -1;
const extraPlugins = [];
if (!options.useTransformReactJSXExperimental) {
extraPlugins.push([
require('@babel/plugin-transform-react-jsx'),
{runtime: 'automatic'},
]);
}
if (
!options.disableStaticViewConfigsCodegen &&
(src === null || /\bcodegenNativeComponent</.test(src))
) {
extraPlugins.push([require('@react-native/babel-plugin-codegen')]);
}
if (!options || !options.disableImportExportTransform) {
extraPlugins.push(
[require('@babel/plugin-proposal-export-default-from')],
[
require('@babel/plugin-transform-modules-commonjs'),
{
strict: false,
strictMode: false, // prevent "use strict" injections
lazy:
options && options.lazyImportExportTransform != null
? options.lazyImportExportTransform
: importSpecifier => lazyImports.has(importSpecifier),
allowTopLevelThis: true, // dont rewrite global `this` -> `undefined`
},
],
);
}
if (hasClass) {
extraPlugins.push([require('@babel/plugin-transform-classes')]);
}
// TODO(gaearon): put this back into '=>' indexOf bailout
// and patch react-refresh to not depend on this transform.
extraPlugins.push([require('@babel/plugin-transform-arrow-functions')]);
if (!isHermes) {
extraPlugins.push([require('@babel/plugin-transform-computed-properties')]);
extraPlugins.push([require('@babel/plugin-transform-parameters')]);
extraPlugins.push([
require('@babel/plugin-transform-shorthand-properties'),
]);
extraPlugins.push([
require('@babel/plugin-proposal-optional-catch-binding'),
]);
extraPlugins.push([require('@babel/plugin-transform-function-name')]);
extraPlugins.push([require('@babel/plugin-transform-literals')]);
extraPlugins.push([require('@babel/plugin-proposal-numeric-separator')]);
extraPlugins.push([require('@babel/plugin-transform-sticky-regex')]);
} else {
extraPlugins.push([
require('@babel/plugin-transform-named-capturing-groups-regex'),
]);
}
if (!isHermesCanary) {
extraPlugins.push([
require('@babel/plugin-transform-destructuring'),
{useBuiltIns: true},
]);
}
if (!isHermes && (isNull || hasClass || src.indexOf('...') !== -1)) {
extraPlugins.push(
[require('@babel/plugin-transform-spread')],
[
require('@babel/plugin-proposal-object-rest-spread'),
// Assume no dependence on getters or evaluation order. See https://github.com/babel/babel/pull/11520
{loose: true, useBuiltIns: true},
],
);
}
if (isNull || src.indexOf('async') !== -1) {
extraPlugins.push([
require('@babel/plugin-proposal-async-generator-functions'),
]);
extraPlugins.push([require('@babel/plugin-transform-async-to-generator')]);
}
if (
isNull ||
src.indexOf('React.createClass') !== -1 ||
src.indexOf('createReactClass') !== -1
) {
extraPlugins.push([require('@babel/plugin-transform-react-display-name')]);
}
if (!isHermes && (isNull || src.indexOf('?.') !== -1)) {
extraPlugins.push([
require('@babel/plugin-proposal-optional-chaining'),
{loose: true},
]);
}
if (!isHermes && (isNull || src.indexOf('??') !== -1)) {
extraPlugins.push([
require('@babel/plugin-proposal-nullish-coalescing-operator'),
{loose: true},
]);
}
if (
!isHermes &&
(isNull ||
src.indexOf('??=') !== -1 ||
src.indexOf('||=') !== -1 ||
src.indexOf('&&=') !== -1)
) {
extraPlugins.push([
require('@babel/plugin-proposal-logical-assignment-operators'),
{loose: true},
]);
}
if (options && options.dev && !options.useTransformReactJSXExperimental) {
extraPlugins.push([require('@babel/plugin-transform-react-jsx-source')]);
extraPlugins.push([require('@babel/plugin-transform-react-jsx-self')]);
}
if (!options || options.enableBabelRuntime !== false) {
// Allows configuring a specific runtime version to optimize output
const isVersion = typeof options?.enableBabelRuntime === 'string';
extraPlugins.push([
require('@babel/plugin-transform-runtime'),
{
helpers: true,
regenerator: !isHermes,
...(isVersion && {version: options.enableBabelRuntime}),
},
]);
}
return {
comments: false,
compact: true,
overrides: [
// the flow strip types plugin must go BEFORE class properties!
// there'll be a test case that fails if you don't.
{
plugins: [require('@babel/plugin-transform-flow-strip-types')],
},
{
plugins: defaultPlugins,
},
{
test: isTypeScriptSource,
plugins: [
[
require('@babel/plugin-transform-typescript'),
{
isTSX: false,
allowNamespaces: true,
},
],
],
},
{
test: isTSXSource,
plugins: [
[
require('@babel/plugin-transform-typescript'),
{
isTSX: true,
allowNamespaces: true,
},
],
],
},
{
plugins: extraPlugins,
},
],
};
};
module.exports = options => {
if (options.withDevTools == null) {
const env = process.env.BABEL_ENV || process.env.NODE_ENV;
if (!env || env === 'development') {
return getPreset(null, {...options, dev: true});
}
}
return getPreset(null, options);
};
module.exports.getPreset = getPreset;

View File

@@ -0,0 +1,20 @@
/**
* 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 main = require('./configs/main');
module.exports = function (babel, options) {
return main(options);
};
module.exports.getPreset = main.getPreset;
module.exports.passthroughSyntaxPlugins = require('./passthrough-syntax-plugins');

View File

@@ -0,0 +1,23 @@
/**
* 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';
// This list of syntax plugins is used for two purposes:
// 1. Enabling experimental syntax features in the INPUT to the Metro Babel
// transformer, regardless of whether we actually transform them.
// 2. Enabling these same features in parser passes that consume the OUTPUT of
// the Metro Babel transformer.
const passthroughSyntaxPlugins = [
[require('@babel/plugin-syntax-nullish-coalescing-operator')],
[require('@babel/plugin-syntax-optional-chaining')],
];
module.exports = passthroughSyntaxPlugins;

View File

@@ -0,0 +1,21 @@
# @react-native/codegen
[![Version][version-badge]][package]
## Installation
```
yarn add --dev @react-native/codegen
```
*Note: We're using `yarn` to install deps. Feel free to change commands to use `npm` 3+ and `npx` if you like*
[version-badge]: https://img.shields.io/npm/v/@react-native/codegen?style=flat-square
[package]: https://www.npmjs.com/package/@react-native/codegen
## Testing
To run the tests in this package, run the following commands from the React Native root folder:
1. `yarn` to install the dependencies. You just need to run this once
2. `yarn jest packages/react-native-codegen`.

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]]);
},
};

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