From 3848f48943d0530c3712394b9e405ebddf21cf3b Mon Sep 17 00:00:00 2001 From: Ramanpreet Nara Date: Mon, 1 Nov 2021 08:58:47 -0700 Subject: [PATCH] Use JavaScript functions for Component templates Summary: ## Rationale **Disclaimer**: This is an incremental step towards more maintainable/readable react-native-codegen generators. In the future, we may want to replace these templates/string concat logic with *something better*. But until we decide what that *something better* is, let's at least get rid of all this gross string find/replace. Benefits of using Function templates over String.prototype.replace. - **Self-documenting**: Template Functions enumerate/describe their exact data dependencies in their signature. You no longer have to read the template implementation to see what data you need to pass into the template. - **Improved Readability**: JavaScript syntax highlighting makes it really easy to see where/how the data is inserted into the templates. Also template variables used be prefixed/suffixed with ::, which made things really confusing in C++ code (e.g: wtf is `::_CLASSNAME_::EventEmitter::::_EVENT_NAME_::`?). - **Simpler Interpolation**: Don't have to worry about .replaceAll vs .replace, or calling these replace functions with regexes or strings. - **Template Type-safety**: Ensure that the correct data types are passed to the component templates (e.g: flow will complain if you accidentally pass null/undefined when a template expects a string). - **Template Type-safety**: Ensure that we don't pass in extra data to templates (this diff catches/fixes instances of this error). Ensure that we don't forget to pass in data to the template. - etc. After this diff, both our Component and NativeModule generators will be using template functions. This string find/replace exists no more in react-native-codegen. This is also a very surface-level change. I made no efforts to simplify these templates. Let's take a look at that later, as necessary. Changelog: [Internal] Reviewed By: yungsters Differential Revision: D32021441 fbshipit-source-id: f8f27069bcbf9d66dcafb7d1411da1f938eb6dcd --- .../GenerateComponentDescriptorH.js | 27 +- .../components/GenerateComponentHObjCpp.js | 132 ++++++--- .../components/GenerateEventEmitterCpp.js | 78 ++++-- .../components/GenerateEventEmitterH.js | 88 +++--- .../generators/components/GeneratePropsCpp.js | 63 +++-- .../generators/components/GeneratePropsH.js | 265 +++++++++++------- .../components/GeneratePropsJavaDelegate.js | 70 +++-- .../components/GeneratePropsJavaInterface.js | 48 ++-- .../components/GenerateShadowNodeCpp.js | 31 +- .../components/GenerateShadowNodeH.js | 49 ++-- .../generators/components/GenerateTests.js | 62 ++-- .../components/GenerateViewConfigJs.js | 78 +++--- 12 files changed, 632 insertions(+), 359 deletions(-) diff --git a/packages/react-native-codegen/src/generators/components/GenerateComponentDescriptorH.js b/packages/react-native-codegen/src/generators/components/GenerateComponentDescriptorH.js index 523def82847..0ae2fb069fc 100644 --- a/packages/react-native-codegen/src/generators/components/GenerateComponentDescriptorH.js +++ b/packages/react-native-codegen/src/generators/components/GenerateComponentDescriptorH.js @@ -15,7 +15,13 @@ import type {SchemaType} from '../../CodegenSchema'; // File path -> contents type FilesOutput = Map; -const template = ` +const FileTemplate = ({ + componentDescriptors, + libraryName, +}: { + componentDescriptors: string, + libraryName: string, +}) => ` /** * ${'C'}opyright (c) Facebook, Inc. and its affiliates. * @@ -27,20 +33,21 @@ const template = ` #pragma once -#include +#include #include namespace facebook { namespace react { -::_COMPONENT_DESCRIPTORS_:: +${componentDescriptors} } // namespace react } // namespace facebook `; -const componentTemplate = ` -using ::_CLASSNAME_::ComponentDescriptor = ConcreteComponentDescriptor<::_CLASSNAME_::ShadowNode>; +const ComponentTemplate = ({className}: {className: string}) => + ` +using ${className}ComponentDescriptor = ConcreteComponentDescriptor<${className}ShadowNode>; `.trim(); module.exports = { @@ -70,16 +77,18 @@ module.exports = { if (components[componentName].interfaceOnly === true) { return; } - return componentTemplate.replace(/::_CLASSNAME_::/g, componentName); + + return ComponentTemplate({className: componentName}); }) .join('\n'); }) .filter(Boolean) .join('\n'); - const replacedTemplate = template - .replace(/::_COMPONENT_DESCRIPTORS_::/g, componentDescriptors) - .replace('::_LIBRARY_::', libraryName); + const replacedTemplate = FileTemplate({ + componentDescriptors, + libraryName, + }); return new Map([[fileName, replacedTemplate]]); }, diff --git a/packages/react-native-codegen/src/generators/components/GenerateComponentHObjCpp.js b/packages/react-native-codegen/src/generators/components/GenerateComponentHObjCpp.js index e78810ef70b..4c0c3119238 100644 --- a/packages/react-native-codegen/src/generators/components/GenerateComponentHObjCpp.js +++ b/packages/react-native-codegen/src/generators/components/GenerateComponentHObjCpp.js @@ -37,53 +37,96 @@ function getOrdinalNumber(num: number): string { return 'unknown'; } -const protocolTemplate = ` -@protocol RCT::_COMPONENT_NAME_::ViewProtocol -::_METHODS_:: +const ProtocolTemplate = ({ + componentName, + methods, +}: { + componentName: string, + methods: string, +}) => + ` +@protocol RCT${componentName}ViewProtocol +${methods} @end `.trim(); -const commandHandlerIfCaseConvertArgTemplate = ` - NSObject *arg::_ARG_NUMBER_:: = args[::_ARG_NUMBER_::]; +const CommandHandlerIfCaseConvertArgTemplate = ({ + componentName, + expectedKind, + argNumber, + argNumberString, + expectedKindString, + argConversion, +}: { + componentName: string, + expectedKind: string, + argNumber: number, + argNumberString: string, + expectedKindString: string, + argConversion: string, +}) => + ` + NSObject *arg${argNumber} = args[${argNumber}]; #if RCT_DEBUG - if (!RCTValidateTypeOfViewCommandArgument(arg::_ARG_NUMBER_::, ::_EXPECTED_KIND_::, @"::_EXPECTED_KIND_STRING_::", @"::_COMPONENT_NAME_::", commandName, @"::_ARG_NUMBER_STR_::")) { + if (!RCTValidateTypeOfViewCommandArgument(arg${argNumber}, ${expectedKind}, @"${expectedKindString}", @"${componentName}", commandName, @"${argNumberString}")) { return; } #endif - ::_ARG_CONVERSION_:: + ${argConversion} `.trim(); -const commandHandlerIfCaseTemplate = ` -if ([commandName isEqualToString:@"::_COMMAND_NAME_::"]) { +const CommandHandlerIfCaseTemplate = ({ + componentName, + commandName, + numArgs, + convertArgs, + commandCall, +}: { + componentName: string, + commandName: string, + numArgs: number, + convertArgs: string, + commandCall: string, +}) => + ` +if ([commandName isEqualToString:@"${commandName}"]) { #if RCT_DEBUG - if ([args count] != ::_NUM_ARGS_::) { - RCTLogError(@"%@ command %@ received %d arguments, expected %d.", @"::_COMPONENT_NAME_::", commandName, (int)[args count], ::_NUM_ARGS_::); + if ([args count] != ${numArgs}) { + RCTLogError(@"%@ command %@ received %d arguments, expected %d.", @"${componentName}", commandName, (int)[args count], ${numArgs}); return; } #endif - ::_CONVERT_ARGS_:: + ${convertArgs} - ::_COMMAND_CALL_:: + ${commandCall} return; } `.trim(); -const commandHandlerTemplate = ` -RCT_EXTERN inline void RCT::_COMPONENT_NAME_::HandleCommand( - id componentView, +const CommandHandlerTemplate = ({ + componentName, + ifCases, +}: { + componentName: string, + ifCases: string, +}) => + ` +RCT_EXTERN inline void RCT${componentName}HandleCommand( + id componentView, NSString const *commandName, NSArray const *args) { - ::_IF_CASES_:: + ${ifCases} #if RCT_DEBUG - RCTLogError(@"%@ received command %@, which is not a supported command.", @"::_COMPONENT_NAME_::", commandName); + RCTLogError(@"%@ received command %@, which is not a supported command.", @"${componentName}", commandName); #endif } `.trim(); -const template = ` +const FileTemplate = ({componentContent}: {componentContent: string}) => + ` /** * ${'C'}opyright (c) Facebook, Inc. and its affiliates. * @@ -99,7 +142,7 @@ const template = ` NS_ASSUME_NONNULL_BEGIN -::_COMPONENT_CONTENT_:: +${componentContent} NS_ASSUME_NONNULL_END `.trim(); @@ -225,7 +268,7 @@ function generateProtocol( component: ComponentShape, componentName: string, ): string { - const commands = component.commands + const methods = component.commands .map(command => { const params = command.typeAnnotation.params; const paramString = @@ -245,9 +288,10 @@ function generateProtocol( .join('\n') .trim(); - return protocolTemplate - .replace(/::_COMPONENT_NAME_::/g, componentName) - .replace('::_METHODS_::', commands); + return ProtocolTemplate({ + componentName, + methods, + }); } function generateConvertAndValidateParam( @@ -262,13 +306,14 @@ function generateConvertAndValidateParam( param.name } = ${getObjCRightHandAssignmentParamType(param, index)};`; - return commandHandlerIfCaseConvertArgTemplate - .replace(/::_COMPONENT_NAME_::/g, componentName) - .replace('::_ARG_CONVERSION_::', argConversion) - .replace(/::_ARG_NUMBER_::/g, '' + index) - .replace('::_ARG_NUMBER_STR_::', getOrdinalNumber(index + 1)) - .replace('::_EXPECTED_KIND_::', expectedKind) - .replace('::_EXPECTED_KIND_STRING_::', expectedKindString); + return CommandHandlerIfCaseConvertArgTemplate({ + componentName, + argConversion, + argNumber: index, + argNumberString: getOrdinalNumber(index + 1), + expectedKind, + expectedKindString, + }); } function generateCommandIfCase( @@ -294,12 +339,13 @@ function generateCommandIfCase( .join(' '); const commandCall = `[componentView ${command.name}${commandCallArgs}];`; - return commandHandlerIfCaseTemplate - .replace(/::_COMPONENT_NAME_::/g, componentName) - .replace(/::_COMMAND_NAME_::/g, command.name) - .replace(/::_NUM_ARGS_::/g, '' + params.length) - .replace('::_CONVERT_ARGS_::', convertArgs) - .replace('::_COMMAND_CALL_::', commandCall); + return CommandHandlerIfCaseTemplate({ + componentName, + commandName: command.name, + numArgs: params.length, + convertArgs, + commandCall, + }); } function generateCommandHandler( @@ -314,9 +360,10 @@ function generateCommandHandler( .map(command => generateCommandIfCase(command, componentName)) .join('\n\n'); - return commandHandlerTemplate - .replace(/::_COMPONENT_NAME_::/g, componentName) - .replace('::_IF_CASES_::', ifCases); + return CommandHandlerTemplate({ + componentName, + ifCases, + }); } module.exports = { @@ -362,10 +409,9 @@ module.exports = { .filter(Boolean) .join('\n\n'); - const replacedTemplate = template.replace( - '::_COMPONENT_CONTENT_::', + const replacedTemplate = FileTemplate({ componentContent, - ); + }); return new Map([[fileName, replacedTemplate]]); }, diff --git a/packages/react-native-codegen/src/generators/components/GenerateEventEmitterCpp.js b/packages/react-native-codegen/src/generators/components/GenerateEventEmitterCpp.js index d9ed177394d..171cb5bb517 100644 --- a/packages/react-native-codegen/src/generators/components/GenerateEventEmitterCpp.js +++ b/packages/react-native-codegen/src/generators/components/GenerateEventEmitterCpp.js @@ -27,7 +27,13 @@ type ComponentCollection = $ReadOnly<{ ..., }>; -const template = ` +const FileTemplate = ({ + events, + libraryName, +}: { + events: string, + libraryName: string, +}) => ` /** * ${'C'}opyright (c) Facebook, Inc. and its affiliates. * @@ -37,28 +43,50 @@ const template = ` * ${'@'}generated by codegen project: GenerateEventEmitterCpp.js */ -#include +#include namespace facebook { namespace react { -::_EVENTS_:: +${events} } // namespace react } // namespace facebook `; -const componentTemplate = ` -void ::_CLASSNAME_::EventEmitter::::_EVENT_NAME_::(::_STRUCT_NAME_:: event) const { - dispatchEvent("::_DISPATCH_EVENT_NAME_::", [event=std::move(event)](jsi::Runtime &runtime) { - ::_IMPLEMENTATION_:: +const ComponentTemplate = ({ + className, + eventName, + structName, + dispatchEventName, + implementation, +}: { + className: string, + eventName: string, + structName: string, + dispatchEventName: string, + implementation: string, +}) => + ` +void ${className}EventEmitter::${eventName}(${structName} event) const { + dispatchEvent("${dispatchEventName}", [event=std::move(event)](jsi::Runtime &runtime) { + ${implementation} }); } `.trim(); -const basicComponentTemplate = ` -void ::_CLASSNAME_::EventEmitter::::_EVENT_NAME_::() const { - dispatchEvent("::_DISPATCH_EVENT_NAME_::"); +const BasicComponentTemplate = ({ + className, + eventName, + dispatchEventName, +}: { + className: string, + eventName: string, + dispatchEventName: string, +}) => + ` +void ${className}EventEmitter::${eventName}() const { + dispatchEvent("${dispatchEventName}"); } `.trim(); @@ -171,18 +199,20 @@ function generateEvent(componentName: string, event): string { throw new Error('Expected the event name to start with `on`'); } - return componentTemplate - .replace(/::_CLASSNAME_::/g, componentName) - .replace(/::_EVENT_NAME_::/g, event.name) - .replace(/::_DISPATCH_EVENT_NAME_::/g, dispatchEventName) - .replace('::_STRUCT_NAME_::', generateEventStructName([event.name])) - .replace('::_IMPLEMENTATION_::', implementation); + return ComponentTemplate({ + className: componentName, + eventName: event.name, + dispatchEventName, + structName: generateEventStructName([event.name]), + implementation, + }); } - return basicComponentTemplate - .replace(/::_CLASSNAME_::/g, componentName) - .replace(/::_EVENT_NAME_::/g, event.name) - .replace(/::_DISPATCH_EVENT_NAME_::/g, dispatchEventName); + return BasicComponentTemplate({ + className: componentName, + eventName: event.name, + dispatchEventName, + }); } module.exports = { @@ -224,10 +254,10 @@ module.exports = { }) .join('\n'); - const replacedTemplate = template - .replace(/::_COMPONENT_EMITTERS_::/g, componentEmitters) - .replace('::_LIBRARY_::', libraryName) - .replace('::_EVENTS_::', componentEmitters); + const replacedTemplate = FileTemplate({ + libraryName, + events: componentEmitters, + }); return new Map([[fileName, replacedTemplate]]); }, diff --git a/packages/react-native-codegen/src/generators/components/GenerateEventEmitterH.js b/packages/react-native-codegen/src/generators/components/GenerateEventEmitterH.js index a4a4f1f98e7..8ada23c7a92 100644 --- a/packages/react-native-codegen/src/generators/components/GenerateEventEmitterH.js +++ b/packages/react-native-codegen/src/generators/components/GenerateEventEmitterH.js @@ -35,7 +35,7 @@ type ComponentCollection = $ReadOnly<{ ..., }>; -const template = ` +const FileTemplate = ({componentEmitters}: {componentEmitters: string}) => ` /** * ${'C'}opyright (c) Facebook, Inc. and its affiliates. * @@ -51,36 +51,61 @@ const template = ` namespace facebook { namespace react { -::_COMPONENT_EMITTERS_:: +${componentEmitters} } // namespace react } // namespace facebook `; -const componentTemplate = ` -class ::_CLASSNAME_::EventEmitter : public ViewEventEmitter { +const ComponentTemplate = ({ + className, + structs, + events, +}: { + className: string, + structs: string, + events: string, +}) => + ` +class ${className}EventEmitter : public ViewEventEmitter { public: using ViewEventEmitter::ViewEventEmitter; - ::_STRUCTS_:: + ${structs} - ::_EVENTS_:: + ${events} }; `.trim(); -const structTemplate = ` - struct ::_STRUCT_NAME_:: { - ::_FIELDS_:: +const StructTemplate = ({ + structName, + fields, +}: { + structName: string, + fields: string, +}) => + ` + struct ${structName} { + ${fields} }; `.trim(); -const enumTemplate = `enum class ::_ENUM_NAME_:: { - ::_VALUES_:: +const EnumTemplate = ({ + enumName, + values, + toCases, +}: { + enumName: string, + values: string, + toCases: string, +}) => + `enum class ${enumName} { + ${values} }; -static char const *toString(const ::_ENUM_NAME_:: value) { +static char const *toString(const ${enumName} value) { switch (value) { - ::_TO_CASES_:: + ${toCases} } } `.trim(); @@ -136,10 +161,11 @@ function generateEnum(structs, options, nameParts) { structs.set( structName, - enumTemplate - .replace(/::_ENUM_NAME_::/g, structName) - .replace('::_VALUES_::', fields) - .replace('::_TO_CASES_::', toCases), + EnumTemplate({ + enumName: structName, + values: fields, + toCases: toCases, + }), ); } @@ -196,9 +222,10 @@ function generateStruct( structs.set( structName, - structTemplate - .replace('::_STRUCT_NAME_::', structName) - .replace('::_FIELDS_::', fields), + StructTemplate({ + structName, + fields, + }), ); } @@ -271,27 +298,20 @@ module.exports = { .map(componentName => { const component = moduleComponents[componentName]; - const replacedTemplate = componentTemplate - .replace(/::_CLASSNAME_::/g, componentName) - .replace( - '::_STRUCTS_::', - indent(generateStructs(componentName, component), 2), - ) - .replace( - '::_EVENTS_::', - generateEvents(componentName, component), - ) - .trim(); + const replacedTemplate = ComponentTemplate({ + className: componentName, + structs: indent(generateStructs(componentName, component), 2), + events: generateEvents(componentName, component), + }); return replacedTemplate; }) .join('\n') : ''; - const replacedTemplate = template.replace( - /::_COMPONENT_EMITTERS_::/g, + const replacedTemplate = FileTemplate({ componentEmitters, - ); + }); return new Map([[fileName, replacedTemplate]]); }, diff --git a/packages/react-native-codegen/src/generators/components/GeneratePropsCpp.js b/packages/react-native-codegen/src/generators/components/GeneratePropsCpp.js index ca3e743c778..03015faee37 100644 --- a/packages/react-native-codegen/src/generators/components/GeneratePropsCpp.js +++ b/packages/react-native-codegen/src/generators/components/GeneratePropsCpp.js @@ -16,7 +16,15 @@ const {convertDefaultTypeToString, getImports} = require('./CppHelpers'); // File path -> contents type FilesOutput = Map; -const template = ` +const FileTemplate = ({ + libraryName, + imports, + componentClasses, +}: { + libraryName: string, + imports: string, + componentClasses: string, +}) => ` /** * ${'C'}opyright (c) Facebook, Inc. and its affiliates. * @@ -26,25 +34,34 @@ const template = ` * ${'@'}generated by codegen project: GeneratePropsCpp.js */ -#include -::_IMPORTS_:: +#include +${imports} namespace facebook { namespace react { -::_COMPONENT_CLASSES_:: +${componentClasses} } // namespace react } // namespace facebook `; -const componentTemplate = ` -::_CLASSNAME_::::::_CLASSNAME_::( +const ComponentTemplate = ({ + className, + extendClasses, + props, +}: { + className: string, + extendClasses: string, + props: string, +}) => + ` +${className}::${className}( const PropsParserContext &context, - const ::_CLASSNAME_:: &sourceProps, - const RawProps &rawProps):::_EXTEND_CLASSES_:: + const ${className} &sourceProps, + const RawProps &rawProps):${extendClasses} - ::_PROPS_:: + ${props} {} `.trim(); @@ -127,10 +144,11 @@ module.exports = { // $FlowFixMe[method-unbinding] added when improving typing for this parameters imports.forEach(allImports.add, allImports); - const replacedTemplate = componentTemplate - .replace(/::_CLASSNAME_::/g, newName) - .replace('::_EXTEND_CLASSES_::', extendString) - .replace('::_PROPS_::', propsString); + const replacedTemplate = ComponentTemplate({ + className: newName, + extendClasses: extendString, + props: propsString, + }); return replacedTemplate; }) @@ -139,17 +157,14 @@ module.exports = { .filter(Boolean) .join('\n'); - const replacedTemplate = template - .replace(/::_COMPONENT_CLASSES_::/g, componentProps) - .replace('::_LIBRARY_::', libraryName) - .replace( - '::_IMPORTS_::', - - Array.from(allImports) - .sort() - .join('\n') - .trim(), - ); + const replacedTemplate = FileTemplate({ + componentClasses: componentProps, + libraryName, + imports: Array.from(allImports) + .sort() + .join('\n') + .trim(), + }); return new Map([[fileName, replacedTemplate]]); }, diff --git a/packages/react-native-codegen/src/generators/components/GeneratePropsH.js b/packages/react-native-codegen/src/generators/components/GeneratePropsH.js index e580ca8c178..72a31c67f1e 100644 --- a/packages/react-native-codegen/src/generators/components/GeneratePropsH.js +++ b/packages/react-native-codegen/src/generators/components/GeneratePropsH.js @@ -32,7 +32,13 @@ import type { type FilesOutput = Map; type StructsMap = Map; -const template = ` +const FileTemplate = ({ + imports, + componentClasses, +}: { + imports: string, + componentClasses: string, +}) => ` /** * ${'C'}opyright (c) Facebook, Inc. and its affiliates. * @@ -43,96 +49,148 @@ const template = ` */ #pragma once -::_IMPORTS_:: +${imports} namespace facebook { namespace react { -::_COMPONENT_CLASSES_:: +${componentClasses} } // namespace react } // namespace facebook `; -const classTemplate = ` -::_ENUMS_:: -::_STRUCTS_:: -class ::_CLASSNAME_:: final::_EXTEND_CLASSES_:: { +const ClassTemplate = ({ + enums, + structs, + className, + props, + extendClasses, +}: { + enums: string, + structs: string, + className: string, + props: string, + extendClasses: string, +}) => + ` +${enums} +${structs} +class ${className} final${extendClasses} { public: - ::_CLASSNAME_::() = default; - ::_CLASSNAME_::(const PropsParserContext& context, const ::_CLASSNAME_:: &sourceProps, const RawProps &rawProps); + ${className}() = default; + ${className}(const PropsParserContext& context, const ${className} &sourceProps, const RawProps &rawProps); #pragma mark - Props - ::_PROPS_:: + ${props} }; `.trim(); -const enumTemplate = ` -enum class ::_ENUM_NAME_:: { ::_VALUES_:: }; +const EnumTemplate = ({ + enumName, + values, + fromCases, + toCases, +}: { + enumName: string, + values: string, + fromCases: string, + toCases: string, +}) => + ` +enum class ${enumName} { ${values} }; -static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, ::_ENUM_NAME_:: &result) { +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, ${enumName} &result) { auto string = (std::string)value; - ::_FROM_CASES_:: + ${fromCases} abort(); } -static inline std::string toString(const ::_ENUM_NAME_:: &value) { +static inline std::string toString(const ${enumName} &value) { switch (value) { - ::_TO_CASES_:: + ${toCases} } } `.trim(); -const intEnumTemplate = ` -enum class ::_ENUM_NAME_:: { ::_VALUES_:: }; +const IntEnumTemplate = ({ + enumName, + values, + fromCases, + toCases, +}: { + enumName: string, + values: string, + fromCases: string, + toCases: string, +}) => + ` +enum class ${enumName} { ${values} }; -static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, ::_ENUM_NAME_:: &result) { +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, ${enumName} &result) { assert(value.hasType()); auto integerValue = (int)value; - switch (integerValue) {::_FROM_CASES_:: + switch (integerValue) {${fromCases} } abort(); } -static inline std::string toString(const ::_ENUM_NAME_:: &value) { +static inline std::string toString(const ${enumName} &value) { switch (value) { - ::_TO_CASES_:: + ${toCases} } } `.trim(); -const structTemplate = `struct ::_STRUCT_NAME_:: { - ::_FIELDS_:: +const StructTemplate = ({ + structName, + fields, + fromCases, +}: { + structName: string, + fields: string, + fromCases: string, +}) => + `struct ${structName} { + ${fields} }; -static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, ::_STRUCT_NAME_:: &result) { +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, ${structName} &result) { auto map = (better::map)value; - ::_FROM_CASES_:: + ${fromCases} } -static inline std::string toString(const ::_STRUCT_NAME_:: &value) { - return "[Object ::_STRUCT_NAME_::]"; +static inline std::string toString(const ${structName} &value) { + return "[Object ${structName}]"; } `.trim(); -const arrayConversionFunction = `static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, std::vector<::_STRUCT_NAME_::> &result) { +const ArrayConversionFunctionTemplate = ({ + structName, +}: { + structName: string, +}) => `static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, std::vector<${structName}> &result) { auto items = (std::vector)value; for (const auto &item : items) { - ::_STRUCT_NAME_:: newItem; + ${structName} newItem; fromRawValue(context, item, newItem); result.emplace_back(newItem); } } `; -const doubleArrayConversionFunction = `static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, std::vector> &result) { +const DoubleArrayConversionFunctionTemplate = ({ + structName, +}: { + structName: string, +}) => `static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, std::vector> &result) { auto items = (std::vector>)value; for (const std::vector &item : items) { - auto nestedArray = std::vector<::_STRUCT_NAME_::>{}; + auto nestedArray = std::vector<${structName}>{}; for (const RawValue &nestedItem : item) { - ::_STRUCT_NAME_:: newItem; + ${structName} newItem; fromRawValue(context, nestedItem, newItem); nestedArray.emplace_back(newItem); } @@ -141,44 +199,57 @@ const doubleArrayConversionFunction = `static inline void fromRawValue(const Pro } `; -const arrayEnumTemplate = ` -using ::_ENUM_MASK_:: = uint32_t; +const ArrayEnumTemplate = ({ + enumName, + enumMask, + values, + fromCases, + toCases, +}: { + enumName: string, + enumMask: string, + values: string, + fromCases: string, + toCases: string, +}) => + ` +using ${enumMask} = uint32_t; -enum class ::_ENUM_NAME_::: ::_ENUM_MASK_:: { - ::_VALUES_:: +enum class ${enumName}: ${enumMask} { + ${values} }; constexpr bool operator&( - ::_ENUM_MASK_:: const lhs, - enum ::_ENUM_NAME_:: const rhs) { - return lhs & static_cast<::_ENUM_MASK_::>(rhs); + ${enumMask} const lhs, + enum ${enumName} const rhs) { + return lhs & static_cast<${enumMask}>(rhs); } -constexpr ::_ENUM_MASK_:: operator|( - ::_ENUM_MASK_:: const lhs, - enum ::_ENUM_NAME_:: const rhs) { - return lhs | static_cast<::_ENUM_MASK_::>(rhs); +constexpr ${enumMask} operator|( + ${enumMask} const lhs, + enum ${enumName} const rhs) { + return lhs | static_cast<${enumMask}>(rhs); } constexpr void operator|=( - ::_ENUM_MASK_:: &lhs, - enum ::_ENUM_NAME_:: const rhs) { - lhs = lhs | static_cast<::_ENUM_MASK_::>(rhs); + ${enumMask} &lhs, + enum ${enumName} const rhs) { + lhs = lhs | static_cast<${enumMask}>(rhs); } -static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, ::_ENUM_MASK_:: &result) { +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, ${enumMask} &result) { auto items = std::vector{value}; for (const auto &item : items) { - ::_FROM_CASES_:: + ${fromCases} abort(); } } -static inline std::string toString(const ::_ENUM_MASK_:: &value) { +static inline std::string toString(const ${enumMask} &value) { auto result = std::string{}; auto separator = std::string{", "}; - ::_TO_CASES_:: + ${toCases} if (!result.empty()) { result.erase(result.length() - separator.length()); } @@ -320,12 +391,13 @@ function generateArrayEnumString( ) .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); + return ArrayEnumTemplate({ + enumName, + enumMask: getEnumMaskName(enumName), + values, + fromCases, + toCases, + }); } function generateStringEnum(componentName, prop) { @@ -352,11 +424,12 @@ function generateStringEnum(componentName, prop) { ) .join('\n' + ' '); - return enumTemplate - .replace(/::_ENUM_NAME_::/g, enumName) - .replace('::_VALUES_::', values.map(toSafeCppString).join(', ')) - .replace('::_FROM_CASES_::', fromCases) - .replace('::_TO_CASES_::', toCases); + return EnumTemplate({ + enumName, + values: values.map(toSafeCppString).join(', '), + fromCases: fromCases, + toCases: toCases, + }); } return ''; @@ -392,11 +465,12 @@ function generateIntEnum(componentName, prop) { .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 IntEnumTemplate({ + enumName, + values: valueVariables, + fromCases, + toCases, + }); } return ''; @@ -628,10 +702,12 @@ function generateStructs( `${[componentName, ...nameParts.concat([prop.name])].join( '', )}ArrayStruct`, - arrayConversionFunction.replace( - /::_STRUCT_NAME_::/g, - generateStructName(componentName, nameParts.concat([prop.name])), - ), + ArrayConversionFunctionTemplate({ + structName: generateStructName( + componentName, + nameParts.concat([prop.name]), + ), + }), ); } if ( @@ -667,10 +743,12 @@ function generateStructs( `${[componentName, ...nameParts.concat([prop.name])].join( '', )}ArrayArrayStruct`, - doubleArrayConversionFunction.replace( - /::_STRUCT_NAME_::/g, - generateStructName(componentName, nameParts.concat([prop.name])), - ), + DoubleArrayConversionFunctionTemplate({ + structName: generateStructName( + componentName, + nameParts.concat([prop.name]), + ), + }), ); } }); @@ -749,10 +827,11 @@ function generateStruct( structs.set( structName, - structTemplate - .replace(/::_STRUCT_NAME_::/g, structName) - .replace('::_FIELDS_::', fields) - .replace('::_FROM_CASES_::', fromCases), + StructTemplate({ + structName, + fields, + fromCases, + }), ); } @@ -810,13 +889,13 @@ module.exports = { // $FlowFixMe[method-unbinding] added when improving typing for this parameters 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(); + const replacedTemplate = ClassTemplate({ + enums: enumString, + structs: structString, + className: newName, + extendClasses: extendString, + props: propsString, + }); return replacedTemplate; }) @@ -825,14 +904,12 @@ module.exports = { .filter(Boolean) .join('\n\n'); - const replacedTemplate = template - .replace(/::_COMPONENT_CLASSES_::/g, componentClasses) - .replace( - '::_IMPORTS_::', - Array.from(allImports) - .sort() - .join('\n'), - ); + const replacedTemplate = FileTemplate({ + componentClasses, + imports: Array.from(allImports) + .sort() + .join('\n'), + }); return new Map([[fileName, replacedTemplate]]); }, diff --git a/packages/react-native-codegen/src/generators/components/GeneratePropsJavaDelegate.js b/packages/react-native-codegen/src/generators/components/GeneratePropsJavaDelegate.js index f3a09291359..030abcd6d71 100644 --- a/packages/react-native-codegen/src/generators/components/GeneratePropsJavaDelegate.js +++ b/packages/react-native-codegen/src/generators/components/GeneratePropsJavaDelegate.js @@ -27,7 +27,21 @@ const { // File path -> contents type FilesOutput = Map; -const template = `/** +const FileTemplate = ({ + packageName, + imports, + className, + extendClasses, + interfaceClassName, + methods, +}: { + packageName: string, + imports: string, + className: string, + extendClasses: string, + interfaceClassName: string, + methods: string, +}) => `/** * ${'C'}opyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the @@ -36,33 +50,35 @@ const template = `/** * ${'@'}generated by codegen project: GeneratePropsJavaDelegate.js */ -package ::_PACKAGE_NAME_::; +package ${packageName}; -::_IMPORTS_:: +${imports} -public class ::_CLASSNAME_:: & ::_INTERFACE_CLASSNAME_::> extends BaseViewManagerDelegate { - public ::_CLASSNAME_::(U viewManager) { +public class ${className} & ${interfaceClassName}> extends BaseViewManagerDelegate { + public ${className}(U viewManager) { super(viewManager); } - ::_METHODS_:: + ${methods} } `; -const propSetterTemplate = ` +const PropSetterTemplate = ({propCases}: {propCases: string}) => + ` @Override public void setProperty(T view, String propName, @Nullable Object value) { - ::_PROP_CASES_:: + ${propCases} } -`; +`.trim(); -const commandsTemplate = ` +const CommandsTemplate = ({commandCases}: {commandCases: string}) => + ` @Override public void receiveCommand(T view, String commandName, ReadableArray args) { switch (commandName) { - ::_COMMAND_CASES_:: + ${commandCases} } } -`; +`.trim(); function getJavaValueForProp( prop: NamedShape, @@ -251,9 +267,9 @@ function getDelegateImports(component) { function generateMethods(propsString, commandsString): string { return [ - propSetterTemplate.trim().replace('::_PROP_CASES_::', propsString), + PropSetterTemplate({propCases: propsString}), commandsString != null - ? commandsTemplate.trim().replace('::_COMMAND_CASES_::', commandsString) + ? CommandsTemplate({commandCases: commandsString}) : '', ] .join('\n\n ') @@ -305,22 +321,16 @@ module.exports = { ); const extendString = getClassExtendString(component); - const replacedTemplate = template - .replace( - /::_IMPORTS_::/g, - Array.from(imports) - .sort() - .join('\n'), - ) - .replace(/::_PACKAGE_NAME_::/g, normalizedPackageName) - .replace(/::_CLASSNAME_::/g, className) - .replace('::_EXTEND_CLASSES_::', extendString) - .replace('::_PROP_CASES_::', propsString) - .replace( - '::_METHODS_::', - generateMethods(propsString, commandsString), - ) - .replace(/::_INTERFACE_CLASSNAME_::/g, interfaceClassName); + const replacedTemplate = FileTemplate({ + imports: Array.from(imports) + .sort() + .join('\n'), + packageName: normalizedPackageName, + className, + extendClasses: extendString, + methods: generateMethods(propsString, commandsString), + interfaceClassName: interfaceClassName, + }); files.set(`${outputDir}/${className}.java`, replacedTemplate); }); diff --git a/packages/react-native-codegen/src/generators/components/GeneratePropsJavaInterface.js b/packages/react-native-codegen/src/generators/components/GeneratePropsJavaInterface.js index 86ed99f80f0..31e27513a2d 100644 --- a/packages/react-native-codegen/src/generators/components/GeneratePropsJavaInterface.js +++ b/packages/react-native-codegen/src/generators/components/GeneratePropsJavaInterface.js @@ -26,7 +26,19 @@ const { // File path -> contents type FilesOutput = Map; -const template = `/** +const FileTemplate = ({ + packageName, + imports, + className, + extendClasses, + methods, +}: { + packageName: string, + imports: string, + className: string, + extendClasses: string, + methods: string, +}) => `/** * ${'C'}opyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the @@ -35,12 +47,12 @@ const template = `/** * ${'@'}generated by codegen project: GeneratePropsJavaInterface.js */ -package ::_PACKAGE_NAME_::; +package ${packageName}; -::_IMPORTS_:: +${imports} -public interface ::_CLASSNAME_:: { - ::_METHODS_:: +public interface ${className} { + ${methods} } `; @@ -253,21 +265,17 @@ module.exports = { ); const extendString = getClassExtendString(component); - const replacedTemplate = template - .replace( - /::_IMPORTS_::/g, - Array.from(imports) - .sort() - .join('\n'), - ) - .replace(/::_PACKAGE_NAME_::/g, normalizedPackageName) - .replace(/::_CLASSNAME_::/g, className) - .replace('::_EXTEND_CLASSES_::', extendString) - .replace( - '::_METHODS_::', - [propsString, commandsString].join('\n' + ' ').trimRight(), - ) - .replace('::_COMMAND_HANDLERS_::', commandsString); + const replacedTemplate = FileTemplate({ + imports: Array.from(imports) + .sort() + .join('\n'), + packageName: normalizedPackageName, + className, + extendClasses: extendString, + methods: [propsString, commandsString] + .join('\n' + ' ') + .trimRight(), + }); files.set(`${outputDir}/${className}.java`, replacedTemplate); }); diff --git a/packages/react-native-codegen/src/generators/components/GenerateShadowNodeCpp.js b/packages/react-native-codegen/src/generators/components/GenerateShadowNodeCpp.js index bc86669d9ca..5c0f3e2a524 100644 --- a/packages/react-native-codegen/src/generators/components/GenerateShadowNodeCpp.js +++ b/packages/react-native-codegen/src/generators/components/GenerateShadowNodeCpp.js @@ -15,7 +15,13 @@ import type {SchemaType} from '../../CodegenSchema'; // File path -> contents type FilesOutput = Map; -const template = ` +const FileTemplate = ({ + libraryName, + componentNames, +}: { + libraryName: string, + componentNames: string, +}) => ` /** * ${'C'}opyright (c) Facebook, Inc. and its affiliates. * @@ -25,19 +31,20 @@ const template = ` * ${'@'}generated by codegen project: GenerateShadowNodeCpp.js */ -#include +#include namespace facebook { namespace react { -::_COMPONENT_NAMES_:: +${componentNames} } // namespace react } // namespace facebook `; -const componentTemplate = ` -extern const char ::_CLASSNAME_::ComponentName[] = "::_CLASSNAME_::"; +const ComponentTemplate = ({className}: {className: string}) => + ` +extern const char ${className}ComponentName[] = "${className}"; `.trim(); module.exports = { @@ -67,10 +74,9 @@ module.exports = { if (components[componentName].interfaceOnly === true) { return; } - const replacedTemplate = componentTemplate.replace( - /::_CLASSNAME_::/g, - componentName, - ); + const replacedTemplate = ComponentTemplate({ + className: componentName, + }); return replacedTemplate; }) @@ -79,9 +85,10 @@ module.exports = { .filter(Boolean) .join('\n'); - const replacedTemplate = template - .replace(/::_COMPONENT_NAMES_::/g, componentNames) - .replace('::_LIBRARY_::', libraryName); + const replacedTemplate = FileTemplate({ + componentNames, + libraryName, + }); return new Map([[fileName, replacedTemplate]]); }, diff --git a/packages/react-native-codegen/src/generators/components/GenerateShadowNodeH.js b/packages/react-native-codegen/src/generators/components/GenerateShadowNodeH.js index 30e5c573f9d..d5aa2531bfa 100644 --- a/packages/react-native-codegen/src/generators/components/GenerateShadowNodeH.js +++ b/packages/react-native-codegen/src/generators/components/GenerateShadowNodeH.js @@ -15,7 +15,15 @@ import type {SchemaType} from '../../CodegenSchema'; // File path -> contents type FilesOutput = Map; -const template = ` +const FileTemplate = ({ + imports, + libraryName, + componentClasses, +}: { + imports: string, + libraryName: string, + componentClasses: string, +}) => ` /** * ${'C'}opyright (c) Facebook, Inc. and its affiliates. * @@ -27,27 +35,34 @@ const template = ` #pragma once -::_IMPORTS_::#include +${imports}#include #include namespace facebook { namespace react { -::_COMPONENT_CLASSES_:: +${componentClasses} } // namespace react } // namespace facebook `; -const componentTemplate = ` -extern const char ::_CLASSNAME_::ComponentName[]; +const ComponentTemplate = ({ + className, + eventEmitter, +}: { + className: string, + eventEmitter: string, +}) => + ` +extern const char ${className}ComponentName[]; /* - * \`ShadowNode\` for <::_CLASSNAME_::> component. + * \`ShadowNode\` for <${className}> component. */ -using ::_CLASSNAME_::ShadowNode = ConcreteViewShadowNode< - ::_CLASSNAME_::ComponentName, - ::_CLASSNAME_::Props::_EVENT_EMITTER_::>; +using ${className}ShadowNode = ConcreteViewShadowNode< + ${className}ComponentName, + ${className}Props${eventEmitter}>; `.trim(); module.exports = { @@ -91,9 +106,10 @@ module.exports = { ? `,\n${componentName}EventEmitter` : ''; - const replacedTemplate = componentTemplate - .replace(/::_CLASSNAME_::/g, componentName) - .replace('::_EVENT_EMITTER_::', eventEmitter); + const replacedTemplate = ComponentTemplate({ + className: componentName, + eventEmitter, + }); return replacedTemplate; }) @@ -104,10 +120,11 @@ module.exports = { const eventEmitterImport = `#include \n`; - const replacedTemplate = template - .replace(/::_COMPONENT_CLASSES_::/g, moduleResults) - .replace('::_LIBRARY_::', libraryName) - .replace('::_IMPORTS_::', hasAnyEvents ? eventEmitterImport : ''); + const replacedTemplate = FileTemplate({ + componentClasses: moduleResults, + libraryName, + imports: hasAnyEvents ? eventEmitterImport : '', + }); return new Map([[fileName, replacedTemplate]]); }, diff --git a/packages/react-native-codegen/src/generators/components/GenerateTests.js b/packages/react-native-codegen/src/generators/components/GenerateTests.js index 691eefffd74..4da5c40c2d9 100644 --- a/packages/react-native-codegen/src/generators/components/GenerateTests.js +++ b/packages/react-native-codegen/src/generators/components/GenerateTests.js @@ -23,7 +23,16 @@ type TestCase = $ReadOnly<{ raw?: boolean, }>; -const fileTemplate = ` +const FileTemplate = ({ + libraryName, + imports, + componentTests, +}: { + libraryName: string, + imports: string, + componentTests: string, +}) => + ` /** * ${'C'}opyright (c) Facebook, Inc. and its affiliates. * @@ -35,25 +44,35 @@ const fileTemplate = ` #include #include -#include -::_IMPORTS_:: +#include +${imports} using namespace facebook::react; -::_COMPONENT_TESTS_:: -`; +${componentTests} +`.trim(); -const testTemplate = ` -TEST(::_COMPONENT_NAME_::_::_TEST_NAME_::, etc) { +const TestTemplate = ({ + componentName, + testName, + propName, + propValue, +}: { + componentName: string, + testName: string, + propName: string, + propValue: string, +}) => ` +TEST(${componentName}_${testName}, etc) { auto propParser = RawPropsParser(); - propParser.prepare<::_COMPONENT_NAME_::>(); - auto const &sourceProps = ::_COMPONENT_NAME_::(); - auto const &rawProps = RawProps(folly::dynamic::object("::_PROP_NAME_::", ::_PROP_VALUE_::)); + propParser.prepare<${componentName}>(); + auto const &sourceProps = ${componentName}(); + auto const &rawProps = RawProps(folly::dynamic::object("${propName}", ${propValue})); ContextContainer contextContainer{}; PropsParserContext parserContext{-1, contextContainer}; rawProps.parse(propParser, parserContext); - ::_COMPONENT_NAME_::(parserContext, sourceProps, rawProps); + ${componentName}(parserContext, sourceProps, rawProps); } `; @@ -120,11 +139,12 @@ function generateTestsString(name, component) { const value = !raw && typeof propValue === 'string' ? `"${propValue}"` : propValue; - return testTemplate - .replace(/::_COMPONENT_NAME_::/g, name) - .replace(/::_TEST_NAME_::/g, testName != null ? testName : propName) - .replace(/::_PROP_NAME_::/g, propName) - .replace(/::_PROP_VALUE_::/g, String(value)); + return TestTemplate({ + componentName: name, + testName: testName != null ? testName : propName, + propName, + propValue: String(value), + }); } const testCases = component.props.reduce((cases, prop) => { @@ -187,11 +207,11 @@ module.exports = { .join('\n') .trim(); - const replacedTemplate = fileTemplate - .replace(/::_IMPORTS_::/g, imports) - .replace(/::_LIBRARY_NAME_::/g, libraryName) - .replace(/::_COMPONENT_TESTS_::/g, componentTests) - .trim(); + const replacedTemplate = FileTemplate({ + imports, + libraryName, + componentTests, + }); return new Map([[fileName, replacedTemplate]]); }, diff --git a/packages/react-native-codegen/src/generators/components/GenerateViewConfigJs.js b/packages/react-native-codegen/src/generators/components/GenerateViewConfigJs.js index 3b74670d90c..519e41885f4 100644 --- a/packages/react-native-codegen/src/generators/components/GenerateViewConfigJs.js +++ b/packages/react-native-codegen/src/generators/components/GenerateViewConfigJs.js @@ -17,7 +17,13 @@ import type {SchemaType} from '../../CodegenSchema'; // File path -> contents type FilesOutput = Map; -const template = ` +const FileTemplate = ({ + imports, + componentConfig, +}: { + imports: string, + componentConfig: string, +}) => ` /** * ${'C'}opyright (c) Facebook, Inc. and its affiliates. * @@ -31,9 +37,9 @@ const template = ` 'use strict'; -::_IMPORTS_:: +${imports} -::_COMPONENT_CONFIG_:: +${componentConfig} `; // We use this to add to a set. Need to make sure we aren't importing @@ -96,19 +102,33 @@ function getReactDiffProcessValue(typeAnnotation) { } } -const componentTemplate = ` -let nativeComponentName = '::_COMPONENT_NAME_WITH_COMPAT_SUPPORT_::'; -::_DEPRECATION_CHECK_:: +const ComponentTemplate = ({ + componentNameWithCompatSupport, + deprecationCheck, +}: { + componentNameWithCompatSupport: string, + deprecationCheck: string, +}) => + ` +let nativeComponentName = '${componentNameWithCompatSupport}'; +${deprecationCheck} export default NativeComponentRegistry.get(nativeComponentName, () => VIEW_CONFIG); `.trim(); -const deprecatedComponentTemplate = ` -if (UIManager.getViewManagerConfig('::_COMPONENT_NAME_::')) { - nativeComponentName = '::_COMPONENT_NAME_::'; -} else if (UIManager.getViewManagerConfig('::_COMPONENT_NAME_DEPRECATED_::')) { - nativeComponentName = '::_COMPONENT_NAME_DEPRECATED_::'; +const DeprecatedComponentTemplate = ({ + componentName, + componentNameDeprecated, +}: { + componentName: string, + componentNameDeprecated: string, +}) => + ` +if (UIManager.getViewManagerConfig('${componentName}')) { + nativeComponentName = '${componentName}'; +} else if (UIManager.getViewManagerConfig('${componentNameDeprecated}')) { + nativeComponentName = '${componentNameDeprecated}'; } else { - throw new Error('Failed to find native component for either "::_COMPONENT_NAME_::" or "::_COMPONENT_NAME_DEPRECATED_::"'); + throw new Error('Failed to find native component for either "${componentName}" or "${componentNameDeprecated}"'); } `.trim(); @@ -354,21 +374,17 @@ module.exports = { } const deprecatedCheckBlock = component.paperComponentNameDeprecated - ? deprecatedComponentTemplate - .replace(/::_COMPONENT_NAME_::/g, componentName) - .replace( - /::_COMPONENT_NAME_DEPRECATED_::/g, + ? DeprecatedComponentTemplate({ + componentName, + componentNameDeprecated: component.paperComponentNameDeprecated || '', - ) + }) : ''; - const replacedTemplate = componentTemplate - .replace(/::_COMPONENT_NAME_::/g, componentName) - .replace( - /::_COMPONENT_NAME_WITH_COMPAT_SUPPORT_::/g, - paperComponentName, - ) - .replace(/::_DEPRECATION_CHECK_::/, deprecatedCheckBlock); + const replacedTemplate = ComponentTemplate({ + componentNameWithCompatSupport: paperComponentName, + deprecationCheck: deprecatedCheckBlock, + }); const replacedSourceRoot = j.withParser('flow')(replacedTemplate); @@ -409,14 +425,12 @@ module.exports = { .filter(Boolean) .join('\n\n'); - const replacedTemplate = template - .replace(/::_COMPONENT_CONFIG_::/g, moduleResults) - .replace( - '::_IMPORTS_::', - Array.from(imports) - .sort() - .join('\n'), - ); + const replacedTemplate = FileTemplate({ + componentConfig: moduleResults, + imports: Array.from(imports) + .sort() + .join('\n'), + }); return new Map([[fileName, replacedTemplate]]); } catch (error) {