/**
 * Copyright (c) 2016-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */ import * as AlgebraicType from '../algebraic-type' ; import * as AlgebraicTypeUtils from '../algebraic-type-utils' ; import * as Code from '../code' ; import * as Error from '../error' ; import * as FileWriter from '../file-writer' ; import * as FunctionUtils from '../function-utils' ; import * as Maybe from '../maybe' ; import * as ObjC from '../objc' ; import * as ObjectGeneration from '../object-generation' ; import * as ObjCTypeUtils from '../objc-type-utils' ; import * as StringUtils from '../string-utils' ; import * as ObjectSpec from '../object-spec' ; import * as ObjectSpecCodeUtils from '../object-spec-code-utils' ; import * as CodingUtils from './coding-utils' ; function underscored ( str ) { return str . replace ( /([a-z\d])([A-Z]+)/g , '$1_$2' ) . replace ( /[-\s]+/g , '_' ) . toLowerCase ( ) ; } export interface CodeableAttribute { name : string ; valueAccessor : string ; constantName : string ; constantValue : string ; legacyKeyNames : string [ ] ; type : ObjC . Type ; originalType : ObjC . Type ; } function codingKeysFromAnnotations ( annotationMap ) { const codingKeyAnnotations = annotationMap [ 'codingKey' ] ; if ( codingKeyAnnotations == null ) { return [ ] ; } return Maybe . catMaybes ( codingKeyAnnotations . map ( ( annotation ) => annotation . properties [ 'name' ] ) ) ; } function getKeyValue ( annotations , annotation , key ) { const jsonCodingAnnotation = annotations [ annotation ] ; if ( jsonCodingAnnotation ) { const propertiesDict = jsonCodingAnnotation [ 0 ] ; if ( propertiesDict ) { const property = propertiesDict [ 'properties' ] ; if ( property ) { const value = property [ key ] ; if ( value ) { return value ; } } } } return null ; } function legacyCodingKeyNameForAnnotation ( legacyKeyAnnotation ) { const legacyKey = legacyKeyAnnotation . properties [ 'name' ] ; return legacyKey === undefined ? '' : legacyKey ; } function legacyCodingKeyNamesForAttribute ( attribute ) { const legacyKeyAnnotations = attribute . annotations [ 'codingLegacyKey' ] ; if ( legacyKeyAnnotations && legacyKeyAnnotations . length > 0 ) { return legacyKeyAnnotations . map ( legacyCodingKeyNameForAnnotation ) ; } else { return [ ] ; } } export function codingAttributeForValueAttribute ( attribute ) { const codingKeys = codingKeysFromAnnotations ( attribute . annotations ) ; const constantValue = codingKeys . length === 1 ? `@"${ codingKeys [ 0 ] }"` : constantValueForAttributeName ( attribute . name ) ; return { name : attribute . name , valueAccessor : ObjectSpecCodeUtils . ivarForAttribute ( attribute ) , constantName : nameOfConstantForValueName ( attribute . name ) , constantValue : constantValue , legacyKeyNames : legacyCodingKeyNamesForAttribute ( attribute ) , type : ObjectSpecCodeUtils . computeTypeOfAttribute ( attribute ) , originalType : ObjectSpecCodeUtils . computeOriginalTypeOfAttribute ( attribute ) } ; } function legacyKeyRespectingDecodeStatementForAttribute ( attribute , secureCoding ) { const defaultDecodeStatement = decodeStatementForAttribute ( attribute , secureCoding ) ; const decodeStatements = [ defaultDecodeStatement ] ; if ( attribute . legacyKeyNames . length > 0 ) { const nilValueForAttribute = nilValueForType ( attribute . type ) ; if ( nilValueForAttribute . length > 0 ) { const legacyDecodeStatements = FunctionUtils . flatMap ( attribute . legacyKeyNames , ( legacyKeyName ) => { return decodeStatementForAttributeAndLegacyKey ( attribute , nilValueForAttribute , legacyKeyName , secureCoding ) ; } ) ; if ( legacyDecodeStatements . length > 0 ) { return decodeStatements . concat ( legacyDecodeStatements ) ; } } } return decodeStatements ; } function decodeStatementForAttributeAndLegacyKey ( attribute , nilValueForAttribute , legacyKeyName , secureCoding ) { if ( legacyKeyName . length > 0 ) { const legacyDecodeStatement = decodeStatementForTypeValueAccessorAndCodingKey ( attribute . type , attribute . originalType , attribute . valueAccessor , '@"' + legacyKeyName + '"' , secureCoding ) ; const conditionalStatement = [ 'if (' + attribute . valueAccessor + ' == ' + nilValueForAttribute + ') {' , StringUtils . indent ( 2 ) ( legacyDecodeStatement ) , '}' , ] ; return conditionalStatement ; } else { return [ ] ; } } export function decodeStatementForAttribute ( attribute , secureCoding ) { return decodeStatementForTypeValueAccessorAndCodingKey ( attribute . type , attribute . originalType , attribute . valueAccessor , attribute . constantName , secureCoding ) ; } function decodeStatementForTypeValueAccessorAndCodingKey ( type , originalType , valueAccessor , codingKey , secureCoding ) { const codingStatements = CodingUtils . codingStatementsForType ( type ) ; const cast = ObjCTypeUtils . isNSObject ( type ) ? `(id)` : type . name != originalType . name ? '(' + originalType . name + ')' : '' ; const decodedRawValuePart = `${ cast }${ codingStatements . decodeStatementGenerator ( type , codingKey , secureCoding ) }` ; const decodedValuePart = codingStatements . decodeValueStatementGenerator ( decodedRawValuePart ) ; return valueAccessor + ' = ' + decodedValuePart + ';' ; } function decodeStatementForSubtype ( attribute , secureCoding ) { const codingStatements = CodingUtils . codingStatementsForType ( attribute . type ) ; const decodedRawValuePart = codingStatements . decodeStatementGenerator ( attribute . type , attribute . constantName , secureCoding ) ; const decodedValuePart = codingStatements . decodeValueStatementGenerator ( decodedRawValuePart ) ; return ( 'NSString *' + attribute . valueAccessor + ' = ' + decodedValuePart + ';' ) ; } export function encodeStatementForAttribute ( attribute ) { const codingStatements = CodingUtils . codingStatementsForType ( attribute . type ) ; const encodeValuePart = codingStatements . encodeValueStatementGenerator ( attribute . valueAccessor ) ; return ( '[aCoder ' + codingStatements . encodeStatement + ':' + encodeValuePart + ' forKey:' + attribute . constantName + '];' ) ; } function nameOfConstantForValueName ( valueName ) { return 'k' + StringUtils . capitalize ( valueName ) + 'Key' ; } function constantValueForAttributeName ( attributeName ) { return '@"' + underscored ( attributeName ) . toUpperCase ( ) + '"' ; } function staticConstantForAttribute ( attribute ) { return { type : { name : 'NSString' , reference : 'NSString *' } , comments : [ ] , name : attribute . constantName , value : attribute . constantValue , memorySemantic : ObjC . MemorySemantic . UnsafeUnretained ( ) } ; } function initBlockWithInternalCode ( internalCode ) { const returnStatement = 'return self;' ; return [ 'if ((self = [super init])) {' ] . concat ( internalCode . map ( StringUtils . indent ( 2 ) ) ) . concat ( '}' ) . concat ( returnStatement ) ; } function decodeMethodWithCode ( code ) { return { preprocessors : [ ] , belongsToProtocol : 'NSCoding' , code : initBlockWithInternalCode ( code ) , comments : [ ] , compilerAttributes : [ ] , keywords : [ { name : 'initWithCoder' , argument : { name : 'aDecoder' , modifiers : [ ] , type : { name : 'NSCoder' , reference : 'NSCoder *' } } } , ] , returnType : { type : { name : 'instancetype' , reference : 'instancetype' } , modifiers : [ ObjC . KeywordArgumentModifier . Nullable ( ) ] } } ; } function encodeMethodWithCode ( code ) { return { preprocessors : [ ] , belongsToProtocol : 'NSCoding' , code : code , comments : [ ] , compilerAttributes : [ ] , keywords : [ { name : 'encodeWithCoder' , argument : { name : 'aCoder' , modifiers : [ ] , type : { name : 'NSCoder' , reference : 'NSCoder *' } } } , ] , returnType : { type : null , modifiers : [ ] } } ; } function isTypeNSCodingCompliant ( type ) { return ObjCTypeUtils . matchType ( { id : function ( ) { return true ; } , NSObject : function ( ) { return true ; } , BOOL : function ( ) { return true ; } , NSInteger : function ( ) { return true ; } , NSUInteger : function ( ) { return true ; } , double : function ( ) { return true ; } , float : function ( ) { return true ; } , CGFloat : function ( ) { return true ; } , NSTimeInterval : function ( ) { return true ; } , uintptr_t : function ( ) { return true ; } , uint32_t : function ( ) { return true ; } , uint64_t : function ( ) { return true ; } , int32_t : function ( ) { return true ; } , int64_t : function ( ) { return true ; } , SEL : function ( ) { return true ; } , NSRange : function ( ) { return true ; } , CGRect : function ( ) { return true ; } , CGPoint : function ( ) { return true ; } , CGSize : function ( ) { return true ; } , UIEdgeInsets : function ( ) { return true ; } , Class : function ( ) { return false ; } , dispatch_block_t : function ( ) { return false ; } , unmatchedType : function ( ) { return true ; } } , type ) ; } function nilValueForType ( type ) { return ObjCTypeUtils . matchType ( { id : function ( ) { return 'nil' ; } , NSObject : function ( ) { return 'nil' ; } , BOOL : function ( ) { return 'NO' ; } , NSInteger : function ( ) { return '0' ; } , NSUInteger : function ( ) { return '0' ; } , double : function ( ) { return '0' ; } , float : function ( ) { return '0' ; } , CGFloat : function ( ) { return '0' ; } , NSTimeInterval : function ( ) { return '0' ; } , uintptr_t : function ( ) { return '0' ; } , uint32_t : function ( ) { return '0' ; } , uint64_t : function ( ) { return '0' ; } , int32_t : function ( ) { return '0' ; } , int64_t : function ( ) { return '0' ; } , SEL : function ( ) { return '' ; } , NSRange : function ( ) { return '' ; } , CGRect : function ( ) { return '' ; } , CGPoint : function ( ) { return '' ; } , CGSize : function ( ) { return '' ; } , UIEdgeInsets : function ( ) { return '' ; } , Class : function ( ) { return 'nil' ; } , dispatch_block_t : function ( ) { return '' ; } , unmatchedType : function ( ) { return '' ; } } , type ) ; } function doesValueAttributeContainAnUnknownType ( attribute ) { const codeableAttribute = codingAttributeForValueAttribute ( attribute ) ; const codingStatements = CodingUtils . codingStatementsForType ( codeableAttribute . type ) ; return codingStatements == null ; } function doesValueAttributeContainAnUnsupportedType ( attribute ) { return ( isTypeNSCodingCompliant ( ObjectSpecCodeUtils . computeTypeOfAttribute ( attribute ) ) === false ) ; } function doesValueAttributeContainAnLegacyKeyForUnsupportedType ( attribute ) { return ( legacyCodingKeyNamesForAttribute ( attribute ) . length > 0 && nilValueForType ( ObjectSpecCodeUtils . computeTypeOfAttribute ( attribute ) ) . length == 0 ) ; } function valueAttributeToUnknownTypeError ( objectType , attribute ) { return Maybe . match ( function ( underlyingType ) : Error . Error { return Error . Error ( 'The Coding plugin does not know how to decode and encode the backing type "' + underlyingType + '" from ' + objectType . typeName + '.' + attribute . name + '. Did you declare the wrong backing type?' ) ; } , function ( ) : Error . Error { return Error . Error ( 'The Coding plugin does not know how to decode and encode the type "' + attribute . type . name + '" from ' + objectType . typeName + '.' + attribute . name + '. Did you forget to declare a backing type?' ) ; } , attribute . type . underlyingType ) ; } function valueAttributeToUnsupportedTypeError ( objectType , attribute ) { return Maybe . match ( function ( underlyingType ) : Error . Error { return Error . Error ( 'The Coding plugin does not know how to decode and encode the backing type "' + underlyingType + '" from ' + objectType . typeName + '.' + attribute . name + '. ' + attribute . type . name + ' is not NSCoding-compilant.' ) ; } , function ( ) : Error . Error { return Error . Error ( 'The Coding plugin does not know how to decode and encode the type "' + attribute . type . name + '" from ' + objectType . typeName + '.' + attribute . name + '. ' + attribute . type . name + ' is not NSCoding-compilant.' ) ; } , attribute . type . underlyingType ) ; } function valueAttributeToUnsupportedLegacyKeyTypeError ( objectType , attribute ) { return Maybe . match ( function ( underlyingType ) : Error . Error { return Error . Error ( '%codingLegacyKey can\'t be used with "' + underlyingType + '" at ' + objectType . typeName + '.' + attribute . name + '.' ) ; } , function ( ) : Error . Error { return Error . Error ( '%codingLegacyKey can\'t be used with "' + attribute . type . name + '" at ' + objectType . typeName + '.' + attribute . name + '.' ) ; } , attribute . type . underlyingType ) ; } function multipleCodingKeyAnnotationErrorForValueAttribute ( objectType , attribute ) { const length = codingKeysFromAnnotations ( attribute . annotations ) . length ; return length > 1 ? Error . Error ( `Only one %codingKey name is supported: ${ objectType . typeName }.${ attribute . name } has ${ length }.` ) : null ; } function importForAttributeCodingMethod ( attribute ) { const codeableAttribute = codingAttributeForValueAttribute ( attribute ) ; const codingStatements = CodingUtils . codingStatementsForType ( codeableAttribute . type ) ; return codingStatements . codingFunctionImport ; } export function createPlugin ( ) { return { additionalFiles : function ( objectType ) : Code . File [ ] { return [ ] ; } , transformBaseFile : function ( objectType , baseFile ) : Code . File { return baseFile ; } , additionalTypes : function ( objectType ) : ObjectSpec . Type [ ] { return [ ] ; } , classMethods : function ( objectType ) : ObjC . Method [ ] { const secureCoding = objectType . includes . indexOf ( 'NSSecureCoding' ) >= 0 ; return secureCoding ? [ CodingUtils . supportsSecureCodingMethod ] : [ ] ; } , attributes : function ( objectType ) : ObjectSpec . Attribute [ ] { return [ ] ; } , transformFileRequest : function ( request ) : FileWriter . Request { return request ; } , fileType : function ( objectType ) : Code . FileType | null { return null ; } , forwardDeclarations : function ( objectType ) : ObjC . ForwardDeclaration [ ] { return [ ] ; } , functions : function ( objectType ) : ObjC . Function [ ] { return [ ] ; } , headerComments : function ( objectType ) : ObjC . Comment [ ] { return [ ] ; } , implementedProtocols : function ( objectType ) : ObjC . ImplementedProtocol [ ] { const secureCoding = objectType . includes . indexOf ( 'NSSecureCoding' ) >= 0 ; if ( secureCoding ) { return [ { name : 'NSSecureCoding' } , ] ; } else { return [ { name : 'NSCoding' } , ] ; } } , imports : function ( objectType ) : ObjC . Import [ ] { const codingImportMaybes = objectType . attributes . map ( importForAttributeCodingMethod ) ; return Maybe . catMaybes ( codingImportMaybes ) ; } , instanceMethods : function ( objectType ) : ObjC . Method [ ] { const secureCoding = objectType . includes . indexOf ( 'NSSecureCoding' ) >= 0 ; if ( objectType . attributes . length > 0 ) { const codingAttributes = objectType . attributes . map ( codingAttributeForValueAttribute ) ; const decodeCode = FunctionUtils . flatMap ( codingAttributes , ( codingAttribute ) => { return legacyKeyRespectingDecodeStatementForAttribute ( codingAttribute , secureCoding ) ; } ) ; const encodeCode = codingAttributes . map ( encodeStatementForAttribute ) ; return [ decodeMethodWithCode ( decodeCode ) , encodeMethodWithCode ( encodeCode ) , ] ; } else { return [ ] ; } } , macros : function ( valueType ) : ObjC . Macro [ ] { return [ ] ; } , properties : function ( objectType ) : ObjC . Property [ ] { return [ ] ; } , requiredIncludesToRun : [ 'RMCoding' ] , staticConstants : function ( objectType ) : ObjC . Constant [ ] { return objectType . attributes . map ( codingAttributeForValueAttribute ) . map ( staticConstantForAttribute ) ; } , validationErrors : function ( objectType ) : Error . Error [ ] { const unknownTypeErrors = objectType . attributes . filter ( doesValueAttributeContainAnUnknownType ) . map ( ( attribute ) => valueAttributeToUnknownTypeError ( objectType , attribute ) ) ; const unsupportedTypeErrors = objectType . attributes . filter ( doesValueAttributeContainAnUnsupportedType ) . map ( ( attribute ) => valueAttributeToUnsupportedTypeError ( objectType , attribute ) ) ; const unsupportedLegacyKeyTypeErrors = objectType . attributes . filter ( doesValueAttributeContainAnLegacyKeyForUnsupportedType ) . map ( ( attribute ) => valueAttributeToUnsupportedLegacyKeyTypeError ( objectType , attribute ) ) ; const multipleCodingKeyErrors = Maybe . catMaybes ( objectType . attributes . map ( ( attribute ) => multipleCodingKeyAnnotationErrorForValueAttribute ( objectType , attribute ) ) ) ; return unknownTypeErrors . concat ( unsupportedTypeErrors , unsupportedLegacyKeyTypeErrors , multipleCodingKeyErrors ) ; } , nullability : function ( objectType ) : ObjC . ClassNullability | null { return null ; } , subclassingRestricted : function ( objectType ) : boolean { return false ; } } ; } function codeableAttributeForSubtypePropertyOfAlgebraicType ( ) { return { name : 'codedSubtype' , valueAccessor : 'codedSubtype' , constantName : nameOfConstantForValueName ( 'codedSubtype' ) , constantValue : constantValueForAttributeName ( 'codedSubtype' ) , legacyKeyNames : [ ] , type : { name : 'NSObject' , reference : 'NSObject' } , originalType : { name : 'NSObject' , reference : 'NSObject' } } ; } function codeableAttributeForAlgebraicSubtypeAttribute ( subtype , attribute ) { const valueName = subtype . match ( function ( namedAttributeCollectionSubtype ) { return ( StringUtils . capitalize ( namedAttributeCollectionSubtype . name ) + StringUtils . capitalize ( attribute . name ) ) ; } , function ( attribute ) { return StringUtils . capitalize ( attribute . name ) ; } ) ; const name = AlgebraicTypeUtils . nameOfInstanceVariableForAttribute ( subtype , attribute ) ; return { name : name , valueAccessor : AlgebraicTypeUtils . valueAccessorForInstanceVariableForAttribute ( subtype , attribute ) , constantName : nameOfConstantForValueName ( valueName ) , constantValue : constantValueForAttributeName ( name ) , legacyKeyNames : legacyCodingKeyNamesForAttribute ( attribute ) , type : AlgebraicTypeUtils . computeTypeOfAttribute ( attribute ) , originalType : AlgebraicTypeUtils . computeOriginalTypeOfAttribute ( attribute ) } ; } function decodeStatementForAlgebraicSubtypeAttribute ( subtype , attribute , secureCoding ) { const codeableAttribute = codeableAttributeForAlgebraicSubtypeAttribute ( subtype , attribute ) ; return decodeStatementForAttribute ( codeableAttribute , secureCoding ) ; } function decodeStatementsForAlgebraicSubtype ( algebraicType , subtype , secureCoding ) { const decodeAttributes = AlgebraicTypeUtils . attributesFromSubtype ( subtype ) . map ( ( attribute ) => { return decodeStatementForAlgebraicSubtypeAttribute ( subtype , attribute , secureCoding ) ; } ) ; return decodeAttributes . concat ( decodedStatementForSubtypeProperty ( algebraicType , subtype ) ) ; } function decodedStatementForSubtypeProperty ( algebraicType , subtype ) { return ( AlgebraicTypeUtils . valueAccessorForInstanceVariableStoringSubtype ( ) + ' = ' + AlgebraicTypeUtils . EnumerationValueNameForSubtype ( algebraicType , subtype ) + ';' ) ; } function decodeCodeForAlgebraicType ( algebraicType , secureCoding , fallbackDecodingFunction ) { const codeableAttributeForSubtypeProperty = codeableAttributeForSubtypePropertyOfAlgebraicType ( ) ; const switchStatement = codeForBranchingOnSubtypeWithSubtypeMapper ( algebraicType , codeableAttributeForSubtypeProperty . valueAccessor , ( algebraicType , subtype ) => decodeStatementsForAlgebraicSubtype ( algebraicType , subtype , secureCoding ) , fallbackDecodingFunction ) ; return [ decodeStatementForSubtype ( codeableAttributeForSubtypeProperty , secureCoding ) , ] . concat ( switchStatement ) ; } function encodeStatementForAlgebraicSubtypeAttribute ( subtype , attribute ) { const codeableAttribute = codeableAttributeForAlgebraicSubtypeAttribute ( subtype , attribute ) ; return encodeStatementForAttribute ( codeableAttribute ) ; } function encodedStatementForSubtypeProperty ( subtype ) { const subtypeAttribute = codeableAttributeForSubtypePropertyOfAlgebraicType ( ) ; const codingStatements = CodingUtils . codingStatementsForType ( subtypeAttribute . type ) ; return ( '[aCoder ' + codingStatements . encodeStatement + ':' + CodingNameForSubtype ( subtype ) + ' forKey:' + subtypeAttribute . constantName + '];' ) ; } function encodeStatementsForAlgebraicSubtype ( algebraicType , subtype ) { const encodeAttributes = AlgebraicTypeUtils . attributesFromSubtype ( subtype ) . map ( ( attribute ) => encodeStatementForAlgebraicSubtypeAttribute ( subtype , attribute ) ) ; return encodeAttributes . concat ( encodedStatementForSubtypeProperty ( subtype ) ) ; } function encodeCodeForAlgebraicType ( algebraicType ) { return AlgebraicTypeUtils . codeForSwitchingOnSubtypeWithSubtypeMapper ( algebraicType , AlgebraicTypeUtils . valueAccessorForInstanceVariableStoringSubtype ( ) , encodeStatementsForAlgebraicSubtype ) ; } function doesAlgebraicAttributeContainAnUnknownType ( attribute ) { const codingStatements = CodingUtils . codingStatementsForType ( AlgebraicTypeUtils . computeTypeOfAttribute ( attribute ) ) ; return codingStatements == null ; } function doesAlgebraicAttributeContainAnUnsupportedType ( attribute ) { return ( isTypeNSCodingCompliant ( AlgebraicTypeUtils . computeTypeOfAttribute ( attribute ) ) === false ) ; } function algebraicAttributeToUnknownTypeError ( algebraicType , attribute ) { return Maybe . match ( function ( underlyingType ) : Error . Error { return Error . Error ( 'The Coding plugin does not know how to decode and encode the backing type "' + underlyingType + '" from ' + algebraicType . name + '.' + attribute . name + '. Did you declare the wrong backing type?' ) ; } , function ( ) : Error . Error { return Error . Error ( 'The Coding plugin does not know how to decode and encode the type "' + attribute . type . name + '" from ' + algebraicType . name + '.' + attribute . name + '. Did you forget to declare a backing type?' ) ; } , attribute . type . underlyingType ) ; } function algebraicAttributeToUnsupportedTypeError ( algebraicType , attribute ) { return Maybe . match ( function ( underlyingType ) : Error . Error { return Error . Error ( 'The Coding plugin does not know how to decode and encode the backing type "' + underlyingType + '" from ' + algebraicType . name + '.' + attribute . name + '. ' + attribute . type . name + ' is not NSCoding-compilant.' ) ; } , function ( ) : Error . Error { return Error . Error ( 'The Coding plugin does not know how to decode and encode the type "' + attribute . type . name + '" from ' + algebraicType . name + '.' + attribute . name + '. ' + attribute . type . name + ' is not NSCoding-compilant.' ) ; } , attribute . type . underlyingType ) ; } function unsupportedAnnotationErrorForAlgebraicAttribute ( attribute ) { return codingKeysFromAnnotations ( attribute . annotations ) . length != 0 ? Error . Error ( 'Custom coding keys are not supported for algebraic type attributes' ) : null ; } export function CodingNameForSubtype ( subtype ) { return constantValueForAttributeName ( 'SUBTYPE_' + AlgebraicTypeUtils . subtypeNameFromSubtype ( subtype ) ) ; } function codeForSubtypeBranchesWithSubtypeMapper ( algebraicType , subtypeValueAccessor , subtypeMapper , soFar , subtype ) { const internalCode = subtypeMapper ( algebraicType , subtype ) ; const code = [ ( soFar . length ? 'else if([' : 'if([' ) + subtypeValueAccessor + ' isEqualToString:' + CodingNameForSubtype ( subtype ) + ']) {' , ] . concat ( internalCode . map ( StringUtils . indent ( 2 ) ) ) . concat ( [ '}' ] ) ; return soFar . concat ( code ) ; } function codeForBranchingOnSubtypeWithSubtypeMapper ( algebraicType , subtypeValueAccessor , subtypeMapper , fallbackDecodingFunction ) { const subtypeBranches = algebraicType . subtypes . reduce ( ( soFar , subtype ) => codeForSubtypeBranchesWithSubtypeMapper ( algebraicType , subtypeValueAccessor , subtypeMapper , soFar , subtype ) , [ ] ) ; if ( fallbackDecodingFunction != null ) { const fallbackDecodingFunctionInvocation = [ 'else {' , StringUtils . indent ( 2 ) ( 'return ' + fallbackDecodingFunction + '(aDecoder);' ) , '}' , ] ; return subtypeBranches . concat ( fallbackDecodingFunctionInvocation ) ; } else { const failureCase = [ 'else {' , StringUtils . indent ( 2 ) ( '[aDecoder failWithError:[NSError errorWithDomain:NSCocoaErrorDomain code:NSCoderReadCorruptError ' + 'userInfo:@{NSDebugDescriptionErrorKey:[NSString stringWithFormat:@"*** [%@ %@]: Invalid subtype provided: %@", ' + '[self class], NSStringFromSelector(_cmd), ' + codeableAttributeForSubtypePropertyOfAlgebraicType ( ) . valueAccessor + ']}]];' ) , StringUtils . indent ( 2 ) ( 'return nil;' ) , '}' , ] ; return subtypeBranches . concat ( failureCase ) ; } } export function createAlgebraicTypePlugin ( ) { return { additionalFiles : function ( algebraicType ) : Code . File [ ] { return [ ] ; } , transformBaseFile : function ( algebraicType , baseFile ) : Code . File { return baseFile ; } , blockTypes : function ( algebraicType ) : ObjC . BlockType [ ] { return [ ] ; } , classMethods : function ( algebraicType ) : ObjC . Method [ ] { const secureCoding = algebraicType . includes . indexOf ( 'NSSecureCoding' ) >= 0 ; return secureCoding ? [ CodingUtils . supportsSecureCodingMethod ] : [ ] ; } , enumerations : function ( algebraicType ) : ObjC . Enumeration [ ] { return [ ] ; } , transformFileRequest : function ( request ) : FileWriter . Request { return request ; } , fileType : function ( algebraicType ) : Code . FileType | null { return null ; } , forwardDeclarations : function ( algebraicType ) : ObjC . ForwardDeclaration [ ] { return [ ] ; } , functions : function ( algebraicType ) : ObjC . Function [ ] { return [ ] ; } , headerComments : function ( algebraicType ) : ObjC . Comment [ ] { return [ ] ; } , implementedProtocols : function ( algebraicType ) : ObjC . ImplementedProtocol [ ] { const secureCoding = algebraicType . includes . indexOf ( 'NSSecureCoding' ) >= 0 ; if ( secureCoding ) { return [ { name : 'NSSecureCoding' } , ] ; } else { return [ { name : 'NSCoding' } , ] ; } } , imports : function ( algebraicType ) : ObjC . Import [ ] { const file = getKeyValue ( algebraicType . annotations , 'fallbackDecoding' , 'file' ) ; return file == null ? [ ] : [ { file : `${ file }.h` , library : getKeyValue ( algebraicType . annotations , 'fallbackDecoding' , 'library' ) , requiresCPlusPlus : false , isPublic : false } , ] ; } , instanceMethods : function ( algebraicType ) : ObjC . Method [ ] { const secureCoding = algebraicType . includes . indexOf ( 'NSSecureCoding' ) >= 0 ; const decodeCode = decodeCodeForAlgebraicType ( algebraicType , secureCoding , getKeyValue ( algebraicType . annotations , 'fallbackDecoding' , 'name' ) ) ; const encodeCode = encodeCodeForAlgebraicType ( algebraicType ) ; return [ decodeMethodWithCode ( decodeCode ) , encodeMethodWithCode ( encodeCode ) , ] ; } , instanceVariables : function ( algebraicType ) : ObjC . InstanceVariable [ ] { return [ ] ; } , macros : function ( algebraicType ) : ObjC . Macro [ ] { return [ ] ; } , requiredIncludesToRun : [ 'RMCoding' ] , staticConstants : function ( algebraicType ) : ObjC . Constant [ ] { const codeableAttributeForSubtypeProperty = codeableAttributeForSubtypePropertyOfAlgebraicType ( ) ; const codeableAttributeForSubtypeAttributes = AlgebraicTypeUtils . mapAttributesWithSubtypeFromSubtypes ( algebraicType . subtypes , codeableAttributeForAlgebraicSubtypeAttribute ) ; const codeableAttributes = [ codeableAttributeForSubtypeProperty , ] . concat ( codeableAttributeForSubtypeAttributes ) ; return codeableAttributes . map ( staticConstantForAttribute ) ; } , validationErrors : function ( algebraicType ) : Error . Error [ ] { const attributes = AlgebraicTypeUtils . allAttributesFromSubtypes ( algebraicType . subtypes ) ; const unknownTypeErrors = attributes . filter ( doesAlgebraicAttributeContainAnUnknownType ) . map ( ( attribute ) => algebraicAttributeToUnknownTypeError ( algebraicType , attribute ) ) ; const unsupportedTypeErrors = attributes . filter ( doesAlgebraicAttributeContainAnUnsupportedType ) . map ( ( attribute ) => algebraicAttributeToUnsupportedTypeError ( algebraicType , attribute ) ) ; const unsupportedAnnotationErrors = Maybe . catMaybes ( attributes . map ( unsupportedAnnotationErrorForAlgebraicAttribute ) ) ; return unknownTypeErrors . concat ( unsupportedTypeErrors ) . concat ( unsupportedAnnotationErrors ) ; } , nullability : function ( algebraicType ) : ObjC . ClassNullability | null { return null ; } , subclassingRestricted : function ( algebraicType ) : boolean { return false ; } } ; }