mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Generate pojo for Component Props
Summary: ## What This Does - **Phase 1:** Given a component, convert its props (and all its ObjectTypeAnnotations) into this Pojo data structure under some namespace. This is a recursive operation. - **Phase 2:** Loop over each Pojo data structure, and serialize it to Java class. So... - Each Component has its own namespace (i.e: its hasteModuleName) for Java Pojo objects. - Each Component generates 1 Pojo object, for its props. - Each Component generates 1 Pojo object for every ObjectTypeAnnotation in its props. ## Decisions By design, [JNI can read/write to private properties on Java objects](https://stackoverflow.com/a/12208643). So, each Pojo, which represents an ObjectTypeAnnotation, contains only a private member variable for each of its properties, and a getter to retrieve the data from each of its private members. ## Todos - Improved type-safety: - ReservedTypeAnnotation (e.g: PointPrimitive). These currently map to ReadableMap. - String enums, and Int enums don't actually generate Java enums. - Verify if there are any compilation issues by wiring this up to the Codegen buck infra. - To actually use the Pojos, we'll need C++/Jni codegen to transform C++ props into these Pojos. Building this out will give a more accurate assessment of the app-size cost of Pojos. However, we can do that if we deem that the app-size increase from adding just the Pojo classes is negligible enough to **not** rule out this entire approach. - ~~Add some annotations to prevent these Pojo classes from being stripped at compile-time.~~ D26041103 Changelog: [Internal] Reviewed By: mdvacca Differential Revision: D26038189 fbshipit-source-id: c137c4ca6b043ee76adb354105aff6e0f270df86
This commit is contained in:
committed by
Facebook GitHub Bot
parent
dc0b917fb5
commit
8461409e88
Vendored
+186
@@ -0,0 +1,186 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its 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 {
|
||||
ReservedPropTypeAnnotation,
|
||||
NamedShape,
|
||||
ObjectTypeAnnotation,
|
||||
BooleanTypeAnnotation,
|
||||
StringTypeAnnotation,
|
||||
DoubleTypeAnnotation,
|
||||
FloatTypeAnnotation,
|
||||
Int32TypeAnnotation,
|
||||
PropTypeAnnotation,
|
||||
} from '../../../CodegenSchema';
|
||||
|
||||
const {capitalize} = require('../../Utils');
|
||||
|
||||
export type Pojo = {
|
||||
name: string,
|
||||
namespace: string,
|
||||
properties: $ReadOnlyArray<PojoProperty>,
|
||||
};
|
||||
|
||||
export type PojoProperty = NamedShape<PojoTypeAnnotation>;
|
||||
|
||||
export type PojoTypeAliasAnnotation = {
|
||||
type: 'PojoTypeAliasTypeAnnotation',
|
||||
name: string,
|
||||
};
|
||||
|
||||
export type PojoTypeAnnotation =
|
||||
| $ReadOnly<{
|
||||
type: 'BooleanTypeAnnotation',
|
||||
default: boolean | null,
|
||||
}>
|
||||
| $ReadOnly<{
|
||||
type: 'StringTypeAnnotation',
|
||||
default: string | null,
|
||||
}>
|
||||
| $ReadOnly<{
|
||||
type: 'DoubleTypeAnnotation',
|
||||
default: number,
|
||||
}>
|
||||
| $ReadOnly<{
|
||||
type: 'FloatTypeAnnotation',
|
||||
default: number | null,
|
||||
}>
|
||||
| $ReadOnly<{
|
||||
type: 'Int32TypeAnnotation',
|
||||
default: number,
|
||||
}>
|
||||
| $ReadOnly<{
|
||||
type: 'StringEnumTypeAnnotation',
|
||||
default: string,
|
||||
options: $ReadOnlyArray<string>,
|
||||
}>
|
||||
| $ReadOnly<{
|
||||
type: 'Int32EnumTypeAnnotation',
|
||||
default: number,
|
||||
options: $ReadOnlyArray<number>,
|
||||
}>
|
||||
| ReservedPropTypeAnnotation
|
||||
| PojoTypeAliasAnnotation
|
||||
| $ReadOnly<{
|
||||
type: 'ArrayTypeAnnotation',
|
||||
elementType:
|
||||
| BooleanTypeAnnotation
|
||||
| StringTypeAnnotation
|
||||
| DoubleTypeAnnotation
|
||||
| FloatTypeAnnotation
|
||||
| Int32TypeAnnotation
|
||||
| $ReadOnly<{
|
||||
type: 'StringEnumTypeAnnotation',
|
||||
default: string,
|
||||
options: $ReadOnlyArray<string>,
|
||||
}>
|
||||
| PojoTypeAliasAnnotation
|
||||
| ReservedPropTypeAnnotation
|
||||
| $ReadOnly<{
|
||||
type: 'ArrayTypeAnnotation',
|
||||
elementType: PojoTypeAliasAnnotation,
|
||||
}>,
|
||||
}>;
|
||||
|
||||
class PojoCollector {
|
||||
_pojos: Map<string, Pojo> = new Map();
|
||||
process(
|
||||
namespace: string,
|
||||
pojoName: string,
|
||||
typeAnnotation: PropTypeAnnotation,
|
||||
): PojoTypeAnnotation {
|
||||
switch (typeAnnotation.type) {
|
||||
case 'ObjectTypeAnnotation': {
|
||||
this._insertPojo(namespace, pojoName, typeAnnotation);
|
||||
return {
|
||||
type: 'PojoTypeAliasTypeAnnotation',
|
||||
name: pojoName,
|
||||
};
|
||||
}
|
||||
case 'ArrayTypeAnnotation': {
|
||||
const arrayTypeAnnotation = typeAnnotation;
|
||||
// TODO: Flow assumes elementType can be any. Fix this.
|
||||
const elementType: $PropertyType<
|
||||
typeof arrayTypeAnnotation,
|
||||
'elementType',
|
||||
> = arrayTypeAnnotation.elementType;
|
||||
|
||||
const pojoElementType = (() => {
|
||||
switch (elementType.type) {
|
||||
case 'ObjectTypeAnnotation': {
|
||||
this._insertPojo(namespace, `${pojoName}Element`, elementType);
|
||||
return {
|
||||
type: 'PojoTypeAliasTypeAnnotation',
|
||||
name: `${pojoName}Element`,
|
||||
};
|
||||
}
|
||||
case 'ArrayTypeAnnotation': {
|
||||
const {elementType: objectTypeAnnotation} = elementType;
|
||||
this._insertPojo(
|
||||
namespace,
|
||||
`${pojoName}ElementElement`,
|
||||
objectTypeAnnotation,
|
||||
);
|
||||
return {
|
||||
type: 'ArrayTypeAnnotation',
|
||||
elementType: {
|
||||
type: 'PojoTypeAliasTypeAnnotation',
|
||||
name: `${pojoName}ElementElement`,
|
||||
},
|
||||
};
|
||||
}
|
||||
default: {
|
||||
return elementType;
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
return {
|
||||
type: 'ArrayTypeAnnotation',
|
||||
elementType: pojoElementType,
|
||||
};
|
||||
}
|
||||
default:
|
||||
return typeAnnotation;
|
||||
}
|
||||
}
|
||||
_insertPojo(
|
||||
namespace: string,
|
||||
pojoName: string,
|
||||
objectTypeAnnotation: ObjectTypeAnnotation<PropTypeAnnotation>,
|
||||
) {
|
||||
const properties = objectTypeAnnotation.properties.map(property => {
|
||||
const propertyPojoName = pojoName + capitalize(property.name);
|
||||
|
||||
return {
|
||||
...property,
|
||||
typeAnnotation: this.process(
|
||||
namespace,
|
||||
propertyPojoName,
|
||||
property.typeAnnotation,
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
this._pojos.set(pojoName, {
|
||||
name: pojoName,
|
||||
namespace,
|
||||
properties,
|
||||
});
|
||||
}
|
||||
|
||||
getAllPojos(): $ReadOnlyArray<Pojo> {
|
||||
return [...this._pojos.values()];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PojoCollector;
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its 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 {SchemaType} from '../../../CodegenSchema';
|
||||
|
||||
const PojoCollector = require('./PojoCollector');
|
||||
const {capitalize} = require('../../Utils');
|
||||
const {serializePojo} = require('./serializePojo');
|
||||
|
||||
type FilesOutput = Map<string, string>;
|
||||
|
||||
module.exports = {
|
||||
generate(
|
||||
libraryName: string,
|
||||
schema: SchemaType,
|
||||
packageName?: string,
|
||||
): FilesOutput {
|
||||
const pojoCollector = new PojoCollector();
|
||||
const basePackageName = 'com.facebook.react.viewmanagers';
|
||||
|
||||
Object.keys(schema.modules).forEach(hasteModuleName => {
|
||||
const module = schema.modules[hasteModuleName];
|
||||
if (module.type !== 'Component') {
|
||||
return;
|
||||
}
|
||||
|
||||
const {components} = module;
|
||||
// No components in this module
|
||||
if (components == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Object.keys(components)
|
||||
.filter(componentName => {
|
||||
const component = components[componentName];
|
||||
return !(
|
||||
component.excludedPlatforms &&
|
||||
component.excludedPlatforms.includes('android')
|
||||
);
|
||||
})
|
||||
.forEach(componentName => {
|
||||
const component = components[componentName];
|
||||
if (component == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {props} = component;
|
||||
|
||||
pojoCollector.process(
|
||||
capitalize(hasteModuleName),
|
||||
`${capitalize(componentName)}Props`,
|
||||
{
|
||||
type: 'ObjectTypeAnnotation',
|
||||
properties: props,
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const pojoDir = basePackageName.split('.').join('/');
|
||||
|
||||
return new Map(
|
||||
pojoCollector.getAllPojos().map(pojo => {
|
||||
return [
|
||||
`java/${pojoDir}/${pojo.namespace}/${pojo.name}.java`,
|
||||
serializePojo(pojo, basePackageName),
|
||||
];
|
||||
}),
|
||||
);
|
||||
},
|
||||
};
|
||||
Vendored
+285
@@ -0,0 +1,285 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its 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 {Pojo, PojoProperty, PojoTypeAnnotation} from './PojoCollector';
|
||||
const {capitalize} = require('../../Utils');
|
||||
|
||||
type ImportCollector = ($import: string) => void;
|
||||
|
||||
function toJavaType(
|
||||
typeAnnotation: PojoTypeAnnotation,
|
||||
addImport: ImportCollector,
|
||||
): string {
|
||||
const importNullable = () => addImport('androidx.annotation.Nullable');
|
||||
const importReadableMap = () =>
|
||||
addImport('com.facebook.react.bridge.ReadableMap');
|
||||
const importArrayList = () => addImport('java.util.ArrayList');
|
||||
switch (typeAnnotation.type) {
|
||||
/**
|
||||
* Primitives
|
||||
*/
|
||||
case 'BooleanTypeAnnotation': {
|
||||
if (typeAnnotation.default === null) {
|
||||
importNullable();
|
||||
return '@Nullable Boolean';
|
||||
} else {
|
||||
return 'boolean';
|
||||
}
|
||||
}
|
||||
case 'StringTypeAnnotation': {
|
||||
importNullable();
|
||||
return '@Nullable String';
|
||||
}
|
||||
case 'DoubleTypeAnnotation': {
|
||||
return 'double';
|
||||
}
|
||||
case 'FloatTypeAnnotation': {
|
||||
if (typeAnnotation.default === null) {
|
||||
importNullable();
|
||||
return '@Nullable Float';
|
||||
} else {
|
||||
return 'float';
|
||||
}
|
||||
}
|
||||
case 'Int32TypeAnnotation': {
|
||||
return 'int';
|
||||
}
|
||||
|
||||
/**
|
||||
* Enums
|
||||
*/
|
||||
// TODO: Make StringEnumTypeAnnotation type-safe?
|
||||
case 'StringEnumTypeAnnotation':
|
||||
importNullable();
|
||||
return '@Nullable String';
|
||||
// TODO: Make Int32EnumTypeAnnotation type-safe?
|
||||
case 'Int32EnumTypeAnnotation':
|
||||
importNullable();
|
||||
return '@Nullable Integer';
|
||||
|
||||
/**
|
||||
* Reserved types
|
||||
*/
|
||||
case 'ReservedPropTypeAnnotation': {
|
||||
switch (typeAnnotation.name) {
|
||||
case 'ColorPrimitive':
|
||||
importNullable();
|
||||
return '@Nullable Integer';
|
||||
|
||||
// TODO: Make ImageSourcePrimitive type-safe
|
||||
case 'ImageSourcePrimitive':
|
||||
importNullable();
|
||||
importReadableMap();
|
||||
return '@Nullable ReadableMap';
|
||||
|
||||
// TODO: Make PointPrimitive type-safe
|
||||
case 'PointPrimitive':
|
||||
importNullable();
|
||||
importReadableMap();
|
||||
return '@Nullable ReadableMap';
|
||||
|
||||
// TODO: Make EdgeInsetsPrimitive type-safe
|
||||
case 'EdgeInsetsPrimitive':
|
||||
importNullable();
|
||||
importReadableMap();
|
||||
return '@Nullable ReadableMap';
|
||||
default:
|
||||
(typeAnnotation.name: empty);
|
||||
throw new Error(
|
||||
`Received unknown ReservedPropTypeAnnotation ${typeAnnotation.name}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Other Pojo objects
|
||||
*/
|
||||
case 'PojoTypeAliasTypeAnnotation': {
|
||||
return typeAnnotation.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Arrays
|
||||
*/
|
||||
case 'ArrayTypeAnnotation': {
|
||||
const {elementType} = typeAnnotation;
|
||||
|
||||
const elementTypeString = (() => {
|
||||
switch (elementType.type) {
|
||||
/**
|
||||
* Primitives
|
||||
*/
|
||||
case 'BooleanTypeAnnotation': {
|
||||
return 'Boolean';
|
||||
}
|
||||
case 'StringTypeAnnotation': {
|
||||
importNullable();
|
||||
return '@Nullable String';
|
||||
}
|
||||
case 'DoubleTypeAnnotation': {
|
||||
return 'Double';
|
||||
}
|
||||
case 'FloatTypeAnnotation': {
|
||||
return 'Float';
|
||||
}
|
||||
case 'Int32TypeAnnotation': {
|
||||
return 'Integer';
|
||||
}
|
||||
|
||||
/**
|
||||
* Enums
|
||||
*/
|
||||
// TODO: Make StringEnums type-safe in Pojos
|
||||
case 'StringEnumTypeAnnotation': {
|
||||
importNullable();
|
||||
return '@Nullable String';
|
||||
}
|
||||
|
||||
/**
|
||||
* Other Pojo objects
|
||||
*/
|
||||
case 'PojoTypeAliasTypeAnnotation': {
|
||||
return elementType.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reserved types
|
||||
*/
|
||||
case 'ReservedPropTypeAnnotation': {
|
||||
switch (elementType.name) {
|
||||
case 'ColorPrimitive':
|
||||
importNullable();
|
||||
return '@Nullable Integer';
|
||||
|
||||
// TODO: Make ImageSourcePrimitive type-safe
|
||||
case 'ImageSourcePrimitive':
|
||||
importNullable();
|
||||
importReadableMap();
|
||||
return '@Nullable ReadableMap';
|
||||
|
||||
// TODO: Make PointPrimitive type-safe
|
||||
case 'PointPrimitive':
|
||||
importNullable();
|
||||
importReadableMap();
|
||||
return '@Nullable ReadableMap';
|
||||
|
||||
// TODO: Make EdgeInsetsPrimitive type-safe
|
||||
case 'EdgeInsetsPrimitive':
|
||||
importNullable();
|
||||
importReadableMap();
|
||||
return '@Nullable ReadableMap';
|
||||
default:
|
||||
(elementType.name: empty);
|
||||
throw new Error(
|
||||
`Received unknown ReservedPropTypeAnnotation ${elementType.name}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Arrays
|
||||
case 'ArrayTypeAnnotation': {
|
||||
const {elementType: pojoTypeAliasTypeAnnotation} = elementType;
|
||||
|
||||
importArrayList();
|
||||
return `ArrayList<${pojoTypeAliasTypeAnnotation.name}>`;
|
||||
}
|
||||
default: {
|
||||
(elementType.type: empty);
|
||||
throw new Error(
|
||||
`Unrecognized PojoTypeAnnotation Array element type annotation '${typeAnnotation.type}'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
importArrayList();
|
||||
return `ArrayList<${elementTypeString}>`;
|
||||
}
|
||||
|
||||
default: {
|
||||
(typeAnnotation.type: empty);
|
||||
throw new Error(
|
||||
`Unrecognized PojoTypeAnnotation '${typeAnnotation.type}'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toJavaMemberName(property: PojoProperty): string {
|
||||
return `m${capitalize(property.name)}`;
|
||||
}
|
||||
|
||||
function toJavaMemberDeclaration(
|
||||
property: PojoProperty,
|
||||
addImport: ImportCollector,
|
||||
): string {
|
||||
const type = toJavaType(property.typeAnnotation, addImport);
|
||||
const memberName = toJavaMemberName(property);
|
||||
return `private ${type} ${memberName};`;
|
||||
}
|
||||
|
||||
function toJavaGetter(property: PojoProperty, addImport: ImportCollector) {
|
||||
const type = toJavaType(property.typeAnnotation, addImport);
|
||||
const getterName = `get${capitalize(property.name)}`;
|
||||
const memberName = toJavaMemberName(property);
|
||||
return `public ${type} ${getterName}() {
|
||||
return ${memberName};
|
||||
}`;
|
||||
}
|
||||
|
||||
function serializePojo(pojo: Pojo, basePackageName: string): string {
|
||||
const importSet: Set<string> = new Set();
|
||||
const addImport = ($import: string) => {
|
||||
importSet.add($import);
|
||||
};
|
||||
|
||||
const indent = ' '.repeat(2);
|
||||
|
||||
const members = pojo.properties
|
||||
.map(property => toJavaMemberDeclaration(property, addImport))
|
||||
.map(member => `${indent}${member}`)
|
||||
.join('\n');
|
||||
|
||||
const getters = pojo.properties
|
||||
.map(property => toJavaGetter(property, addImport))
|
||||
.map(getter =>
|
||||
getter
|
||||
.split('\n')
|
||||
.map(line => `${indent}${line}`)
|
||||
.join('\n'),
|
||||
)
|
||||
.join('\n');
|
||||
|
||||
const imports = [...importSet]
|
||||
.map($import => `import ${$import};`)
|
||||
.sort()
|
||||
.join('\n');
|
||||
|
||||
return `/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* ${'@'}generated by codegen project: GeneratePropsJavaPojo.js
|
||||
*/
|
||||
|
||||
package ${basePackageName}.${pojo.namespace};
|
||||
${imports === '' ? '' : `\n${imports}\n`}
|
||||
public class ${pojo.name} {
|
||||
${members}
|
||||
${getters}
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
module.exports = {serializePojo};
|
||||
Reference in New Issue
Block a user