UNPKG

@react-native/codegen

Version:
647 lines (632 loc) • 23.1 kB
/** * 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(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; }, };