diff --git a/packages/react-native-codegen/src/generators/RNCodegen.js b/packages/react-native-codegen/src/generators/RNCodegen.js index 33f86c7f80eb1d..b83fce50853915 100644 --- a/packages/react-native-codegen/src/generators/RNCodegen.js +++ b/packages/react-native-codegen/src/generators/RNCodegen.js @@ -257,6 +257,7 @@ module.exports = { useLocalIncludePaths, includeGetDebugPropsImplementation = false, libraryGenerators = LIBRARY_GENERATORS, + generateOptionalProperties = false, }: LibraryOptions, {generators, test}: LibraryConfig, ): boolean { @@ -301,6 +302,7 @@ module.exports = { assumeNonnull, headerPrefix, includeGetDebugPropsImplementation, + generateOptionalProperties, ).forEach((contents: string, fileName: string) => { generatedFiles.push({ name: fileName, diff --git a/packages/react-native-codegen/src/generators/components/GenerateEventEmitterCpp.js b/packages/react-native-codegen/src/generators/components/GenerateEventEmitterCpp.js index 7ebe0e337a7a90..760ebee90aa535 100644 --- a/packages/react-native-codegen/src/generators/components/GenerateEventEmitterCpp.js +++ b/packages/react-native-codegen/src/generators/components/GenerateEventEmitterCpp.js @@ -98,43 +98,84 @@ void ${className}EventEmitter::${eventName}() const { function generateSetter( variableName: string, - propertyName: string, + property: $ReadOnly>, propertyParts: $ReadOnlyArray, usingEvent: boolean, valueMapper: string => string = value => value, + isVector = false, + generateOptionalProperties = false, ) { - const eventChain = usingEvent - ? `event.${[...propertyParts, propertyName].join('.')}` - : [...propertyParts, propertyName].join('.'); - return `${variableName}.setProperty(runtime, "${propertyName}", ${valueMapper( + let eventChain = usingEvent ? 'event' : ''; + + if (propertyParts.length > 0) { + if (eventChain.length > 0) { + eventChain += '.'; + } + eventChain += propertyParts.join('.'); + } + + if (eventChain.length > 0) { + eventChain += isVector && generateOptionalProperties ? '->' : '.'; + } + eventChain += property.name; + + if (property.optional && generateOptionalProperties) { + if (isVector) { + return `if (${eventChain} != nil) { + ${variableName}.setProperty(runtime, "${property.name}", ${valueMapper( + eventChain, + )}); + }`; + } + return `if (${eventChain} != nil) { + ${variableName}.setProperty(runtime, "${property.name}", ${valueMapper( + eventChain, + )}); + }`; + } + + return `${variableName}.setProperty(runtime, "${property.name}", ${valueMapper( eventChain, )});`; } function generateObjectSetter( variableName: string, - propertyName: string, + property: $ReadOnly>, propertyParts: $ReadOnlyArray, typeAnnotation: ObjectTypeAnnotation, extraIncludes: Set, usingEvent: boolean, + generateOptionalProperties = false, ) { - return ` + const internalCode = ` { - auto ${propertyName} = jsi::Object(runtime); + auto ${property.name} = jsi::Object(runtime); ${indent( generateSetters( - propertyName, + property.name, typeAnnotation.properties, - propertyParts.concat([propertyName]), + propertyParts.concat([property.name]), extraIncludes, usingEvent, + property.optional, + generateOptionalProperties, ), 2, )} - ${variableName}.setProperty(runtime, "${propertyName}", ${propertyName}); + ${variableName}.setProperty(runtime, "${property.name}", ${property.name}); } `.trim(); + + if (generateOptionalProperties && property.optional) { + return ` +if (${propertyParts.join('.')}.${property.name}) { + ${internalCode} +} + `; + } + + return internalCode; } function setValueAtIndex( @@ -150,48 +191,51 @@ function setValueAtIndex( function generateArraySetter( variableName: string, - propertyName: string, + property: $ReadOnly>, propertyParts: $ReadOnlyArray, elementType: EventTypeAnnotation, extraIncludes: Set, usingEvent: boolean, + generateOptionalProperties = false, ): string { const eventChain = usingEvent - ? `event.${[...propertyParts, propertyName].join('.')}` - : [...propertyParts, propertyName].join('.'); - const indexVar = `${propertyName}Index`; - const innerLoopVar = `${propertyName}Value`; + ? `event.${[...propertyParts, property.name].join('.')}` + : [...propertyParts, property.name].join('.'); + const indexVar = `${property.name}Index`; + const innerLoopVar = `${property.name}Value`; return ` - auto ${propertyName} = jsi::Array(runtime, ${eventChain}.size()); + auto ${property.name} = jsi::Array(runtime, ${eventChain}.size()); size_t ${indexVar} = 0; for (auto ${innerLoopVar} : ${eventChain}) { ${handleArrayElementType( - elementType, - propertyName, - indexVar, - innerLoopVar, - propertyParts, - extraIncludes, - usingEvent, - )} + elementType, + property, + indexVar, + innerLoopVar, + propertyParts, + extraIncludes, + usingEvent, + generateOptionalProperties, + )} } - ${variableName}.setProperty(runtime, "${propertyName}", ${propertyName}); + ${variableName}.setProperty(runtime, "${property.name}", ${property.name}); `; } function handleArrayElementType( elementType: EventTypeAnnotation, - propertyName: string, + property: $ReadOnly>, indexVariable: string, loopLocalVariable: string, propertyParts: $ReadOnlyArray, extraIncludes: Set, usingEvent: boolean, + generateOptionalProperties = false, ): string { switch (elementType.type) { case 'BooleanTypeAnnotation': return setValueAtIndex( - propertyName, + property.name, indexVariable, loopLocalVariable, val => `(bool)${val}`, @@ -200,10 +244,10 @@ function handleArrayElementType( case 'Int32TypeAnnotation': case 'DoubleTypeAnnotation': case 'FloatTypeAnnotation': - return setValueAtIndex(propertyName, indexVariable, loopLocalVariable); + return setValueAtIndex(property.name, indexVariable, loopLocalVariable); case 'MixedTypeAnnotation': return setValueAtIndex( - propertyName, + property.name, indexVariable, loopLocalVariable, val => `jsi::valueFromDynamic(runtime, ${val})`, @@ -214,29 +258,31 @@ function handleArrayElementType( throw new Error('Invalid since it is a union of non strings'); } return setValueAtIndex( - propertyName, + property.name, indexVariable, loopLocalVariable, val => `toString(${val})`, ); case 'ObjectTypeAnnotation': return convertObjectTypeArray( - propertyName, + property, indexVariable, loopLocalVariable, propertyParts, elementType, extraIncludes, + generateOptionalProperties, ); case 'ArrayTypeAnnotation': return convertArrayTypeArray( - propertyName, + property, indexVariable, loopLocalVariable, propertyParts, elementType, extraIncludes, usingEvent, + generateOptionalProperties, ); default: throw new Error( @@ -246,22 +292,25 @@ function handleArrayElementType( } function convertObjectTypeArray( - propertyName: string, + property: $ReadOnly>, indexVariable: string, loopLocalVariable: string, propertyParts: $ReadOnlyArray, objectTypeAnnotation: ObjectTypeAnnotation, extraIncludes: Set, + generateOptionalProperties = false, ): string { - return `auto ${propertyName}Object = jsi::Object(runtime); + return `auto ${property.name}Object = jsi::Object(runtime); ${generateSetters( - `${propertyName}Object`, + `${property.name}Object`, objectTypeAnnotation.properties, [].concat([loopLocalVariable]), extraIncludes, false, + false, + generateOptionalProperties, )} - ${setValueAtIndex(propertyName, indexVariable, `${propertyName}Object`)}`; + ${setValueAtIndex(property.name, indexVariable, `${property.name}Object`)}`; } function convertArrayTypeArray( @@ -272,6 +321,7 @@ function convertArrayTypeArray( eventTypeAnnotation: EventTypeAnnotation, extraIncludes: Set, usingEvent: boolean, + generateOptionalProperties = false, ): string { if (eventTypeAnnotation.type !== 'ArrayTypeAnnotation') { throw new Error( @@ -283,12 +333,13 @@ function convertArrayTypeArray( for (auto ${loopLocalVariable}Internal : ${loopLocalVariable}) { ${handleArrayElementType( eventTypeAnnotation.elementType, - `${propertyName}Array`, + {name: `${propertyName}Array`}, `${indexVariable}Internal`, `${loopLocalVariable}Internal`, propertyParts, extraIncludes, usingEvent, + generateOptionalProperties, )} } ${setValueAtIndex(propertyName, indexVariable, `${propertyName}Array`)}`; @@ -300,70 +351,94 @@ function generateSetters( propertyParts: $ReadOnlyArray, extraIncludes: Set, usingEvent: boolean = true, + isVector = false, + generateOptionalProperties = false, ): string { const propSetters = properties .map(eventProperty => { const {typeAnnotation} = eventProperty; switch (typeAnnotation.type) { case 'BooleanTypeAnnotation': - case 'StringTypeAnnotation': case 'Int32TypeAnnotation': - case 'DoubleTypeAnnotation': case 'FloatTypeAnnotation': return generateSetter( parentPropertyName, - eventProperty.name, + eventProperty, propertyParts, usingEvent, + value => value, + isVector, + generateOptionalProperties, + ); + case 'StringTypeAnnotation': + return generateSetter( + parentPropertyName, + eventProperty, + propertyParts, + usingEvent, + prop => eventProperty.optional && generateOptionalProperties ? `jsi::String::createFromUtf8(runtime, *${prop})` : prop, + isVector, + generateOptionalProperties, + ); + case 'DoubleTypeAnnotation': + return generateSetter( + parentPropertyName, + eventProperty, + propertyParts, + usingEvent, + prop => eventProperty.optional && generateOptionalProperties ? `jsi::Value(*(${prop}))` : prop, + isVector, + generateOptionalProperties, ); case 'MixedTypeAnnotation': extraIncludes.add('#include '); return generateSetter( parentPropertyName, - eventProperty.name, + eventProperty, propertyParts, usingEvent, prop => `jsi::valueFromDynamic(runtime, ${prop})`, + false, + generateOptionalProperties, ); - case 'UnionTypeAnnotation': - const validUnionType = parseValidUnionType(typeAnnotation); - if (validUnionType !== 'string') { - throw new Error('Invalid since it is a union of non strings'); - } + case 'StringLiteralUnionTypeAnnotation': return generateSetter( parentPropertyName, - eventProperty.name, + eventProperty, propertyParts, usingEvent, - prop => `toString(${prop})`, + prop => eventProperty.optional && generateOptionalProperties ? `toString(*${prop})` : `toString(${prop})`, + false, + generateOptionalProperties, ); case 'ObjectTypeAnnotation': return generateObjectSetter( parentPropertyName, - eventProperty.name, + eventProperty, propertyParts, typeAnnotation, extraIncludes, usingEvent, + generateOptionalProperties, ); case 'ArrayTypeAnnotation': return generateArraySetter( parentPropertyName, - eventProperty.name, + eventProperty, propertyParts, typeAnnotation.elementType, extraIncludes, usingEvent, + generateOptionalProperties, ); default: - (typeAnnotation.type: empty); + typeAnnotation.type; throw new Error( `Received invalid event property type ${typeAnnotation.type}`, ); } }) .join('\n'); - return propSetters; } @@ -371,6 +446,7 @@ function generateEvent( componentName: string, event: EventTypeShape, extraIncludes: Set, + generateOptionalProperties = false, ): string { // This is a gross hack necessary because native code is sending // events named things like topChange to JS which is then converted back to @@ -391,6 +467,9 @@ function generateEvent( event.typeAnnotation.argument.properties, [], extraIncludes, + true, + false, + generateOptionalProperties, )} return payload; `.trim(); @@ -422,7 +501,8 @@ module.exports = { packageName?: string, assumeNonnull: boolean = false, headerPrefix?: string, - includeGetDebugPropsImplementation?: boolean = false, + includeGetDebugPropsImplementation: boolean = false, + generateOptionalProperties = false, ): FilesOutput { const moduleComponents: ComponentCollection = Object.keys(schema.modules) .map(moduleName => { @@ -448,7 +528,7 @@ module.exports = { .map(componentName => { const component = moduleComponents[componentName]; return component.events - .map(event => generateEvent(componentName, event, extraIncludes)) + .map(event => generateEvent(componentName, event, extraIncludes, generateOptionalProperties)) .join('\n'); }) .join('\n'); diff --git a/packages/react-native-codegen/src/generators/components/GenerateEventEmitterH.js b/packages/react-native-codegen/src/generators/components/GenerateEventEmitterH.js index b947b727f5a7c4..c4962c8677d0f8 100644 --- a/packages/react-native-codegen/src/generators/components/GenerateEventEmitterH.js +++ b/packages/react-native-codegen/src/generators/components/GenerateEventEmitterH.js @@ -118,23 +118,33 @@ function getNativeTypeFromAnnotation( componentName: string, eventProperty: NamedShape, nameParts: $ReadOnlyArray, + generateOptionalProperties = false, ): string { - const {typeAnnotation} = eventProperty; - switch (typeAnnotation.type) { + const {type} = eventProperty.typeAnnotation; + switch (type) { case 'BooleanTypeAnnotation': case 'StringTypeAnnotation': case 'Int32TypeAnnotation': case 'DoubleTypeAnnotation': case 'FloatTypeAnnotation': case 'MixedTypeAnnotation': - return getCppTypeForAnnotation(typeAnnotation.type); + if (generateOptionalProperties && eventProperty.optional) { + return getCppTypeForAnnotation(type)+'*'; + } + return getCppTypeForAnnotation(type); case 'UnionTypeAnnotation': const validUnionType = parseValidUnionType(typeAnnotation); if (validUnionType !== 'string') { throw new Error('Invalid since it is a union of non strings'); } + if (generateOptionalProperties && eventProperty.optional) { + return 'const ' + generateEventStructName([...nameParts, eventProperty.name+'*']); + } return generateEventStructName([...nameParts, eventProperty.name]); case 'ObjectTypeAnnotation': + if (generateOptionalProperties && eventProperty.optional) { + return 'const ' + generateEventStructName([...nameParts, eventProperty.name+'*']); + } return generateEventStructName([...nameParts, eventProperty.name]); case 'ArrayTypeAnnotation': const eventTypeAnnotation = eventProperty.typeAnnotation; @@ -148,10 +158,8 @@ function getNativeTypeFromAnnotation( eventProperty.name, ]); default: - (typeAnnotation.type: empty); - throw new Error( - `Received invalid event property type ${typeAnnotation.type}`, - ); + type; + throw new Error(`Received invalid event property type ${type}`); } } function generateEnum( @@ -187,6 +195,7 @@ function handleGenerateStructForArray( componentName: string, elementType: EventTypeAnnotation, nameParts: $ReadOnlyArray, + generateOptionalProperties = false, ): void { if (elementType.type === 'ObjectTypeAnnotation') { generateStruct( @@ -194,6 +203,7 @@ function handleGenerateStructForArray( componentName, nameParts.concat([name]), nullthrows(elementType.properties), + generateOptionalProperties, ); } else if (elementType.type === 'UnionTypeAnnotation') { const validUnionType = parseValidUnionType(elementType); @@ -212,6 +222,7 @@ function handleGenerateStructForArray( componentName, elementType.elementType, nameParts, + generateOptionalProperties, ); } } @@ -221,6 +232,7 @@ function generateStruct( componentName: string, nameParts: $ReadOnlyArray, properties: $ReadOnlyArray>, + generateOptionalProperties = false, ): void { const structNameParts = nameParts; const structName = generateEventStructName(structNameParts); @@ -231,6 +243,7 @@ function generateStruct( componentName, property, structNameParts, + generateOptionalProperties, )} ${property.name};`; }) .join('\n' + ' '); @@ -252,6 +265,7 @@ function generateStruct( componentName, typeAnnotation.elementType, nameParts, + generateOptionalProperties, ); return; case 'ObjectTypeAnnotation': @@ -260,6 +274,7 @@ function generateStruct( componentName, nameParts.concat([name]), nullthrows(typeAnnotation.properties), + generateOptionalProperties, ); return; case 'UnionTypeAnnotation': @@ -293,6 +308,7 @@ function generateStruct( function generateStructs( componentName: string, component: ComponentShape, + generateOptionalProperties = false, ): string { const structs: StructsMap = new Map(); @@ -303,6 +319,7 @@ function generateStructs( componentName, [event.name], event.typeAnnotation.argument.properties, + generateOptionalProperties, ); } }); @@ -335,7 +352,8 @@ module.exports = { packageName?: string, assumeNonnull: boolean = false, headerPrefix?: string, - includeGetDebugPropsImplementation?: boolean = false, + includeGetDebugPropsImplementation: boolean = false, + generateOptionalProperties = false, ): FilesOutput { const moduleComponents: ComponentCollection = Object.keys(schema.modules) .map(moduleName => { @@ -373,7 +391,7 @@ module.exports = { const replacedTemplate = ComponentTemplate({ className: componentName, - structs: indent(generateStructs(componentName, component), 2), + structs: indent(generateStructs(componentName, component, generateOptionalProperties), 2), events: generateEvents(componentName, component), });