UNPKG

@react-native/codegen

Version:
377 lines (339 loc) • 9.15 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. * * @flow strict-local * @format */ 'use strict'; import type { ComponentShape, EventTypeAnnotation, EventTypeShape, NamedShape, SchemaType, } from '../../CodegenSchema'; const {indent, toSafeCppString} = require('../Utils'); const { generateEventStructName, getCppArrayTypeForAnnotation, getCppTypeForAnnotation, getImports, } = require('./CppHelpers'); const nullthrows = require('nullthrows'); // File path -> contents type FilesOutput = Map<string, string>; type StructsMap = Map<string, string>; type ComponentCollection = $ReadOnly<{ [component: string]: ComponentShape, ... }>; const FileTemplate = ({ componentEmitters, extraIncludes, }: { componentEmitters: string, extraIncludes: Set<string>, }) => ` /** * 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: GenerateEventEmitterH.js */ #pragma once #include <react/renderer/components/view/ViewEventEmitter.h> ${[...extraIncludes].join('\n')} namespace facebook::react { ${componentEmitters} } // namespace facebook::react `; const ComponentTemplate = ({ className, structs, events, }: { className: string, structs: string, events: string, }) => ` class ${className}EventEmitter : public ViewEventEmitter { public: using ViewEventEmitter::ViewEventEmitter; ${structs} ${events} }; `.trim(); const StructTemplate = ({ structName, fields, }: { structName: string, fields: string, }) => ` struct ${structName} { ${fields} }; `.trim(); const EnumTemplate = ({ enumName, values, toCases, }: { enumName: string, values: string, toCases: string, }) => `enum class ${enumName} { ${values} }; static char const *toString(const ${enumName} value) { switch (value) { ${toCases} } } `.trim(); function getNativeTypeFromAnnotation( componentName: string, eventProperty: NamedShape<EventTypeAnnotation>, nameParts: $ReadOnlyArray<string>, ): string { const {type} = eventProperty.typeAnnotation; switch (type) { case 'BooleanTypeAnnotation': case 'StringTypeAnnotation': case 'Int32TypeAnnotation': case 'DoubleTypeAnnotation': case 'FloatTypeAnnotation': case 'MixedTypeAnnotation': return getCppTypeForAnnotation(type); case 'StringLiteralUnionTypeAnnotation': case 'ObjectTypeAnnotation': return generateEventStructName([...nameParts, eventProperty.name]); case 'ArrayTypeAnnotation': const eventTypeAnnotation = eventProperty.typeAnnotation; if (eventTypeAnnotation.type !== 'ArrayTypeAnnotation') { throw new Error( "Inconsistent Codegen state: type was ArrayTypeAnnotation at the beginning of the body and now it isn't", ); } return getCppArrayTypeForAnnotation(eventTypeAnnotation.elementType, [ ...nameParts, eventProperty.name, ]); default: (type: empty); throw new Error(`Received invalid event property type ${type}`); } } function generateEnum( structs: StructsMap, options: $ReadOnlyArray<string>, nameParts: Array<string>, ) { const structName = generateEventStructName(nameParts); const fields = options .map((option, index) => `${toSafeCppString(option)}`) .join(',\n '); const toCases = options .map( option => `case ${structName}::${toSafeCppString(option)}: return "${option}";`, ) .join('\n' + ' '); structs.set( structName, EnumTemplate({ enumName: structName, values: fields, toCases: toCases, }), ); } function handleGenerateStructForArray( structs: StructsMap, name: string, componentName: string, elementType: EventTypeAnnotation, nameParts: $ReadOnlyArray<string>, ): void { if (elementType.type === 'ObjectTypeAnnotation') { generateStruct( structs, componentName, nameParts.concat([name]), nullthrows(elementType.properties), ); } else if (elementType.type === 'StringLiteralUnionTypeAnnotation') { generateEnum( structs, elementType.types.map(literal => literal.value), nameParts.concat([name]), ); } else if (elementType.type === 'ArrayTypeAnnotation') { handleGenerateStructForArray( structs, name, componentName, elementType.elementType, nameParts, ); } } function generateStruct( structs: StructsMap, componentName: string, nameParts: $ReadOnlyArray<string>, properties: $ReadOnlyArray<NamedShape<EventTypeAnnotation>>, ): void { const structNameParts = nameParts; const structName = generateEventStructName(structNameParts); const fields = properties .map(property => { return `${getNativeTypeFromAnnotation( componentName, property, structNameParts, )} ${property.name};`; }) .join('\n' + ' '); properties.forEach(property => { const {name, typeAnnotation} = property; switch (typeAnnotation.type) { case 'BooleanTypeAnnotation': case 'StringTypeAnnotation': case 'Int32TypeAnnotation': case 'DoubleTypeAnnotation': case 'FloatTypeAnnotation': case 'MixedTypeAnnotation': return; case 'ArrayTypeAnnotation': handleGenerateStructForArray( structs, name, componentName, typeAnnotation.elementType, nameParts, ); return; case 'ObjectTypeAnnotation': generateStruct( structs, componentName, nameParts.concat([name]), nullthrows(typeAnnotation.properties), ); return; case 'StringLiteralUnionTypeAnnotation': generateEnum( structs, typeAnnotation.types.map(literal => literal.value), nameParts.concat([name]), ); return; default: (typeAnnotation.type: empty); throw new Error( `Received invalid event property type ${typeAnnotation.type}`, ); } }); structs.set( structName, StructTemplate({ structName, fields, }), ); } function generateStructs( componentName: string, component: ComponentShape, ): string { const structs: StructsMap = new Map(); component.events.forEach(event => { if (event.typeAnnotation.argument) { generateStruct( structs, componentName, [event.name], event.typeAnnotation.argument.properties, ); } }); return Array.from(structs.values()).join('\n\n'); } function generateEvent(componentName: string, event: EventTypeShape): string { if (event.typeAnnotation.argument) { const structName = generateEventStructName([event.name]); return `void ${event.name}(${structName} value) const;`; } return `void ${event.name}() const;`; } function generateEvents( componentName: string, component: ComponentShape, ): string { return component.events .map(event => generateEvent(componentName, event)) .join('\n\n' + ' '); } module.exports = { generate( libraryName: string, schema: SchemaType, packageName?: string, assumeNonnull: boolean = false, headerPrefix?: string, ): FilesOutput { const moduleComponents: ComponentCollection = Object.keys(schema.modules) .map(moduleName => { const module = schema.modules[moduleName]; if (module.type !== 'Component') { return null; } const {components} = module; // No components in this module if (components == null) { return null; } return components; }) .filter(Boolean) // $FlowFixMe[unsafe-object-assign] .reduce((acc, components) => Object.assign(acc, components), {}); const extraIncludes = new Set<string>(); const componentEmitters = Object.keys(moduleComponents) .map(componentName => { const component = moduleComponents[componentName]; component.events.forEach(event => { if (event.typeAnnotation.argument) { const argIncludes = getImports( event.typeAnnotation.argument.properties, ); // $FlowFixMe[method-unbinding] argIncludes.forEach(extraIncludes.add, extraIncludes); } }); const replacedTemplate = ComponentTemplate({ className: componentName, structs: indent(generateStructs(componentName, component), 2), events: generateEvents(componentName, component), }); return replacedTemplate; }) .join('\n'); const fileName = 'EventEmitters.h'; const replacedTemplate = FileTemplate({ componentEmitters, extraIncludes, }); return new Map([[fileName, replacedTemplate]]); }, };