UNPKG

@react-native/codegen

Version:
400 lines (393 loc) • 11 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 _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 'StringLiteralUnionTypeAnnotation': 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 'StringLiteralUnionTypeAnnotation': 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]]); }, };