UNPKG

@react-native/codegen

Version:
466 lines (457 loc) • 15.2 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'; const { unwrapNullable, wrapNullable, } = require('../../../parsers/parsers-commons'); const {wrapOptional} = require('../../TypeUtils/Objective-C'); const {capitalize} = require('../../Utils'); const {getNamespacedStructName} = require('./Utils'); const invariant = require('invariant'); const ProtocolMethodTemplate = ({returnObjCType, methodName, params}) => `- (${returnObjCType})${methodName}${params};`; function serializeMethod( hasteModuleName, property, structCollector, resolveAlias, ) { const {name: methodName, typeAnnotation: nullableTypeAnnotation} = property; const [propertyTypeAnnotation] = unwrapNullable(nullableTypeAnnotation); const {params} = propertyTypeAnnotation; if (methodName === 'getConstants') { return serializeConstantsProtocolMethods( hasteModuleName, property, structCollector, resolveAlias, ); } const methodParams = []; const structParamRecords = []; params.forEach((param, index) => { const structName = getParamStructName(methodName, param); const {objCType, isStruct} = getParamObjCType( hasteModuleName, methodName, param, structName, structCollector, resolveAlias, ); methodParams.push({ paramName: param.name, objCType, }); if (isStruct) { structParamRecords.push({ paramIndex: index, structName, }); } }); // Unwrap returnTypeAnnotation, so we check if the return type is Promise // TODO(T76719514): Disallow nullable PromiseTypeAnnotations const [returnTypeAnnotation] = unwrapNullable( propertyTypeAnnotation.returnTypeAnnotation, ); if (returnTypeAnnotation.type === 'PromiseTypeAnnotation') { methodParams.push( { paramName: 'resolve', objCType: 'RCTPromiseResolveBlock', }, { paramName: 'reject', objCType: 'RCTPromiseRejectBlock', }, ); } /** * Build Protocol Method **/ const returnObjCType = getReturnObjCType( methodName, propertyTypeAnnotation.returnTypeAnnotation, ); const paddingMax = `- (${returnObjCType})${methodName}`.length; const objCParams = methodParams.reduce( ($objCParams, {objCType, paramName}, i) => { const rhs = `(${objCType})${paramName}`; const padding = ' '.repeat(Math.max(0, paddingMax - paramName.length)); return i === 0 ? `:${rhs}` : `${$objCParams}\n${padding}${paramName}:${rhs}`; }, '', ); const protocolMethod = ProtocolMethodTemplate({ methodName, returnObjCType, params: objCParams, }); /** * Build ObjC Selector */ // $FlowFixMe[missing-type-arg] const selector = methodParams .map(({paramName}) => paramName) .reduce(($selector, paramName, i) => { return i === 0 ? `${$selector}:` : `${$selector}${paramName}:`; }, methodName); /** * Build JS Return type */ const returnJSType = getReturnJSType(methodName, returnTypeAnnotation); return [ { methodName, protocolMethod, selector: `@selector(${selector})`, structParamRecords, returnJSType, argCount: params.length, }, ]; } function getParamStructName(methodName, param) { const [typeAnnotation] = unwrapNullable(param.typeAnnotation); if (typeAnnotation.type === 'TypeAliasTypeAnnotation') { return typeAnnotation.name; } return `Spec${capitalize(methodName)}${capitalize(param.name)}`; } function getParamObjCType( hasteModuleName, methodName, param, structName, structCollector, resolveAlias, ) { const {name: paramName, typeAnnotation: nullableTypeAnnotation} = param; const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation); const isRequired = !param.optional && !nullable; const isStruct = objCType => ({ isStruct: true, objCType, }); const notStruct = objCType => ({ isStruct: false, objCType, }); // Handle types that can only be in parameters switch (typeAnnotation.type) { case 'FunctionTypeAnnotation': { return notStruct('RCTResponseSenderBlock'); } case 'ArrayTypeAnnotation': { /** * Array in params always codegen NSArray * * * TODO(T73933406): Support codegen for Arrays of structs and primitives * * For example: * Array<number> => NSArray<NSNumber *> * type Animal = {}; * Array<Animal> => NSArray<JS::NativeSampleTurboModule::Animal *>, etc. */ return notStruct(wrapOptional('NSArray *', !nullable)); } } const [structTypeAnnotation] = unwrapNullable( structCollector.process( structName, 'REGULAR', resolveAlias, wrapNullable(nullable, typeAnnotation), ), ); invariant( structTypeAnnotation.type !== 'ArrayTypeAnnotation', 'ArrayTypeAnnotations should have been processed earlier', ); switch (structTypeAnnotation.type) { case 'TypeAliasTypeAnnotation': { /** * TODO(T73943261): Support nullable object literals and aliases? */ return isStruct( getNamespacedStructName(hasteModuleName, structTypeAnnotation.name) + ' &', ); } case 'ReservedTypeAnnotation': switch (structTypeAnnotation.name) { case 'RootTag': return notStruct(isRequired ? 'double' : 'NSNumber *'); default: structTypeAnnotation.name; throw new Error( `Unsupported type for param "${paramName}" in ${methodName}. Found: ${structTypeAnnotation.type}`, ); } case 'StringTypeAnnotation': return notStruct(wrapOptional('NSString *', !nullable)); case 'StringLiteralTypeAnnotation': return notStruct(wrapOptional('NSString *', !nullable)); case 'StringLiteralUnionTypeAnnotation': return notStruct(wrapOptional('NSString *', !nullable)); case 'NumberTypeAnnotation': return notStruct(isRequired ? 'double' : 'NSNumber *'); case 'NumberLiteralTypeAnnotation': return notStruct(isRequired ? 'double' : 'NSNumber *'); case 'FloatTypeAnnotation': return notStruct(isRequired ? 'float' : 'NSNumber *'); case 'DoubleTypeAnnotation': return notStruct(isRequired ? 'double' : 'NSNumber *'); case 'Int32TypeAnnotation': return notStruct(isRequired ? 'NSInteger' : 'NSNumber *'); case 'BooleanTypeAnnotation': return notStruct(isRequired ? 'BOOL' : 'NSNumber *'); case 'EnumDeclaration': switch (typeAnnotation.memberType) { case 'NumberTypeAnnotation': return notStruct(isRequired ? 'double' : 'NSNumber *'); case 'StringTypeAnnotation': return notStruct(wrapOptional('NSString *', !nullable)); default: throw new Error( `Unsupported enum type for param "${paramName}" in ${methodName}. Found: ${typeAnnotation.type}`, ); } case 'GenericObjectTypeAnnotation': return notStruct(wrapOptional('NSDictionary *', !nullable)); default: structTypeAnnotation.type; throw new Error( `Unsupported type for param "${paramName}" in ${methodName}. Found: ${typeAnnotation.type}`, ); } } function getReturnObjCType(methodName, nullableTypeAnnotation) { const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation); const isRequired = !nullable; switch (typeAnnotation.type) { case 'VoidTypeAnnotation': return 'void'; case 'PromiseTypeAnnotation': return 'void'; case 'ObjectTypeAnnotation': return wrapOptional('NSDictionary *', isRequired); case 'TypeAliasTypeAnnotation': return wrapOptional('NSDictionary *', isRequired); case 'ArrayTypeAnnotation': if (typeAnnotation.elementType.type === 'AnyTypeAnnotation') { return wrapOptional('NSArray<id<NSObject>> *', isRequired); } return wrapOptional( `NSArray<${getReturnObjCType( methodName, typeAnnotation.elementType, )}> *`, isRequired, ); case 'ReservedTypeAnnotation': switch (typeAnnotation.name) { case 'RootTag': return wrapOptional('NSNumber *', isRequired); default: typeAnnotation.name; throw new Error( `Unsupported return type for ${methodName}. Found: ${typeAnnotation.name}`, ); } case 'StringTypeAnnotation': // TODO: Can NSString * returns not be _Nullable? // In the legacy codegen, we don't surround NSSTring * with _Nullable return wrapOptional('NSString *', isRequired); case 'StringLiteralTypeAnnotation': // TODO: Can NSString * returns not be _Nullable? // In the legacy codegen, we don't surround NSSTring * with _Nullable return wrapOptional('NSString *', isRequired); case 'StringLiteralUnionTypeAnnotation': // TODO: Can NSString * returns not be _Nullable? // In the legacy codegen, we don't surround NSSTring * with _Nullable return wrapOptional('NSString *', isRequired); case 'NumberTypeAnnotation': return wrapOptional('NSNumber *', isRequired); case 'NumberLiteralTypeAnnotation': return wrapOptional('NSNumber *', isRequired); case 'FloatTypeAnnotation': return wrapOptional('NSNumber *', isRequired); case 'DoubleTypeAnnotation': return wrapOptional('NSNumber *', isRequired); case 'Int32TypeAnnotation': return wrapOptional('NSNumber *', isRequired); case 'BooleanTypeAnnotation': return wrapOptional('NSNumber *', isRequired); case 'EnumDeclaration': switch (typeAnnotation.memberType) { case 'NumberTypeAnnotation': return wrapOptional('NSNumber *', isRequired); case 'StringTypeAnnotation': return wrapOptional('NSString *', isRequired); default: throw new Error( `Unsupported enum return type for ${methodName}. Found: ${typeAnnotation.type}`, ); } case 'UnionTypeAnnotation': switch (typeAnnotation.memberType) { case 'NumberTypeAnnotation': return wrapOptional('NSNumber *', isRequired); case 'ObjectTypeAnnotation': return wrapOptional('NSDictionary *', isRequired); case 'StringTypeAnnotation': // TODO: Can NSString * returns not be _Nullable? // In the legacy codegen, we don't surround NSSTring * with _Nullable return wrapOptional('NSString *', isRequired); default: throw new Error( `Unsupported union return type for ${methodName}, found: ${typeAnnotation.memberType}"`, ); } case 'GenericObjectTypeAnnotation': return wrapOptional('NSDictionary *', isRequired); default: typeAnnotation.type; throw new Error( `Unsupported return type for ${methodName}. Found: ${typeAnnotation.type}`, ); } } function getReturnJSType(methodName, nullableTypeAnnotation) { const [typeAnnotation] = unwrapNullable(nullableTypeAnnotation); switch (typeAnnotation.type) { case 'VoidTypeAnnotation': return 'VoidKind'; case 'PromiseTypeAnnotation': return 'PromiseKind'; case 'ObjectTypeAnnotation': return 'ObjectKind'; case 'TypeAliasTypeAnnotation': return 'ObjectKind'; case 'ArrayTypeAnnotation': return 'ArrayKind'; case 'ReservedTypeAnnotation': return 'NumberKind'; case 'StringTypeAnnotation': return 'StringKind'; case 'StringLiteralTypeAnnotation': return 'StringKind'; case 'StringLiteralUnionTypeAnnotation': return 'StringKind'; case 'NumberTypeAnnotation': return 'NumberKind'; case 'NumberLiteralTypeAnnotation': return 'NumberKind'; case 'FloatTypeAnnotation': return 'NumberKind'; case 'DoubleTypeAnnotation': return 'NumberKind'; case 'Int32TypeAnnotation': return 'NumberKind'; case 'BooleanTypeAnnotation': return 'BooleanKind'; case 'GenericObjectTypeAnnotation': return 'ObjectKind'; case 'EnumDeclaration': switch (typeAnnotation.memberType) { case 'NumberTypeAnnotation': return 'NumberKind'; case 'StringTypeAnnotation': return 'StringKind'; default: throw new Error( `Unsupported return type for ${methodName}. Found: ${typeAnnotation.type}`, ); } case 'UnionTypeAnnotation': switch (typeAnnotation.memberType) { case 'NumberTypeAnnotation': return 'NumberKind'; case 'ObjectTypeAnnotation': return 'ObjectKind'; case 'StringTypeAnnotation': return 'StringKind'; default: throw new Error( `Unsupported return type for ${methodName}. Found: ${typeAnnotation.type}`, ); } default: typeAnnotation.type; throw new Error( `Unsupported return type for ${methodName}. Found: ${typeAnnotation.type}`, ); } } function serializeConstantsProtocolMethods( hasteModuleName, property, structCollector, resolveAlias, ) { const [propertyTypeAnnotation] = unwrapNullable(property.typeAnnotation); if (propertyTypeAnnotation.params.length !== 0) { throw new Error( `${hasteModuleName}.getConstants() may only accept 0 arguments.`, ); } let {returnTypeAnnotation} = propertyTypeAnnotation; 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') { throw new Error( `${hasteModuleName}.getConstants() may only return an object literal: {...}` + ` or a type alias of such. Got '${propertyTypeAnnotation.returnTypeAnnotation.type}'.`, ); } if ( returnTypeAnnotation.type === 'ObjectTypeAnnotation' && returnTypeAnnotation.properties.length === 0 ) { return []; } const realTypeAnnotation = structCollector.process( 'Constants', 'CONSTANTS', resolveAlias, returnTypeAnnotation, ); invariant( realTypeAnnotation.type === 'TypeAliasTypeAnnotation', "Unable to generate C++ struct from module's getConstants() method return type.", ); const returnObjCType = `facebook::react::ModuleConstants<JS::${hasteModuleName}::Constants::Builder>`; // $FlowFixMe[missing-type-arg] return ['constantsToExport', 'getConstants'].map(methodName => { const protocolMethod = ProtocolMethodTemplate({ methodName, returnObjCType, params: '', }); return { methodName, protocolMethod, returnJSType: 'ObjectKind', selector: `@selector(${methodName})`, structParamRecords: [], argCount: 0, }; }); } module.exports = { serializeMethod, };