Skip to content

Commit

Permalink
Generate Custom Native State (#34796)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #34796

This diff introduces the generation of custom native states using basic types as we do with the Props.

To make it work, the custom types are already writte in the Props.h file, therefore the State.h file must import that other file to have access to the required types.

This diff adds and updates the tests for the State.

## Changelog
[General][Added] - Generate custom Native State

Reviewed By: cortinico

Differential Revision: D39816763

fbshipit-source-id: 42d1aa9a6df23145f4a46ae8ccfb43d81fa651fb
  • Loading branch information
cipolleschi authored and facebook-github-bot committed Oct 10, 2022
1 parent 5fa51e6 commit 7490ad4
Show file tree
Hide file tree
Showing 22 changed files with 8,243 additions and 310 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/**
* 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
* @format
*/

'use strict';

import type {
NamedShape,
PropTypeAnnotation,
StateTypeAnnotation,
} from '../../CodegenSchema';

import type {
StringTypeAnnotation,
ReservedPropTypeAnnotation,
ObjectTypeAnnotation,
Int32TypeAnnotation,
FloatTypeAnnotation,
DoubleTypeAnnotation,
BooleanTypeAnnotation,
} from '../../CodegenSchema';

const {
convertDefaultTypeToString,
getCppTypeForAnnotation,
getEnumMaskName,
getEnumName,
generateStructName,
} = require('./CppHelpers.js');

function getNativeTypeFromAnnotation(
componentName: string,
prop:
| NamedShape<PropTypeAnnotation>
| NamedShape<StateTypeAnnotation>
| {
name: string,
typeAnnotation:
| $FlowFixMe
| DoubleTypeAnnotation
| FloatTypeAnnotation
| BooleanTypeAnnotation
| Int32TypeAnnotation
| StringTypeAnnotation
| ObjectTypeAnnotation<PropTypeAnnotation>
| ReservedPropTypeAnnotation
| {
+default: string,
+options: $ReadOnlyArray<string>,
+type: 'StringEnumTypeAnnotation',
}
| {
+elementType: ObjectTypeAnnotation<PropTypeAnnotation>,
+type: 'ArrayTypeAnnotation',
},
},
nameParts: $ReadOnlyArray<string>,
): string {
const typeAnnotation = prop.typeAnnotation;

switch (typeAnnotation.type) {
case 'BooleanTypeAnnotation':
case 'StringTypeAnnotation':
case 'Int32TypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
return getCppTypeForAnnotation(typeAnnotation.type);
case 'ReservedPropTypeAnnotation':
switch (typeAnnotation.name) {
case 'ColorPrimitive':
return 'SharedColor';
case 'ImageSourcePrimitive':
return 'ImageSource';
case 'PointPrimitive':
return 'Point';
case 'EdgeInsetsPrimitive':
return 'EdgeInsets';
default:
(typeAnnotation.name: empty);
throw new Error('Received unknown ReservedPropTypeAnnotation');
}
case 'ArrayTypeAnnotation': {
const arrayType = typeAnnotation.elementType.type;
if (arrayType === 'ArrayTypeAnnotation') {
return `std::vector<${getNativeTypeFromAnnotation(
componentName,
{typeAnnotation: typeAnnotation.elementType, name: ''},
nameParts.concat([prop.name]),
)}>`;
}
if (arrayType === 'ObjectTypeAnnotation') {
const structName = generateStructName(
componentName,
nameParts.concat([prop.name]),
);
return `std::vector<${structName}>`;
}
if (arrayType === 'StringEnumTypeAnnotation') {
const enumName = getEnumName(componentName, prop.name);
return getEnumMaskName(enumName);
}
const itemAnnotation = getNativeTypeFromAnnotation(
componentName,
{
typeAnnotation: typeAnnotation.elementType,
name: componentName,
},
nameParts.concat([prop.name]),
);
return `std::vector<${itemAnnotation}>`;
}
case 'ObjectTypeAnnotation': {
return generateStructName(componentName, nameParts.concat([prop.name]));
}
case 'StringEnumTypeAnnotation':
return getEnumName(componentName, prop.name);
case 'Int32EnumTypeAnnotation':
return getEnumName(componentName, prop.name);
default:
(typeAnnotation: empty);
throw new Error(
`Received invalid typeAnnotation for ${componentName} prop ${prop.name}, received ${typeAnnotation.type}`,
);
}
}

function getStateConstituents(
componentName: string,
stateShape: NamedShape<StateTypeAnnotation>,
): {
name: string,
varName: string,
type: string,
defaultValue: $FlowFixMe,
} {
const name = stateShape.name;
const varName = `${name}_`;
const type = getNativeTypeFromAnnotation(componentName, stateShape, []);
const defaultValue = convertDefaultTypeToString(componentName, stateShape);

return {
name,
varName,
type,
defaultValue,
};
}

module.exports = {
getNativeTypeFromAnnotation,
getStateConstituents,
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,12 @@
*/

'use strict';
import type {
StringTypeAnnotation,
ReservedPropTypeAnnotation,
ObjectTypeAnnotation,
Int32TypeAnnotation,
FloatTypeAnnotation,
DoubleTypeAnnotation,
ComponentShape,
BooleanTypeAnnotation,
} from '../../CodegenSchema';
import type {ComponentShape} from '../../CodegenSchema';

const {getNativeTypeFromAnnotation} = require('./ComponentsGeneratorUtils.js');

const {
convertDefaultTypeToString,
getCppTypeForAnnotation,
getEnumMaskName,
getEnumName,
toSafeCppString,
Expand Down Expand Up @@ -294,101 +286,6 @@ function getClassExtendString(component: ComponentShape): string {
return extendString;
}

function getNativeTypeFromAnnotation(
componentName: string,
prop:
| NamedShape<PropTypeAnnotation>
| {
name: string,
typeAnnotation:
| $FlowFixMe
| DoubleTypeAnnotation
| FloatTypeAnnotation
| BooleanTypeAnnotation
| Int32TypeAnnotation
| StringTypeAnnotation
| ObjectTypeAnnotation<PropTypeAnnotation>
| ReservedPropTypeAnnotation
| {
+default: string,
+options: $ReadOnlyArray<string>,
+type: 'StringEnumTypeAnnotation',
}
| {
+elementType: ObjectTypeAnnotation<PropTypeAnnotation>,
+type: 'ArrayTypeAnnotation',
},
},
nameParts: $ReadOnlyArray<string>,
): string {
const typeAnnotation = prop.typeAnnotation;

switch (typeAnnotation.type) {
case 'BooleanTypeAnnotation':
case 'StringTypeAnnotation':
case 'Int32TypeAnnotation':
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
return getCppTypeForAnnotation(typeAnnotation.type);
case 'ReservedPropTypeAnnotation':
switch (typeAnnotation.name) {
case 'ColorPrimitive':
return 'SharedColor';
case 'ImageSourcePrimitive':
return 'ImageSource';
case 'PointPrimitive':
return 'Point';
case 'EdgeInsetsPrimitive':
return 'EdgeInsets';
default:
(typeAnnotation.name: empty);
throw new Error('Received unknown ReservedPropTypeAnnotation');
}
case 'ArrayTypeAnnotation': {
const arrayType = typeAnnotation.elementType.type;
if (arrayType === 'ArrayTypeAnnotation') {
return `std::vector<${getNativeTypeFromAnnotation(
componentName,
{typeAnnotation: typeAnnotation.elementType, name: ''},
nameParts.concat([prop.name]),
)}>`;
}
if (arrayType === 'ObjectTypeAnnotation') {
const structName = generateStructName(
componentName,
nameParts.concat([prop.name]),
);
return `std::vector<${structName}>`;
}
if (arrayType === 'StringEnumTypeAnnotation') {
const enumName = getEnumName(componentName, prop.name);
return getEnumMaskName(enumName);
}
const itemAnnotation = getNativeTypeFromAnnotation(
componentName,
{
typeAnnotation: typeAnnotation.elementType,
name: componentName,
},
nameParts.concat([prop.name]),
);
return `std::vector<${itemAnnotation}>`;
}
case 'ObjectTypeAnnotation': {
return generateStructName(componentName, nameParts.concat([prop.name]));
}
case 'StringEnumTypeAnnotation':
return getEnumName(componentName, prop.name);
case 'Int32EnumTypeAnnotation':
return getEnumName(componentName, prop.name);
default:
(typeAnnotation: empty);
throw new Error(
`Received invalid typeAnnotation for ${componentName} prop ${prop.name}, received ${typeAnnotation.type}`,
);
}
}

function convertValueToEnumOption(value: string): string {
return toSafeCppString(value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,23 @@

'use strict';

import type {SchemaType} from '../../CodegenSchema';
import type {
NamedShape,
SchemaType,
StateTypeAnnotation,
} from '../../CodegenSchema';
const {capitalize} = require('../Utils.js');
const {getStateConstituents} = require('./ComponentsGeneratorUtils.js');

// File path -> contents
type FilesOutput = Map<string, string>;

const FileTemplate = ({
libraryName,
stateClasses,
stateGetters,
}: {
libraryName: string,
stateClasses: string,
stateGetters: string,
}) => `
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
Expand All @@ -35,13 +41,32 @@ const FileTemplate = ({
namespace facebook {
namespace react {
${stateClasses}
${stateGetters}
} // namespace react
} // namespace facebook
`;

const StateTemplate = ({stateName}: {stateName: string}) => '';
function generateStrings(
componentName: string,
state: $ReadOnlyArray<NamedShape<StateTypeAnnotation>>,
) {
let getters = '';
state.forEach(stateShape => {
const {name, varName, type} = getStateConstituents(
componentName,
stateShape,
);

getters += `
${type} ${componentName}::get${capitalize(name)}() const {
return ${varName};
}
`;
});

return getters.trim();
}

module.exports = {
generate(
Expand All @@ -52,7 +77,7 @@ module.exports = {
): FilesOutput {
const fileName = 'States.cpp';

const stateClasses = Object.keys(schema.modules)
const stateGetters = Object.keys(schema.modules)
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
Expand All @@ -67,12 +92,16 @@ module.exports = {

return Object.keys(components)
.map(componentName => {
if (components[componentName].interfaceOnly === true) {
const component = components[componentName];
if (component.interfaceOnly === true) {
return null;
}
return StateTemplate({
stateName: `${componentName}State`,
});

const state = component.state;
if (!state) {
return '';
}
return generateStrings(componentName, state);
})
.filter(Boolean)
.join('\n');
Expand All @@ -82,7 +111,7 @@ module.exports = {

const replacedTemplate = FileTemplate({
libraryName,
stateClasses,
stateGetters,
});

return new Map([[fileName, replacedTemplate]]);
Expand Down
Loading

0 comments on commit 7490ad4

Please sign in to comment.