create-expo-cljs-app
Version:
Create a react native application with Expo and Shadow-CLJS!
235 lines (210 loc) • 6.46 kB
Flow
/**
* Copyright (c) Facebook, Inc. and its 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
*/
;
import type {
SchemaType,
Nullable,
NamedShape,
NativeModulePropertyShape,
NativeModuleFunctionTypeAnnotation,
NativeModuleParamTypeAnnotation,
} from '../../CodegenSchema';
import type {AliasResolver} from './Utils';
const {createAliasResolver, getModules} = require('./Utils');
const {unwrapNullable} = require('../../parsers/flow/modules/utils');
type FilesOutput = Map<string, string>;
const HostFunctionTemplate = ({
hasteModuleName,
methodName,
isVoid,
args,
}: $ReadOnly<{
hasteModuleName: string,
methodName: string,
isVoid: boolean,
args: Array<string>,
}>) => {
const methodCallArgs = ['rt', ...args].join(', ');
const methodCall = `static_cast<${hasteModuleName}CxxSpecJSI *>(&turboModule)->${methodName}(${methodCallArgs});`;
return `static jsi::Value __hostFunction_${hasteModuleName}CxxSpecJSI_${methodName}(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {${
isVoid ? `\n ${methodCall}` : ''
}
return ${isVoid ? 'jsi::Value::undefined();' : methodCall}
}`;
};
const ModuleTemplate = ({
hasteModuleName,
hostFunctions,
moduleName,
methods,
}: $ReadOnly<{
hasteModuleName: string,
hostFunctions: $ReadOnlyArray<string>,
moduleName: string,
methods: $ReadOnlyArray<$ReadOnly<{methodName: string, paramCount: number}>>,
}>) => {
return `${hostFunctions.join('\n')}
${hasteModuleName}CxxSpecJSI::${hasteModuleName}CxxSpecJSI(std::shared_ptr<CallInvoker> jsInvoker)
: TurboModule("${moduleName}", jsInvoker) {
${methods
.map(({methodName, paramCount}) => {
return ` methodMap_["${methodName}"] = MethodMetadata {${paramCount}, __hostFunction_${hasteModuleName}CxxSpecJSI_${methodName}};`;
})
.join('\n')}
}`;
};
const FileTemplate = ({
libraryName,
modules,
}: $ReadOnly<{
libraryName: string,
modules: string,
}>) => {
return `/**
* ${'C'}opyright (c) Facebook, Inc. and its 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: GenerateModuleH.js
*/
#include <react/modules/${libraryName}/NativeModules.h>
namespace facebook {
namespace react {
${modules}
} // namespace react
} // namespace facebook
`;
};
type Param = NamedShape<Nullable<NativeModuleParamTypeAnnotation>>;
function serializeArg(
arg: Param,
index: number,
resolveAlias: AliasResolver,
): string {
function wrap(suffix) {
return `args[${index}]${suffix}`;
}
const {typeAnnotation: nullableTypeAnnotation} = arg;
const [typeAnnotation] = unwrapNullable<NativeModuleParamTypeAnnotation>(
nullableTypeAnnotation,
);
let realTypeAnnotation = typeAnnotation;
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
}
switch (realTypeAnnotation.type) {
case 'ReservedTypeAnnotation':
switch (realTypeAnnotation.name) {
case 'RootTag':
return wrap('.getNumber()');
default:
(realTypeAnnotation.name: empty);
throw new Error(
`Unknown prop type for "${arg.name}, found: ${realTypeAnnotation.name}"`,
);
}
case 'StringTypeAnnotation':
return wrap('.getString(rt)');
case 'BooleanTypeAnnotation':
return wrap('.getBool()');
case 'NumberTypeAnnotation':
return wrap('.getNumber()');
case 'FloatTypeAnnotation':
return wrap('.getNumber()');
case 'DoubleTypeAnnotation':
return wrap('.getNumber()');
case 'Int32TypeAnnotation':
return wrap('.getNumber()');
case 'ArrayTypeAnnotation':
return wrap('.getObject(rt).getArray(rt)');
case 'FunctionTypeAnnotation':
return `std::move(${wrap('.getObject(rt).getFunction(rt)')})`;
case 'GenericObjectTypeAnnotation':
return wrap('.getObject(rt)');
case 'ObjectTypeAnnotation':
return wrap('.getObject(rt)');
default:
(realTypeAnnotation.type: empty);
throw new Error(
`Unknown prop type for "${arg.name}, found: ${realTypeAnnotation.type}"`,
);
}
}
function serializePropertyIntoHostFunction(
hasteModuleName: string,
property: NativeModulePropertyShape,
resolveAlias: AliasResolver,
): string {
const [
propertyTypeAnnotation,
] = unwrapNullable<NativeModuleFunctionTypeAnnotation>(
property.typeAnnotation,
);
const isVoid =
propertyTypeAnnotation.returnTypeAnnotation.type === 'VoidTypeAnnotation';
return HostFunctionTemplate({
hasteModuleName,
methodName: property.name,
isVoid,
args: propertyTypeAnnotation.params.map((p, i) =>
serializeArg(p, i, resolveAlias),
),
});
}
module.exports = {
generate(
libraryName: string,
schema: SchemaType,
moduleSpecName: string,
packageName?: string,
): FilesOutput {
const nativeModules = getModules(schema);
const modules = Object.keys(nativeModules)
.map((hasteModuleName: string) => {
const nativeModule = nativeModules[hasteModuleName];
const {
aliases,
spec: {properties},
moduleNames,
} = nativeModule;
const resolveAlias = createAliasResolver(aliases);
const hostFunctions = properties.map(property =>
serializePropertyIntoHostFunction(
hasteModuleName,
property,
resolveAlias,
),
);
return ModuleTemplate({
hasteModuleName,
hostFunctions,
// TODO: What happens when there are more than one NativeModule requires?
moduleName: moduleNames[0],
methods: properties.map(
({name: propertyName, typeAnnotation: nullableTypeAnnotation}) => {
const [{params}] = unwrapNullable(nullableTypeAnnotation);
return {
methodName: propertyName,
paramCount: params.length,
};
},
),
});
})
.join('\n');
const fileName = 'NativeModules.cpp';
const replacedTemplate = FileTemplate({
modules,
libraryName,
});
return new Map([[fileName, replacedTemplate]]);
},
};