@react-native/codegen
Version:
Code generation tools for React Native
377 lines (339 loc) • 9.15 kB
Flow
/**
* 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
*/
;
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]]);
},
};