create-expo-cljs-app
Version:
Create a react native application with Expo and Shadow-CLJS!
791 lines (688 loc) • 22.9 kB
JavaScript
/**
* 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.
*
*
* @format
*/
;
const _require = require('./CppHelpers.js'),
convertDefaultTypeToString = _require.convertDefaultTypeToString,
getCppTypeForAnnotation = _require.getCppTypeForAnnotation,
getEnumMaskName = _require.getEnumMaskName,
getEnumName = _require.getEnumName,
toSafeCppString = _require.toSafeCppString,
generateStructName = _require.generateStructName,
getImports = _require.getImports,
toIntEnumValueName = _require.toIntEnumValueName;
const template = `
/**
* 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: GeneratePropsH.js
*/
#pragma once
::_IMPORTS_::
namespace facebook {
namespace react {
::_COMPONENT_CLASSES_::
} // namespace react
} // namespace facebook
`;
const classTemplate = `
::_ENUMS_::
::_STRUCTS_::
class ::_CLASSNAME_:: final::_EXTEND_CLASSES_:: {
public:
::_CLASSNAME_::() = default;
::_CLASSNAME_::(const ::_CLASSNAME_:: &sourceProps, const RawProps &rawProps);
#pragma mark - Props
::_PROPS_::
};
`.trim();
const enumTemplate = `
enum class ::_ENUM_NAME_:: { ::_VALUES_:: };
static inline void fromRawValue(const RawValue &value, ::_ENUM_NAME_:: &result) {
auto string = (std::string)value;
::_FROM_CASES_::
abort();
}
static inline std::string toString(const ::_ENUM_NAME_:: &value) {
switch (value) {
::_TO_CASES_::
}
}
`.trim();
const intEnumTemplate = `
enum class ::_ENUM_NAME_:: { ::_VALUES_:: };
static inline void fromRawValue(const RawValue &value, ::_ENUM_NAME_:: &result) {
assert(value.hasType<int>());
auto integerValue = (int)value;
switch (integerValue) {::_FROM_CASES_::
}
abort();
}
static inline std::string toString(const ::_ENUM_NAME_:: &value) {
switch (value) {
::_TO_CASES_::
}
}
`.trim();
const structTemplate = `struct ::_STRUCT_NAME_:: {
::_FIELDS_::
};
static inline void fromRawValue(const RawValue &value, ::_STRUCT_NAME_:: &result) {
auto map = (better::map<std::string, RawValue>)value;
::_FROM_CASES_::
}
static inline std::string toString(const ::_STRUCT_NAME_:: &value) {
return "[Object ::_STRUCT_NAME_::]";
}
`.trim();
const arrayConversionFunction = `static inline void fromRawValue(const RawValue &value, std::vector<::_STRUCT_NAME_::> &result) {
auto items = (std::vector<RawValue>)value;
for (const auto &item : items) {
::_STRUCT_NAME_:: newItem;
fromRawValue(item, newItem);
result.emplace_back(newItem);
}
}
`;
const doubleArrayConversionFunction = `static inline void fromRawValue(const RawValue &value, std::vector<std::vector<::_STRUCT_NAME_::>> &result) {
auto items = (std::vector<std::vector<RawValue>>)value;
for (const std::vector<RawValue> &item : items) {
auto nestedArray = std::vector<::_STRUCT_NAME_::>{};
for (const RawValue &nestedItem : item) {
::_STRUCT_NAME_:: newItem;
fromRawValue(nestedItem, newItem);
nestedArray.emplace_back(newItem);
}
result.emplace_back(nestedArray);
}
}
`;
const arrayEnumTemplate = `
using ::_ENUM_MASK_:: = uint32_t;
enum class ::_ENUM_NAME_::: ::_ENUM_MASK_:: {
::_VALUES_::
};
constexpr bool operator&(
::_ENUM_MASK_:: const lhs,
enum ::_ENUM_NAME_:: const rhs) {
return lhs & static_cast<::_ENUM_MASK_::>(rhs);
}
constexpr ::_ENUM_MASK_:: operator|(
::_ENUM_MASK_:: const lhs,
enum ::_ENUM_NAME_:: const rhs) {
return lhs | static_cast<::_ENUM_MASK_::>(rhs);
}
constexpr void operator|=(
::_ENUM_MASK_:: &lhs,
enum ::_ENUM_NAME_:: const rhs) {
lhs = lhs | static_cast<::_ENUM_MASK_::>(rhs);
}
static inline void fromRawValue(const RawValue &value, ::_ENUM_MASK_:: &result) {
auto items = std::vector<std::string>{value};
for (const auto &item : items) {
::_FROM_CASES_::
abort();
}
}
static inline std::string toString(const ::_ENUM_MASK_:: &value) {
auto result = std::string{};
auto separator = std::string{", "};
::_TO_CASES_::
if (!result.empty()) {
result.erase(result.length() - separator.length());
}
return result;
}
`.trim();
function getClassExtendString(component) {
const extendString =
' : ' +
component.extendsProps
.map(extendProps => {
switch (extendProps.type) {
case 'ReactNativeBuiltInType':
switch (extendProps.knownTypeName) {
case 'ReactNativeCoreViewProps':
return 'public ViewProps';
default:
extendProps.knownTypeName;
throw new Error('Invalid knownTypeName');
}
default:
extendProps.type;
throw new Error('Invalid extended type');
}
})
.join(' ');
return extendString;
}
function getNativeTypeFromAnnotation(componentName, prop, nameParts) {
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;
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;
throw new Error(
`Received invalid typeAnnotation for ${componentName} prop ${prop.name}, received ${typeAnnotation.type}`,
);
}
}
function convertValueToEnumOption(value) {
return toSafeCppString(value);
}
function generateArrayEnumString(componentName, name, options) {
const enumName = getEnumName(componentName, name);
const values = options
.map((option, index) => `${toSafeCppString(option)} = 1 << ${index}`)
.join(',\n ');
const fromCases = options
.map(
option => `if (item == "${option}") {
result |= ${enumName}::${toSafeCppString(option)};
continue;
}`,
)
.join('\n ');
const toCases = options
.map(
option => `if (value & ${enumName}::${toSafeCppString(option)}) {
result += "${option}" + separator;
}`,
)
.join('\n' + ' ');
return arrayEnumTemplate
.replace(/::_ENUM_NAME_::/g, enumName)
.replace(/::_ENUM_MASK_::/g, getEnumMaskName(enumName))
.replace('::_VALUES_::', values)
.replace('::_FROM_CASES_::', fromCases)
.replace('::_TO_CASES_::', toCases);
}
function generateStringEnum(componentName, prop) {
const typeAnnotation = prop.typeAnnotation;
if (typeAnnotation.type === 'StringEnumTypeAnnotation') {
const values = typeAnnotation.options;
const enumName = getEnumName(componentName, prop.name);
const fromCases = values
.map(
value =>
`if (string == "${value}") { result = ${enumName}::${convertValueToEnumOption(
value,
)}; return; }`,
)
.join('\n' + ' ');
const toCases = values
.map(
value =>
`case ${enumName}::${convertValueToEnumOption(
value,
)}: return "${value}";`,
)
.join('\n' + ' ');
return enumTemplate
.replace(/::_ENUM_NAME_::/g, enumName)
.replace('::_VALUES_::', values.map(toSafeCppString).join(', '))
.replace('::_FROM_CASES_::', fromCases)
.replace('::_TO_CASES_::', toCases);
}
return '';
}
function generateIntEnum(componentName, prop) {
const typeAnnotation = prop.typeAnnotation;
if (typeAnnotation.type === 'Int32EnumTypeAnnotation') {
const values = typeAnnotation.options;
const enumName = getEnumName(componentName, prop.name);
const fromCases = values
.map(
value => `
case ${value}:
result = ${enumName}::${toIntEnumValueName(prop.name, value)};
return;`,
)
.join('');
const toCases = values
.map(
value =>
`case ${enumName}::${toIntEnumValueName(
prop.name,
value,
)}: return "${value}";`,
)
.join('\n' + ' ');
const valueVariables = values
.map(val => `${toIntEnumValueName(prop.name, val)} = ${val}`)
.join(', ');
return intEnumTemplate
.replace(/::_ENUM_NAME_::/g, enumName)
.replace('::_VALUES_::', valueVariables)
.replace('::_FROM_CASES_::', fromCases)
.replace('::_TO_CASES_::', toCases);
}
return '';
}
function generateEnumString(componentName, component) {
return component.props
.map(prop => {
if (
prop.typeAnnotation.type === 'ArrayTypeAnnotation' &&
prop.typeAnnotation.elementType.type === 'StringEnumTypeAnnotation'
) {
return generateArrayEnumString(
componentName,
prop.name,
prop.typeAnnotation.elementType.options,
);
}
if (prop.typeAnnotation.type === 'StringEnumTypeAnnotation') {
return generateStringEnum(componentName, prop);
}
if (prop.typeAnnotation.type === 'Int32EnumTypeAnnotation') {
return generateIntEnum(componentName, prop);
}
if (prop.typeAnnotation.type === 'ObjectTypeAnnotation') {
return prop.typeAnnotation.properties
.map(property => {
if (property.typeAnnotation.type === 'StringEnumTypeAnnotation') {
return generateStringEnum(componentName, property);
} else if (
property.typeAnnotation.type === 'Int32EnumTypeAnnotation'
) {
return generateIntEnum(componentName, property);
}
return null;
})
.filter(Boolean)
.join('\n');
}
})
.filter(Boolean)
.join('\n');
}
function generatePropsString(componentName, props) {
return props
.map(prop => {
const nativeType = getNativeTypeFromAnnotation(componentName, prop, []);
const defaultValue = convertDefaultTypeToString(componentName, prop);
return `${nativeType} ${prop.name}{${defaultValue}};`;
})
.join('\n' + ' ');
}
function getExtendsImports(extendsProps) {
const imports = new Set();
extendsProps.forEach(extendProps => {
switch (extendProps.type) {
case 'ReactNativeBuiltInType':
switch (extendProps.knownTypeName) {
case 'ReactNativeCoreViewProps':
imports.add(
'#include <react/renderer/components/view/ViewProps.h>',
);
return;
default:
extendProps.knownTypeName;
throw new Error('Invalid knownTypeName');
}
default:
extendProps.type;
throw new Error('Invalid extended type');
}
});
return imports;
}
function getLocalImports(properties) {
const imports = new Set();
function addImportsForNativeName(name) {
switch (name) {
case 'ColorPrimitive':
imports.add('#include <react/renderer/graphics/Color.h>');
return;
case 'ImageSourcePrimitive':
imports.add('#include <react/renderer/imagemanager/primitives.h>');
return;
case 'PointPrimitive':
imports.add('#include <react/renderer/graphics/Geometry.h>');
return;
case 'EdgeInsetsPrimitive':
imports.add('#include <react/renderer/graphics/Geometry.h>');
return;
default:
name;
throw new Error(`Invalid ReservedPropTypeAnnotation name, got ${name}`);
}
}
properties.forEach(prop => {
const typeAnnotation = prop.typeAnnotation;
if (typeAnnotation.type === 'ReservedPropTypeAnnotation') {
addImportsForNativeName(typeAnnotation.name);
}
if (typeAnnotation.type === 'ArrayTypeAnnotation') {
imports.add('#include <vector>');
if (typeAnnotation.elementType.type === 'StringEnumTypeAnnotation') {
imports.add('#include <cinttypes>');
}
}
if (
typeAnnotation.type === 'ArrayTypeAnnotation' &&
typeAnnotation.elementType.type === 'ReservedPropTypeAnnotation'
) {
addImportsForNativeName(typeAnnotation.elementType.name);
}
if (
typeAnnotation.type === 'ArrayTypeAnnotation' &&
typeAnnotation.elementType.type === 'ObjectTypeAnnotation'
) {
const objectProps = typeAnnotation.elementType.properties;
const objectImports = getImports(objectProps);
const localImports = getLocalImports(objectProps);
objectImports.forEach(imports.add, imports);
localImports.forEach(imports.add, imports);
}
if (typeAnnotation.type === 'ObjectTypeAnnotation') {
imports.add('#include <react/renderer/core/propsConversions.h>');
const objectImports = getImports(typeAnnotation.properties);
const localImports = getLocalImports(typeAnnotation.properties);
objectImports.forEach(imports.add, imports);
localImports.forEach(imports.add, imports);
}
});
return imports;
}
function generateStructsForComponent(componentName, component) {
const structs = generateStructs(componentName, component.props, []);
const structArray = Array.from(structs.values());
if (structArray.length < 1) {
return '';
}
return structArray.join('\n\n');
}
function generateStructs(componentName, properties, nameParts) {
const structs = new Map();
properties.forEach(prop => {
const typeAnnotation = prop.typeAnnotation;
if (typeAnnotation.type === 'ObjectTypeAnnotation') {
// Recursively visit all of the object properties.
// Note: this is depth first so that the nested structs are ordered first.
const elementProperties = typeAnnotation.properties;
const nestedStructs = generateStructs(
componentName,
elementProperties,
nameParts.concat([prop.name]),
);
nestedStructs.forEach(function(value, key) {
structs.set(key, value);
});
generateStruct(
structs,
componentName,
nameParts.concat([prop.name]),
typeAnnotation.properties,
);
}
if (
prop.typeAnnotation.type === 'ArrayTypeAnnotation' &&
prop.typeAnnotation.elementType.type === 'ObjectTypeAnnotation'
) {
// Recursively visit all of the object properties.
// Note: this is depth first so that the nested structs are ordered first.
const elementProperties = prop.typeAnnotation.elementType.properties;
const nestedStructs = generateStructs(
componentName,
elementProperties,
nameParts.concat([prop.name]),
);
nestedStructs.forEach(function(value, key) {
structs.set(key, value);
}); // Generate this struct and its conversion function.
generateStruct(
structs,
componentName,
nameParts.concat([prop.name]),
elementProperties,
); // Generate the conversion function for std:vector<Object>.
// Note: This needs to be at the end since it references the struct above.
structs.set(
`${[componentName, ...nameParts.concat([prop.name])].join(
'',
)}ArrayStruct`,
arrayConversionFunction.replace(
/::_STRUCT_NAME_::/g,
generateStructName(componentName, nameParts.concat([prop.name])),
),
);
}
if (
prop.typeAnnotation.type === 'ArrayTypeAnnotation' &&
prop.typeAnnotation.elementType.type === 'ArrayTypeAnnotation' &&
prop.typeAnnotation.elementType.elementType.type ===
'ObjectTypeAnnotation'
) {
// Recursively visit all of the object properties.
// Note: this is depth first so that the nested structs are ordered first.
const elementProperties =
prop.typeAnnotation.elementType.elementType.properties;
const nestedStructs = generateStructs(
componentName,
elementProperties,
nameParts.concat([prop.name]),
);
nestedStructs.forEach(function(value, key) {
structs.set(key, value);
}); // Generate this struct and its conversion function.
generateStruct(
structs,
componentName,
nameParts.concat([prop.name]),
elementProperties,
); // Generate the conversion function for std:vector<Object>.
// Note: This needs to be at the end since it references the struct above.
structs.set(
`${[componentName, ...nameParts.concat([prop.name])].join(
'',
)}ArrayArrayStruct`,
doubleArrayConversionFunction.replace(
/::_STRUCT_NAME_::/g,
generateStructName(componentName, nameParts.concat([prop.name])),
),
);
}
});
return structs;
}
function generateStruct(structs, componentName, nameParts, properties) {
const structNameParts = nameParts;
const structName = generateStructName(componentName, structNameParts);
const fields = properties
.map(property => {
return `${getNativeTypeFromAnnotation(
componentName,
property,
structNameParts,
)} ${property.name};`;
})
.join('\n' + ' ');
properties.forEach(property => {
const name = property.name;
switch (property.typeAnnotation.type) {
case 'BooleanTypeAnnotation':
return;
case 'StringTypeAnnotation':
return;
case 'Int32TypeAnnotation':
return;
case 'DoubleTypeAnnotation':
return;
case 'FloatTypeAnnotation':
return;
case 'ReservedPropTypeAnnotation':
return;
case 'ArrayTypeAnnotation':
return;
case 'StringEnumTypeAnnotation':
return;
case 'Int32EnumTypeAnnotation':
return;
case 'DoubleTypeAnnotation':
return;
case 'ObjectTypeAnnotation':
const props = property.typeAnnotation.properties;
if (props == null) {
throw new Error(
`Properties are expected for ObjectTypeAnnotation (see ${name} in ${componentName})`,
);
}
generateStruct(structs, componentName, nameParts.concat([name]), props);
return;
default:
property.typeAnnotation.type;
throw new Error(
`Received invalid component property type ${property.typeAnnotation.type}`,
);
}
});
const fromCases = properties
.map(property => {
const variable = property.name;
return `auto ${variable} = map.find("${property.name}");
if (${variable} != map.end()) {
fromRawValue(${variable}->second, result.${variable});
}`;
})
.join('\n ');
structs.set(
structName,
structTemplate
.replace(/::_STRUCT_NAME_::/g, structName)
.replace('::_FIELDS_::', fields)
.replace('::_FROM_CASES_::', fromCases),
);
}
module.exports = {
generate(libraryName, schema, moduleSpecName, packageName) {
const fileName = 'Props.h';
const allImports = new Set();
const componentClasses = 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 Object.keys(components)
.filter(componentName => {
const component = components[componentName];
return !(
component.excludedPlatforms &&
component.excludedPlatforms.includes('iOS')
);
})
.map(componentName => {
const component = components[componentName];
const newName = `${componentName}Props`;
const structString = generateStructsForComponent(
componentName,
component,
);
const enumString = generateEnumString(componentName, component);
const propsString = generatePropsString(
componentName,
component.props,
);
const extendString = getClassExtendString(component);
const extendsImports = getExtendsImports(component.extendsProps);
const imports = getLocalImports(component.props);
extendsImports.forEach(allImports.add, allImports);
imports.forEach(allImports.add, allImports);
const replacedTemplate = classTemplate
.replace('::_ENUMS_::', enumString)
.replace('::_STRUCTS_::', structString)
.replace(/::_CLASSNAME_::/g, newName)
.replace('::_EXTEND_CLASSES_::', extendString)
.replace('::_PROPS_::', propsString)
.trim();
return replacedTemplate;
})
.join('\n\n');
})
.filter(Boolean)
.join('\n\n');
const replacedTemplate = template
.replace(/::_COMPONENT_CLASSES_::/g, componentClasses)
.replace(
'::_IMPORTS_::',
Array.from(allImports)
.sort()
.join('\n'),
);
return new Map([[fileName, replacedTemplate]]);
},
};