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